Class: HeadMusic::Notation::ABC::VoiceState
- Inherits:
-
Object
- Object
- HeadMusic::Notation::ABC::VoiceState
- Defined in:
- lib/head_music/notation/abc/voice_state.rb
Overview
Per-voice interpretation state and the note-assembly it drives. Accidentals, the deferred note, ties, and volta tracking are all independent between voices, so each voice owns one of these. Beam adjacency and tie bookkeeping are exposed as transitions rather than raw setters, and the parser hands notes and chords here to be buffered, tied, and flushed onto the voice — the behavior that reads and mutates this state lives with the state itself.
Defined Under Namespace
Classes: PendingNote
Constant Summary collapse
- ONE =
The identity length scale, used wherever no stretching applies.
Rational(1)
Instance Attribute Summary collapse
-
#active_passes ⇒ Object
Returns the value of attribute active_passes.
-
#awaiting_scale ⇒ Object
Returns the value of attribute awaiting_scale.
-
#broken_line ⇒ Object
Returns the value of attribute broken_line.
-
#pending_note ⇒ Object
Returns the value of attribute pending_note.
-
#pitch_builder ⇒ Object
readonly
Returns the value of attribute pitch_builder.
-
#tie_line ⇒ Object
readonly
Returns the value of attribute tie_line.
-
#voice ⇒ Object
readonly
Returns the value of attribute voice.
-
#volta_start_bar ⇒ Object
Returns the value of attribute volta_start_bar.
Instance Method Summary collapse
- #bar_start?(position) ⇒ Boolean private
- #close_tie ⇒ Object
-
#completed_bar_number ⇒ Object
Counted from where the last note ends rather than where it starts: a note tied across a bar line is still pending when the repeat tagger asks, and a note longer than its bar has crossed one already.
-
#defer_placement(pitches, length, inner_scale = ONE) ⇒ Object
Buffers a note or chord as the pending note, flushing whatever was pending first.
- #entered_bar_number ⇒ Object
-
#flush_pending_note ⇒ Object
Places the pending note onto the voice, if any, carrying its authored beam break onto the placement.
-
#initialize(voice, pitch_builder, duration_resolver = nil) ⇒ VoiceState
constructor
A new instance of VoiceState.
- #letters_and_registers(pitches) ⇒ Object private
-
#mark_beam_break ⇒ Object
Records an explicit beam break; the next note consumes it.
-
#next_beam_break ⇒ Object
The beam-break flag for the note now beginning: true if a break was marked, false if the previous token was also a note (so they beam together), or nil when there is no prior note to beam against.
- #open_tie(line) ⇒ Object
- #pending_end_position ⇒ Object private
-
#pending_rhythmic_value(pending) ⇒ Object
private
A pending note's own value, with any tied prefix appended ahead of it so the whole tie chain renders as one sounding note.
-
#place(length, pitches, scale: ONE) ⇒ Object
Places a note, chord, or rest (nil pitches) directly onto the voice, bypassing the pending-note buffer.
- #place_next(rhythmic_value, pitches) ⇒ Object private
-
#reset_beam_adjacency ⇒ Object
After a rest, bar line, volta, or voice change, the next note must not beam to whatever preceded the boundary.
- #same_letters?(pitches, other_pitches) ⇒ Boolean private
-
#tie_onto_pending(pitches, length, scale) ⇒ Object
private
Closes an open tie: the pending note becomes the new note's tied prefix, so the pair (and any longer chain) resolves to a single placement whose rhythmic value carries the author's chosen split.
- #tie_open? ⇒ Boolean
-
#tied_pitches(pending, pitches) ⇒ Object
private
A tie carries its accidental across the bar line, where the key signature would otherwise respell the note, so the same letters in the same octaves are the same pitches.
Constructor Details
#initialize(voice, pitch_builder, duration_resolver = nil) ⇒ VoiceState
Returns a new instance of VoiceState.
28 29 30 31 32 33 34 35 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 28 def initialize(voice, pitch_builder, duration_resolver = nil) @voice = voice @pitch_builder = pitch_builder @duration_resolver = duration_resolver @beam_break_pending = false @beam_last_was_note = false @tie_open = false end |
Instance Attribute Details
#active_passes ⇒ Object
Returns the value of attribute active_passes.
25 26 27 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 25 def active_passes @active_passes end |
#awaiting_scale ⇒ Object
Returns the value of attribute awaiting_scale.
25 26 27 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 25 def awaiting_scale @awaiting_scale end |
#broken_line ⇒ Object
Returns the value of attribute broken_line.
25 26 27 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 25 def broken_line @broken_line end |
#pending_note ⇒ Object
Returns the value of attribute pending_note.
25 26 27 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 25 def pending_note @pending_note end |
#pitch_builder ⇒ Object (readonly)
Returns the value of attribute pitch_builder.
24 25 26 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 24 def pitch_builder @pitch_builder end |
#tie_line ⇒ Object (readonly)
Returns the value of attribute tie_line.
24 25 26 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 24 def tie_line @tie_line end |
#voice ⇒ Object (readonly)
Returns the value of attribute voice.
24 25 26 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 24 def voice @voice end |
#volta_start_bar ⇒ Object
Returns the value of attribute volta_start_bar.
25 26 27 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 25 def end |
Instance Method Details
#bar_start?(position) ⇒ Boolean (private)
166 167 168 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 166 def (position) position.to_a.drop(1) == [1, 0, 0] end |
#close_tie ⇒ Object
87 88 89 90 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 87 def close_tie @tie_open = false @tie_line = nil end |
#completed_bar_number ⇒ Object
Counted from where the last note ends rather than where it starts: a note tied across a bar line is still pending when the repeat tagger asks, and a note longer than its bar has crossed one already.
40 41 42 43 44 45 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 40 def finish = pending_note ? pending_end_position : voice.last_placement&.next_position return unless finish (finish) ? finish. - 1 : finish. end |
#defer_placement(pitches, length, inner_scale = ONE) ⇒ Object
Buffers a note or chord as the pending note, flushing whatever was pending first. A broken-rhythm scale awaiting its right note, and any per-chord inner scale, fold into the buffered length. When a tie is open the arriving note instead extends the pending tie chain.
96 97 98 99 100 101 102 103 104 105 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 96 def defer_placement(pitches, length, inner_scale = ONE) scale = (awaiting_scale || ONE) * inner_scale self.awaiting_scale = nil return tie_onto_pending(pitches, length, scale) if tie_open? flush_pending_note self.pending_note = PendingNote.new( pitches: pitches, length: length, scale: scale, beam_break: next_beam_break ) end |
#entered_bar_number ⇒ Object
47 48 49 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 47 def (pending_note ? pending_end_position : voice.next_position). end |
#flush_pending_note ⇒ Object
Places the pending note onto the voice, if any, carrying its authored beam break onto the placement.
109 110 111 112 113 114 115 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 109 def flush_pending_note pending = pending_note return unless pending self.pending_note = nil place_next(pending_rhythmic_value(pending), pending.pitches).beam_break_before = pending.beam_break end |
#letters_and_registers(pitches) ⇒ Object (private)
158 159 160 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 158 def letters_and_registers(pitches) pitches.map { |pitch| [pitch.letter_name.to_s, pitch.register] }.sort end |
#mark_beam_break ⇒ Object
Records an explicit beam break; the next note consumes it.
52 53 54 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 52 def mark_beam_break @beam_break_pending = true end |
#next_beam_break ⇒ Object
The beam-break flag for the note now beginning: true if a break was marked, false if the previous token was also a note (so they beam together), or nil when there is no prior note to beam against. Consumes the pending break and records that a note is now current.
60 61 62 63 64 65 66 67 68 69 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 60 def next_beam_break flag = if @beam_break_pending true elsif @beam_last_was_note false end @beam_break_pending = false @beam_last_was_note = true flag end |
#open_tie(line) ⇒ Object
82 83 84 85 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 82 def open_tie(line) @tie_open = true @tie_line = line end |
#pending_end_position ⇒ Object (private)
162 163 164 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 162 def pending_end_position voice.next_position + pending_rhythmic_value(pending_note) end |
#pending_rhythmic_value(pending) ⇒ Object (private)
A pending note's own value, with any tied prefix appended ahead of it so the whole tie chain renders as one sounding note.
172 173 174 175 176 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 172 def pending_rhythmic_value(pending) own = @duration_resolver.rhythmic_value(pending.length, scale: pending.scale) prefix = pending.tied_prefix prefix ? prefix.append_tied(own) : own end |
#place(length, pitches, scale: ONE) ⇒ Object
Places a note, chord, or rest (nil pitches) directly onto the voice, bypassing the pending-note buffer.
119 120 121 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 119 def place(length, pitches, scale: ONE) place_next(@duration_resolver.rhythmic_value(length, scale: scale), pitches) end |
#place_next(rhythmic_value, pitches) ⇒ Object (private)
125 126 127 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 125 def place_next(rhythmic_value, pitches) voice.place(voice.next_position, rhythmic_value, pitches) end |
#reset_beam_adjacency ⇒ Object
After a rest, bar line, volta, or voice change, the next note must not beam to whatever preceded the boundary.
73 74 75 76 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 73 def reset_beam_adjacency @beam_last_was_note = false @beam_break_pending = false end |
#same_letters?(pitches, other_pitches) ⇒ Boolean (private)
154 155 156 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 154 def same_letters?(pitches, other_pitches) letters_and_registers(pitches) == letters_and_registers(other_pitches) end |
#tie_onto_pending(pitches, length, scale) ⇒ Object (private)
Closes an open tie: the pending note becomes the new note's tied prefix, so the pair (and any longer chain) resolves to a single placement whose rhythmic value carries the author's chosen split.
132 133 134 135 136 137 138 139 140 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 132 def tie_onto_pending(pitches, length, scale) pending = pending_note prefix = pending_rhythmic_value(pending) close_tie self.pending_note = PendingNote.new( pitches: tied_pitches(pending, pitches), length: length, scale: scale, tied_prefix: prefix, beam_break: pending.beam_break ) end |
#tie_open? ⇒ Boolean
78 79 80 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 78 def tie_open? @tie_open end |
#tied_pitches(pending, pitches) ⇒ Object (private)
A tie carries its accidental across the bar line, where the key signature would otherwise respell the note, so the same letters in the same octaves are the same pitches.
145 146 147 148 149 150 151 152 |
# File 'lib/head_music/notation/abc/voice_state.rb', line 145 def tied_pitches(pending, pitches) return pending.pitches if same_letters?(pending.pitches, pitches) raise ParseError.new( "A tie must connect two notes of the same pitch", line_number: tie_line, snippet: "-" ) end |