lydown 0.7.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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