Class: HeadMusic::Notation::LilyPond::PitchReader
- Inherits:
-
Object
- Object
- HeadMusic::Notation::LilyPond::PitchReader
- Defined in:
- lib/head_music/notation/lily_pond/pitch_reader.rb
Overview
Converts note tokens into pitches in absolute or \relative mode.
The two modes differ only in where octave marks count from: absolute mode counts from c' as middle C, relative mode from the octave that puts the letter within a fourth of the previous note. Accidentals never influence the relative calculation; only letters do.
Constant Summary collapse
- LETTERS =
%w[c d e f g a b].freeze
- RELATIVE_REACH =
The letter step count beyond which relative mode wraps to the other octave.
3- UNMARKED_REGISTER =
Register 3 is LilyPond's unmarked octave (c is C3), the inverse of PitchWriter.
3- FRAGMENTS_BY_SEMITONES =
{0 => "", 1 => "#", -1 => "b", 2 => "x", -2 => "bb"}.freeze
Class Method Summary collapse
- .absolute ⇒ Object
-
.alterations_by_suffix ⇒ Object
The inverse of the writer's table, built lazily because the reader loads before the writer.
- .relative(reference_pitch) ⇒ Object
Instance Method Summary collapse
- #absolute_register(token) ⇒ Object private
- #build(token, register) ⇒ Object private
-
#chord_pitches(tokens) ⇒ Object
Within a chord each note is relative to the one before it, and the chord as a whole leaves its first note as the reference for what follows.
-
#initialize(reference) ⇒ PitchReader
constructor
A new instance of PitchReader.
- #letter_index(token) ⇒ Object private
- #mark_shift(token) ⇒ Object private
- #pitch(token) ⇒ Object
- #relative? ⇒ Boolean
- #relative_register(token) ⇒ Object private
-
#resolve(token, register) ⇒ Object
private
A register outside the numbered range is checked before the name is built, because Register.get answers an unreachable number with its default rather than with nil, which would silently move the pitch.
- #step(pitch) ⇒ Object private
Constructor Details
#initialize(reference) ⇒ PitchReader
Returns a new instance of PitchReader.
31 32 33 |
# File 'lib/head_music/notation/lily_pond/pitch_reader.rb', line 31 def initialize(reference) @reference = reference end |
Class Method Details
.absolute ⇒ Object
23 24 25 |
# File 'lib/head_music/notation/lily_pond/pitch_reader.rb', line 23 def self.absolute new(nil) end |
.alterations_by_suffix ⇒ Object
The inverse of the writer's table, built lazily because the reader loads before the writer.
19 20 21 |
# File 'lib/head_music/notation/lily_pond/pitch_reader.rb', line 19 def self.alterations_by_suffix @alterations_by_suffix ||= PitchWriter::ALTERATION_SUFFIXES.invert.freeze end |
Instance Method Details
#absolute_register(token) ⇒ Object (private)
56 57 58 |
# File 'lib/head_music/notation/lily_pond/pitch_reader.rb', line 56 def absolute_register(token) UNMARKED_REGISTER + mark_shift(token) end |
#build(token, register) ⇒ Object (private)
82 83 84 85 86 87 88 |
# File 'lib/head_music/notation/lily_pond/pitch_reader.rb', line 82 def build(token, register) resolve(token, register) || raise(ParseError.new( %(Pitch "#{token.lexeme}" is out of range), line_number: token.line, column: token.column, snippet: token.lexeme )) end |
#chord_pitches(tokens) ⇒ Object
Within a chord each note is relative to the one before it, and the chord as a whole leaves its first note as the reference for what follows.
48 49 50 51 52 |
# File 'lib/head_music/notation/lily_pond/pitch_reader.rb', line 48 def chord_pitches(tokens) pitches = tokens.map { |token| pitch(token) } @reference = pitches.first if relative? pitches end |
#letter_index(token) ⇒ Object (private)
73 74 75 |
# File 'lib/head_music/notation/lily_pond/pitch_reader.rb', line 73 def letter_index(token) LETTERS.index(token.letter) end |
#mark_shift(token) ⇒ Object (private)
77 78 79 80 |
# File 'lib/head_music/notation/lily_pond/pitch_reader.rb', line 77 def mark_shift(token) marks = token.octave_marks.to_s marks.count("'") - marks.count(",") end |
#pitch(token) ⇒ Object
39 40 41 42 43 44 |
# File 'lib/head_music/notation/lily_pond/pitch_reader.rb', line 39 def pitch(token) register = relative? ? relative_register(token) : absolute_register(token) resolved = build(token, register) @reference = resolved if relative? resolved end |
#relative? ⇒ Boolean
35 36 37 |
# File 'lib/head_music/notation/lily_pond/pitch_reader.rb', line 35 def relative? !@reference.nil? end |
#relative_register(token) ⇒ Object (private)
60 61 62 63 64 65 66 67 |
# File 'lib/head_music/notation/lily_pond/pitch_reader.rb', line 60 def relative_register(token) candidate = @reference.register * LETTERS.length + letter_index(token) difference = candidate - step(@reference) candidate -= LETTERS.length if difference > RELATIVE_REACH candidate += LETTERS.length if difference < -RELATIVE_REACH candidate += LETTERS.length * mark_shift(token) candidate.div(LETTERS.length) end |
#resolve(token, register) ⇒ Object (private)
A register outside the numbered range is checked before the name is built, because Register.get answers an unreachable number with its default rather than with nil, which would silently move the pitch.
93 94 95 96 97 98 |
# File 'lib/head_music/notation/lily_pond/pitch_reader.rb', line 93 def resolve(token, register) return unless HeadMusic::Rudiment::Register.from_number(register) semitones = self.class.alterations_by_suffix.fetch(token.suffix.to_s) HeadMusic::Rudiment::Pitch.from_name("#{token.letter.upcase}#{FRAGMENTS_BY_SEMITONES.fetch(semitones)}#{register}") end |