lydown 0.3.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.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +357 -0
- data/bin/lydown +149 -0
- data/lib/lydown/core_ext.rb +121 -0
- data/lib/lydown/errors.rb +2 -0
- data/lib/lydown/lilypond.rb +41 -0
- data/lib/lydown/parsing/lydown.treetop +160 -0
- data/lib/lydown/parsing/nodes.rb +191 -0
- data/lib/lydown/parsing.rb +33 -0
- data/lib/lydown/rendering/base.rb +24 -0
- data/lib/lydown/rendering/comments.rb +7 -0
- data/lib/lydown/rendering/defaults.yml +185 -0
- data/lib/lydown/rendering/figures.rb +106 -0
- data/lib/lydown/rendering/lyrics.rb +14 -0
- data/lib/lydown/rendering/movement.rb +19 -0
- data/lib/lydown/rendering/music.rb +362 -0
- data/lib/lydown/rendering/settings.rb +107 -0
- data/lib/lydown/rendering/staff.rb +83 -0
- data/lib/lydown/rendering.rb +45 -0
- data/lib/lydown/templates/lilypond_doc.erb +15 -0
- data/lib/lydown/templates/movement.erb +48 -0
- data/lib/lydown/templates/part.erb +55 -0
- data/lib/lydown/templates.rb +22 -0
- data/lib/lydown/version.rb +3 -0
- data/lib/lydown/work.rb +192 -0
- data/lib/lydown.rb +11 -0
- metadata +84 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Lydown::Rendering
|
2
|
+
module Movement
|
3
|
+
def self.movement_title(work, name)
|
4
|
+
return nil if name.nil? || name.empty?
|
5
|
+
|
6
|
+
if name =~ /^(?:([0-9])+([a-z]*))\-(.+)$/
|
7
|
+
title = "#{$1.to_i}#{$2}. #{$3.capitalize}"
|
8
|
+
else
|
9
|
+
title = name
|
10
|
+
end
|
11
|
+
|
12
|
+
if work["movements/#{name}/parts"].empty?
|
13
|
+
title += " - tacet"
|
14
|
+
end
|
15
|
+
|
16
|
+
title
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,362 @@
|
|
1
|
+
require 'lydown/rendering/figures'
|
2
|
+
|
3
|
+
module Lydown::Rendering
|
4
|
+
module Accidentals
|
5
|
+
KEY_CYCLE = %w{c- g- d- a- e- b- f c g d a e b f+ c+ g+ d+ a+ e+ b+}
|
6
|
+
C_IDX = KEY_CYCLE.index('c'); A_IDX = KEY_CYCLE.index('a')
|
7
|
+
SHARPS_IDX = KEY_CYCLE.index('f+'); FLATS_IDX = KEY_CYCLE.index('b-')
|
8
|
+
KEY_ACCIDENTALS = {}
|
9
|
+
|
10
|
+
def self.accidentals_for_key_signature(signature)
|
11
|
+
KEY_ACCIDENTALS[signature] ||= calc_accidentals_for_key_signature(signature)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.calc_accidentals_for_key_signature(signature)
|
15
|
+
unless signature =~ /^([a-g][\+\-]*) (major|minor)$/
|
16
|
+
raise "Invalid key signature #{signature.inspect}"
|
17
|
+
end
|
18
|
+
|
19
|
+
key = $1; mode = $2
|
20
|
+
|
21
|
+
# calculate offset from c major / a minor
|
22
|
+
base_idx = (mode == 'major') ? C_IDX : A_IDX
|
23
|
+
offset = KEY_CYCLE.index(key) - base_idx
|
24
|
+
|
25
|
+
if offset >= 0
|
26
|
+
calc_accidentals_map(KEY_CYCLE[SHARPS_IDX, offset])
|
27
|
+
else
|
28
|
+
calc_accidentals_map(KEY_CYCLE[FLATS_IDX + offset + 1, -offset])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.calc_accidentals_map(accidentals)
|
33
|
+
accidentals.inject({}) { |h, a| h[a[0]] = (a[1] == '+') ? 1 : -1; h}
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.lilypond_note_name(note, key_signature = 'c major')
|
37
|
+
value = 0
|
38
|
+
# accidental value from note
|
39
|
+
note = note.gsub(/[\-\+]/) { |c| value += (c == '+') ? 1 : -1; '' }
|
40
|
+
# add key signature value
|
41
|
+
value += accidentals_for_key_signature(key_signature)[note] || 0
|
42
|
+
|
43
|
+
note + (value >= 0 ? 'is' * value : 'es' * -value)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Takes into account the accidentals mode
|
47
|
+
def self.translate_note_name(work, note)
|
48
|
+
if work[:accidentals] == 'manual'
|
49
|
+
key = 'c major'
|
50
|
+
else
|
51
|
+
key = work[:key]
|
52
|
+
end
|
53
|
+
lilypond_note_name(note, key)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module Notes
|
58
|
+
include Lydown::Rendering::Figures
|
59
|
+
|
60
|
+
def add_note(event)
|
61
|
+
return add_macro_note(event) if @work['process/duration_macro']
|
62
|
+
|
63
|
+
@work['process/last_note_head'] = event[:head]
|
64
|
+
|
65
|
+
value = @work['process/duration_values'].first
|
66
|
+
@work['process/duration_values'].rotate!
|
67
|
+
|
68
|
+
add_figures(event[:figures], value) if event[:figures]
|
69
|
+
|
70
|
+
# push value into running values accumulator. This is used to synthesize
|
71
|
+
# the bass figures durations.
|
72
|
+
unless event[:figures]
|
73
|
+
@work['process/running_values'] ||= []
|
74
|
+
if event[:rest_value]
|
75
|
+
@work['process/running_values'] << event[:rest_value]
|
76
|
+
else
|
77
|
+
@work['process/running_values'] << value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# only add the value if different than the last used
|
82
|
+
if value == @work['process/last_value']
|
83
|
+
value = ''
|
84
|
+
else
|
85
|
+
@work['process/last_value'] = value
|
86
|
+
end
|
87
|
+
|
88
|
+
@work.emit(event[:stream] || :music, lilypond_note(event, value: value))
|
89
|
+
end
|
90
|
+
|
91
|
+
def lilypond_note(event, options = {})
|
92
|
+
head = Accidentals.translate_note_name(@work, event[:head])
|
93
|
+
if options[:head_only]
|
94
|
+
head
|
95
|
+
else
|
96
|
+
"%s%s%s%s%s%s " % [
|
97
|
+
head,
|
98
|
+
event[:octave],
|
99
|
+
event[:accidental_flag],
|
100
|
+
options[:value],
|
101
|
+
lilypond_phrasing(event),
|
102
|
+
event[:expressions] ? event[:expressions].join : ''
|
103
|
+
]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def lilypond_phrasing(event)
|
108
|
+
phrasing = ''
|
109
|
+
if @work['process/open_beam']
|
110
|
+
phrasing << '['
|
111
|
+
@work['process/open_beam'] = nil
|
112
|
+
end
|
113
|
+
if @work['process/open_slur']
|
114
|
+
phrasing << '('
|
115
|
+
@work['process/open_slur'] = nil
|
116
|
+
end
|
117
|
+
phrasing << ']' if event[:beam_close]
|
118
|
+
phrasing << ')' if event[:slur_close]
|
119
|
+
phrasing
|
120
|
+
end
|
121
|
+
|
122
|
+
def lydown_phrasing_open(event)
|
123
|
+
phrasing = ''
|
124
|
+
if @work['process/open_beam']
|
125
|
+
phrasing << '['
|
126
|
+
@work['process/open_beam'] = nil
|
127
|
+
end
|
128
|
+
if @work['process/open_slur']
|
129
|
+
phrasing << '('
|
130
|
+
@work['process/open_slur'] = nil
|
131
|
+
end
|
132
|
+
phrasing
|
133
|
+
end
|
134
|
+
|
135
|
+
def lydown_phrasing_close(event)
|
136
|
+
phrasing = ''
|
137
|
+
phrasing << ']' if event[:beam_close]
|
138
|
+
phrasing << ')' if event[:slur_close]
|
139
|
+
phrasing
|
140
|
+
end
|
141
|
+
|
142
|
+
def add_macro_note(event)
|
143
|
+
@work['process/macro_group'] ||= @work['process/duration_macro'].clone
|
144
|
+
underscore_count = 0
|
145
|
+
|
146
|
+
lydown_note = "%s%s%s%s%s%s%s" % [
|
147
|
+
lydown_phrasing_open(event),
|
148
|
+
event[:head], event[:octave], event[:accidental_flag],
|
149
|
+
lydown_phrasing_close(event),
|
150
|
+
event[:figures] ? "<#{event[:figures].join}>" : '',
|
151
|
+
event[:expressions] ? event[:expressions].join : ''
|
152
|
+
]
|
153
|
+
|
154
|
+
# replace place holder and repeaters in macro group with actual note
|
155
|
+
@work['process/macro_group'].gsub!(/[_@]/) do |match|
|
156
|
+
case match
|
157
|
+
when '_'
|
158
|
+
underscore_count += 1
|
159
|
+
underscore_count == 1 ? lydown_note : match
|
160
|
+
when '@'
|
161
|
+
underscore_count < 2 ? event[:head] : match
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# if group is complete, compile it just like regular code
|
166
|
+
unless @work['process/macro_group'].include?('_')
|
167
|
+
# stash macro, in order to compile macro group
|
168
|
+
macro = @work['process/duration_macro']
|
169
|
+
@work['process/duration_macro'] = nil
|
170
|
+
|
171
|
+
code = LydownParser.parse(@work['process/macro_group'])
|
172
|
+
@work.process(code)
|
173
|
+
|
174
|
+
# restore macro
|
175
|
+
@work['process/duration_macro'] = macro
|
176
|
+
@work['process/macro_group'] = nil
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
LILYPOND_DURATIONS = {
|
182
|
+
'6' => '16',
|
183
|
+
'3' => '32'
|
184
|
+
}
|
185
|
+
|
186
|
+
class Duration < Base
|
187
|
+
def translate
|
188
|
+
value = @event[:value].sub(/^[0-9]+/) {|m| LILYPOND_DURATIONS[m] || m}
|
189
|
+
|
190
|
+
if next_event && next_event[:type] == :stand_alone_figures
|
191
|
+
@work['process/figures_duration_value'] = value
|
192
|
+
else
|
193
|
+
@work['process/duration_values'] = [value]
|
194
|
+
@work['process/duration_macro'] = nil unless @work['process/macro_group']
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
class Note < Base
|
200
|
+
include Notes
|
201
|
+
|
202
|
+
def translate
|
203
|
+
translate_expressions
|
204
|
+
|
205
|
+
# look ahead and see if any beam or slur closing after note
|
206
|
+
look_ahead_idx = @idx + 1
|
207
|
+
while event = @stream[look_ahead_idx]
|
208
|
+
case @stream[@idx + 1][:type]
|
209
|
+
when :beam_close
|
210
|
+
@event[:beam_close] = true
|
211
|
+
when :slur_close
|
212
|
+
@event[:slur_close] = true
|
213
|
+
else
|
214
|
+
break
|
215
|
+
end
|
216
|
+
look_ahead_idx += 1
|
217
|
+
end
|
218
|
+
|
219
|
+
add_note(@event)
|
220
|
+
end
|
221
|
+
|
222
|
+
LILYPOND_EXPRESSIONS = {
|
223
|
+
'_' => '--',
|
224
|
+
'.' => '-.',
|
225
|
+
'`' => '-!'
|
226
|
+
}
|
227
|
+
|
228
|
+
def translate_expressions
|
229
|
+
return unless @event[:expressions]
|
230
|
+
|
231
|
+
@event[:expressions] = @event[:expressions].map do |expr|
|
232
|
+
if expr =~ /^\\/
|
233
|
+
expr
|
234
|
+
elsif LILYPOND_EXPRESSIONS[expr]
|
235
|
+
LILYPOND_EXPRESSIONS[expr]
|
236
|
+
else
|
237
|
+
raise LydownError, "Invalid expression #{expr.inspect}"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
class StandAloneFigures < Base
|
244
|
+
include Notes
|
245
|
+
|
246
|
+
def translate
|
247
|
+
add_stand_alone_figures(@event[:figures])
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
class BeamOpen < Base
|
252
|
+
def translate
|
253
|
+
@work['process/open_beam'] = true
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
class BeamClose < Base
|
258
|
+
def translate
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
class SlurOpen < Base
|
263
|
+
def translate
|
264
|
+
@work['process/open_slur'] = true
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
class SlurClose < Base
|
269
|
+
end
|
270
|
+
|
271
|
+
class Tie < Base
|
272
|
+
def translate
|
273
|
+
@work.emit(:music, '~ ')
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
class ShortTie < Base
|
278
|
+
include Notes
|
279
|
+
|
280
|
+
def translate
|
281
|
+
note_head = @work['process/last_note_head']
|
282
|
+
@work.emit(:music, '~ ')
|
283
|
+
add_note({head: note_head})
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
class Rest < Base
|
288
|
+
include Notes
|
289
|
+
|
290
|
+
def full_bar_value(time)
|
291
|
+
r = Rational(time)
|
292
|
+
case r.numerator
|
293
|
+
when 1
|
294
|
+
r.denominator.to_s
|
295
|
+
when 3
|
296
|
+
"#{r.denominator / 2}."
|
297
|
+
else
|
298
|
+
nil
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def translate
|
303
|
+
if @event[:multiplier]
|
304
|
+
value = full_bar_value(@work[:time])
|
305
|
+
if value
|
306
|
+
@event[:rest_value] = "#{value}*#{@event[:multiplier]}"
|
307
|
+
@event[:head] = "#{@event[:head]}#{@event[:rest_value]}"
|
308
|
+
else
|
309
|
+
@event[:head] = "#{@event[:head]}#{@event[:multiplier]}*#{@work[:time]}"
|
310
|
+
end
|
311
|
+
# reset the last value so the next note will be rendered with its value
|
312
|
+
@work['process/last_value'] = nil
|
313
|
+
@work['process/duration_values'] = []
|
314
|
+
end
|
315
|
+
|
316
|
+
add_note(@event)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
class Silence < Base
|
321
|
+
include Notes
|
322
|
+
|
323
|
+
def translate
|
324
|
+
add_note(@event)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
class FiguresSilence < Base
|
329
|
+
include Notes
|
330
|
+
|
331
|
+
def translate
|
332
|
+
@event[:stream] = :figures
|
333
|
+
add_note(@event)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
class DurationMacro < Base
|
338
|
+
def translate
|
339
|
+
if @event[:macro] =~ /^[a-zA-Z_]/
|
340
|
+
macro = @work['macros'][@event[:macro]]
|
341
|
+
if macro
|
342
|
+
if macro =~ /^\{(.+)\}$/
|
343
|
+
macro = $1
|
344
|
+
end
|
345
|
+
@work['process/duration_macro'] = macro
|
346
|
+
else
|
347
|
+
raise LydownError, "Unknown macro #{@event[:macro]}"
|
348
|
+
end
|
349
|
+
else
|
350
|
+
@work['process/duration_macro'] = @event[:macro]
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
class Barline < Base
|
356
|
+
def translate
|
357
|
+
barline = @event[:barline]
|
358
|
+
barline = '' if barline == '?|'
|
359
|
+
@work.emit(:music, "\\bar \"#{barline}\" ")
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Lydown::Rendering
|
2
|
+
class Setting < Base
|
3
|
+
SETTING_KEYS = [
|
4
|
+
'key', 'time', 'pickup', 'clef', 'part', 'movement',
|
5
|
+
'accidentals', 'beams', 'end_barline', 'macros'
|
6
|
+
]
|
7
|
+
|
8
|
+
RENDERABLE_SETTING_KEYS = [
|
9
|
+
'key', 'time', 'clef', 'beams'
|
10
|
+
]
|
11
|
+
|
12
|
+
ALLOWED_SETTING_VALUES = {
|
13
|
+
'accidentals' => ['manual', 'auto'],
|
14
|
+
'beams' => ['manual', 'auto']
|
15
|
+
}
|
16
|
+
|
17
|
+
def translate
|
18
|
+
key = @event[:key]
|
19
|
+
value = @event[:value]
|
20
|
+
level = @event[:level] || 0
|
21
|
+
|
22
|
+
unless (level > 0) || SETTING_KEYS.include?(key)
|
23
|
+
raise Lydown, "Invalid setting (#{key})"
|
24
|
+
end
|
25
|
+
|
26
|
+
if level == 0
|
27
|
+
@work[key] = check_setting_value(key, value)
|
28
|
+
case key
|
29
|
+
when 'part'
|
30
|
+
# when changing parts we repeat the last set time and key signature
|
31
|
+
render_setting('time', @work[:time]) unless @work[:time] == '4/4'
|
32
|
+
key = @work[:key]
|
33
|
+
render_setting('key', key) unless key == 'c major'
|
34
|
+
|
35
|
+
@work.reset_context(:part)
|
36
|
+
when 'movement'
|
37
|
+
@work.reset_context(:movement)
|
38
|
+
end
|
39
|
+
|
40
|
+
if RENDERABLE_SETTING_KEYS.include?(key)
|
41
|
+
render_setting(key, value)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
# nested settings
|
45
|
+
l, path = 0, ''
|
46
|
+
while l < level
|
47
|
+
path << "#{@work['process/setting_levels'][l]}/"; l += 1
|
48
|
+
end
|
49
|
+
path << key
|
50
|
+
@work[path] = value
|
51
|
+
end
|
52
|
+
|
53
|
+
@work['process/setting_levels'] ||= {}
|
54
|
+
@work['process/setting_levels'][level] = key
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_setting_value(key, value)
|
58
|
+
if ALLOWED_SETTING_VALUES[key]
|
59
|
+
unless ALLOWED_SETTING_VALUES[key].include?(value)
|
60
|
+
raise LydownError, "Invalid value for setting #{key}: #{value.inspect}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
value
|
64
|
+
end
|
65
|
+
|
66
|
+
def render_setting(key, value)
|
67
|
+
setting = ""
|
68
|
+
case key
|
69
|
+
when 'time'
|
70
|
+
cadenza_mode = @work[:cadenza_mode]
|
71
|
+
should_cadence = value == 'unmetered'
|
72
|
+
@work[:cadenza_mode] = should_cadence
|
73
|
+
|
74
|
+
if should_cadence && !cadenza_mode
|
75
|
+
setting = "\\cadenzaOn "
|
76
|
+
elsif !should_cadence && cadenza_mode
|
77
|
+
setting = "\\cadenzaOff "
|
78
|
+
end
|
79
|
+
|
80
|
+
unless should_cadence
|
81
|
+
signature = value.sub(/[0-9]+$/) { |m| LILYPOND_DURATIONS[m] || m }
|
82
|
+
setting << "\\#{key} #{signature} "
|
83
|
+
end
|
84
|
+
when 'key'
|
85
|
+
# If the next event is a key signature, no need to emit this one
|
86
|
+
e = next_event
|
87
|
+
return if e && (e[:type] == :setting) && (e[:key] == 'key')
|
88
|
+
|
89
|
+
unless value =~ /^([a-g][\+\-]*) (major|minor)$/
|
90
|
+
raise LydownError, "Invalid key signature #{value.inspect}"
|
91
|
+
end
|
92
|
+
|
93
|
+
note = Lydown::Rendering::Accidentals.lilypond_note_name($1)
|
94
|
+
mode = $2
|
95
|
+
setting = "\\#{key} #{note} \\#{mode} "
|
96
|
+
when 'clef'
|
97
|
+
setting = "\\#{key} \"#{value}\" "
|
98
|
+
when 'beams'
|
99
|
+
setting = (value == 'manual') ? '\autoBeamOff ' : '\autoBeamOn '
|
100
|
+
else
|
101
|
+
setting = "\\#{key} #{value} "
|
102
|
+
end
|
103
|
+
|
104
|
+
@work.emit(:music, setting)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Lydown::Rendering
|
2
|
+
module Staff
|
3
|
+
def self.staff_groups(work, movement, parts)
|
4
|
+
model = work['score/order'] || movement['score/order'] || DEFAULTS['score/order']
|
5
|
+
parts_copy = parts.clone
|
6
|
+
|
7
|
+
groups = []
|
8
|
+
|
9
|
+
model.each do |group|
|
10
|
+
group = [group] unless group.is_a?(Array)
|
11
|
+
filtered = group.select do |p|
|
12
|
+
if parts_copy.include?(p)
|
13
|
+
parts_copy.delete(p)
|
14
|
+
true
|
15
|
+
end
|
16
|
+
end
|
17
|
+
groups << filtered unless filtered.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
# add any remaining unknown parts, in their original order
|
21
|
+
parts_copy.each {|p| groups << [p]}
|
22
|
+
|
23
|
+
groups
|
24
|
+
end
|
25
|
+
|
26
|
+
SYSTEM_START = {
|
27
|
+
"brace" => "SystemStartBrace",
|
28
|
+
"bracket" => "SystemStartBracket"
|
29
|
+
}
|
30
|
+
|
31
|
+
BRACKET_PARTS = %w{soprano alto tenore basso}
|
32
|
+
|
33
|
+
def self.staff_group_directive(group)
|
34
|
+
if group.size == 1
|
35
|
+
nil
|
36
|
+
elsif BRACKET_PARTS.include?(group.first)
|
37
|
+
"SystemStartBracket"
|
38
|
+
else
|
39
|
+
"SystemStartBrace"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# renders a systemStartDelimiterHierarchy expression
|
44
|
+
def self.staff_hierarchy(staff_groups)
|
45
|
+
expr = staff_groups.inject('') do |m, group|
|
46
|
+
directive = staff_group_directive(group)
|
47
|
+
if directive
|
48
|
+
m << "(#{directive} #{group.join(' ')}) "
|
49
|
+
else
|
50
|
+
m << "#{group.join(' ')} "
|
51
|
+
end
|
52
|
+
m
|
53
|
+
end
|
54
|
+
|
55
|
+
"#'(SystemStartBracket #{expr})"
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.clef(part)
|
59
|
+
DEFAULTS["parts/#{part}/clef"]
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.beaming_mode(part)
|
63
|
+
beaming = DEFAULTS["parts/#{part}/beaming"]
|
64
|
+
return nil if beaming.nil?
|
65
|
+
|
66
|
+
case beaming
|
67
|
+
when 'auto'
|
68
|
+
'\autoBeamOn'
|
69
|
+
when 'manual'
|
70
|
+
'\autoBeamOff'
|
71
|
+
else
|
72
|
+
raise LydownError, "Invalid beaming mode (#{beaming.inspect})"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
DEFAULT_END_BARLINE = '|.'
|
77
|
+
|
78
|
+
def self.end_barline(work, movement)
|
79
|
+
barline = movement['end_barline'] || work['end_barline'] || DEFAULT_END_BARLINE
|
80
|
+
barline == 'none' ? nil : barline
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'lydown/templates'
|
2
|
+
require 'lydown/work'
|
3
|
+
require 'lydown/rendering/base'
|
4
|
+
require 'lydown/rendering/comments'
|
5
|
+
require 'lydown/rendering/lyrics'
|
6
|
+
require 'lydown/rendering/music'
|
7
|
+
require 'lydown/rendering/settings'
|
8
|
+
require 'lydown/rendering/staff'
|
9
|
+
require 'lydown/rendering/movement'
|
10
|
+
|
11
|
+
require 'yaml'
|
12
|
+
|
13
|
+
module Lydown::Rendering
|
14
|
+
DEFAULTS = YAML.load(IO.read(File.join(File.dirname(__FILE__), 'rendering/defaults.yml'))).deep!
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def translate(work, e, lydown_stream, idx)
|
18
|
+
klass = class_for_event(e)
|
19
|
+
klass.new(e, work, lydown_stream, idx).translate
|
20
|
+
end
|
21
|
+
|
22
|
+
def class_for_event(e)
|
23
|
+
Lydown::Rendering.const_get(e[:type].to_s.camelize)
|
24
|
+
rescue
|
25
|
+
raise LydownError, "Invalid lydown event: #{e.inspect}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def part_title(part_name)
|
29
|
+
if part_name =~ /^([^\d]+)(\d+)$/
|
30
|
+
"#{$1.titlize} #{$2.to_i.to_roman}"
|
31
|
+
else
|
32
|
+
part_name.titlize
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Base
|
38
|
+
def initialize(event, work, stream, idx)
|
39
|
+
@event = event
|
40
|
+
@work = work
|
41
|
+
@stream = stream
|
42
|
+
@idx = idx
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
<%
|
2
|
+
score_mode = self['render_opts/mode'] == :score
|
3
|
+
staff_groups = Lydown::Rendering::Staff.staff_groups(
|
4
|
+
self, movement, movement['parts'].keys)
|
5
|
+
parts_in_order = staff_groups.flatten
|
6
|
+
staff_hierarchy = Lydown::Rendering::Staff.staff_hierarchy(staff_groups)
|
7
|
+
|
8
|
+
parts = parts_in_order.inject({}) do |m, p|
|
9
|
+
m[p] = movement['parts'][p]
|
10
|
+
m
|
11
|
+
end
|
12
|
+
|
13
|
+
movement_title = Lydown::Rendering::Movement.movement_title(self, name)
|
14
|
+
%>
|
15
|
+
|
16
|
+
\bookpart {
|
17
|
+
<% if movement_title %>
|
18
|
+
\header {
|
19
|
+
piece = \markup {
|
20
|
+
\column {
|
21
|
+
\fill-line {\bold \large "<%= movement_title %>"}
|
22
|
+
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
<% end %>
|
27
|
+
|
28
|
+
<% if score_mode %>
|
29
|
+
\score {
|
30
|
+
\new StaffGroup <<
|
31
|
+
\set StaffGroup.systemStartDelimiterHierarchy = <%= staff_hierarchy %>
|
32
|
+
<% end %>
|
33
|
+
|
34
|
+
<% if n = movement['bar_number'] %>
|
35
|
+
\set Score.currentBarNumber = #<%= n %>
|
36
|
+
\set Score.barNumberVisibility = #all-bar-numbers-visible
|
37
|
+
\bar ""
|
38
|
+
<% end %>
|
39
|
+
|
40
|
+
<% parts.each do |n, p| %>
|
41
|
+
<%= Lydown::Templates.render(:part, self,
|
42
|
+
name: n, part: p, movement: movement) %>
|
43
|
+
<% end %>
|
44
|
+
|
45
|
+
<% if score_mode %>
|
46
|
+
>> }
|
47
|
+
<% end %>
|
48
|
+
}
|