Class: HeadMusic::Notation::ABC::PitchBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/notation/abc/pitch_builder.rb

Overview

Converts lexed ABC note data into pitches, applying the key signature and ABC’s bar-persistent accidental rules.

Constant Summary collapse

ACCIDENTAL_FRAGMENTS =

ABC accidental marks mapped to pitch-name fragments. The natural sign maps to an empty fragment because a natural pitch name carries no alteration symbol.

{
  "^" => "#",
  "^^" => "x",
  "_" => "b",
  "__" => "bb",
  "=" => ""
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key_signature) ⇒ PitchBuilder

Returns a new instance of PitchBuilder.



19
20
21
22
# File 'lib/head_music/notation/abc/pitch_builder.rb', line 19

def initialize(key_signature)
  @key_signature = HeadMusic::Rudiment::KeySignature.get(key_signature)
  start_new_bar
end

Instance Attribute Details

#key_signatureObject (readonly)

Returns the value of attribute key_signature.



17
18
19
# File 'lib/head_music/notation/abc/pitch_builder.rb', line 17

def key_signature
  @key_signature
end

Instance Method Details

#accidental_fragment(letter_name, octave, accidental_marks) ⇒ Object (private)



49
50
51
52
53
# File 'lib/head_music/notation/abc/pitch_builder.rb', line 49

def accidental_fragment(letter_name, octave, accidental_marks)
  return explicit_fragment(letter_name, octave, accidental_marks) if present?(accidental_marks)

  @bar_accidentals.fetch([letter_name, octave]) { key_signature_fragment(letter_name) }
end

#explicit_fragment(letter_name, octave, accidental_marks) ⇒ Object (private)

An explicit accidental applies to later unmarked notes of the same letter and octave until the next bar line.



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

def explicit_fragment(letter_name, octave, accidental_marks)
  fragment = ACCIDENTAL_FRAGMENTS.fetch(accidental_marks) do
    raise ParseError, "invalid accidental marks: #{accidental_marks.inspect}"
  end
  @bar_accidentals[[letter_name, octave]] = fragment
end

#key_signature_fragment(letter_name) ⇒ Object (private)



64
65
66
67
# File 'lib/head_music/notation/abc/pitch_builder.rb', line 64

def key_signature_fragment(letter_name)
  spelling = key_signature.alterations.find { |altered| altered.letter_name.to_s == letter_name }
  spelling ? spelling.alteration.to_s : ""
end

#octave_for(letter, octave_marks) ⇒ Object (private)



43
44
45
46
47
# File 'lib/head_music/notation/abc/pitch_builder.rb', line 43

def octave_for(letter, octave_marks)
  base_octave = (letter == letter.upcase) ? 4 : 5
  marks = octave_marks.to_s
  base_octave + marks.count("'") - marks.count(",")
end

#pitch(letter, octave_marks = "", accidental_marks = nil) ⇒ Object

letter: raw ABC letter preserving case (“A”..”G” or “a”..”g”) octave_marks: a string of “’” and “,” characters (possibly empty or nil) accidental_marks: nil, “^”, “^^”, “_”, “__”, or “=”



27
28
29
30
31
32
33
34
# File 'lib/head_music/notation/abc/pitch_builder.rb', line 27

def pitch(letter, octave_marks = "", accidental_marks = nil)
  letter_name = letter.upcase
  octave = octave_for(letter, octave_marks)
  fragment = accidental_fragment(letter_name, octave, accidental_marks)
  name = "#{letter_name}#{fragment}#{octave}"
  HeadMusic::Rudiment::Pitch.from_name(name) ||
    raise(ParseError, "invalid pitch: #{name.inspect}")
end

#present?(accidental_marks) ⇒ Boolean (private)

Returns:

  • (Boolean)


69
70
71
# File 'lib/head_music/notation/abc/pitch_builder.rb', line 69

def present?(accidental_marks)
  !accidental_marks.to_s.empty?
end

#start_new_barObject

In ABC, accidentals persist only to the end of the bar.



37
38
39
# File 'lib/head_music/notation/abc/pitch_builder.rb', line 37

def start_new_bar
  @bar_accidentals = {}
end