lydown 0.4.0 → 0.6.1
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 +4 -4
- data/README.md +51 -4
- data/lib/lydown/parsing/lydown.treetop +36 -14
- data/lib/lydown/parsing/nodes.rb +87 -5
- data/lib/lydown/parsing.rb +1 -1
- data/lib/lydown/rendering/command.rb +10 -0
- data/lib/lydown/rendering/lyrics.rb +5 -1
- data/lib/lydown/rendering/music.rb +61 -29
- data/lib/lydown/rendering/settings.rb +14 -6
- data/lib/lydown/rendering/voices.rb +42 -0
- data/lib/lydown/rendering.rb +2 -0
- data/lib/lydown/templates/movement.erb +10 -1
- data/lib/lydown/templates/multi_voice.erb +16 -0
- data/lib/lydown/templates/part.erb +35 -15
- data/lib/lydown/version.rb +1 -1
- data/lib/lydown/work.rb +20 -8
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 768154bba0df3c6550e92d7bab1846d78e767652
|
4
|
+
data.tar.gz: a964448d153898925ee16869131f9888a1298ca2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdc5ae5af05d007763399b2e33b4d7c7ab98050f2abbfd2ffc2f84a49fa8ed6e907762aed1ea577c58bbe98a5ff3d44ab21e11a9382fcd02a830719740fda29d
|
7
|
+
data.tar.gz: 286f620033bd8c01c53bd1dffacb6d715264d4a135674389d892b5602e8c4d23fc3f55f540514a75a1e46b499316cea09766aa9764252e2e9b02b64332e76ab3
|
data/README.md
CHANGED
@@ -87,6 +87,12 @@ Augmentation dots are entered like in lilypond:
|
|
87
87
|
|
88
88
|
8.c6d 8.e6f 2g => c8. d16 e8. f16 g2
|
89
89
|
8..g 3g 4c => g8.. g32 c4
|
90
|
+
|
91
|
+
Notes can be repeated using the <code>@</code> placeholder:
|
92
|
+
|
93
|
+
4c@@@ => c4 c c c
|
94
|
+
|
95
|
+
(The repeating note placeholder is useful when entering repeated notes with accidentals).
|
90
96
|
|
91
97
|
### Rests
|
92
98
|
|
@@ -239,6 +245,11 @@ In the case of key signatures, accidentals will follow the lydown syntax:
|
|
239
245
|
|
240
246
|
- key: b- major
|
241
247
|
- key: f+ minor
|
248
|
+
|
249
|
+
Key signatures can also be specified using shorthand notation (upper case for major, lower case for minor):
|
250
|
+
|
251
|
+
- key: B- // b flat major
|
252
|
+
- key: f+ // f sharp minor
|
242
253
|
|
243
254
|
The default key signature is C major, and the default time signature is 4/4.
|
244
255
|
|
@@ -248,7 +259,7 @@ Key or time signatures can be changed on the fly:
|
|
248
259
|
4c e g b
|
249
260
|
- time: 3/4
|
250
261
|
c e g 2.c
|
251
|
-
|
262
|
+
|
252
263
|
### Pickup bars
|
253
264
|
|
254
265
|
[Pickup bars](http://www.lilypond.org/doc/v2.18/Documentation/notation/displaying-rhythms#upbeats) (anacrusis, upbeat) are defined with the pickup setting:
|
@@ -259,6 +270,20 @@ Key or time signatures can be changed on the fly:
|
|
259
270
|
c8cdcb4aaa
|
260
271
|
d8dedc4bb
|
261
272
|
|
273
|
+
### Lilypond Commands and inline settings
|
274
|
+
|
275
|
+
Lilypond commands and settings can be entered inline as part of the note stream:
|
276
|
+
|
277
|
+
cd \key:E- e \stemDown f
|
278
|
+
|
279
|
+
A useful shorthand is for one-time (<code>\once</code>) overrides, with an exclamation mark between the backslash and the command:
|
280
|
+
|
281
|
+
\!override:"NoteHead.color = #red"
|
282
|
+
|
283
|
+
Multiple arguments can be given, separated by colons. Arguments need to be quoted only if they contain whitespace, or colons:
|
284
|
+
|
285
|
+
\!override:AccidentalSuggestion:"#'avoid-slur = #'outside"
|
286
|
+
|
262
287
|
### Inline lyrics
|
263
288
|
|
264
289
|
Lyrics for vocal parts can be entered on separate lines prefixed by a > symbol:
|
@@ -266,7 +291,17 @@ Lyrics for vocal parts can be entered on separate lines prefixed by a > symbol:
|
|
266
291
|
4c[8de]4fd(4c[8de]2f)
|
267
292
|
> Ly-down is the bomb__
|
268
293
|
|
269
|
-
|
294
|
+
Or between notes using quotes:
|
295
|
+
|
296
|
+
4c[8de]4fd(4c[8de]2f) >"Ly-down is the bomb__"
|
297
|
+
|
298
|
+
Text alignment follows the duration, beaming and slurring of the music, [just like in lilypond](http://www.lilypond.org/doc/v2.18/Documentation/notation/common-notation-for-vocal-music#automatic-syllable-durations). Sillables are expected to be separated by a dash. Melismas, i.e. a single sillable streched over multiple notes, is signified by one or more underscores.
|
299
|
+
|
300
|
+
Multiple stanzas for the same music can be specified by including the stanza number in parens:
|
301
|
+
|
302
|
+
4cege1c
|
303
|
+
> Ly-down is the bomb.
|
304
|
+
>(2) Li-ly-pond is too.
|
270
305
|
|
271
306
|
### Stream switching
|
272
307
|
|
@@ -280,6 +315,13 @@ Lyrics can be entered in a block, before or after musical notation, by switching
|
|
280
315
|
=music
|
281
316
|
8g'gffeed4
|
282
317
|
...
|
318
|
+
|
319
|
+
Multiple lyrics stanzas can be written by including the stanza number in parens:
|
320
|
+
|
321
|
+
=lyrics(1) // optional, same as =lyrics
|
322
|
+
...
|
323
|
+
=lyrics(2)
|
324
|
+
...
|
283
325
|
|
284
326
|
### Figured bass
|
285
327
|
|
@@ -308,8 +350,13 @@ For multi-movement works, prefix each movement with a -movement setting:
|
|
308
350
|
|
309
351
|
[Multiple voices](http://www.lilypond.org/doc/v2.18/Documentation/notation/multiple-voices#single_002dstaff-polyphony) on the same staff can be easily entered using the following notation:
|
310
352
|
|
311
|
-
1: 8egfdeg4f
|
312
|
-
|
353
|
+
1: 8egfdeg4f 2: 4cded u: ...
|
354
|
+
|
355
|
+
the <code>u:<\code> command is used to return to single voice (_unisono_) mode.
|
356
|
+
|
357
|
+
Lyrics can be added for individual voices by using inline lyrics:
|
358
|
+
|
359
|
+
1: ceg >"yeah yeah yeah" 2: gbd >"no no no" u: ...
|
313
360
|
|
314
361
|
## Piano scores
|
315
362
|
|
@@ -5,16 +5,13 @@ grammar Lydown
|
|
5
5
|
line ([\n] line)* <Root>
|
6
6
|
end
|
7
7
|
rule stream_switch
|
8
|
-
music_stream / lyrics_stream
|
8
|
+
music_stream / lyrics_stream
|
9
9
|
end
|
10
10
|
rule music_stream
|
11
11
|
'=music' white_space? [\n] music ([\n] !stream_breaker music)*
|
12
12
|
end
|
13
13
|
rule lyrics_stream
|
14
|
-
'=lyrics' white_space? [\n] lyrics_content ([\n] !stream_breaker lyrics_content)*
|
15
|
-
end
|
16
|
-
rule lyrics2_stream
|
17
|
-
'=lyrics2' white_space? [\n] lyrics2_content ([\n] !stream_breaker lyrics2_content)*
|
14
|
+
'=lyrics' stream_idx? white_space? [\n] lyrics_content ([\n] !stream_breaker lyrics_content)* <Lyrics>
|
18
15
|
end
|
19
16
|
rule stream_breaker
|
20
17
|
stream_switch / stream_breaking_setting
|
@@ -47,7 +44,8 @@ grammar Lydown
|
|
47
44
|
[ \t]+
|
48
45
|
end
|
49
46
|
rule event
|
50
|
-
(
|
47
|
+
(inline_command / inline_lyrics / voice_selector / barline / duration /
|
48
|
+
note / standalone_figures / rest / silence / phrasing / tie) white_space*
|
51
49
|
end
|
52
50
|
rule barline
|
53
51
|
('?|' / ':|][|:' / '[|:' / ':|]' / [\|\.\:]+) <Barline>
|
@@ -91,7 +89,7 @@ grammar Lydown
|
|
91
89
|
note_head octave* accidental_flag? figures? expression* <Note>
|
92
90
|
end
|
93
91
|
rule expression
|
94
|
-
(expression_shorthand / expression_longhand) <Note::Expression>
|
92
|
+
(expression_shorthand / expression_longhand / string) <Note::Expression>
|
95
93
|
end
|
96
94
|
|
97
95
|
rule expression_shorthand
|
@@ -100,6 +98,9 @@ grammar Lydown
|
|
100
98
|
rule expression_longhand
|
101
99
|
'\\' [^\s\n]+
|
102
100
|
end
|
101
|
+
rule string
|
102
|
+
'"' ('\"' / !'"' .)* '"'
|
103
|
+
end
|
103
104
|
rule figures # bass figures
|
104
105
|
'<' figures_component? (white_space? figures_component)* '>'
|
105
106
|
end
|
@@ -110,13 +111,16 @@ grammar Lydown
|
|
110
111
|
duration_value? figures <StandAloneFigures>
|
111
112
|
end
|
112
113
|
rule rest
|
113
|
-
[rR] multiplier* <Rest>
|
114
|
+
[rR] multiplier* rest_expression* <Rest>
|
115
|
+
end
|
116
|
+
rule rest_expression
|
117
|
+
(expression_longhand / string) <Note::Expression>
|
114
118
|
end
|
115
119
|
rule silence
|
116
120
|
[s] multiplier* <Silence>
|
117
121
|
end
|
118
122
|
rule note_head
|
119
|
-
[a-g] octave* accidental* <Note::Head>
|
123
|
+
[a-g@] octave* accidental* <Note::Head>
|
120
124
|
end
|
121
125
|
rule accidental
|
122
126
|
[\+\-]+
|
@@ -125,7 +129,7 @@ grammar Lydown
|
|
125
129
|
[\,']+ <Note::Octave>
|
126
130
|
end
|
127
131
|
rule accidental_flag
|
128
|
-
[
|
132
|
+
[\!\?\^] <Note::AccidentalFlag>
|
129
133
|
end
|
130
134
|
rule phrasing
|
131
135
|
beam_open / beam_close / slur_open / slur_close
|
@@ -152,12 +156,30 @@ grammar Lydown
|
|
152
156
|
'&' <ShortTie>
|
153
157
|
end
|
154
158
|
rule lyrics
|
155
|
-
'>' white_space* lyrics_content
|
159
|
+
'>' stream_idx? white_space* lyrics_content <Lyrics>
|
160
|
+
end
|
161
|
+
rule inline_lyrics
|
162
|
+
'>' stream_idx? lyrics_quoted_content <Lyrics>
|
163
|
+
end
|
164
|
+
rule stream_idx
|
165
|
+
'(' [\d] ')' <StreamIndex>
|
156
166
|
end
|
157
167
|
rule lyrics_content
|
158
|
-
(!"\n" !"//" .)* <Lyrics>
|
168
|
+
(!"\n" !"//" .)* <Lyrics::Content>
|
169
|
+
end
|
170
|
+
rule lyrics_quoted_content
|
171
|
+
string <Lyrics::QuotedContent>
|
172
|
+
end
|
173
|
+
rule inline_command
|
174
|
+
'\\' '!'? inline_command_key (':' inline_command_argument)* <Command>
|
175
|
+
end
|
176
|
+
rule inline_command_key
|
177
|
+
[a-zA-Z_0-9]+ <Command::Key>
|
178
|
+
end
|
179
|
+
rule inline_command_argument
|
180
|
+
(string / [^\s\t\n\:]+) <Command::Argument>
|
159
181
|
end
|
160
|
-
rule
|
161
|
-
|
182
|
+
rule voice_selector
|
183
|
+
[1234u] ':' <VoiceSelector>
|
162
184
|
end
|
163
185
|
end
|
data/lib/lydown/parsing/nodes.rb
CHANGED
@@ -171,12 +171,16 @@ module Lydown::Parsing
|
|
171
171
|
end
|
172
172
|
|
173
173
|
module Rest
|
174
|
+
include Root
|
175
|
+
|
174
176
|
def to_stream(stream)
|
175
177
|
rest = {type: :rest, raw: text_value, head: text_value[0]}
|
176
178
|
if text_value =~ /^R(\*([0-9]+))?$/
|
177
179
|
rest[:multiplier] = $2 || '1'
|
178
180
|
end
|
179
181
|
|
182
|
+
_to_stream(self, rest)
|
183
|
+
|
180
184
|
stream << rest
|
181
185
|
end
|
182
186
|
end
|
@@ -194,14 +198,47 @@ module Lydown::Parsing
|
|
194
198
|
end
|
195
199
|
|
196
200
|
module Lyrics
|
201
|
+
include Root
|
197
202
|
def to_stream(stream)
|
198
|
-
|
203
|
+
o = {type: :lyrics}
|
204
|
+
_to_stream(self, o)
|
205
|
+
stream << o
|
199
206
|
end
|
200
|
-
|
207
|
+
|
208
|
+
module Content
|
209
|
+
def to_stream(o)
|
210
|
+
if o[:content]
|
211
|
+
o[:content] << ' ' << text_value
|
212
|
+
else
|
213
|
+
o[:content] = text_value
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
module QuotedContent
|
219
|
+
def to_stream(o)
|
220
|
+
if text_value =~ /^"(.+)"$/
|
221
|
+
content = $1
|
222
|
+
else
|
223
|
+
raise LydownError, "Unexpected quoted lyrics content (#{text_value.inspect})"
|
224
|
+
end
|
201
225
|
|
202
|
-
|
203
|
-
|
204
|
-
|
226
|
+
if o[:content]
|
227
|
+
o[:content] << ' ' << content
|
228
|
+
else
|
229
|
+
o[:content] = content
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
module StreamIndex
|
236
|
+
def to_stream(o)
|
237
|
+
idx = (text_value =~ /\(([\d]+)\)/) && $1.to_i
|
238
|
+
if idx.nil?
|
239
|
+
raise LydownError, "Invalid stream index (#{text_value.inspect})"
|
240
|
+
end
|
241
|
+
o[:stream_index] = idx
|
205
242
|
end
|
206
243
|
end
|
207
244
|
|
@@ -210,4 +247,49 @@ module Lydown::Parsing
|
|
210
247
|
stream << {type: :barline, barline: text_value}
|
211
248
|
end
|
212
249
|
end
|
250
|
+
|
251
|
+
module Command
|
252
|
+
include Root
|
253
|
+
def to_stream(stream)
|
254
|
+
cmd = {type: :command}
|
255
|
+
cmd[:once] = true if text_value =~ /^\\\!/
|
256
|
+
_to_stream(self, cmd)
|
257
|
+
stream << cmd
|
258
|
+
end
|
259
|
+
|
260
|
+
SETTING_KEYS = %w{time key clef}
|
261
|
+
|
262
|
+
module Key
|
263
|
+
def to_stream(cmd)
|
264
|
+
cmd[:key] = text_value
|
265
|
+
if SETTING_KEYS.include?(text_value)
|
266
|
+
cmd[:type] = :setting
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
module Argument
|
272
|
+
def to_stream(cmd)
|
273
|
+
if text_value =~ /^"(.+)"$/
|
274
|
+
value = $1
|
275
|
+
else
|
276
|
+
value = text_value
|
277
|
+
end
|
278
|
+
|
279
|
+
if cmd[:type] == :setting
|
280
|
+
cmd[:value] = value
|
281
|
+
else
|
282
|
+
cmd[:arguments] ||= []
|
283
|
+
cmd[:arguments] << value
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
module VoiceSelector
|
290
|
+
def to_stream(stream)
|
291
|
+
voice = (text_value =~ /^([1234])/) && $1.to_i
|
292
|
+
stream << {type: :voice_select, voice: voice}
|
293
|
+
end
|
294
|
+
end
|
213
295
|
end
|
data/lib/lydown/parsing.rb
CHANGED
@@ -26,7 +26,7 @@ class LydownParser
|
|
26
26
|
else
|
27
27
|
msg << "#{parser.failure_reason}:\n"
|
28
28
|
end
|
29
|
-
msg << " #{source.lines[parser.failure_line - 1]} #{' ' * parser.failure_column}^"
|
29
|
+
msg << " #{source.lines[parser.failure_line - 1].chomp}\n #{' ' * parser.failure_column}^"
|
30
30
|
|
31
31
|
msg
|
32
32
|
end
|
@@ -2,7 +2,11 @@ module Lydown::Rendering
|
|
2
2
|
class Lyrics < Base
|
3
3
|
def translate
|
4
4
|
value = lilypond_lyrics(@event[:content])
|
5
|
-
|
5
|
+
|
6
|
+
lyrics_idx = @event[:stream_index] || 1
|
7
|
+
voice = @work['process/voice_selector'] || 'voice1'
|
8
|
+
|
9
|
+
@work.emit("lyrics/#{voice}/#{lyrics_idx}", value, ' ')
|
6
10
|
end
|
7
11
|
|
8
12
|
def lilypond_lyrics(lyrics)
|
@@ -60,7 +60,12 @@ module Lydown::Rendering
|
|
60
60
|
def add_note(event)
|
61
61
|
return add_macro_note(event) if @work['process/duration_macro']
|
62
62
|
|
63
|
-
@
|
63
|
+
if @event[:head] == '@'
|
64
|
+
# replace repeating note head
|
65
|
+
@event[:head] = @work['process/last_note_head']
|
66
|
+
else
|
67
|
+
@work['process/last_note_head'] = event[:head]
|
68
|
+
end
|
64
69
|
|
65
70
|
value = @work['process/duration_values'].first
|
66
71
|
@work['process/duration_values'].rotate!
|
@@ -87,20 +92,35 @@ module Lydown::Rendering
|
|
87
92
|
|
88
93
|
@work.emit(event[:stream] || :music, lilypond_note(event, value: value))
|
89
94
|
end
|
95
|
+
|
96
|
+
FICTA_CODE = <<EOF
|
97
|
+
\\once \\override AccidentalSuggestion #'avoid-slur = #'outside
|
98
|
+
\\once \\set suggestAccidentals = ##t
|
99
|
+
EOF
|
90
100
|
|
91
101
|
def lilypond_note(event, options = {})
|
92
102
|
head = Accidentals.translate_note_name(@work, event[:head])
|
93
103
|
if options[:head_only]
|
94
104
|
head
|
95
105
|
else
|
96
|
-
|
106
|
+
if event[:accidental_flag] == '^'
|
107
|
+
accidental_flag = ''
|
108
|
+
prefix = FICTA_CODE
|
109
|
+
else
|
110
|
+
accidental_flag = event[:accidental_flag]
|
111
|
+
prefix = ''
|
112
|
+
end
|
113
|
+
|
114
|
+
[
|
115
|
+
prefix,
|
97
116
|
head,
|
98
117
|
event[:octave],
|
99
|
-
|
118
|
+
accidental_flag,
|
100
119
|
options[:value],
|
101
120
|
lilypond_phrasing(event),
|
102
|
-
event[:expressions] ? event[:expressions].join : ''
|
103
|
-
|
121
|
+
event[:expressions] ? event[:expressions].join : '',
|
122
|
+
' '
|
123
|
+
].join
|
104
124
|
end
|
105
125
|
end
|
106
126
|
|
@@ -152,12 +172,12 @@ module Lydown::Rendering
|
|
152
172
|
]
|
153
173
|
|
154
174
|
# replace place holder and repeaters in macro group with actual note
|
155
|
-
@work['process/macro_group'].gsub!(/[_
|
175
|
+
@work['process/macro_group'].gsub!(/[_∞]/) do |match|
|
156
176
|
case match
|
157
177
|
when '_'
|
158
178
|
underscore_count += 1
|
159
179
|
underscore_count == 1 ? lydown_note : match
|
160
|
-
when '
|
180
|
+
when '∞'
|
161
181
|
underscore_count < 2 ? event[:head] : match
|
162
182
|
end
|
163
183
|
end
|
@@ -176,6 +196,34 @@ module Lydown::Rendering
|
|
176
196
|
@work['process/macro_group'] = nil
|
177
197
|
end
|
178
198
|
end
|
199
|
+
|
200
|
+
LILYPOND_EXPRESSIONS = {
|
201
|
+
'_' => '--',
|
202
|
+
'.' => '-.',
|
203
|
+
'`' => '-!'
|
204
|
+
}
|
205
|
+
|
206
|
+
def translate_expressions
|
207
|
+
return unless @event[:expressions]
|
208
|
+
|
209
|
+
@event[:expressions] = @event[:expressions].map do |expr|
|
210
|
+
if expr =~ /^(?:\\(_?))?"(.+)"$/
|
211
|
+
placement = ($1 == '_') ? '_' : '^'
|
212
|
+
"#{placement}\\markup { #{translate_string_expression($2)} }"
|
213
|
+
elsif expr =~ /^\\/
|
214
|
+
expr
|
215
|
+
elsif LILYPOND_EXPRESSIONS[expr]
|
216
|
+
LILYPOND_EXPRESSIONS[expr]
|
217
|
+
else
|
218
|
+
raise LydownError, "Invalid expression #{expr.inspect}"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def translate_string_expression(expr)
|
224
|
+
expr.gsub(/__([^_]+)__/) {|m| "\\bold { #{$1} }" }.
|
225
|
+
gsub(/_([^_]+)_/) {|m| "\\italic { #{$1} }" }
|
226
|
+
end
|
179
227
|
end
|
180
228
|
|
181
229
|
LILYPOND_DURATIONS = {
|
@@ -232,7 +280,7 @@ module Lydown::Rendering
|
|
232
280
|
# look ahead and see if any beam or slur closing after note
|
233
281
|
look_ahead_idx = @idx + 1
|
234
282
|
while event = @stream[look_ahead_idx]
|
235
|
-
case
|
283
|
+
case event[:type]
|
236
284
|
when :beam_close
|
237
285
|
@event[:beam_close] = true
|
238
286
|
when :slur_close
|
@@ -245,26 +293,6 @@ module Lydown::Rendering
|
|
245
293
|
|
246
294
|
add_note(@event)
|
247
295
|
end
|
248
|
-
|
249
|
-
LILYPOND_EXPRESSIONS = {
|
250
|
-
'_' => '--',
|
251
|
-
'.' => '-.',
|
252
|
-
'`' => '-!'
|
253
|
-
}
|
254
|
-
|
255
|
-
def translate_expressions
|
256
|
-
return unless @event[:expressions]
|
257
|
-
|
258
|
-
@event[:expressions] = @event[:expressions].map do |expr|
|
259
|
-
if expr =~ /^\\/
|
260
|
-
expr
|
261
|
-
elsif LILYPOND_EXPRESSIONS[expr]
|
262
|
-
LILYPOND_EXPRESSIONS[expr]
|
263
|
-
else
|
264
|
-
raise LydownError, "Invalid expression #{expr.inspect}"
|
265
|
-
end
|
266
|
-
end
|
267
|
-
end
|
268
296
|
end
|
269
297
|
|
270
298
|
class StandAloneFigures < Base
|
@@ -327,6 +355,8 @@ module Lydown::Rendering
|
|
327
355
|
end
|
328
356
|
|
329
357
|
def translate
|
358
|
+
translate_expressions
|
359
|
+
|
330
360
|
if @event[:multiplier]
|
331
361
|
value = full_bar_value(@work[:time])
|
332
362
|
if value
|
@@ -369,7 +399,9 @@ module Lydown::Rendering
|
|
369
399
|
if macro =~ /^\{(.+)\}$/
|
370
400
|
macro = $1
|
371
401
|
end
|
372
|
-
|
402
|
+
# replace the repeating note placeholder with another sign in order to
|
403
|
+
# avoid mixing up with repeating notes from outside the macro
|
404
|
+
@work['process/duration_macro'] = macro.gsub('@', '∞')
|
373
405
|
else
|
374
406
|
raise LydownError, "Unknown macro #{@event[:macro]}"
|
375
407
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Lydown::Rendering
|
2
2
|
class Setting < Base
|
3
3
|
SETTING_KEYS = [
|
4
|
-
'key', 'time', 'pickup', 'clef', 'part', 'movement',
|
5
|
-
'accidentals', 'beams', 'end_barline', 'macros'
|
4
|
+
'key', 'time', 'pickup', 'clef', 'part', 'movement', 'tempo',
|
5
|
+
'accidentals', 'beams', 'end_barline', 'macros', 'empty_staves'
|
6
6
|
]
|
7
7
|
|
8
8
|
RENDERABLE_SETTING_KEYS = [
|
@@ -11,7 +11,8 @@ module Lydown::Rendering
|
|
11
11
|
|
12
12
|
ALLOWED_SETTING_VALUES = {
|
13
13
|
'accidentals' => ['manual', 'auto'],
|
14
|
-
'beams' => ['manual', 'auto']
|
14
|
+
'beams' => ['manual', 'auto'],
|
15
|
+
'empty_staves' => ['hide', 'show']
|
15
16
|
}
|
16
17
|
|
17
18
|
def translate
|
@@ -20,11 +21,12 @@ module Lydown::Rendering
|
|
20
21
|
level = @event[:level] || 0
|
21
22
|
|
22
23
|
unless (level > 0) || SETTING_KEYS.include?(key)
|
23
|
-
raise
|
24
|
+
raise LydownError, "Invalid setting (#{key})"
|
24
25
|
end
|
25
26
|
|
26
27
|
if level == 0
|
27
|
-
|
28
|
+
value = check_setting_value(key, value)
|
29
|
+
@work[key] = value
|
28
30
|
case key
|
29
31
|
when 'part'
|
30
32
|
# when changing parts we repeat the last set time and key signature
|
@@ -55,7 +57,13 @@ module Lydown::Rendering
|
|
55
57
|
end
|
56
58
|
|
57
59
|
def check_setting_value(key, value)
|
58
|
-
if
|
60
|
+
if key == 'key'
|
61
|
+
# process shorthand notation
|
62
|
+
if value =~ /^([a-gA-G])[\+\-]*$/
|
63
|
+
mode = $1.downcase == $1 ? 'minor' : 'major'
|
64
|
+
value = "#{value.downcase} #{mode}"
|
65
|
+
end
|
66
|
+
elsif ALLOWED_SETTING_VALUES[key]
|
59
67
|
unless ALLOWED_SETTING_VALUES[key].include?(value)
|
60
68
|
raise LydownError, "Invalid value for setting #{key}: #{value.inspect}"
|
61
69
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Lydown::Rendering
|
2
|
+
class VoiceSelect < Base
|
3
|
+
def translate
|
4
|
+
if @event[:voice]
|
5
|
+
@work['process/voice_selector'] = "voice#{@event[:voice]}"
|
6
|
+
else
|
7
|
+
self.class.render_voices(@work)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.render_voices(work)
|
12
|
+
work['process/voice_selector'] = nil
|
13
|
+
|
14
|
+
music = Lydown::Templates.render(:multi_voice, work, part: work[:part])
|
15
|
+
|
16
|
+
work.emit(:music, music)
|
17
|
+
|
18
|
+
work['process/voices'].each_value do |stream|
|
19
|
+
if stream['lyrics']
|
20
|
+
stream['lyrics'].each do |voice, lyrics_stream|
|
21
|
+
lyrics_stream.each do |idx, content|
|
22
|
+
work.emit("lyrics/#{voice}/#{idx}", content)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
work['process/voices'] = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
VOICE_COMMANDS = {
|
32
|
+
'voice1' => '\voiceOne',
|
33
|
+
'voice2' => '\voiceTwo',
|
34
|
+
'voice3' => '\voiceThree',
|
35
|
+
'voice4' => '\voiceFour',
|
36
|
+
}
|
37
|
+
|
38
|
+
def self.voice_command(voice)
|
39
|
+
VOICE_COMMANDS[voice]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/lydown/rendering.rb
CHANGED
@@ -27,6 +27,15 @@
|
|
27
27
|
|
28
28
|
<% if score_mode %>
|
29
29
|
\score {
|
30
|
+
<% if self['empty_staves'] == 'hide' %>
|
31
|
+
\layout {
|
32
|
+
\context {
|
33
|
+
\RemoveEmptyStaffContext
|
34
|
+
\override VerticalAxisGroup #'remove-first = ##t
|
35
|
+
}
|
36
|
+
}
|
37
|
+
<% end %>
|
38
|
+
|
30
39
|
\new StaffGroup <<
|
31
40
|
\set StaffGroup.systemStartDelimiterHierarchy = <%= staff_hierarchy %>
|
32
41
|
<% end %>
|
@@ -36,7 +45,7 @@
|
|
36
45
|
\set Score.barNumberVisibility = #all-bar-numbers-visible
|
37
46
|
\bar ""
|
38
47
|
<% end %>
|
39
|
-
|
48
|
+
|
40
49
|
<% parts.each do |n, p| %>
|
41
50
|
<%= Lydown::Templates.render(:part, self,
|
42
51
|
name: n, part: p, movement: movement) %>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<<
|
2
|
+
<% beaming_mode = Lydown::Rendering::Staff.beaming_mode(part) %>
|
3
|
+
<% voice_prefix = part.nil? || (part == "") ? nil : "#{part}_" %>
|
4
|
+
|
5
|
+
<% self['process/voices'].each do |voice, stream| %>
|
6
|
+
<% if voice != 'voice1' %>
|
7
|
+
\new Voice = "<%= voice_prefix %><%= voice %>"
|
8
|
+
<% end %>
|
9
|
+
{
|
10
|
+
<%= beaming_mode %>
|
11
|
+
<%= Lydown::Rendering::VoiceSelect.voice_command(voice) %>
|
12
|
+
<%= stream['music'] %>
|
13
|
+
}
|
14
|
+
<% end %>
|
15
|
+
>>
|
16
|
+
\oneVoice
|
@@ -2,8 +2,13 @@
|
|
2
2
|
title = nil
|
3
3
|
if name && (name != '')
|
4
4
|
title = Lydown::Rendering.part_title(name)
|
5
|
+
voice_prefix = "#{name}_"
|
6
|
+
else
|
7
|
+
title = nil
|
8
|
+
voice_prefix = nil
|
5
9
|
end
|
6
|
-
|
10
|
+
staff_id = "#{title && title.gsub(' ', '')}Staff"
|
11
|
+
|
7
12
|
|
8
13
|
score_mode = self['render_opts/mode'] == :score
|
9
14
|
|
@@ -18,19 +23,27 @@
|
|
18
23
|
|
19
24
|
<<
|
20
25
|
|
21
|
-
|
26
|
+
<% if self[:tempo] %>
|
27
|
+
\tempo "<%= self[:tempo] %>"
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
\new Staff = <%= staff_id %> \with {
|
22
31
|
}
|
23
32
|
|
24
|
-
\context Staff = <%=
|
33
|
+
\context Staff = <%= staff_id %> {
|
25
34
|
<% if score_mode %>\set Staff.instrumentName = #"<%= title %>"<% end %>
|
26
35
|
\relative c {
|
27
36
|
<% if clef %>
|
28
37
|
\clef "<%= clef %>"
|
29
38
|
<% end %>
|
30
|
-
<%= beaming_mode %>
|
31
39
|
<%= partial %>
|
32
|
-
|
33
|
-
|
40
|
+
|
41
|
+
<<
|
42
|
+
\new Voice = "<%= voice_prefix %>voice1" {
|
43
|
+
<%= beaming_mode %>
|
44
|
+
<%= part['music'] %>
|
45
|
+
}
|
46
|
+
>>
|
34
47
|
<% if end_barline %>
|
35
48
|
\bar "<%= end_barline %>"
|
36
49
|
<% end %>
|
@@ -38,15 +51,22 @@
|
|
38
51
|
}
|
39
52
|
|
40
53
|
<% if part['lyrics'] %>
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
54
|
+
<% multi_voice = part['lyrics'].size > 1 %>
|
55
|
+
<% part['lyrics'].each_key do |voice| %>
|
56
|
+
<% above_staff = multi_voice && ['voice1', 'voice3'].include?(voice) %>
|
57
|
+
<% part['lyrics'][voice].keys.sort.each do |idx| %>
|
58
|
+
\new Lyrics
|
59
|
+
<% if above_staff %>
|
60
|
+
\with { alignAboveContext = "<%= staff_id %>" }
|
61
|
+
<% end %>
|
62
|
+
{
|
63
|
+
\lyricsto "<%= voice_prefix %><%= voice %>" {
|
64
|
+
<%= part['lyrics'][voice][idx] %>
|
65
|
+
}
|
66
|
+
}
|
67
|
+
<% end %>
|
68
|
+
<% end %>
|
69
|
+
|
50
70
|
<% end %>
|
51
71
|
|
52
72
|
<% if part['figures'] %>
|
data/lib/lydown/version.rb
CHANGED
data/lib/lydown/work.rb
CHANGED
@@ -28,6 +28,7 @@ module Lydown
|
|
28
28
|
case mode
|
29
29
|
when :work, :movement
|
30
30
|
@context[:time] = '4/4'
|
31
|
+
@context[:tempo] = nil
|
31
32
|
@context[:cadenza_mode] = nil
|
32
33
|
@context[:key] = 'c major'
|
33
34
|
@context[:pickup] = nil
|
@@ -38,6 +39,10 @@ module Lydown
|
|
38
39
|
if @context['process/tuplet_mode']
|
39
40
|
Lydown::Rendering::TupletDuration.emit_tuplet_end(self)
|
40
41
|
end
|
42
|
+
|
43
|
+
if @context['process/voice_selector']
|
44
|
+
Lydown::Rendering::VoiceSelect.render_voices(self)
|
45
|
+
end
|
41
46
|
|
42
47
|
# reset processing variables
|
43
48
|
@context['process'] = {
|
@@ -65,16 +70,20 @@ module Lydown
|
|
65
70
|
reset_context(:part) unless opts[:no_reset]
|
66
71
|
end
|
67
72
|
|
68
|
-
def emit(
|
69
|
-
stream = current_stream(
|
73
|
+
def emit(path, *content)
|
74
|
+
stream = current_stream(path)
|
70
75
|
|
71
76
|
content.each {|c| stream << c}
|
72
77
|
end
|
73
78
|
|
74
|
-
def current_stream(
|
75
|
-
|
76
|
-
|
77
|
-
|
79
|
+
def current_stream(subpath)
|
80
|
+
if @context['process/voice_selector']
|
81
|
+
path = "process/voices/#{@context['process/voice_selector']}/#{subpath}"
|
82
|
+
else
|
83
|
+
movement = @context[:movement]
|
84
|
+
part = @context[:part]
|
85
|
+
path = "movements/#{movement}/parts/#{part}/#{subpath}"
|
86
|
+
end
|
78
87
|
@context[path] ||= ''
|
79
88
|
end
|
80
89
|
|
@@ -101,8 +110,11 @@ module Lydown
|
|
101
110
|
def filter_context(opts = {})
|
102
111
|
filtered = @context.deep_clone
|
103
112
|
|
104
|
-
|
105
|
-
|
113
|
+
if filtered['movements'].nil? || filtered['movements'].size == 0
|
114
|
+
# no movements found, so no music
|
115
|
+
raise LydownError, "No music found"
|
116
|
+
elsif filtered['movements'].size > 1
|
117
|
+
# delete default movement if other movements are present
|
106
118
|
filtered['movements'].delete('')
|
107
119
|
end
|
108
120
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lydown
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: treetop
|
@@ -43,6 +43,7 @@ files:
|
|
43
43
|
- lib/lydown/parsing/nodes.rb
|
44
44
|
- lib/lydown/rendering.rb
|
45
45
|
- lib/lydown/rendering/base.rb
|
46
|
+
- lib/lydown/rendering/command.rb
|
46
47
|
- lib/lydown/rendering/comments.rb
|
47
48
|
- lib/lydown/rendering/defaults.yml
|
48
49
|
- lib/lydown/rendering/figures.rb
|
@@ -51,9 +52,11 @@ files:
|
|
51
52
|
- lib/lydown/rendering/music.rb
|
52
53
|
- lib/lydown/rendering/settings.rb
|
53
54
|
- lib/lydown/rendering/staff.rb
|
55
|
+
- lib/lydown/rendering/voices.rb
|
54
56
|
- lib/lydown/templates.rb
|
55
57
|
- lib/lydown/templates/lilypond_doc.erb
|
56
58
|
- lib/lydown/templates/movement.erb
|
59
|
+
- lib/lydown/templates/multi_voice.erb
|
57
60
|
- lib/lydown/templates/part.erb
|
58
61
|
- lib/lydown/version.rb
|
59
62
|
- lib/lydown/work.rb
|