Class: HeadMusic::Notation::ABC::PitchWriter

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

Overview

Converts pitches into ABC note tokens, emitting the minimal accidental marks required under the key signature and ABC’s bar-persistent rules.

A live PitchBuilder acts as an oracle: if an unmarked token would already parse back to the target pitch, no accidental is written. Emitted tokens are fed back through the oracle so its bar state matches what a parser will accumulate when re-reading the output.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key_signature) ⇒ PitchWriter

Returns a new instance of PitchWriter.



13
14
15
16
# File 'lib/head_music/notation/abc/pitch_writer.rb', line 13

def initialize(key_signature)
  @key_signature = HeadMusic::Rudiment::KeySignature.get(key_signature)
  @oracle = PitchBuilder.new(@key_signature)
end

Instance Attribute Details

#key_signatureObject (readonly)

Returns the value of attribute key_signature.



11
12
13
# File 'lib/head_music/notation/abc/pitch_writer.rb', line 11

def key_signature
  @key_signature
end

#oracleObject (readonly, private)

Returns the value of attribute oracle.



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

def oracle
  @oracle
end

Instance Method Details

#accidental_marks(pitch) ⇒ Object (private)



52
53
54
55
56
57
# File 'lib/head_music/notation/abc/pitch_writer.rb', line 52

def accidental_marks(pitch)
  fragment = pitch.alteration&.ascii.to_s
  accidental_marks_by_fragment.fetch(fragment) do
    raise RenderError, "cannot express #{pitch} in ABC notation"
  end
end

#accidental_marks_by_fragmentObject (private)



59
60
61
# File 'lib/head_music/notation/abc/pitch_writer.rb', line 59

def accidental_marks_by_fragment
  @accidental_marks_by_fragment ||= PitchBuilder::ACCIDENTAL_FRAGMENTS.invert
end

#commit_and_verify(pitch, letter, octave_marks, marks) ⇒ Object (private)

Feeding the marked token through the oracle updates its bar state exactly as a parser reading the output would — this call is load-bearing, not just a verification; later unmarked notes depend on the state it writes.

Raises:



66
67
68
69
70
# File 'lib/head_music/notation/abc/pitch_writer.rb', line 66

def commit_and_verify(pitch, letter, octave_marks, marks)
  return if oracle.pitch(letter, octave_marks, marks) == pitch

  raise RenderError, "cannot express #{pitch} in ABC notation"
end

#letter_token(pitch) ⇒ Object (private)



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

def letter_token(pitch)
  letter = pitch.letter_name.to_s
  (pitch.register >= 5) ? letter.downcase : letter
end

#octave_marks(pitch) ⇒ Object (private)



44
45
46
47
48
49
50
# File 'lib/head_music/notation/abc/pitch_writer.rb', line 44

def octave_marks(pitch)
  if pitch.register >= 5
    "'" * (pitch.register - 5)
  else
    "," * (4 - pitch.register)
  end
end

#start_new_barObject

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



31
32
33
# File 'lib/head_music/notation/abc/pitch_writer.rb', line 31

def start_new_bar
  oracle.start_new_bar
end

#token(pitch) ⇒ Object

Returns the ABC note token for the pitch, without any duration multiplier.



19
20
21
22
23
24
25
26
27
28
# File 'lib/head_music/notation/abc/pitch_writer.rb', line 19

def token(pitch)
  pitch = HeadMusic::Rudiment::Pitch.get(pitch)
  letter = letter_token(pitch)
  octave_marks = octave_marks(pitch)
  return "#{letter}#{octave_marks}" if oracle.pitch(letter, octave_marks) == pitch

  marks = accidental_marks(pitch)
  commit_and_verify(pitch, letter, octave_marks, marks)
  "#{marks}#{letter}#{octave_marks}"
end