Class: HeadMusic::Notation::ABC::VoiceState

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

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_passesObject

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_scaleObject

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_lineObject

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_noteObject

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_builderObject (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_lineObject (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

#voiceObject (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_barObject

Returns the value of attribute volta_start_bar.



25
26
27
# File 'lib/head_music/notation/abc/voice_state.rb', line 25

def volta_start_bar
  @volta_start_bar
end

Instance Method Details

#append_tied(head, tail) ⇒ Object (private)

Attaches tail at the deep end of head’s tied chain, rebuilding each link (RhythmicValue exposes no setter) so a chain like “half tied to eighth” gains a further “tied to quarter”.



155
156
157
158
159
# File 'lib/head_music/notation/abc/voice_state.rb', line 155

def append_tied(head, tail)
  tied = head.tied_value
  inner = tied ? append_tied(tied, tail) : tail
  HeadMusic::Rudiment::RhythmicValue.new(head.unit, dots: head.dots, tied_value: inner)
end

#close_tieObject



81
82
83
84
# File 'lib/head_music/notation/abc/voice_state.rb', line 81

def close_tie
  @tie_open = false
  @tie_line = nil
end

#completed_bar_numberObject



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

def completed_bar_number
  voice.last_placement&.position&.bar_number
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.



90
91
92
93
94
95
96
97
98
99
# File 'lib/head_music/notation/abc/voice_state.rb', line 90

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

#ensure_tie_pitches_match(pending, pitches) ⇒ Object (private)

Raises:



135
136
137
138
139
140
141
142
# File 'lib/head_music/notation/abc/voice_state.rb', line 135

def ensure_tie_pitches_match(pending, pitches)
  return if pending.pitches.sort == pitches.sort

  raise ParseError.new(
    "A tie must connect two notes of the same pitch",
    line_number: tie_line, snippet: "-"
  )
end

#entered_bar_numberObject



41
42
43
# File 'lib/head_music/notation/abc/voice_state.rb', line 41

def entered_bar_number
  voice.next_position.bar_number
end

#flush_pending_noteObject

Places the pending note onto the voice, if any, carrying its authored beam break onto the placement.



103
104
105
106
107
108
109
110
# File 'lib/head_music/notation/abc/voice_state.rb', line 103

def flush_pending_note
  pending = pending_note
  return unless pending

  self.pending_note = nil
  placement = voice.place(voice.next_position, pending_rhythmic_value(pending), pending.pitches)
  placement.beam_break_before = pending.beam_break
end

#mark_beam_breakObject

Records an explicit beam break; the next note consumes it.



46
47
48
# File 'lib/head_music/notation/abc/voice_state.rb', line 46

def mark_beam_break
  @beam_break_pending = true
end

#next_beam_breakObject

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.



54
55
56
57
58
59
60
61
62
63
# File 'lib/head_music/notation/abc/voice_state.rb', line 54

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



76
77
78
79
# File 'lib/head_music/notation/abc/voice_state.rb', line 76

def open_tie(line)
  @tie_open = true
  @tie_line = line
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.



146
147
148
149
150
# File 'lib/head_music/notation/abc/voice_state.rb', line 146

def pending_rhythmic_value(pending)
  own = @duration_resolver.rhythmic_value(pending.length, scale: pending.scale)
  prefix = pending.tied_prefix
  prefix ? append_tied(prefix, 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.



114
115
116
117
# File 'lib/head_music/notation/abc/voice_state.rb', line 114

def place(length, pitches, scale: ONE)
  rhythmic_value = @duration_resolver.rhythmic_value(length, scale: scale)
  voice.place(voice.next_position, rhythmic_value, pitches)
end

#reset_beam_adjacencyObject

After a rest, bar line, volta, or voice change, the next note must not beam to whatever preceded the boundary.



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

def reset_beam_adjacency
  @beam_last_was_note = false
  @beam_break_pending = false
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.



124
125
126
127
128
129
130
131
132
133
# File 'lib/head_music/notation/abc/voice_state.rb', line 124

def tie_onto_pending(pitches, length, scale)
  pending = pending_note
  ensure_tie_pitches_match(pending, pitches)
  prefix = pending_rhythmic_value(pending)
  close_tie
  self.pending_note = PendingNote.new(
    pitches: pitches, length: length, scale: scale, tied_prefix: prefix,
    beam_break: pending.beam_break
  )
end

#tie_open?Boolean

Returns:

  • (Boolean)


72
73
74
# File 'lib/head_music/notation/abc/voice_state.rb', line 72

def tie_open?
  @tie_open
end