lydown 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +159 -2
  3. data/lib/lydown.rb +8 -2
  4. data/lib/lydown/cache.rb +54 -0
  5. data/lib/lydown/cli.rb +1 -0
  6. data/lib/lydown/cli/commands.rb +27 -9
  7. data/lib/lydown/cli/compiler.rb +218 -54
  8. data/lib/lydown/cli/diff.rb +1 -1
  9. data/lib/lydown/cli/proofing.rb +3 -3
  10. data/lib/lydown/cli/signals.rb +23 -0
  11. data/lib/lydown/cli/support.rb +23 -1
  12. data/lib/lydown/core_ext.rb +41 -5
  13. data/lib/lydown/{rendering/defaults.yml → defaults.yml} +3 -3
  14. data/lib/lydown/errors.rb +3 -0
  15. data/lib/lydown/lilypond.rb +73 -31
  16. data/lib/lydown/ly_lib/lib.ly +297 -0
  17. data/lib/lydown/parsing.rb +1 -2
  18. data/lib/lydown/parsing/lydown.treetop +16 -10
  19. data/lib/lydown/parsing/nodes.rb +29 -5
  20. data/lib/lydown/rendering.rb +32 -6
  21. data/lib/lydown/rendering/command.rb +79 -2
  22. data/lib/lydown/rendering/figures.rb +29 -8
  23. data/lib/lydown/rendering/literal.rb +7 -0
  24. data/lib/lydown/rendering/movement.rb +61 -0
  25. data/lib/lydown/rendering/music.rb +37 -5
  26. data/lib/lydown/rendering/notes.rb +26 -8
  27. data/lib/lydown/rendering/settings.rb +41 -13
  28. data/lib/lydown/rendering/skipping.rb +43 -10
  29. data/lib/lydown/rendering/staff.rb +72 -16
  30. data/lib/lydown/templates.rb +8 -2
  31. data/lib/lydown/templates/lilypond_doc.erb +10 -1
  32. data/lib/lydown/templates/movement.erb +87 -34
  33. data/lib/lydown/templates/multi_voice.erb +1 -1
  34. data/lib/lydown/templates/part.erb +83 -55
  35. data/lib/lydown/templates/variables.erb +38 -0
  36. data/lib/lydown/version.rb +1 -1
  37. data/lib/lydown/work.rb +39 -26
  38. data/lib/lydown/work_context.rb +252 -14
  39. metadata +138 -8
  40. data/lib/lydown/rendering/lib.ly +0 -88
@@ -3,6 +3,8 @@ require 'lydown/rendering/notes'
3
3
 
4
4
  module Lydown::Rendering
5
5
  LILYPOND_DURATIONS = {
6
+ '0' => "\\breve",
7
+ 'l' => "\\longa",
6
8
  '6' => '16',
7
9
  '3' => '32'
8
10
  }
@@ -22,7 +24,7 @@ module Lydown::Rendering
22
24
  @context['process/grace_mode'] = nil
23
25
  end
24
26
 
25
- value = @event[:value].sub(/^[0-9]+/) {|m| LILYPOND_DURATIONS[m] || m}
27
+ value = @event[:value].sub(/^[0-9]+|l/) {|m| LILYPOND_DURATIONS[m] || m}
26
28
 
27
29
  if next_event && next_event[:type] == :stand_alone_figures
28
30
  @context['process/figures_duration_value'] = value
@@ -105,7 +107,7 @@ module Lydown::Rendering
105
107
  end
106
108
  look_ahead_idx += 1
107
109
  end
108
-
110
+
109
111
  add_note(@event)
110
112
  end
111
113
  end
@@ -114,6 +116,8 @@ module Lydown::Rendering
114
116
  include Notes
115
117
 
116
118
  def translate
119
+ translate_expressions
120
+
117
121
  look_ahead_idx = @idx + 1
118
122
  while event = @stream[look_ahead_idx]
119
123
  case event[:type]
@@ -194,13 +198,14 @@ module Lydown::Rendering
194
198
  translate_expressions
195
199
 
196
200
  if @event[:multiplier]
197
- value = full_bar_value(@context[:time])
201
+ value = full_bar_value(@context.get_current_setting(:time))
198
202
  @context['process/duration_macro'] = nil unless @context['process/macro_group']
199
203
  if value
200
204
  @event[:rest_value] = "#{value}*#{@event[:multiplier]}"
201
205
  @event[:head] = "#{@event[:head]}#{@event[:rest_value]}"
202
206
  else
203
- @event[:head] = "#{@event[:head]}#{@event[:multiplier]}*#{@context[:time]}"
207
+ @event[:head] = "#{@event[:head]}#{@event[:multiplier]}*#{
208
+ @context.get_current_setting(:time)}"
204
209
  end
205
210
  # reset the last value so the next note will be rendered with its value
206
211
  @context['process/last_value'] = nil
@@ -214,7 +219,34 @@ module Lydown::Rendering
214
219
  class Silence < Base
215
220
  include Notes
216
221
 
222
+ def full_bar_value(time)
223
+ r = Rational(time)
224
+ case r.numerator
225
+ when 1
226
+ r.denominator.to_s
227
+ when 3
228
+ "#{r.denominator / 2}."
229
+ else
230
+ nil
231
+ end
232
+ end
233
+
217
234
  def translate
235
+ if @event[:multiplier]
236
+ value = full_bar_value(@context.get_current_setting(:time))
237
+ @context['process/duration_macro'] = nil unless @context['process/macro_group']
238
+ if value
239
+ @event[:rest_value] = "#{value}*#{@event[:multiplier]}"
240
+ @event[:head] = "s#{@event[:rest_value]}"
241
+ else
242
+ @event[:head] = "s#{@event[:multiplier]}*#{
243
+ @context.get_current_setting(:time)}"
244
+ end
245
+ # reset the last value so the next note will be rendered with its value
246
+ @context['process/last_value'] = nil
247
+ @context['process/duration_values'] = []
248
+ end
249
+
218
250
  add_note(@event)
219
251
  end
220
252
  end
@@ -233,7 +265,7 @@ module Lydown::Rendering
233
265
  Notes.cleanup_duration_macro(@context)
234
266
 
235
267
  if @event[:macro] =~ /^[a-zA-Z_]/
236
- macro = @context['macros'][@event[:macro]]
268
+ macro = @context.get_current_setting('macros')[@event[:macro]]
237
269
  if macro
238
270
  if macro =~ /^\{(.+)\}$/
239
271
  macro = $1
@@ -74,10 +74,10 @@ module Lydown::Rendering
74
74
 
75
75
  # Takes into account the accidentals mode
76
76
  def self.translate_note_name(context, note)
77
- if context[:accidentals] == 'manual'
77
+ if context.get_current_setting(:accidentals) == 'manual'
78
78
  key = 'c major'
79
79
  else
80
- key = context[:key]
80
+ key = context.get_current_setting(:key)
81
81
  end
82
82
  lilypond_note_name(note, key)
83
83
  end
@@ -139,6 +139,8 @@ module Lydown::Rendering
139
139
  include Lydown::Rendering::Figures
140
140
 
141
141
  def add_note(event, options = {})
142
+ @context.set_setting(:got_music, true)
143
+
142
144
  return add_macro_note(event) if @context['process/duration_macro']
143
145
 
144
146
  # calculate relative octave markers for first note
@@ -170,7 +172,7 @@ module Lydown::Rendering
170
172
  @context['process/running_values'] << value
171
173
  end
172
174
  end
173
-
175
+
174
176
  # only add the value if different than the last used
175
177
  if options[:no_value] || (value == @context['process/last_value'])
176
178
  value = ''
@@ -253,6 +255,7 @@ module Lydown::Rendering
253
255
  options[:value],
254
256
  lilypond_phrasing(event),
255
257
  event[:expressions] ? event[:expressions].join : '',
258
+ ' '
256
259
  ].join
257
260
  end
258
261
 
@@ -379,15 +382,30 @@ module Lydown::Rendering
379
382
  '`' => '-!'
380
383
  }
381
384
 
385
+ MARKUP_ALIGNMENT = {
386
+ '<' => 'right-align',
387
+ '>' => 'left-align',
388
+ '|' => 'center-align'
389
+ }
390
+
391
+ DYNAMICS = %w{
392
+ pppp ppp pp p mp mf f ff fff ffff fp sf sff sp spp sfz rfz
393
+ }
394
+
382
395
  def translate_expressions
383
396
  return unless @event[:expressions]
384
397
 
385
398
  @event[:expressions] = @event[:expressions].map do |expr|
386
- if expr =~ /^(?:\\(_?))?"(.+)"$/
387
- placement = ($1 == '_') ? '_' : '^'
388
- "#{placement}\\markup { #{translate_string_expression($2)} }"
389
- elsif expr =~ /^\\/
390
- expr
399
+ if expr =~ /^(?:\\(_?)([<>\|])?)?"(.+)"$/
400
+ v_pos = ($1 == '_') ? '_' : '^'
401
+ content = translate_string_expression($3)
402
+ if MARKUP_ALIGNMENT[$2]
403
+ content = "\\#{MARKUP_ALIGNMENT[$2]} { #{content} }"
404
+ end
405
+ "#{v_pos}\\markup { #{content} }"
406
+ elsif expr =~ /^\\([_^]?)(.+)$/
407
+ v_pos = $1
408
+ "#{v_pos}\\#{$2}"
391
409
  elsif LILYPOND_EXPRESSIONS[expr]
392
410
  LILYPOND_EXPRESSIONS[expr]
393
411
  else
@@ -5,7 +5,9 @@ module Lydown::Rendering
5
5
  SETTING_KEYS = [
6
6
  'key', 'time', 'pickup', 'clef', 'part', 'movement', 'tempo',
7
7
  'accidentals', 'beams', 'end_barline', 'macros', 'empty_staves',
8
- 'midi_tempo'
8
+ 'midi_tempo', 'instrument_names', 'instrument_name_style',
9
+ 'parts', 'score', 'movement_source', 'colla_parte', 'include',
10
+ 'mode', 'nomode', 'bar_numbers'
9
11
  ]
10
12
 
11
13
  RENDERABLE_SETTING_KEYS = [
@@ -15,7 +17,12 @@ module Lydown::Rendering
15
17
  ALLOWED_SETTING_VALUES = {
16
18
  'accidentals' => ['manual', 'auto'],
17
19
  'beams' => ['manual', 'auto'],
18
- 'empty_staves' => ['hide', 'show']
20
+ 'empty_staves' => ['hide', 'show'],
21
+ 'instrument_names' => ['hide', 'show', 'inline', 'inline-right-align', 'inline-center-align'],
22
+ 'instrument_name_style' => ['normal', 'smallcaps'],
23
+ 'page_break' => ['none', 'before', 'after', 'before and after', 'blank page before'],
24
+ 'mode' => ['score', 'part', 'none'],
25
+ 'bar_numbers' => ['hide', 'shows']
19
26
  }
20
27
 
21
28
  def translate
@@ -32,26 +39,37 @@ module Lydown::Rendering
32
39
  raise LydownError, "Invalid setting (#{key})"
33
40
  end
34
41
 
42
+ value = check_setting_value(key, value)
43
+
35
44
  if level == 0
36
- value = check_setting_value(key, value)
37
- @context[key] = value
45
+ movement = @context[:movement]
38
46
  case key
39
47
  when 'part'
48
+ @context[:part] = value
40
49
  @context.set_part_context(value)
41
50
 
42
51
  # when changing parts we repeat the last set time and key signature
43
- render_setting('time', @context[:time]) unless @context[:time] == '4/4'
44
-
45
- key = @context[:key]
52
+ time = @context.get_current_setting(:time)
53
+ key = @context.get_current_setting(:key)
54
+
55
+ render_setting('time', time) unless time == '4/4'
46
56
  render_setting('key', key) unless key == 'c major'
47
57
 
48
58
  @context.reset(:part)
49
59
  when 'movement'
60
+ @context[:movement] = value
50
61
  @context.reset(:movement)
51
- when 'midi_tempo'
52
- movement = @context[:movement]
53
- path = "movements/#{movement}/settings/midi_tempo"
54
- @context[path] = value
62
+ when 'include'
63
+ includes = @context.get_current_setting(:includes)
64
+ includes ||= []
65
+ includes << value
66
+ @context.set_setting(:includes, includes)
67
+ when 'mode'
68
+ set_mode(value.nil? ? :none : value.to_sym)
69
+ when 'nomode'
70
+ set_mode(:none)
71
+ else
72
+ @context.set_setting(key, value) unless @event[:ephemeral]
55
73
  end
56
74
 
57
75
  if RENDERABLE_SETTING_KEYS.include?(key)
@@ -64,7 +82,7 @@ module Lydown::Rendering
64
82
  path << "#{@context['process/setting_levels'][l]}/"; l += 1
65
83
  end
66
84
  path << key
67
- @context[path] = value
85
+ @context.set_setting(path, value)
68
86
  end
69
87
 
70
88
  @context['process/setting_levels'] ||= {}
@@ -102,7 +120,7 @@ module Lydown::Rendering
102
120
 
103
121
  unless should_cadence
104
122
  signature = value.sub(/[0-9]+$/) { |m| LILYPOND_DURATIONS[m] || m }
105
- setting << "\\#{key} #{signature} "
123
+ setting << "\\time #{signature} "
106
124
  end
107
125
  when 'key'
108
126
  # If the next event is a key signature, no need to emit this one
@@ -118,6 +136,12 @@ module Lydown::Rendering
118
136
  setting = "\\#{key} #{note} \\#{mode} "
119
137
  when 'clef'
120
138
  setting = "\\#{key} \"#{value}\" "
139
+ # If no music is there, and we're rendering a clef command, we need
140
+ # tell lydown to not render a first clef command inside the Staff
141
+ # context.
142
+ unless @context.get_current_setting(:got_music)
143
+ @context.set_setting(:inhibit_first_clef, true)
144
+ end
121
145
  when 'beams'
122
146
  setting = (value == 'manual') ? '\autoBeamOff ' : '\autoBeamOn '
123
147
  else
@@ -126,6 +150,10 @@ module Lydown::Rendering
126
150
 
127
151
  @context.emit(:music, setting)
128
152
  end
153
+
154
+ def set_mode(mode)
155
+ @context['process/mode'] = (mode == :none) ? nil : mode
156
+ end
129
157
  end
130
158
  end
131
159
 
@@ -1,35 +1,68 @@
1
1
  module Lydown::Rendering
2
+ PROOFING_LY_SETTINGS = <<EOF
3
+ \\paper {
4
+ indent=0\\mm
5
+ line-width=110\\mm
6
+ oddFooterMarkup=##f
7
+ oddHeaderMarkup=##f
8
+ bookTitleMarkup = ##f
9
+ scoreTitleMarkup = ##f
10
+ page-breaking = #ly:minimal-breaking
11
+ }
12
+ EOF
13
+
2
14
  class << self
3
- SKIP_ON_CMD = {type: :command, key: 'set', arguments: ['Score.skipTypesetting = ##t']}
4
- SKIP_OFF_CMD = {type: :command, key: 'set', arguments: ['Score.skipTypesetting = ##f']}
15
+ SKIP_GRACE = {type: :literal, content: "\\grace s8"}
5
16
 
6
- BARLINE = {type: :barline, barline: '|'}
7
- BAR_NUMBERS_CMD = {type: :command, key: 'set', arguments: ['Score.barNumberVisibility = #all-bar-numbers-visible']}
17
+ SKIP_ON_CMD = {type: :literal, content: "\\set Score.skipTypesetting = ##t"}
18
+ SKIP_OFF_CMD = {type: :literal, content: "\\set Score.skipTypesetting = ##f"}
19
+
20
+ SKIP_BARLINE = {type: :literal, content: "\\bar \"\""}
21
+ SKIP_BAR_NUMBERS_CMD = {type: :literal, content: "\\override Score.BarNumber.break-visibility = ##(#t #t #t) \
22
+ \\set Score.barNumberVisibility = #(every-nth-bar-number-visible 1)"}
8
23
 
9
- HIGHLIGHT_NOTES_CMD = {type: :command, key: 'override', arguments: ['NoteHead.color = #red']}
10
- NORMAL_NOTES_CMD = {type: :command, key: 'override', arguments: ['NoteHead.color = #black']}
24
+ HIGHLIGHT_NOTES_CMD = {type: :literal, content: "\\override NoteHead.color = #red"}
25
+ NORMAL_NOTES_CMD = {type: :literal, content: "\\override NoteHead.color = #black"}
11
26
 
12
27
  def insert_skip_markers(stream, line_range)
13
28
  # find indexes of first and last changed lines
14
- changed_first_idx = find_line_idx(stream, line_range.first)
15
- changed_last_idx = find_line_idx(stream, line_range.last, true)
29
+ changed_first_idx = find_line_idx(stream, line_range[0])
30
+ changed_last_idx = find_line_idx(stream, line_range[1], true)
16
31
 
17
32
  # find index of first line to include
18
33
  start_line = line_range.first - 2; start_line = 0 if start_line < 0
19
34
  start_idx = find_line_idx(stream, start_line)
20
35
 
36
+ # find index of last line to include
37
+ end_line = line_range.last + 3
38
+ end_idx = find_line_idx(stream, end_line)
39
+
40
+ if end_idx && end_idx > changed_last_idx
41
+ stream.insert(end_idx, SKIP_ON_CMD)
42
+ stream.insert(end_idx, SKIP_GRACE)
43
+ end
44
+
21
45
  if changed_last_idx
22
46
  stream.insert(changed_last_idx + 1, NORMAL_NOTES_CMD)
23
47
  end
24
48
 
25
49
  if changed_first_idx
26
- stream.insert(changed_first_idx, BARLINE)
27
- stream.insert(changed_first_idx, BAR_NUMBERS_CMD)
28
50
  stream.insert(changed_first_idx, HIGHLIGHT_NOTES_CMD)
29
51
  end
30
52
 
31
53
  if start_line > 0 && start_idx
54
+ # Insert an invisible barline (in order to show bar number for first
55
+ # bar), and show bar numbers on every subsequent barline.
56
+ stream.insert(start_idx, SKIP_BARLINE)
57
+ stream.insert(start_idx, SKIP_BAR_NUMBERS_CMD)
58
+
32
59
  stream.insert(start_idx, SKIP_OFF_CMD)
60
+ # "Help" lilypond correctly render rhythms when skipping on/off by
61
+ # inserting a silent grace note.
62
+ # See https://code.google.com/p/lilypond/issues/detail?id=1543&q=skiptypesetting&colspec=ID%20Type%20Status%20Stars%20Owner%20Patch%20Needs%20Summary
63
+ stream.insert(start_idx, SKIP_GRACE)
64
+
65
+ # Skip beginning
33
66
  stream.insert(0, SKIP_ON_CMD)
34
67
  end
35
68
  end
@@ -1,7 +1,7 @@
1
1
  module Lydown::Rendering
2
2
  module Staff
3
- def self.staff_groups(context, movement, parts)
4
- model = context['score/order'] || movement['score/order'] || DEFAULTS['score/order']
3
+ def self.staff_groups(context, opts, parts)
4
+ model = context.get_setting('score/order', opts)
5
5
  parts_copy = parts.clone
6
6
 
7
7
  groups = []
@@ -28,7 +28,8 @@ module Lydown::Rendering
28
28
  "bracket" => "SystemStartBracket"
29
29
  }
30
30
 
31
- BRACKET_PARTS = %w{soprano alto tenore basso}
31
+ BRACKET_PARTS = %w{soprano alto tenore basso soprano1 soprano2
32
+ alto1 alto2 tenore1 tenore2 basso1 basso2}
32
33
 
33
34
  def self.staff_group_directive(group)
34
35
  if group.size == 1
@@ -42,6 +43,7 @@ module Lydown::Rendering
42
43
 
43
44
  # renders a systemStartDelimiterHierarchy expression
44
45
  def self.staff_hierarchy(staff_groups)
46
+ directive = nil
45
47
  expr = staff_groups.inject('') do |m, group|
46
48
  directive = staff_group_directive(group)
47
49
  if directive
@@ -51,36 +53,90 @@ module Lydown::Rendering
51
53
  end
52
54
  m
53
55
  end
54
-
55
- "#'(SystemStartBracket #{expr})"
56
+
57
+ if (staff_groups.size == 1) && (staff_groups[0].size > 1) && directive == "SystemStartBracket"
58
+ # If all staves are already group, no need to add the system bracket
59
+ "#'#{expr}"
60
+ else
61
+ "#'(SystemStartBar #{expr})"
62
+ end
56
63
  end
57
64
 
58
- def self.clef(part)
59
- DEFAULTS["parts/#{part}/clef"]
65
+ def self.clef(context, opts)
66
+ !context.get_setting(:inhibit_first_clef, opts) &&
67
+ context.get_setting(:clef, opts)
68
+ end
69
+
70
+ def self.prevent_remove_empty(context, opts)
71
+ case context.get_setting(:remove_empty, opts)
72
+ when false, 'false'
73
+ true
74
+ else
75
+ false
76
+ end
60
77
  end
61
78
 
62
- def self.midi_instrument(part)
63
- DEFAULTS["parts/#{part}/midi_instrument"]
79
+ def self.midi_instrument(context, opts)
80
+ context.get_setting(:midi_instrument, opts)
64
81
  end
65
82
 
66
- def self.beaming_mode(part)
67
- beaming = DEFAULTS["parts/#{part}/beaming"]
83
+ def self.beaming_mode(context, opts)
84
+ beaming = context.get_setting(:beaming, opts)
68
85
  return nil if beaming.nil?
69
86
 
70
87
  case beaming
71
88
  when 'auto'
72
- '\autoBeamOn'
89
+ '\\set Staff.autoBeaming = ##t'
73
90
  when 'manual'
74
- '\autoBeamOff'
91
+ '\\set Staff.autoBeaming = ##f'
75
92
  else
76
93
  raise LydownError, "Invalid beaming mode (#{beaming.inspect})"
77
94
  end
78
95
  end
79
96
 
80
- DEFAULT_END_BARLINE = '|.'
97
+ def self.staff_id(part)
98
+ title = Lydown::Rendering.default_part_title(part).gsub(/\s+/, '')
99
+ "#{title}Staff"
100
+ end
101
+
102
+ def self.qualified_part_title(context, opts)
103
+ title = context.get_setting("parts/#{opts[:part]}/title", opts) ||
104
+ Lydown::Rendering.default_part_title(opts[:part])
105
+ title.strip
106
+ end
81
107
 
82
- def self.end_barline(context, movement)
83
- barline = movement['end_barline'] || context['end_barline'] || DEFAULT_END_BARLINE
108
+ def self.part_title(context, opts)
109
+ title = qualified_part_title(context, opts)
110
+
111
+ if title.empty?
112
+ "#\"\" "
113
+ else
114
+ if context.get_setting('instrument_name_style', opts) == 'smallcaps'
115
+ "\\markup { \\smallCaps { #{title} } } "
116
+ else
117
+ "#\"#{title}\" "
118
+ end
119
+ end
120
+ end
121
+
122
+ def self.inline_part_title(context, opts)
123
+ title = qualified_part_title(context, opts)
124
+
125
+ if title.empty?
126
+ "#\"\""
127
+ else
128
+ if context.get_setting('instrument_name_style', opts) == 'smallcaps'
129
+ "<>^\\markup { #{opts[:alignment]} \\smallCaps { #{title} } } "
130
+ else
131
+ "<>^\\markup { #{opts[:alignment]} { #{title} } } "
132
+ end
133
+ end
134
+ end
135
+
136
+ def self.end_barline(context, opts)
137
+ return nil if context['global/settings/inhibit_end_barline']
138
+
139
+ barline = context.get_setting('end_barline', opts)
84
140
  barline == 'none' ? nil : barline
85
141
  end
86
142
  end