Class: HeadMusic::Notation::LilyPond::VoiceStream

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

Instance Method Summary collapse

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 add_whole_bar_rest(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, tie_message = UNFOLLOWED_TIE, **attributes)
  terminate(tie_message, 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 bar_check(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(message, line)
  ParseError.new(message, 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

Returns:

  • (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(tie_message, line)
  raise error(tie_message, line || @tie_line) if @tie_open

  flush_pending_note
end