Class: HeadMusic::Notation::LilyPond::VoiceStream
- Inherits:
-
Object
- Object
- HeadMusic::Notation::LilyPond::VoiceStream
- Defined in:
- lib/head_music/notation/lily_pond/voice_stream.rb
Overview
The ordered events of one voice as read from the document, before any flow exists: notes, rests, whole-bar rests, bar checks, and key or meter commands. Ties fold here, so a tied pair reaches the builder as one note whose rhythmic value carries the author's split.
Defined Under Namespace
Classes: Event
Constant Summary collapse
- UNFOLLOWED_TIE =
"A tie must be followed by a note"
Instance Attribute Summary collapse
-
#events ⇒ Object
readonly
Returns the value of attribute events.
-
#role ⇒ Object
readonly
Returns the value of attribute role.
Instance Method Summary collapse
- #add_note(pitches, rhythmic_value, line) ⇒ Object
- #add_rest(rhythmic_value, line) ⇒ Object
- #add_whole_bar_rest(fraction, line) ⇒ Object
- #append(line, tie_message = UNFOLLOWED_TIE, **attributes) ⇒ Object private
- #bar_check(line) ⇒ Object
- #change_key_signature(key_signature, line) ⇒ Object
- #change_meter(meter, line) ⇒ Object
- #error(message, line) ⇒ Object private
-
#extend_tie(pitches, rhythmic_value, line) ⇒ Object
private
Closes an open tie: the arriving note's value is appended at the deep end of the pending note's chain, so the pair (and any longer chain) becomes a single note.
- #finish ⇒ Object
- #flush_pending_note ⇒ Object private
-
#initialize(role = nil) ⇒ VoiceStream
constructor
A new instance of VoiceStream.
- #music? ⇒ Boolean
- #open_tie(line) ⇒ Object
-
#terminate(tie_message, line) ⇒ Object
private
Anything that is not a note ends the pending note; an open tie can then never close, so it is rejected with the caller's message.
Constructor Details
#initialize(role = nil) ⇒ VoiceStream
Returns a new instance of VoiceStream.
22 23 24 25 26 27 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 22 def initialize(role = nil) @role = role @events = [] @pending_note = nil @tie_open = false end |
Instance Attribute Details
#events ⇒ Object (readonly)
Returns the value of attribute events.
20 21 22 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 20 def events @events end |
#role ⇒ Object (readonly)
Returns the value of attribute role.
20 21 22 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 20 def role @role end |
Instance Method Details
#add_note(pitches, rhythmic_value, line) ⇒ Object
34 35 36 37 38 39 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 34 def add_note(pitches, rhythmic_value, line) return extend_tie(pitches, rhythmic_value, line) if @tie_open flush_pending_note @pending_note = Event.new(kind: :note, line: line, pitches: pitches, rhythmic_value: rhythmic_value) end |
#add_rest(rhythmic_value, line) ⇒ Object
41 42 43 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 41 def add_rest(rhythmic_value, line) append(line, kind: :rest, rhythmic_value: rhythmic_value) end |
#add_whole_bar_rest(fraction, line) ⇒ Object
45 46 47 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 45 def (fraction, line) append(line, kind: :whole_bar_rest, fraction: fraction) end |
#append(line, tie_message = UNFOLLOWED_TIE, **attributes) ⇒ Object (private)
75 76 77 78 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 75 def append(line, = UNFOLLOWED_TIE, **attributes) terminate(, line) events << Event.new(line: line, **attributes) end |
#bar_check(line) ⇒ Object
56 57 58 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 56 def (line) append(line, "Ties across bar checks are not yet supported", kind: :bar_check) end |
#change_key_signature(key_signature, line) ⇒ Object
60 61 62 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 60 def change_key_signature(key_signature, line) append(line, kind: :key, key_signature: key_signature) end |
#change_meter(meter, line) ⇒ Object
64 65 66 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 64 def change_meter(meter, line) append(line, kind: :time, meter: meter) end |
#error(message, line) ⇒ Object (private)
108 109 110 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 108 def error(, line) ParseError.new(, line_number: line, snippet: "~") end |
#extend_tie(pitches, rhythmic_value, line) ⇒ Object (private)
Closes an open tie: the arriving note's value is appended at the deep end of the pending note's chain, so the pair (and any longer chain) becomes a single note.
98 99 100 101 102 103 104 105 106 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 98 def extend_tie(pitches, rhythmic_value, line) pending = @pending_note unless pending.pitches.sort == pitches.sort raise error("A tie must connect two notes of the same pitch", line) end @tie_open = false @pending_note = pending.with(rhythmic_value: pending.rhythmic_value.append_tied(rhythmic_value)) end |
#finish ⇒ Object
68 69 70 71 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 68 def finish terminate(UNFOLLOWED_TIE, nil) self end |
#flush_pending_note ⇒ Object (private)
88 89 90 91 92 93 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 88 def flush_pending_note return unless @pending_note events << @pending_note @pending_note = nil end |
#music? ⇒ Boolean
29 30 31 32 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 29 def music? finish events.any?(&:music?) end |
#open_tie(line) ⇒ Object
49 50 51 52 53 54 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 49 def open_tie(line) raise error("A tie must follow a note", line) if @pending_note.nil? || @tie_open @tie_open = true @tie_line = line end |
#terminate(tie_message, line) ⇒ Object (private)
Anything that is not a note ends the pending note; an open tie can then never close, so it is rejected with the caller's message.
82 83 84 85 86 |
# File 'lib/head_music/notation/lily_pond/voice_stream.rb', line 82 def terminate(, line) raise error(, line || @tie_line) if @tie_open flush_pending_note end |