lydown 0.7.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +25 -14
  3. data/bin/lydown +1 -122
  4. data/lib/lydown.rb +3 -3
  5. data/lib/lydown/cli.rb +4 -0
  6. data/lib/lydown/cli/commands.rb +98 -0
  7. data/lib/lydown/cli/compiler.rb +11 -14
  8. data/lib/lydown/cli/diff.rb +3 -3
  9. data/lib/lydown/cli/output.rb +18 -0
  10. data/lib/lydown/cli/proofing.rb +8 -6
  11. data/lib/lydown/cli/support.rb +34 -0
  12. data/lib/lydown/cli/translation.rb +73 -0
  13. data/lib/lydown/core_ext.rb +1 -1
  14. data/lib/lydown/lilypond.rb +49 -11
  15. data/lib/lydown/parsing.rb +37 -6
  16. data/lib/lydown/parsing/nodes.rb +57 -56
  17. data/lib/lydown/rendering.rb +0 -9
  18. data/lib/lydown/rendering/base.rb +2 -2
  19. data/lib/lydown/rendering/command.rb +2 -2
  20. data/lib/lydown/rendering/comments.rb +1 -1
  21. data/lib/lydown/rendering/figures.rb +14 -14
  22. data/lib/lydown/rendering/lib.ly +22 -0
  23. data/lib/lydown/rendering/lyrics.rb +2 -2
  24. data/lib/lydown/rendering/movement.rb +2 -2
  25. data/lib/lydown/rendering/music.rb +46 -46
  26. data/lib/lydown/rendering/notes.rb +149 -70
  27. data/lib/lydown/rendering/settings.rb +21 -16
  28. data/lib/lydown/rendering/skipping.rb +1 -1
  29. data/lib/lydown/rendering/source_ref.rb +4 -4
  30. data/lib/lydown/rendering/staff.rb +8 -4
  31. data/lib/lydown/rendering/voices.rb +9 -9
  32. data/lib/lydown/templates.rb +9 -0
  33. data/lib/lydown/templates/lilypond_doc.erb +1 -1
  34. data/lib/lydown/templates/movement.erb +9 -1
  35. data/lib/lydown/templates/part.erb +12 -3
  36. data/lib/lydown/translation.rb +17 -0
  37. data/lib/lydown/translation/ripple.rb +30 -0
  38. data/lib/lydown/translation/ripple/nodes.rb +191 -0
  39. data/lib/lydown/translation/ripple/ripple.treetop +100 -0
  40. data/lib/lydown/version.rb +1 -1
  41. data/lib/lydown/work.rb +144 -198
  42. data/lib/lydown/work_context.rb +152 -0
  43. metadata +11 -2
@@ -38,15 +38,6 @@ module Lydown::Rendering
38
38
  end
39
39
  end
40
40
  end
41
-
42
- class Base
43
- def initialize(event, work, stream, idx)
44
- @event = event
45
- @work = work
46
- @stream = stream
47
- @idx = idx
48
- end
49
- end
50
41
  end
51
42
 
52
43
  LY_LIB_DIR = File.join(File.dirname(__FILE__), 'rendering')
@@ -1,8 +1,8 @@
1
1
  module Lydown::Rendering
2
2
  class Base
3
- def initialize(event, work, stream, idx)
3
+ def initialize(event, context, stream, idx)
4
4
  @event = event
5
- @work = work
5
+ @context = context
6
6
  @stream = stream
7
7
  @idx = idx
8
8
  end
@@ -3,12 +3,12 @@ module Lydown::Rendering
3
3
  include Notes
4
4
 
5
5
  def translate
6
- if @work['process/duration_macro']
6
+ if @context['process/duration_macro']
7
7
  add_macro_event(@event[:raw] || cmd_to_lydown(@event))
8
8
  else
9
9
  once = @event[:once] ? '\once ' : ''
10
10
  cmd = "#{once}\\#{@event[:key]} #{(@event[:arguments] || []).join(' ')} "
11
- @work.emit(:music, cmd)
11
+ @context.emit(:music, cmd)
12
12
  end
13
13
  end
14
14
 
@@ -1,7 +1,7 @@
1
1
  module Lydown::Rendering
2
2
  class Comment < Base
3
3
  def translate
4
- @work.emit(:music, "\n%{#{@event[:content]}%}\n")
4
+ @context.emit(:music, "\n%{#{@event[:content]}%}\n")
5
5
  end
6
6
  end
7
7
  end
@@ -1,37 +1,37 @@
1
1
  module Lydown::Rendering
2
2
  module Figures
3
3
  def add_figures(figures, value)
4
- if @work['process/running_values']
5
- @work['process/running_values'].each do |v|
4
+ if @context['process/running_values']
5
+ @context['process/running_values'].each do |v|
6
6
  silence = "s"
7
- if v != @work['process/last_figures_value']
7
+ if v != @context['process/last_figures_value']
8
8
  silence << v
9
- @work['process/last_figures_value'] = v
9
+ @context['process/last_figures_value'] = v
10
10
  end
11
- @work.emit(:figures, "#{silence} ")
11
+ @context.emit(:figures, "#{silence} ")
12
12
  end
13
- @work['process/running_values'] = []
13
+ @context['process/running_values'] = []
14
14
  end
15
15
 
16
16
  figures = lilypond_figures(figures)
17
- if value != @work['process/last_figures_value']
17
+ if value != @context['process/last_figures_value']
18
18
  figures << value
19
- @work['process/last_figures_value'] = value
19
+ @context['process/last_figures_value'] = value
20
20
  end
21
21
 
22
- @work.emit(:figures, "#{figures} ")
23
- @work.emit(:figures, EXTENDERS_ON) if @event[:figure_extenders_on]
24
- @work.emit(:figures, EXTENDERS_OFF) if @event[:figure_extenders_off]
22
+ @context.emit(:figures, "#{figures} ")
23
+ @context.emit(:figures, EXTENDERS_ON) if @event[:figure_extenders_on]
24
+ @context.emit(:figures, EXTENDERS_OFF) if @event[:figure_extenders_off]
25
25
  end
26
26
 
27
27
  def add_stand_alone_figures(figures)
28
- if @work['process/running_values']
28
+ if @context['process/running_values']
29
29
  # for stand alone figures, we regard the stand alone figure as being
30
30
  # aligned to the last note. Therefore we pop its value from
31
31
  # running_values array.
32
- @work['process/running_values'].pop
32
+ @context['process/running_values'].pop
33
33
  end
34
- value = @work['process/figures_duration_value'] || @work['process/last_value']
34
+ value = @context['process/figures_duration_value'] || @context['process/last_value']
35
35
  add_figures(figures, value)
36
36
  end
37
37
 
@@ -1,3 +1,18 @@
1
+ \header {
2
+ tagline = \markup {
3
+ Engraved using \bold {
4
+ \with-url #"http://github.com/ciconia/lydown" {
5
+ Lydown
6
+ }
7
+ }
8
+ and \bold {
9
+ \with-url #"http://lilypond.org/" {
10
+ Lilypond
11
+ }
12
+ }
13
+ }
14
+ }
15
+
1
16
  segno = {
2
17
  \once \override Score.RehearsalMark #'font-size = #-2
3
18
  \mark \markup { \musicglyph #"scripts.segno" }
@@ -48,6 +63,13 @@ ficta = {
48
63
  \once \set suggestAccidentals = ##t
49
64
  }
50
65
 
66
+ prallupbefore = {
67
+ \mark\markup {
68
+ \musicglyph #"scripts.prallup"
69
+ \hspace #1
70
+ }
71
+ }
72
+
51
73
  %{
52
74
  http://www.lilypond.org/doc/v2.18/Documentation/snippets/editorial-annotations#editorial-annotations-adding-links-to-objects
53
75
  %}
@@ -4,9 +4,9 @@ module Lydown::Rendering
4
4
  value = lilypond_lyrics(@event[:content])
5
5
 
6
6
  lyrics_idx = @event[:stream_index] || 1
7
- voice = @work['process/voice_selector'] || 'voice1'
7
+ voice = @context['process/voice_selector'] || 'voice1'
8
8
 
9
- @work.emit("lyrics/#{voice}/#{lyrics_idx}", value, ' ')
9
+ @context.emit("lyrics/#{voice}/#{lyrics_idx}", value, ' ')
10
10
  end
11
11
 
12
12
  def lilypond_lyrics(lyrics)
@@ -1,6 +1,6 @@
1
1
  module Lydown::Rendering
2
2
  module Movement
3
- def self.movement_title(work, name)
3
+ def self.movement_title(context, name)
4
4
  return nil if name.nil? || name.empty?
5
5
 
6
6
  if name =~ /^(?:([0-9]+)([a-z]*))\-(.+)$/
@@ -9,7 +9,7 @@ module Lydown::Rendering
9
9
  title = name
10
10
  end
11
11
 
12
- if work["movements/#{name}/parts"].empty?
12
+ if context["movements/#{name}/parts"].empty?
13
13
  title += " - tacet"
14
14
  end
15
15
 
@@ -9,80 +9,80 @@ module Lydown::Rendering
9
9
 
10
10
  class Duration < Base
11
11
  def translate
12
- Notes.cleanup_duration_macro(@work)
12
+ Notes.cleanup_duration_macro(@context)
13
13
 
14
14
  # close tuplet braces
15
- if @work['process/tuplet_mode']
16
- TupletDuration.emit_tuplet_end(@work)
17
- @work['process/tuplet_mode'] = nil
15
+ if @context['process/tuplet_mode']
16
+ TupletDuration.emit_tuplet_end(@context)
17
+ @context['process/tuplet_mode'] = nil
18
18
  end
19
19
 
20
- if @work['process/grace_mode']
21
- Grace.emit_grace_end(@work)
22
- @work['process/grace_mode'] = nil
20
+ if @context['process/grace_mode']
21
+ Grace.emit_grace_end(@context)
22
+ @context['process/grace_mode'] = nil
23
23
  end
24
24
 
25
25
  value = @event[:value].sub(/^[0-9]+/) {|m| LILYPOND_DURATIONS[m] || m}
26
26
 
27
27
  if next_event && next_event[:type] == :stand_alone_figures
28
- @work['process/figures_duration_value'] = value
28
+ @context['process/figures_duration_value'] = value
29
29
  else
30
- @work['process/duration_values'] = [value]
31
- @work['process/tuplet_mode'] = nil
32
- @work['process/duration_macro'] = nil unless @work['process/macro_group']
30
+ @context['process/duration_values'] = [value]
31
+ @context['process/tuplet_mode'] = nil
32
+ @context['process/duration_macro'] = nil unless @context['process/macro_group']
33
33
  end
34
34
  end
35
35
 
36
36
  end
37
37
 
38
38
  class TupletDuration < Base
39
- def self.emit_tuplet_end(work)
40
- work.emit(:music, '} ')
39
+ def self.emit_tuplet_end(context)
40
+ context.emit(:music, '} ')
41
41
  end
42
42
 
43
43
  def translate
44
- Notes.cleanup_duration_macro(@work)
44
+ Notes.cleanup_duration_macro(@context)
45
45
 
46
46
  # close tuplet braces
47
- if @work['process/tuplet_mode']
48
- TupletDuration.emit_tuplet_end(@work)
49
- @work['process/tuplet_mode'] = nil
47
+ if @context['process/tuplet_mode']
48
+ TupletDuration.emit_tuplet_end(@context)
49
+ @context['process/tuplet_mode'] = nil
50
50
  end
51
51
 
52
52
  if next_event && next_event[:type] == :stand_alone_figures
53
- @work['process/figures_duration_value'] = "#{@event[:value]}*#{@event[:fraction]}"
53
+ @context['process/figures_duration_value'] = "#{@event[:value]}*#{@event[:fraction]}"
54
54
  else
55
55
  value = LILYPOND_DURATIONS[@event[:value]] || @event[:value]
56
56
 
57
- @work['process/duration_values'] = [value]
58
- @work['process/last_value'] = nil
59
- @work['process/tuplet_mode'] = true
57
+ @context['process/duration_values'] = [value]
58
+ @context['process/last_value'] = nil
59
+ @context['process/tuplet_mode'] = true
60
60
 
61
61
  group_value = value.to_i / @event[:group_length].to_i
62
- @work.emit(:music, "\\tuplet #{@event[:fraction]} #{group_value} { ")
62
+ @context.emit(:music, "\\tuplet #{@event[:fraction]} #{group_value} { ")
63
63
  end
64
64
  end
65
65
  end
66
66
 
67
67
  class Grace < Base
68
- def self.emit_grace_end(work)
69
- work.emit(:music, '} ')
68
+ def self.emit_grace_end(context)
69
+ context.emit(:music, '} ')
70
70
  end
71
71
 
72
72
  def translate
73
73
  # close tuplet braces
74
- if @work['process/grace_mode']
75
- Grace.emit_grace_end(@work)
76
- @work['process/grace_mode'] = nil
74
+ if @context['process/grace_mode']
75
+ Grace.emit_grace_end(@context)
76
+ @context['process/grace_mode'] = nil
77
77
  end
78
78
 
79
79
  value = LILYPOND_DURATIONS[@event[:value]] || @event[:value]
80
80
 
81
- @work['process/duration_values'] = [value]
82
- @work['process/last_value'] = nil
83
- @work['process/grace_mode'] = true
81
+ @context['process/duration_values'] = [value]
82
+ @context['process/last_value'] = nil
83
+ @context['process/grace_mode'] = true
84
84
 
85
- @work.emit(:music, "\\#{@event[:kind]} { ")
85
+ @context.emit(:music, "\\#{@event[:kind]} { ")
86
86
  end
87
87
  end
88
88
 
@@ -141,7 +141,7 @@ module Lydown::Rendering
141
141
 
142
142
  class BeamOpen < Base
143
143
  def translate
144
- @work['process/open_beam'] = true
144
+ @context['process/open_beam'] = true
145
145
  end
146
146
  end
147
147
 
@@ -152,7 +152,7 @@ module Lydown::Rendering
152
152
 
153
153
  class SlurOpen < Base
154
154
  def translate
155
- @work['process/open_slur'] = true
155
+ @context['process/open_slur'] = true
156
156
  end
157
157
  end
158
158
 
@@ -161,7 +161,7 @@ module Lydown::Rendering
161
161
 
162
162
  class Tie < Base
163
163
  def translate
164
- @work.emit(:music, '~ ')
164
+ @context.emit(:music, '~ ')
165
165
  end
166
166
  end
167
167
 
@@ -169,8 +169,8 @@ module Lydown::Rendering
169
169
  include Notes
170
170
 
171
171
  def translate
172
- note_head = @work['process/last_note_head']
173
- @work.emit(:music, '~ ')
172
+ note_head = @context['process/last_note_head']
173
+ @context.emit(:music, '~ ')
174
174
  add_note({head: note_head})
175
175
  end
176
176
  end
@@ -194,17 +194,17 @@ module Lydown::Rendering
194
194
  translate_expressions
195
195
 
196
196
  if @event[:multiplier]
197
- value = full_bar_value(@work[:time])
198
- @work['process/duration_macro'] = nil unless @work['process/macro_group']
197
+ value = full_bar_value(@context[:time])
198
+ @context['process/duration_macro'] = nil unless @context['process/macro_group']
199
199
  if value
200
200
  @event[:rest_value] = "#{value}*#{@event[:multiplier]}"
201
201
  @event[:head] = "#{@event[:head]}#{@event[:rest_value]}"
202
202
  else
203
- @event[:head] = "#{@event[:head]}#{@event[:multiplier]}*#{@work[:time]}"
203
+ @event[:head] = "#{@event[:head]}#{@event[:multiplier]}*#{@context[:time]}"
204
204
  end
205
205
  # reset the last value so the next note will be rendered with its value
206
- @work['process/last_value'] = nil
207
- @work['process/duration_values'] = []
206
+ @context['process/last_value'] = nil
207
+ @context['process/duration_values'] = []
208
208
  end
209
209
 
210
210
  add_note(@event)
@@ -230,22 +230,22 @@ module Lydown::Rendering
230
230
 
231
231
  class DurationMacro < Base
232
232
  def translate
233
- Notes.cleanup_duration_macro(@work)
233
+ Notes.cleanup_duration_macro(@context)
234
234
 
235
235
  if @event[:macro] =~ /^[a-zA-Z_]/
236
- macro = @work['macros'][@event[:macro]]
236
+ macro = @context['macros'][@event[:macro]]
237
237
  if macro
238
238
  if macro =~ /^\{(.+)\}$/
239
239
  macro = $1
240
240
  end
241
241
  # replace the repeating note placeholder with another sign in order to
242
242
  # avoid mixing up with repeating notes from outside the macro
243
- @work['process/duration_macro'] = macro.gsub('@', '∞')
243
+ @context['process/duration_macro'] = macro.gsub('@', '∞')
244
244
  else
245
245
  raise LydownError, "Unknown macro #{@event[:macro]}"
246
246
  end
247
247
  else
248
- @work['process/duration_macro'] = @event[:macro]
248
+ @context['process/duration_macro'] = @event[:macro]
249
249
  end
250
250
  end
251
251
  end
@@ -254,7 +254,7 @@ module Lydown::Rendering
254
254
  def translate
255
255
  barline = @event[:barline]
256
256
  barline = '' if barline == '?|'
257
- @work.emit(:music, "\\bar \"#{barline}\" ")
257
+ @context.emit(:music, "\\bar \"#{barline}\" ")
258
258
  end
259
259
  end
260
260
  end
@@ -59,85 +59,156 @@ module Lydown::Rendering
59
59
 
60
60
  note + (value >= 0 ? 'is' * value : 'es' * -value)
61
61
  end
62
+
63
+ def self.chromatic_to_diatonic(note, key_signature = 'c major')
64
+ note =~ /([a-g])([\+\-]*)/
65
+ diatonic_note = $1
66
+ chromatic_value = $2.count('+') - $2.count('-')
67
+
68
+ key_accidentals = accidentals_for_key_signature(key_signature)
69
+ diatonic_value = key_accidentals[diatonic_note] || 0
70
+ value = chromatic_value - diatonic_value
71
+
72
+ "#{diatonic_note}#{value >= 0 ? '+' * value : '-' * -value}"
73
+ end
62
74
 
63
75
  # Takes into account the accidentals mode
64
- def self.translate_note_name(work, note)
65
- if work[:accidentals] == 'manual'
76
+ def self.translate_note_name(context, note)
77
+ if context[:accidentals] == 'manual'
66
78
  key = 'c major'
67
79
  else
68
- key = work[:key]
80
+ key = context[:key]
69
81
  end
70
82
  lilypond_note_name(note, key)
71
83
  end
72
84
  end
85
+
86
+ module Octaves
87
+ DIATONICS = %w{a b c d e f g}
88
+
89
+ # calculates the octave markers needed to put a first note in the right
90
+ # octave. In lydown, octaves are relative (i.e. lilypond's relative mode).
91
+ # But the first note gives the octave to start on, rather than a relative
92
+ # note to c (or any other reference note).
93
+ #
94
+ # In that manner, d' is d above middle c, g'' is g an octave and fifth
95
+ # above middle c, a is a a below middle c, and eß, is great e flat.
96
+ #
97
+ # The return value is a string with octave markers for relative mode,
98
+ # based on the refence note
99
+ def self.relative_octave(note, ref_note = 'c')
100
+ note_diatonic, ref_diatonic = note[0], ref_note[0]
101
+ raise LydownError, "Invalid note #{note}" unless DIATONICS.index(note_diatonic)
102
+ raise LydownError, "Invalid reference note #{ref_note}" unless DIATONICS.index(ref_diatonic)
103
+
104
+ # calculate diatonic interval
105
+ note_array = DIATONICS.rotate(DIATONICS.index(ref_diatonic))
106
+ interval = note_array.index(note_diatonic)
107
+
108
+ # calculate octave interval and
109
+ octave_value = note.count("'") - note.count(',')
110
+ ref_value = ref_note.count("'") - ref_note.count(',')
111
+ octave_interval = octave_value - ref_value
112
+ octave_interval += 1 if interval >= 4
113
+
114
+ # generate octave markers
115
+ octave_interval >= 0 ? "'" * octave_interval : "," * -octave_interval
116
+ end
117
+
118
+ def self.absolute_octave(note, ref_note = 'c')
119
+ note_diatonic, ref_diatonic = note[0], ref_note[0]
120
+ raise LydownError, "Invalid note #{note}" unless DIATONICS.index(note_diatonic)
121
+ raise LydownError, "Invalid reference note #{ref_note}" unless DIATONICS.index(ref_diatonic)
122
+
123
+ # calculate diatonic interval
124
+ note_array = DIATONICS.rotate(DIATONICS.index(ref_diatonic))
125
+ interval = note_array.index(note_diatonic)
126
+
127
+ # calculate octave interval and
128
+ note_value = note.count("'") - note.count(',')
129
+ ref_value = ref_note.count("'") - ref_note.count(',')
130
+ octave_interval = ref_value + note_value
131
+ octave_interval -= 1 if interval >= 4
132
+
133
+ # generate octave markers
134
+ octave_interval >= 0 ? "'" * octave_interval : "," * -octave_interval
135
+ end
136
+ end
73
137
 
74
138
  module Notes
75
139
  include Lydown::Rendering::Figures
76
140
 
77
141
  def add_note(event, options = {})
78
- return add_macro_note(event) if @work['process/duration_macro']
142
+ return add_macro_note(event) if @context['process/duration_macro']
143
+
144
+ # calculate relative octave markers for first note
145
+ unless @context['process/first_note'] || event[:head] =~ /^[rsR]/
146
+ note = event[:head] + (event[:octave] || '')
147
+ event[:octave] = Lydown::Rendering::Octaves.relative_octave(note)
148
+ @context['process/first_note'] = note
149
+ end
79
150
 
80
- if @event[:head] == '@'
151
+ if event[:head] == '@'
81
152
  # replace repeating note head
82
- @event[:head] = @work['process/last_note_head']
153
+ event[:head] = @context['process/last_note_head']
83
154
  else
84
- @work['process/last_note_head'] = event[:head]
155
+ @context['process/last_note_head'] = event[:head]
85
156
  end
86
157
 
87
- value = @work['process/duration_values'].first
88
- @work['process/duration_values'].rotate!
158
+ value = @context['process/duration_values'].first
159
+ @context['process/duration_values'].rotate!
89
160
 
90
161
  add_figures(event[:figures], value) if event[:figures]
91
162
 
92
163
  # push value into running values accumulator. This is used to synthesize
93
164
  # the bass figures durations.
94
165
  unless event[:figures]
95
- @work['process/running_values'] ||= []
166
+ @context['process/running_values'] ||= []
96
167
  if event[:rest_value]
97
- @work['process/running_values'] << event[:rest_value]
168
+ @context['process/running_values'] << event[:rest_value]
98
169
  else
99
- @work['process/running_values'] << value
170
+ @context['process/running_values'] << value
100
171
  end
101
172
  end
102
173
 
103
174
  # only add the value if different than the last used
104
- if options[:no_value] || (value == @work['process/last_value'])
175
+ if options[:no_value] || (value == @context['process/last_value'])
105
176
  value = ''
106
177
  else
107
- @work['process/last_value'] = value
178
+ @context['process/last_value'] = value
108
179
  end
109
-
110
- if event[:line] && @work['options/proof_mode']
111
- @work.emit(event[:stream] || :music, note_event_url_link(event))
112
- # @work.emit(event[:stream] || :music, "%{#{event[:line]}:#{event[:column]}%}")
180
+
181
+ if event[:line] && @context['options/proof_mode']
182
+ @context.emit(event[:stream] || :music, note_event_url_link(event))
183
+ # @context.emit(event[:stream] || :music, "%{#{event[:line]}:#{event[:column]}%}")
113
184
  end
114
185
 
115
186
  code = lilypond_note(event, options.merge(value: value))
116
- @work.emit(event[:stream] || :music, code)
187
+ @context.emit(event[:stream] || :music, code)
117
188
  end
118
189
 
119
190
  def add_chord(event, options = {})
120
- value = @work['process/duration_values'].first
121
- @work['process/duration_values'].rotate!
191
+ value = @context['process/duration_values'].first
192
+ @context['process/duration_values'].rotate!
122
193
 
123
194
  add_figures(event[:figures], value) if event[:figures]
124
195
 
125
196
  # push value into running values accumulator. This is used to synthesize
126
197
  # the bass figures durations.
127
198
  unless event[:figures]
128
- @work['process/running_values'] ||= []
199
+ @context['process/running_values'] ||= []
129
200
  if event[:rest_value]
130
- @work['process/running_values'] << event[:rest_value]
201
+ @context['process/running_values'] << event[:rest_value]
131
202
  else
132
- @work['process/running_values'] << value
203
+ @context['process/running_values'] << value
133
204
  end
134
205
  end
135
206
 
136
207
  # only add the value if different than the last used
137
- if value == @work['process/last_value']
208
+ if value == @context['process/last_value']
138
209
  value = ''
139
210
  else
140
- @work['process/last_value'] = value
211
+ @context['process/last_value'] = value
141
212
  end
142
213
 
143
214
  notes = event[:notes].map do |note|
@@ -145,11 +216,11 @@ module Lydown::Rendering
145
216
  end
146
217
 
147
218
  options = options.merge(value: value)
148
- @work.emit(event[:stream] || :music, lilypond_chord(event, notes, options))
219
+ @context.emit(event[:stream] || :music, lilypond_chord(event, notes, options))
149
220
  end
150
221
 
151
222
  def lilypond_note(event, options = {})
152
- head = Accidentals.translate_note_name(@work, event[:head])
223
+ head = Accidentals.translate_note_name(@context, event[:head])
153
224
  if options[:head_only]
154
225
  head
155
226
  else
@@ -187,13 +258,13 @@ module Lydown::Rendering
187
258
 
188
259
  def lilypond_phrasing(event)
189
260
  phrasing = ''
190
- if @work['process/open_beam']
261
+ if @context['process/open_beam']
191
262
  phrasing << '['
192
- @work['process/open_beam'] = nil
263
+ @context['process/open_beam'] = nil
193
264
  end
194
- if @work['process/open_slur']
265
+ if @context['process/open_slur']
195
266
  phrasing << '('
196
- @work['process/open_slur'] = nil
267
+ @context['process/open_slur'] = nil
197
268
  end
198
269
  phrasing << ']' if event[:beam_close]
199
270
  phrasing << ')' if event[:slur_close]
@@ -202,13 +273,13 @@ module Lydown::Rendering
202
273
 
203
274
  def lydown_phrasing_open(event)
204
275
  phrasing = ''
205
- if @work['process/open_beam']
276
+ if @context['process/open_beam']
206
277
  phrasing << '['
207
- @work['process/open_beam'] = nil
278
+ @context['process/open_beam'] = nil
208
279
  end
209
- if @work['process/open_slur']
280
+ if @context['process/open_slur']
210
281
  phrasing << '('
211
- @work['process/open_slur'] = nil
282
+ @context['process/open_slur'] = nil
212
283
  end
213
284
  phrasing
214
285
  end
@@ -221,7 +292,7 @@ module Lydown::Rendering
221
292
  end
222
293
 
223
294
  def add_macro_note(event)
224
- @work['process/macro_group'] ||= @work['process/duration_macro'].clone
295
+ @context['process/macro_group'] ||= @context['process/duration_macro'].clone
225
296
  underscore_count = 0
226
297
 
227
298
  lydown_note = "{%d:%d}%s%s%s%s%s%s%s" % [
@@ -233,9 +304,9 @@ module Lydown::Rendering
233
304
  event[:figures] ? "<#{event[:figures].join}>" : '',
234
305
  event[:expressions] ? event[:expressions].join + ' ' : ''
235
306
  ]
236
-
307
+
237
308
  # replace place holder and repeaters in macro group with actual note
238
- @work['process/macro_group'].gsub!(/[_∞]/) do |match|
309
+ @context['process/macro_group'].gsub!(/[_∞]/) do |match|
239
310
  case match
240
311
  when '_'
241
312
  underscore_count += 1
@@ -245,52 +316,60 @@ module Lydown::Rendering
245
316
  end
246
317
  end
247
318
 
248
- # if group is complete, compile it just like regular code
249
- unless @work['process/macro_group'].include?('_')
250
- code = LydownParser.parse(@work['process/macro_group'], {
251
- filename: event[:filename],
252
- source: event[:source]
253
- })
254
-
255
- # stash macro
256
- macro = @work['process/duration_macro']
257
- @work['process/duration_macro'] = nil
258
- @work['process/macro_group'] = nil
259
-
260
- @work.process(code, no_reset: true)
319
+ # keep filename and source in order to ensure source references are kept
320
+ # correct
321
+ @context['process/macro_filename'] = event[:filename]
322
+ @context['process/macro_source'] = event[:source]
323
+
324
+ # increment group note count
325
+ @context['process/macro_group_note_count'] ||= 0
326
+ @context['process/macro_group_note_count'] += 1
261
327
 
262
- # restore macro
263
- @work['process/duration_macro'] = macro
328
+ # if group is complete, compile it just like regular code
329
+ unless @context['process/macro_group'].include?('_')
330
+ Notes.add_duration_macro_group(@context, @context['process/macro_group'])
264
331
  end
265
332
  end
266
333
 
267
334
  # emits the current macro group up to the first placeholder character.
268
335
  # this method is called
269
- def self.cleanup_duration_macro(work)
270
- return unless work['process/macro_group']
336
+ def self.cleanup_duration_macro(context)
337
+ return unless context['process/macro_group_note_count'] &&
338
+ context['process/macro_group_note_count'] > 0
271
339
 
272
340
  # truncate macro group up until first placeholder
273
- group = work['process/macro_group'].sub(/_.*$/, '')
274
-
275
- # stash macro, in order to compile macro group
276
- macro = work['process/duration_macro']
277
- work['process/duration_macro'] = nil
278
- work['process/macro_group'] = nil
279
-
280
- code = LydownParser.parse(group)
281
- work.process(code, no_reset: true)
341
+ group = context['process/macro_group'].sub(/_.*$/, '')
282
342
 
343
+ # Refrain from adding
344
+ add_duration_macro_group(context, group)
345
+ end
346
+
347
+ def self.add_duration_macro_group(context, group)
348
+ opts = (context[:options] || {}).merge({
349
+ filename: context['process/macro_filename'],
350
+ source: context['process/macro_source']
351
+ }).deep!
352
+ code = LydownParser.parse_macro_group(group, opts)
353
+
354
+ # stash macro
355
+ macro = context['process/duration_macro']
356
+ context['process/duration_macro'] = nil
357
+ context['process/macro_group'] = nil
358
+ context['process/macro_group_note_count'] = nil
359
+
360
+ context.translate(code, macro_group: true)
361
+ ensure
283
362
  # restore macro
284
- work['process/duration_macro'] = macro
363
+ context['process/duration_macro'] = macro
285
364
  end
286
365
 
287
366
  def add_macro_event(code)
288
- case @work['process/macro_group']
367
+ case @context['process/macro_group']
289
368
  when nil
290
- @work['process/macro_group'] = @work['process/duration_macro'].clone
291
- @work['process/macro_group'].insert(0, " #{code} ")
369
+ @context['process/macro_group'] = @context['process/duration_macro'].clone
370
+ @context['process/macro_group'].insert(0, " #{code} ")
292
371
  when /_/
293
- @work['process/macro_group'].sub!(/([_∞])/, " #{code} \\0")
372
+ @context['process/macro_group'].sub!(/([_∞])/, " #{code} \\0")
294
373
  end
295
374
  end
296
375