lydown 0.4.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5f65c145556a28db3b86c183d1f12460e903c4fa
4
- data.tar.gz: 2c7faca659a582e802793be8c84551efdb0e863d
3
+ metadata.gz: 768154bba0df3c6550e92d7bab1846d78e767652
4
+ data.tar.gz: a964448d153898925ee16869131f9888a1298ca2
5
5
  SHA512:
6
- metadata.gz: 138a326275fd7b015c991439ef18c9533d4928d04fcfe9700a8a102712250d2e0e1839a844ea31cb0fe96cbe7c3caa56a9ec0f4c02759f0550d41976f5738ba0
7
- data.tar.gz: 3c8d435a1971f739c50b3b9bd8d4987a7f928ab418583fa1a8c0125e725deebc5f38420bc261902dd53dd2ab9767a0430f2be44d3f3a89807fa78f683fc5e832
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
- Text alignment follows the duration, beaming and slurring of the music, just like in lilypond. 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.
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
- 2: 4cded
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 / lyrics2_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
- (barline / duration / note / standalone_figures / rest / silence / phrasing / tie) white_space*
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
- [\!\?] <Note::AccidentalFlag>
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 lyrics2_content
161
- (!"\n" !"//" .)* <Lyrics2>
182
+ rule voice_selector
183
+ [1234u] ':' <VoiceSelector>
162
184
  end
163
185
  end
@@ -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
- stream << {type: :lyrics, content: text_value}
203
+ o = {type: :lyrics}
204
+ _to_stream(self, o)
205
+ stream << o
199
206
  end
200
- end
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
- module Lyrics2
203
- def to_stream(stream)
204
- stream << {type: :lyrics, stream: :lyrics2, content: text_value}
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
@@ -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
@@ -0,0 +1,10 @@
1
+ module Lydown::Rendering
2
+ class Command < Base
3
+ def translate
4
+ once = @event[:once] ? '\once ' : ''
5
+
6
+ cmd = "#{once}\\#{@event[:key]} #{(@event[:arguments] || []).join(' ')} "
7
+ @work.emit(:music, cmd)
8
+ end
9
+ end
10
+ 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
- @work.emit(@event[:stream] || :lyrics, value, ' ')
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
- @work['process/last_note_head'] = event[:head]
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
- "%s%s%s%s%s%s " % [
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
- event[:accidental_flag],
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!(/[_@]/) do |match|
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 @stream[@idx + 1][:type]
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
- @work['process/duration_macro'] = macro
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 Lydown, "Invalid setting (#{key})"
24
+ raise LydownError, "Invalid setting (#{key})"
24
25
  end
25
26
 
26
27
  if level == 0
27
- @work[key] = check_setting_value(key, value)
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 ALLOWED_SETTING_VALUES[key]
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
@@ -7,6 +7,8 @@ require 'lydown/rendering/music'
7
7
  require 'lydown/rendering/settings'
8
8
  require 'lydown/rendering/staff'
9
9
  require 'lydown/rendering/movement'
10
+ require 'lydown/rendering/command'
11
+ require 'lydown/rendering/voices'
10
12
 
11
13
  require 'yaml'
12
14
 
@@ -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
- id = "#{title && title.gsub(' ', '')}Staff"
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
- \new Staff = <%= id %> \with {
26
+ <% if self[:tempo] %>
27
+ \tempo "<%= self[:tempo] %>"
28
+ <% end %>
29
+
30
+ \new Staff = <%= staff_id %> \with {
22
31
  }
23
32
 
24
- \context Staff = <%= id %> {
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
- <%= part['music'] %>
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
- \addlyrics {
42
- <%= part['lyrics'] %>
43
- }
44
- <% end %>
45
-
46
- <% if part['lyrics2'] %>
47
- \addlyrics {
48
- <%= part['lyrics2'] %>
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'] %>
@@ -1,3 +1,3 @@
1
1
  module Lydown
2
- VERSION = "0.4.0"
2
+ VERSION = "0.6.1"
3
3
  end
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(stream_type, *content)
69
- stream = current_stream(stream_type)
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(type)
75
- movement = @context[:movement]
76
- part = @context[:part]
77
- path = "movements/#{movement}/parts/#{part}/#{type}"
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
- # delete default movement if other movements are present
105
- if filtered['movements'].size > 1
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.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-05-27 00:00:00.000000000 Z
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