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