Class: HeadMusic::Notation::ABC::KeyMapper

Inherits:
Object
  • Object
show all
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 =
/\A([A-G])([#♯b♭]?)\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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value, line_number: nil) ⇒ KeyMapper

Returns a new instance of KeyMapper.



70
71
72
73
# File 'lib/head_music/notation/abc/key_mapper.rb', line 70

def initialize(value, line_number: nil)
  @value = value.to_s.strip
  @line_number = line_number
end

Instance Attribute Details

#line_numberObject (readonly)

Returns the value of attribute line_number.



68
69
70
# File 'lib/head_music/notation/abc/key_mapper.rb', line 68

def line_number
  @line_number
end

#valueObject (readonly)

Returns the value of attribute value.



68
69
70
# File 'lib/head_music/notation/abc/key_mapper.rb', line 68

def value
  @value
end

Class Method Details

.abc_value(key_signature) ⇒ Object

Returns the ABC K: field value for a key signature.



39
40
41
42
# File 'lib/head_music/notation/abc/key_mapper.rb', line 39

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)



57
58
59
60
# File 'lib/head_music/notation/abc/key_mapper.rb', line 57

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)



63
64
65
# File 'lib/head_music/notation/abc/key_mapper.rb', line 63

def self.raise_render_error(message)
  raise HeadMusic::Notation::ABC::RenderError, message
end

.tonic_string(key_signature) ⇒ Object (private)



44
45
46
47
48
49
50
51
52
53
54
# File 'lib/head_music/notation/abc/key_mapper.rb', line 44

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.
  "#{spelling.letter_name}#{alteration&.ascii}"
end

Instance Method Details

#key_signatureObject



79
80
81
# File 'lib/head_music/notation/abc/key_mapper.rb', line 79

def key_signature
  HeadMusic::Rudiment::KeySignature.get(key_signature_name)
end

#key_signature_nameObject



75
76
77
# File 'lib/head_music/notation/abc/key_mapper.rb', line 75

def key_signature_name
  "#{tonic} #{mode_name}"
end

#matchObject (private)



85
86
87
88
# File 'lib/head_music/notation/abc/key_mapper.rb', line 85

def match
  @match ||= KEY_PATTERN.match(value) ||
    raise_parse_error("Unrecognized key")
end

#mode_nameObject (private)



94
95
96
97
98
99
100
101
# File 'lib/head_music/notation/abc/key_mapper.rb', line 94

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)



103
104
105
106
107
108
109
# File 'lib/head_music/notation/abc/key_mapper.rb', line 103

def raise_parse_error(message)
  raise HeadMusic::Notation::ABC::ParseError.new(
    "#{message} in K: field value #{value.inspect}",
    line_number: line_number,
    snippet: value
  )
end

#tonicObject (private)



90
91
92
# File 'lib/head_music/notation/abc/key_mapper.rb', line 90

def tonic
  match[1] + match[2]
end