Class: HeadMusic::Notation::LilyPond::ContextReader

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

Overview

Reads the declaration of a Staff or Voice context — \new, the optional context name, and the \with block that can name the instrument — and the rules about where a context may appear. The music inside it is read by the MusicReader that opened this one.

Constant Summary collapse

CONTEXT_TYPES =
%w[Staff Voice].freeze
WITH_FIELDS =
%w[instrumentName].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cursor, document, music) ⇒ ContextReader

Returns a new instance of ContextReader.



11
12
13
14
15
# File 'lib/head_music/notation/lily_pond/context_reader.rb', line 11

def initialize(cursor, document, music)
  @cursor = cursor
  @document = document
  @music = music
end

Instance Attribute Details

#cursor ⇒ Object (readonly, private)

Returns the value of attribute cursor.



59
60
61
# File 'lib/head_music/notation/lily_pond/context_reader.rb', line 59

def cursor
  @cursor
end

#document ⇒ Object (readonly, private)

Returns the value of attribute document.



59
60
61
# File 'lib/head_music/notation/lily_pond/context_reader.rb', line 59

def document
  @document
end

#music ⇒ Object (readonly, private)

Returns the value of attribute music.



59
60
61
# File 'lib/head_music/notation/lily_pond/context_reader.rb', line 59

def music
  @music
end

Instance Method Details

#read_new(context) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/head_music/notation/lily_pond/context_reader.rb', line 17

def read_new(context)
  opener = cursor.advance
  type = cursor.expect(:word, "\\new expects a context type")
  unless CONTEXT_TYPES.include?(type.lexeme)
    raise cursor.unsupported(%(\\new #{type.lexeme} contexts are not supported), type)
  end

  skip_context_name
  child = VoiceContext.new(document, role(context, type), explicit: true)
  context.children += 1
  music.nested(opener) { music.read_expression(child) }
  child.close
end

#read_parallel_item(context) ⇒ Object

Each parallel item must open a context; parallel bare music is voice separation the model cannot represent.



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

def read_parallel_item(context)
  token = cursor.peek
  raise cursor.unsupported_token(token) if token.type == :unsupported

  case token.type == :command && token.lexeme
  when "new" then read_new(context)
  when "relative" then music.read_relative { read_parallel_item(context) }
  when "absolute" then music.read_absolute { read_parallel_item(context) }
  else raise cursor.unsupported("Simultaneous music without \\new contexts is not supported", token)
  end
end

#read_sequential_new(context) ⇒ Object

LilyPond starts a context where the sequence around it has reached, but a stream always starts at bar one, so a context inside a sequence is only placed correctly when it is the whole of that sequence.



48
49
50
51
52
53
54
55
# File 'lib/head_music/notation/lily_pond/context_reader.rb', line 48

def read_sequential_new(context)
  raise cursor.unsupported("A \\new context that follows music in a sequence is not supported", cursor.peek) if context.stream.music?

  read_new(context)
  return if cursor.peek&.type == :close_brace

  raise cursor.unsupported("Music that follows a \\new context in a sequence is not supported", cursor.peek)
end

#role(context, type) ⇒ Object (private)

A Voice with no name of its own belongs to the staff that holds it.



62
63
64
# File 'lib/head_music/notation/lily_pond/context_reader.rb', line 62

def role(context, type)
  with_role || (context.role if type.lexeme == "Voice")
end

#skip_context_name ⇒ Object (private)



66
67
68
69
70
71
# File 'lib/head_music/notation/lily_pond/context_reader.rb', line 66

def skip_context_name
  return unless cursor.peek&.type == :equals

  cursor.advance
  cursor.expect(:string, "A context name must be a quoted string")
end

#with_role ⇒ Object (private)



73
74
75
76
77
78
79
80
81
82
83
# File 'lib/head_music/notation/lily_pond/context_reader.rb', line 73

def with_role
  return unless cursor.peek&.type == :command && cursor.peek.lexeme == "with"

  cursor.advance
  cursor.expect(:open_brace, "\\with expects a block")
  role = nil
  AssignmentReader.new(cursor, "\\with", WITH_FIELDS).read do |name, value|
    role = value if name == "instrumentName"
  end
  role
end