Class: HeadMusic::Notation::LilyPond::MusicItemReader

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/notation/lily_pond/music_item_reader.rb

Overview

Reads the items a sequence of music is made of — notes, rests, chords, ties, bar checks, and the \key, \time, and \clef commands that appear among them — into the stream of the context that holds them. Everything that opens a level of its own belongs to the MusicReader that calls this one.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cursor, readers) ⇒ MusicItemReader

Returns a new instance of MusicItemReader.



9
10
11
12
13
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 9

def initialize(cursor, readers)
  @cursor = cursor
  @readers = readers
  @duration_reader = DurationReader.new
end

Instance Attribute Details

#cursor ⇒ Object (readonly, private)

Returns the value of attribute cursor.



78
79
80
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 78

def cursor
  @cursor
end

#duration_reader ⇒ Object (readonly, private)

Returns the value of attribute duration_reader.



78
79
80
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 78

def duration_reader
  @duration_reader
end

#readers ⇒ Object (readonly, private)

Returns the value of attribute readers.



78
79
80
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 78

def readers
  @readers
end

Instance Method Details

#chord_note ⇒ Object (private)



80
81
82
83
84
85
86
87
88
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 80

def chord_note
  token = cursor.advance
  raise cursor.unsupported_token(token) if token.type == :unsupported
  raise cursor.error(%(Unexpected token "#{token.lexeme}" inside a chord), token) unless token.type == :note
  raise cursor.error("Chord notes cannot carry durations", token) if token.duration

  reject_multiplier(token)
  token
end

#located(command) ⇒ Object (private)

A key or meter reader rejects a token without knowing where in the document it came from, so an error that carries no line is given the line of the command that introduced it.



99
100
101
102
103
104
105
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 99

def located(command)
  yield
rescue ParseError => parse_error
  raise parse_error if parse_error.line_number

  raise cursor.error(parse_error.message, command)
end

#read_bar_check(context) ⇒ Object



49
50
51
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 49

def read_bar_check(context)
  context.stream.bar_check(cursor.advance.line)
end

#read_chord(context) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 33

def read_chord(context)
  opener = cursor.advance
  notes = []
  notes << chord_note until cursor.peek.type == :close_chord
  closer = cursor.advance
  raise cursor.error("Empty chord", opener) if notes.empty?

  reject_multiplier(closer)
  pitches = readers.current.chord_pitches(notes)
  context.stream.add_note(pitches, duration_reader.rhythmic_value(closer), opener.line)
end

#read_clef ⇒ Object



68
69
70
71
72
73
74
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 68

def read_clef
  command = cursor.advance
  token = cursor.advance
  return if token && (%i[word string].include?(token.type) || (token.type == :note && token.duration.nil?))

  raise cursor.error("\\clef expects a clef name", token || command)
end

#read_key(context) ⇒ Object



53
54
55
56
57
58
59
60
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 53

def read_key(context)
  command = cursor.advance
  pitch_token = cursor.advance
  mode_token = cursor.advance
  located(command) do
    context.stream.change_key_signature(KeyReader.key_signature(pitch_token, mode_token), command.line)
  end
end

#read_note(context) ⇒ Object



15
16
17
18
19
20
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 15

def read_note(context)
  token = cursor.advance
  reject_multiplier(token)
  pitch = readers.current.pitch(token)
  context.stream.add_note([pitch], duration_reader.rhythmic_value(token), token.line)
end

#read_rest(context) ⇒ Object



22
23
24
25
26
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 22

def read_rest(context)
  token = cursor.advance
  reject_multiplier(token)
  context.stream.add_rest(duration_reader.rhythmic_value(token), token.line)
end

#read_tie(context) ⇒ Object



45
46
47
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 45

def read_tie(context)
  context.stream.open_tie(cursor.advance.line)
end

#read_time(context) ⇒ Object



62
63
64
65
66
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 62

def read_time(context)
  command = cursor.advance
  meter_token = cursor.advance
  located(command) { context.stream.change_meter(MeterReader.meter(meter_token), command.line) }
end

#read_whole_bar_rest(context) ⇒ Object



28
29
30
31
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 28

def read_whole_bar_rest(context)
  token = cursor.advance
  context.stream.add_whole_bar_rest(duration_reader.whole_bar_fraction(token), token.line)
end

#reject_multiplier(token) ⇒ Object (private)



90
91
92
93
94
# File 'lib/head_music/notation/lily_pond/music_item_reader.rb', line 90

def reject_multiplier(token)
  return unless token.multiplier

  raise cursor.unsupported("Duration multipliers on notes and rests are not supported", token)
end