Class: HeadMusic::Notation::LilyPond::KeyReader

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/notation/lily_pond/key_reader.rb

Overview

Converts the pitch and mode tokens of a \key command into a key signature.

Constant Summary collapse

TONIC_FRAGMENTS_BY_SEMITONES =
{0 => "", 1 => "#", -1 => "b"}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pitch_token, mode_token) ⇒ KeyReader

Returns a new instance of KeyReader.



20
21
22
23
# File 'lib/head_music/notation/lily_pond/key_reader.rb', line 20

def initialize(pitch_token, mode_token)
  @pitch_token = pitch_token
  @mode_token = mode_token
end

Instance Attribute Details

#mode_token ⇒ Object (readonly, private)

Returns the value of attribute mode_token.



32
33
34
# File 'lib/head_music/notation/lily_pond/key_reader.rb', line 32

def mode_token
  @mode_token
end

#pitch_token ⇒ Object (readonly, private)

Returns the value of attribute pitch_token.



32
33
34
# File 'lib/head_music/notation/lily_pond/key_reader.rb', line 32

def pitch_token
  @pitch_token
end

Class Method Details

.key_signature(pitch_token, mode_token) ⇒ Object



16
17
18
# File 'lib/head_music/notation/lily_pond/key_reader.rb', line 16

def self.key_signature(pitch_token, mode_token)
  new(pitch_token, mode_token).key_signature
end

.scale_types_by_mode_command ⇒ Object

The inverse of the writer’s table, keyed by the mode command’s name without its backslash. Built lazily because the reader loads before the writer.



10
11
12
13
14
# File 'lib/head_music/notation/lily_pond/key_reader.rb', line 10

def self.scale_types_by_mode_command
  @scale_types_by_mode_command ||= KeyMapper::MODE_COMMANDS_BY_SCALE_TYPE
    .to_h { |scale_type, command| [command.delete_prefix("\\"), scale_type] }
    .freeze
end

Instance Method Details

#ensure_bare_pitch ⇒ Object (private)



34
35
36
37
38
39
# File 'lib/head_music/notation/lily_pond/key_reader.rb', line 34

def ensure_bare_pitch
  return unless pitch_token.nil? || pitch_token.type != :note ||
    pitch_token.octave_marks || pitch_token.duration || pitch_token.multiplier

  raise error("\\key expects a pitch and a mode", pitch_token || mode_token)
end

#error(message, token) ⇒ Object (private)



59
60
61
# File 'lib/head_music/notation/lily_pond/key_reader.rb', line 59

def error(message, token)
  ParseError.new(message, line_number: token&.line, column: token&.column, snippet: token&.lexeme)
end

#key_signature ⇒ Object



25
26
27
28
# File 'lib/head_music/notation/lily_pond/key_reader.rb', line 25

def key_signature
  ensure_bare_pitch
  HeadMusic::Rudiment::KeySignature.get("#{tonic} #{scale_type}")
end

#scale_type ⇒ Object (private)



49
50
51
52
53
54
55
56
57
# File 'lib/head_music/notation/lily_pond/key_reader.rb', line 49

def scale_type
  unless mode_token&.type == :command
    raise error("\\key expects a pitch and a mode", mode_token || pitch_token)
  end

  self.class.scale_types_by_mode_command.fetch(mode_token.lexeme) do
    raise error(%(Unrecognized mode "\\#{mode_token.lexeme}" in \\key command), mode_token)
  end
end

#tonic ⇒ Object (private)



41
42
43
44
45
46
47
# File 'lib/head_music/notation/lily_pond/key_reader.rb', line 41

def tonic
  semitones = PitchReader.alterations_by_suffix.fetch(pitch_token.suffix.to_s)
  fragment = TONIC_FRAGMENTS_BY_SEMITONES.fetch(semitones) do
    raise error(%(Cannot build a key signature on the double-altered tonic "#{pitch_token.lexeme}"), pitch_token)
  end
  "#{pitch_token.letter.upcase}#{fragment}"
end