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 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