lydown 0.12.4 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -1
  3. data/bin/lydown +2 -0
  4. data/lib/lydown.rb +6 -2
  5. data/lib/lydown/cache.rb +5 -1
  6. data/lib/lydown/cli.rb +1 -1
  7. data/lib/lydown/cli/commands.rb +7 -11
  8. data/lib/lydown/cli/compiler.rb +5 -0
  9. data/lib/lydown/cli/proofing.rb +2 -2
  10. data/lib/lydown/cli/repl.rb +1 -1
  11. data/lib/lydown/cli/support.rb +0 -33
  12. data/lib/lydown/defaults.yml +50 -8
  13. data/lib/lydown/inverso.rb +84 -0
  14. data/lib/lydown/lilypond.rb +76 -10
  15. data/lib/lydown/ly_lib/lib.ly +140 -127
  16. data/lib/lydown/parsing/lydown.treetop +25 -12
  17. data/lib/lydown/parsing/nodes.rb +55 -19
  18. data/lib/lydown/rendering.rb +72 -1
  19. data/lib/lydown/rendering/base.rb +21 -0
  20. data/lib/lydown/rendering/command.rb +53 -0
  21. data/lib/lydown/rendering/layout.rb +83 -0
  22. data/lib/lydown/rendering/lyrics.rb +1 -1
  23. data/lib/lydown/rendering/markup.rb +23 -0
  24. data/lib/lydown/rendering/movement.rb +7 -4
  25. data/lib/lydown/rendering/music.rb +35 -29
  26. data/lib/lydown/rendering/notes.rb +75 -41
  27. data/lib/lydown/rendering/repeats.rb +27 -0
  28. data/lib/lydown/rendering/settings.rb +36 -9
  29. data/lib/lydown/rendering/skipping.rb +10 -2
  30. data/lib/lydown/rendering/staff.rb +38 -31
  31. data/lib/lydown/rendering/voices.rb +1 -1
  32. data/lib/lydown/templates.rb +8 -8
  33. data/lib/lydown/templates/layout.rb +40 -0
  34. data/lib/lydown/templates/lilypond_doc.rb +95 -0
  35. data/lib/lydown/templates/movement.rb +188 -0
  36. data/lib/lydown/templates/multi_voice.rb +25 -0
  37. data/lib/lydown/templates/part.rb +146 -0
  38. data/lib/lydown/templates/variables.rb +43 -0
  39. data/lib/lydown/translation/ripple.rb +1 -1
  40. data/lib/lydown/translation/ripple/nodes.rb +51 -2
  41. data/lib/lydown/translation/ripple/ripple.treetop +87 -10
  42. data/lib/lydown/version.rb +1 -1
  43. data/lib/lydown/work.rb +19 -2
  44. data/lib/lydown/work_context.rb +10 -2
  45. metadata +12 -8
  46. data/lib/lydown/cli/installer.rb +0 -175
  47. data/lib/lydown/templates/lilypond_doc.erb +0 -34
  48. data/lib/lydown/templates/movement.erb +0 -118
  49. data/lib/lydown/templates/multi_voice.erb +0 -16
  50. data/lib/lydown/templates/part.erb +0 -118
  51. data/lib/lydown/templates/variables.erb +0 -43
@@ -12,7 +12,7 @@ module Lydown::Rendering
12
12
  def lilypond_lyrics(lyrics)
13
13
  lyrics.
14
14
  gsub(/_+/) {|m| " __ #{'_ ' * (m.size - 1)}"}.
15
- gsub(/\-+/) {|m| " -- #{'_ ' * (m.size - 1)}"}
15
+ gsub(/(\\\-)?(\-+)/) {|m| "#{$1 && "-"} -- #{'_ ' * ($2.size - 1)}"}
16
16
  end
17
17
  end
18
18
  end
@@ -0,0 +1,23 @@
1
+ module Lydown::Rendering
2
+ module Markup
3
+ def self.convert(str)
4
+ convert_line_breaks(str)
5
+ end
6
+
7
+ def self.convert_styling(str)
8
+ str.
9
+ gsub(/__([^_]+)__/) {|m| "\\bold {#{$1} }" }.
10
+ gsub(/_([^_]+)_/) {|m| "\\italic {#{$1} }" }
11
+ gsub(/\^([^\^]+)\^/) {|m| "\\smallCaps {#{$1} }" }
12
+ end
13
+
14
+ def self.convert_line_breaks(str)
15
+ if str =~/\n/
16
+ lines = str.lines.map {|s| "\\fill-line { \"#{s.chomp}\" }"}
17
+ "\\markup \\column { #{lines.join(' ')} }"
18
+ else
19
+ "\"#{str}\""
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,7 +3,9 @@ module Lydown::Rendering
3
3
  def self.movement_title(context, name)
4
4
  return nil if name.nil? || name.empty?
5
5
 
6
- if name =~ /^(?:([0-9]+)([a-z]*))\-(.+)$/
6
+ if t = context.get_setting('movement_title', movement: name)
7
+ title = t
8
+ elsif name =~ /^(?:([0-9]+)([a-z]*))\-(.+)$/
7
9
  title = "#{$1.to_i}#{$2}. #{$3.capitalize}"
8
10
  else
9
11
  title = name
@@ -24,7 +26,8 @@ module Lydown::Rendering
24
26
  'before' => {before: true},
25
27
  'after' => {after: true},
26
28
  'before and after' => {before: true, after: true},
27
- 'blank page before' => {blank_page_before: true}
29
+ 'blank page before' => {blank_page_before: true},
30
+ 'bookpart before' => {bookpart_before: true}
28
31
  }
29
32
 
30
33
  def self.page_breaks(context, opts)
@@ -32,8 +35,8 @@ module Lydown::Rendering
32
35
  when :score
33
36
  context.get_setting('score/page_break', opts)
34
37
  when :part
35
- part = opts[:part] || context[:part] ||
36
- (context['options/parts'] ? context['options/parts'][0] : '')
38
+ part = opts[:part] || context[:part] || context['render_opts/parts']
39
+
37
40
  context.get_setting(:page_break, opts.merge(part: part)) ||
38
41
  context.get_setting('parts/page_break', opts)
39
42
  else
@@ -10,8 +10,25 @@ module Lydown::Rendering
10
10
  '6' => '16',
11
11
  '3' => '32'
12
12
  }
13
-
13
+
14
14
  class Duration < Base
15
+ def self.full_bar_value(time)
16
+ r = Rational(time)
17
+ case r.numerator
18
+ when 1
19
+ r.denominator.to_s
20
+ when 3
21
+ case r.denominator
22
+ when 1
23
+ "\\breve."
24
+ else
25
+ "#{r.denominator / 2}."
26
+ end
27
+ else
28
+ nil
29
+ end
30
+ end
31
+
15
32
  def translate
16
33
  Notes.cleanup_duration_macro(@context)
17
34
 
@@ -32,6 +49,7 @@ module Lydown::Rendering
32
49
  @context['process/figures_duration_value'] = value
33
50
  else
34
51
  @context['process/duration_values'] = [value]
52
+ @context['process/cross_bar_dotting'] = @event[:cross_bar_dotting]
35
53
  @context['process/tuplet_mode'] = nil
36
54
  @context['process/duration_macro'] = nil unless @context['process/macro_group']
37
55
  end
@@ -184,23 +202,11 @@ module Lydown::Rendering
184
202
  class Rest < Base
185
203
  include Notes
186
204
 
187
- def full_bar_value(time)
188
- r = Rational(time)
189
- case r.numerator
190
- when 1
191
- r.denominator.to_s
192
- when 3
193
- "#{r.denominator / 2}."
194
- else
195
- nil
196
- end
197
- end
198
-
199
205
  def translate
200
206
  translate_expressions
201
207
 
202
208
  if @event[:multiplier]
203
- value = full_bar_value(@context.get_current_setting(:time))
209
+ value = Duration.full_bar_value(@context.get_current_setting(:time))
204
210
  @context['process/duration_macro'] = nil unless @context['process/macro_group']
205
211
  if value
206
212
  @event[:rest_value] = "#{value}*#{@event[:multiplier]}"
@@ -221,21 +227,9 @@ module Lydown::Rendering
221
227
  class Silence < Base
222
228
  include Notes
223
229
 
224
- def full_bar_value(time)
225
- r = Rational(time)
226
- case r.numerator
227
- when 1
228
- r.denominator.to_s
229
- when 3
230
- "#{r.denominator / 2}."
231
- else
232
- nil
233
- end
234
- end
235
-
236
230
  def translate
237
231
  if @event[:multiplier]
238
- value = full_bar_value(@context.get_current_setting(:time))
232
+ value = Duration.full_bar_value(@context.get_current_setting(:time))
239
233
  @context['process/duration_macro'] = nil unless @context['process/macro_group']
240
234
  if value
241
235
  @event[:rest_value] = "#{value}*#{@event[:multiplier]}"
@@ -285,10 +279,22 @@ module Lydown::Rendering
285
279
  end
286
280
 
287
281
  class Barline < Base
282
+
283
+ LILYPOND_BARLINES = {
284
+ '|:' => '.|:',
285
+ ':|' => ':|.',
286
+ '|\''=> '\halfBarline',
287
+ '?|' => ''
288
+ }
289
+
288
290
  def translate
289
291
  barline = @event[:barline]
290
- barline = '' if barline == '?|'
291
- @context.emit(:music, "\\bar \"#{barline}\" ")
292
+ barline = LILYPOND_BARLINES[barline] || barline
293
+ if barline =~ /^\\/
294
+ @context.emit(:music, "#{barline} ")
295
+ else
296
+ @context.emit(:music, "\\bar \"#{barline}\" ")
297
+ end
292
298
  end
293
299
  end
294
300
  end
@@ -40,7 +40,7 @@ module Lydown::Rendering
40
40
  def self.calc_accidentals_map(accidentals)
41
41
  accidentals.inject({}) { |h, a| h[a[0]] = (a[1] == '+') ? 1 : -1; h}
42
42
  end
43
-
43
+
44
44
  ACCIDENTAL_VALUES = {
45
45
  '+' => 1,
46
46
  '-' => -1,
@@ -51,26 +51,26 @@ module Lydown::Rendering
51
51
  def self.lilypond_note_name(note, key_signature = 'c major')
52
52
  # if the natural sign (h) is used, no need to calculate the note name
53
53
  return $1 if note =~ /([a-g])h/
54
-
54
+
55
55
  value = 0
56
56
  # accidental value from note
57
57
  note = note.gsub(/[\-\+#ß]/) { |c| value += ACCIDENTAL_VALUES[c]; '' }
58
-
58
+
59
59
  # add key signature value
60
60
  value += accidentals_for_key_signature(key_signature)[note] || 0
61
61
 
62
62
  note + (value >= 0 ? 'is' * value : 'es' * -value)
63
63
  end
64
-
64
+
65
65
  def self.chromatic_to_diatonic(note, key_signature = 'c major')
66
66
  note =~ /([a-g])([\+\-]*)/
67
67
  diatonic_note = $1
68
68
  chromatic_value = $2.count('+') - $2.count('-')
69
-
69
+
70
70
  key_accidentals = accidentals_for_key_signature(key_signature)
71
71
  diatonic_value = key_accidentals[diatonic_note] || 0
72
72
  value = chromatic_value - diatonic_value
73
-
73
+
74
74
  "#{diatonic_note}#{value >= 0 ? '+' * value : '-' * -value}"
75
75
  end
76
76
 
@@ -84,35 +84,35 @@ module Lydown::Rendering
84
84
  lilypond_note_name(note, key)
85
85
  end
86
86
  end
87
-
87
+
88
88
  module Octaves
89
89
  DIATONICS = %w{a b c d e f g}
90
90
 
91
- # calculates the octave markers needed to put a first note in the right
91
+ # calculates the octave markers needed to put a first note in the right
92
92
  # octave. In lydown, octaves are relative (i.e. lilypond's relative mode).
93
- # But the first note gives the octave to start on, rather than a relative
93
+ # But the first note gives the octave to start on, rather than a relative
94
94
  # note to c (or any other reference note).
95
95
  #
96
- # In that manner, d' is d above middle c, g'' is g an octave and fifth
96
+ # In that manner, d' is d above middle c, g'' is g an octave and fifth
97
97
  # above middle c, a is a a below middle c, and eß, is great e flat.
98
- #
98
+ #
99
99
  # The return value is a string with octave markers for relative mode,
100
100
  # based on the refence note
101
101
  def self.relative_octave(note, ref_note = 'c')
102
102
  note_diatonic, ref_diatonic = note[0], ref_note[0]
103
103
  raise LydownError, "Invalid note #{note}" unless DIATONICS.index(note_diatonic)
104
104
  raise LydownError, "Invalid reference note #{ref_note}" unless DIATONICS.index(ref_diatonic)
105
-
105
+
106
106
  # calculate diatonic interval
107
107
  note_array = DIATONICS.rotate(DIATONICS.index(ref_diatonic))
108
108
  interval = note_array.index(note_diatonic)
109
109
 
110
- # calculate octave interval and
110
+ # calculate octave interval and
111
111
  octave_value = note.count("'") - note.count(',')
112
112
  ref_value = ref_note.count("'") - ref_note.count(',')
113
113
  octave_interval = octave_value - ref_value
114
114
  octave_interval += 1 if interval >= 4
115
-
115
+
116
116
  # generate octave markers
117
117
  octave_interval >= 0 ? "'" * octave_interval : "," * -octave_interval
118
118
  end
@@ -121,17 +121,17 @@ module Lydown::Rendering
121
121
  note_diatonic, ref_diatonic = note[0], ref_note[0]
122
122
  raise LydownError, "Invalid note #{note}" unless DIATONICS.index(note_diatonic)
123
123
  raise LydownError, "Invalid reference note #{ref_note}" unless DIATONICS.index(ref_diatonic)
124
-
124
+
125
125
  # calculate diatonic interval
126
126
  note_array = DIATONICS.rotate(DIATONICS.index(ref_diatonic))
127
127
  interval = note_array.index(note_diatonic)
128
128
 
129
- # calculate octave interval and
129
+ # calculate octave interval and
130
130
  note_value = note.count("'") - note.count(',')
131
131
  ref_value = ref_note.count("'") - ref_note.count(',')
132
132
  octave_interval = ref_value + note_value
133
133
  octave_interval -= 1 if interval >= 4
134
-
134
+
135
135
  # generate octave markers
136
136
  octave_interval >= 0 ? "'" * octave_interval : "," * -octave_interval
137
137
  end
@@ -142,19 +142,19 @@ module Lydown::Rendering
142
142
 
143
143
  def add_note(event, options = {})
144
144
  @context.set_setting(:got_music, true)
145
-
145
+
146
146
  return add_macro_note(event) if @context['process/duration_macro']
147
-
147
+
148
148
  # calculate relative octave markers for first note
149
149
  unless @context['process/first_note'] || event[:head] =~ /^[rsR]/
150
150
  note = event[:head] + (event[:octave] || '')
151
151
  event[:octave] = Lydown::Rendering::Octaves.relative_octave(note)
152
152
  @context['process/first_note'] = note
153
153
  end
154
-
154
+
155
155
  if event[:head] == '@'
156
156
  # replace repeating note head
157
- event[:head] = @context['process/last_note_head']
157
+ event[:head] = @context['process/last_note_head']
158
158
  else
159
159
  @context['process/last_note_head'] = event[:head]
160
160
  end
@@ -174,7 +174,7 @@ module Lydown::Rendering
174
174
  @context['process/running_values'] << value
175
175
  end
176
176
  end
177
-
177
+
178
178
  # only add the value if different than the last used
179
179
  if options[:no_value] || (value == @context['process/last_value'])
180
180
  value = ''
@@ -190,7 +190,7 @@ module Lydown::Rendering
190
190
  code = lilypond_note(event, options.merge(value: value))
191
191
  @context.emit(event[:stream] || :music, code)
192
192
  end
193
-
193
+
194
194
  def add_chord(event, options = {})
195
195
  value = @context['process/duration_values'].first
196
196
  @context['process/duration_values'].rotate!
@@ -214,16 +214,20 @@ module Lydown::Rendering
214
214
  else
215
215
  @context['process/last_value'] = value
216
216
  end
217
-
217
+
218
218
  notes = event[:notes].map do |note|
219
219
  lilypond_note(note)
220
220
  end
221
-
221
+
222
222
  options = options.merge(value: value)
223
223
  @context.emit(event[:stream] || :music, lilypond_chord(event, notes, options))
224
224
  end
225
-
225
+
226
226
  def lilypond_note(event, options = {})
227
+ if @context['process/cross_bar_dotting']
228
+ return cross_bar_dot_lilypond_note(event, options)
229
+ end
230
+
227
231
  head = Accidentals.translate_note_name(@context, event[:head])
228
232
  if options[:head_only]
229
233
  head
@@ -235,7 +239,7 @@ module Lydown::Rendering
235
239
  accidental_flag = event[:accidental_flag]
236
240
  prefix = ''
237
241
  end
238
-
242
+
239
243
  [
240
244
  prefix,
241
245
  head,
@@ -249,6 +253,37 @@ module Lydown::Rendering
249
253
  end
250
254
  end
251
255
 
256
+ TRANSPARENT_TIE = "\\once \\override Tie #'transparent = ##t"
257
+ TRANSPARENT_NOTE = <<EOF
258
+ \\once \\override NoteHead #'transparent = ##t
259
+ \\once \\override Dots #'extra-offset = #'(-1.3 . 0)
260
+ \\once \\override Stem #'transparent = ##t
261
+ EOF
262
+
263
+ def cross_bar_dot_lilypond_note(event, options)
264
+ @context['process/cross_bar_dotting'] = nil
265
+
266
+ original_duration = @context['process/duration_values'][0]
267
+ original_duration =~ /([0-9]+)(\.+)/
268
+ value, dots = $1, $2
269
+
270
+ main_note = lilypond_note(event, options.merge(value: value))
271
+
272
+ cross_bar_note_head = lilypond_note(event, options.merge(head_only: true))
273
+ cross_bar_note = "#{cross_bar_note_head}#{original_duration}*0"
274
+
275
+ silence = "s#{value.to_i * 2} "
276
+
277
+ [
278
+ TRANSPARENT_TIE,
279
+ main_note,
280
+ '~',
281
+ TRANSPARENT_NOTE,
282
+ cross_bar_note,
283
+ silence
284
+ ].join(' ')
285
+ end
286
+
252
287
  def lilypond_chord(event, notes, options = {})
253
288
  [
254
289
  '<',
@@ -309,7 +344,7 @@ module Lydown::Rendering
309
344
  event[:figures] ? "<#{event[:figures].join}>" : '',
310
345
  event[:expressions] ? event[:expressions].join + ' ' : ''
311
346
  ]
312
-
347
+
313
348
  # replace place holder and repeaters in macro group with actual note
314
349
  @context['process/macro_group'].gsub!(/[_∞]/) do |match|
315
350
  case match
@@ -325,7 +360,7 @@ module Lydown::Rendering
325
360
  # correct
326
361
  @context['process/macro_filename'] = event[:filename]
327
362
  @context['process/macro_source'] = event[:source]
328
-
363
+
329
364
  # increment group note count
330
365
  @context['process/macro_group_note_count'] ||= 0
331
366
  @context['process/macro_group_note_count'] += 1
@@ -335,20 +370,19 @@ module Lydown::Rendering
335
370
  Notes.add_duration_macro_group(@context, @context['process/macro_group'])
336
371
  end
337
372
  end
338
-
373
+
339
374
  # emits the current macro group up to the first placeholder character.
340
- # this method is called
375
+ # this method is called
341
376
  def self.cleanup_duration_macro(context)
342
377
  return unless context['process/macro_group_note_count'] &&
343
378
  context['process/macro_group_note_count'] > 0
344
-
379
+
345
380
  # truncate macro group up until first placeholder
346
- group = context['process/macro_group'].sub(/_.*$/, '')
381
+ group = context['process/macro_group'].sub(/(?<!\<)_.*$/, '')
347
382
 
348
- # Refrain from adding
349
383
  add_duration_macro_group(context, group)
350
384
  end
351
-
385
+
352
386
  def self.add_duration_macro_group(context, group)
353
387
  opts = (context[:options] || {}).merge({
354
388
  filename: context['process/macro_filename'],
@@ -367,7 +401,7 @@ module Lydown::Rendering
367
401
  # restore macro
368
402
  context['process/duration_macro'] = macro
369
403
  end
370
-
404
+
371
405
  def add_macro_event(code)
372
406
  case @context['process/macro_group']
373
407
  when nil
@@ -389,7 +423,7 @@ module Lydown::Rendering
389
423
  '>' => 'left-align',
390
424
  '|' => 'center-align'
391
425
  }
392
-
426
+
393
427
  DYNAMICS = %w{
394
428
  pppp ppp pp p mp mf f ff fff ffff fp sf sff sp spp sfz rfz
395
429
  }
@@ -421,19 +455,19 @@ module Lydown::Rendering
421
455
  gsub(/__([^_]+)__/) {|m| "\\bold { #{$1} }" }.
422
456
  gsub(/_([^_]+)_/) {|m| "\\italic { #{$1} }" }
423
457
  end
424
-
458
+
425
459
  TEXTMATE_URL = "txmt://open?url=file://%s&line=%d&column=%d"
426
-
460
+
427
461
  ADD_LINK_COMMAND = '\once \override NoteHead.after-line-breaking =
428
462
  #(add-link "%s") '
429
-
463
+
430
464
  def note_event_url_link(event)
431
465
  url = TEXTMATE_URL % [
432
466
  File.expand_path(event[:filename]).uri_escape,
433
467
  event[:line],
434
468
  event[:column]
435
469
  ]
436
-
470
+
437
471
  ADD_LINK_COMMAND % [url]
438
472
  end
439
473
  end