Class: HeadMusic::Notation::ABC::KeyMapper
- Inherits:
-
Object
- Object
- HeadMusic::Notation::ABC::KeyMapper
- Defined in:
- lib/head_music/notation/abc/key_mapper.rb
Overview
Converts an ABC K: field value into a key signature.
ABC key values like “Ador” or “F#m” cannot be passed directly to KeySignature.get, which splits tonic and scale type on whitespace, so the mode word is normalized into a “tonic mode” string first.
Constant Summary collapse
- KEY_PATTERN =
The alteration group wraps the pattern in (?:…) before making it optional. Writing (…)? instead leaves match[2] nil for every unaltered key (“C”, “Ador”), which raises downstream where the capture is interpolated into a string.
/\A([A-G])((?:#{HeadMusic::Rudiment::Alteration::PATTERN.source})?)\s*([A-Za-z]*)/- MODE_NAMES_BY_PREFIX =
Mode words are matched case-insensitively on their first three letters, so abbreviations (“dor”) and full names (“Dorian”) both resolve.
{ "maj" => "major", "ion" => "major", "min" => "minor", "aeo" => "minor", "dor" => "dorian", "phr" => "phrygian", "lyd" => "lydian", "mix" => "mixolydian", "loc" => "locrian" }.freeze
- ABC_SUFFIXES_BY_MODE =
MODE_NAMES_BY_PREFIX is many-to-one, so rendering uses this explicit inverse map. Every suffix here parses back to an equal key signature (ionian/aeolian differ from major/minor only in name, not alterations).
{ "major" => "", "ionian" => "", "minor" => "m", "aeolian" => "m", "dorian" => "dor", "phrygian" => "phr", "lydian" => "lyd", "mixolydian" => "mix", "locrian" => "loc" }.freeze
Instance Attribute Summary collapse
-
#line_number ⇒ Object
readonly
Returns the value of attribute line_number.
-
#value ⇒ Object
readonly
Returns the value of attribute value.
Class Method Summary collapse
-
.abc_value(key_signature) ⇒ Object
Returns the ABC K: field value for a key signature.
-
.mode_suffix(key_signature) ⇒ Object
private
-
.raise_render_error(message) ⇒ Object
private
-
.tonic_string(key_signature) ⇒ Object
private
Instance Method Summary collapse
-
#initialize(value, line_number: nil) ⇒ KeyMapper
constructor
A new instance of KeyMapper.
-
#key_signature ⇒ Object
-
#key_signature_name ⇒ Object
-
#match ⇒ Object
private
-
#mode_name ⇒ Object
private
-
#raise_parse_error(message) ⇒ Object
private
-
#tonic ⇒ Object
private
An ABC K: field cannot express a double-altered tonic, and .abc_value raises rather than rendering one.
Constructor Details
#initialize(value, line_number: nil) ⇒ KeyMapper
Returns a new instance of KeyMapper.
79 80 81 82 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 79 def initialize(value, line_number: nil) @value = value.to_s.strip @line_number = line_number end |
Instance Attribute Details
#line_number ⇒ Object (readonly)
Returns the value of attribute line_number.
77 78 79 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 77 def line_number @line_number end |
#value ⇒ Object (readonly)
Returns the value of attribute value.
77 78 79 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 77 def value @value end |
Class Method Details
.abc_value(key_signature) ⇒ Object
Returns the ABC K: field value for a key signature.
42 43 44 45 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 42 def self.abc_value(key_signature) key_signature = HeadMusic::Rudiment::KeySignature.get(key_signature) "#{tonic_string(key_signature)}#{mode_suffix(key_signature)}" end |
.mode_suffix(key_signature) ⇒ Object (private)
66 67 68 69 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 66 def self.mode_suffix(key_signature) ABC_SUFFIXES_BY_MODE[key_signature.scale_type.name.to_s] || raise_render_error("Cannot render scale type #{key_signature.scale_type} in an ABC K: field") end |
.raise_render_error(message) ⇒ Object (private)
72 73 74 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 72 def self.raise_render_error() raise HeadMusic::Notation::ABC::RenderError, end |
.tonic_string(key_signature) ⇒ Object (private)
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 47 def self.tonic_string(key_signature) spelling = key_signature.tonic_spelling alteration = spelling.alteration if alteration && (alteration.double_sharp? || alteration.double_flat?) raise_render_error("Cannot render double-altered tonic #{spelling} in an ABC K: field") end # ABC convention uses ASCII "#"/"b" rather than the unicode signs that # Spelling#to_s produces. The double-altered guard above runs first, since this # would otherwise happily emit "Cx". # # A natural sign is dropped rather than converted. Accidentals.to_ascii preserves # "♮" on purpose — mapping it away would turn the spelling "C♮" into the different # spelling "C" — but an ABC K: field has no way to express a natural tonic, and # "C♮ major" names the same key signature as "C major". HeadMusic::Utilities::Accidentals.to_ascii(spelling.to_s).delete("♮") end |
Instance Method Details
#key_signature ⇒ Object
88 89 90 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 88 def key_signature HeadMusic::Rudiment::KeySignature.get(key_signature_name) end |
#key_signature_name ⇒ Object
84 85 86 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 84 def key_signature_name "#{tonic} #{mode_name}" end |
#match ⇒ Object (private)
94 95 96 97 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 94 def match @match ||= KEY_PATTERN.match(value) || raise_parse_error("Unrecognized key") end |
#mode_name ⇒ Object (private)
114 115 116 117 118 119 120 121 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 114 def mode_name word = match[3].downcase return "major" if word.empty? return "minor" if word == "m" MODE_NAMES_BY_PREFIX[word[0, 3]] || raise_parse_error("Unrecognized mode") end |
#raise_parse_error(message) ⇒ Object (private)
123 124 125 126 127 128 129 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 123 def raise_parse_error() raise HeadMusic::Notation::ABC::ParseError.new( "#{} in K: field value #{value.inspect}", line_number: line_number, snippet: value ) end |
#tonic ⇒ Object (private)
An ABC K: field cannot express a double-altered tonic, and .abc_value raises rather than rendering one. Reject it on the way in too: KEY_PATTERN interpolates the whole Alteration::PATTERN so that “bb” and “##” bind correctly, which also makes them matchable here. Without this guard “K:Cx” would parse to C𝄪 major and report “no sharps or flats” — a plausible-looking wrong answer where the narrower pattern used to give a clear parse error.
105 106 107 108 109 110 111 112 |
# File 'lib/head_music/notation/abc/key_mapper.rb', line 105 def tonic alteration = HeadMusic::Rudiment::Alteration.get(match[2]) if alteration && (alteration.double_sharp? || alteration.double_flat?) raise_parse_error("Cannot express double-altered tonic #{match[1]}#{match[2]} in an ABC K: field") end match[1] + match[2] end |