lydown 0.9.0 → 0.10.0

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.
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
@@ -23,7 +23,6 @@ class LydownParser
23
23
 
24
24
  unless ast
25
25
  error_msg = format_parser_error(source, parser, opts)
26
- $stderr.puts error_msg
27
26
  raise LydownError, error_msg
28
27
  else
29
28
  stream = []
@@ -54,7 +53,7 @@ class LydownParser
54
53
  end
55
54
 
56
55
  def self.format_parser_error(source, parser, opts)
57
- msg = opts[:filename] ? "#{opts[:filename]}: " : ""
56
+ msg = opts[:filename] ? "#{Pathname.relative_pwd(opts[:filename])}: " : ""
58
57
  if opts[:nice_error]
59
58
  msg << "Unexpected character at line #{parser.failure_line} column #{parser.failure_column}:\n"
60
59
  else
@@ -8,10 +8,10 @@ grammar Lydown
8
8
  music_stream / lyrics_stream
9
9
  end
10
10
  rule music_stream
11
- '=music' white_space? [\n] music ([\n] !stream_breaker music)*
11
+ '=' white_space? 'music' white_space? [\n] music ([\n] !stream_breaker music)*
12
12
  end
13
13
  rule lyrics_stream
14
- '=lyrics' stream_idx? white_space? [\n] lyrics_content
14
+ '=' white_space? 'lyrics' stream_idx? white_space? [\n] lyrics_content
15
15
  ([\n] !stream_breaker lyrics_content)* <Lyrics>
16
16
  end
17
17
  rule stream_breaker
@@ -47,7 +47,7 @@ grammar Lydown
47
47
  end
48
48
  rule event
49
49
  (inline_command / inline_lyrics / voice_selector / barline / source_ref /
50
- grace_duration / duration / chord / note / standalone_figures / rest /
50
+ grace_duration / duration / standalone_figures / chord / note / rest /
51
51
  silence / phrasing / tie) white_space*
52
52
  end
53
53
  rule barline
@@ -66,7 +66,10 @@ grammar Lydown
66
66
  number '%' (number '/' number)? <TupletValue>
67
67
  end
68
68
  rule duration_value
69
- number dots* multiplier? <DurationValue>
69
+ duration_number dots* multiplier? <DurationValue>
70
+ end
71
+ rule duration_number
72
+ [0-9]+ / 'l'
70
73
  end
71
74
  rule number
72
75
  [0-9]+
@@ -100,14 +103,17 @@ grammar Lydown
100
103
  '<' note white_space* (note white_space*)* '>' expression* <Chord>
101
104
  end
102
105
  rule expression
103
- (expression_shorthand / expression_longhand / string) <Note::Expression>
106
+ (expression_shorthand / expression_string / expression_longhand) <Note::Expression>
104
107
  end
105
108
 
106
109
  rule expression_shorthand
107
110
  [\_\.`]
108
111
  end
109
112
  rule expression_longhand
110
- '\\' [^\s\n]+
113
+ '\\' [_\^]? [a-zA-Z]+
114
+ end
115
+ rule expression_string
116
+ '\\' '_'? [<>\|]? string
111
117
  end
112
118
  rule string
113
119
  '"' ('\"' / !'"' .)* '"'
@@ -116,7 +122,7 @@ grammar Lydown
116
122
  '<' figures_component? (white_space? figures_component)* '>'
117
123
  end
118
124
  rule figures_component
119
- ('_' / [#bh] / ([1-9] [\+\-\!\\'`]*)) <FiguresComponent>
125
+ ([_\-\.] / [#bh] / ([1-9] [\+\-\!\\'`]*)) <FiguresComponent>
120
126
  end
121
127
  rule standalone_figures
122
128
  duration_value? figures <StandAloneFigures>
@@ -125,10 +131,10 @@ grammar Lydown
125
131
  [rR] multiplier* rest_expression* <Rest>
126
132
  end
127
133
  rule rest_expression
128
- (expression_longhand / string) <Note::Expression>
134
+ (expression_string / expression_longhand) <Note::Expression>
129
135
  end
130
136
  rule silence
131
- [s] multiplier* <Silence>
137
+ [sS] multiplier* <Silence>
132
138
  end
133
139
  rule note_head
134
140
  [a-g@] octave* accidental* <Note::Head>
@@ -185,7 +191,7 @@ grammar Lydown
185
191
  '\\' '!'? inline_command_key (':' inline_command_argument)* <Command>
186
192
  end
187
193
  rule inline_command_key
188
- [a-zA-Z_0-9]+ <Command::Key>
194
+ [\<\>\|\\]? [a-zA-Z_\-\.0-9]+ <Command::Key>
189
195
  end
190
196
  rule inline_command_argument
191
197
  (string / [^\s\t\n\:]+) <Command::Argument>
@@ -11,6 +11,16 @@ module Lydown::Parsing
11
11
  stream
12
12
  end
13
13
 
14
+ def each_child(ele = nil, &block)
15
+ ele ||= elements
16
+ return unless ele
17
+
18
+ ele.each do |e|
19
+ block[e] if e.respond_to?(:to_stream)
20
+ each_child(e.elements, &block) if e.elements
21
+ end
22
+ end
23
+
14
24
  def to_stream(stream = [], opts = {})
15
25
  _to_stream(self, stream, opts)
16
26
  stream
@@ -178,7 +188,15 @@ module Lydown::Parsing
178
188
  chord = event_hash(stream, opts, {
179
189
  type: :chord, notes: []
180
190
  })
181
- _to_stream(self, chord[:notes], opts)
191
+ each_child do |c|
192
+ if c.is_a?(Note)
193
+ c.to_stream(chord[:notes], opts)
194
+ elsif c.is_a?(Note::Expression)
195
+ c.to_stream(chord, opts)
196
+ end
197
+ end
198
+
199
+ # _to_stream(self, chord[:notes], opts)
182
200
  stream << chord
183
201
  end
184
202
  end
@@ -243,10 +261,9 @@ module Lydown::Parsing
243
261
  rest = event_hash(stream, opts, {
244
262
  type: :rest, raw: text_value, head: text_value[0]
245
263
  })
246
- if text_value =~ /^R(\*([0-9]+))?$/
264
+ if text_value =~ /^R(\*([0-9]+))?/
247
265
  rest[:multiplier] = $2 || '1'
248
266
  end
249
-
250
267
  _to_stream(self, rest, opts)
251
268
 
252
269
  stream << rest
@@ -255,9 +272,14 @@ module Lydown::Parsing
255
272
 
256
273
  class Silence < Root
257
274
  def to_stream(stream, opts)
258
- stream << event_hash(stream, opts, {
275
+ silence = event_hash(stream, opts, {
259
276
  type: :silence, raw: text_value, head: text_value[0]
260
277
  })
278
+ if text_value =~ /^S(\*([0-9]+))?/
279
+ silence[:multiplier] = $2 || '1'
280
+ end
281
+ _to_stream(self, silence, opts)
282
+ stream << silence
261
283
  end
262
284
  end
263
285
 
@@ -334,13 +356,15 @@ module Lydown::Parsing
334
356
  stream << cmd
335
357
  end
336
358
 
337
- SETTING_KEYS = %w{time key clef}
359
+ SETTING_KEYS = %w{time key clef pickup mode nomode}
360
+ NON_EPHEMERAL_KEYS = %w{time key}
338
361
 
339
362
  module Key
340
363
  def to_stream(cmd, opts)
341
364
  cmd[:key] = text_value
342
365
  if SETTING_KEYS.include?(text_value)
343
366
  cmd[:type] = :setting
367
+ cmd[:ephemeral] = !NON_EPHEMERAL_KEYS.include?(cmd[:key])
344
368
  end
345
369
  end
346
370
  end
@@ -1,6 +1,7 @@
1
1
  require 'lydown/templates'
2
2
  require 'lydown/work'
3
3
  require 'lydown/rendering/base'
4
+ require 'lydown/rendering/literal'
4
5
  require 'lydown/rendering/comments'
5
6
  require 'lydown/rendering/lyrics'
6
7
  require 'lydown/rendering/notes'
@@ -13,11 +14,7 @@ require 'lydown/rendering/voices'
13
14
  require 'lydown/rendering/source_ref'
14
15
  require 'lydown/rendering/skipping'
15
16
 
16
- require 'yaml'
17
-
18
17
  module Lydown::Rendering
19
- DEFAULTS = YAML.load(IO.read(File.join(File.dirname(__FILE__), 'rendering/defaults.yml'))).deep!
20
-
21
18
  class << self
22
19
  def translate(work, e, lydown_stream, idx)
23
20
  klass = class_for_event(e)
@@ -30,14 +27,43 @@ module Lydown::Rendering
30
27
  raise LydownError, "Invalid lydown event: #{e.inspect}"
31
28
  end
32
29
 
33
- def part_title(part_name)
30
+ def default_part_title(part_name)
34
31
  if part_name =~ /^([^\d]+)(\d+)$/
35
32
  "#{$1.titlize} #{$2.to_i.to_roman}"
36
33
  else
37
34
  part_name.titlize
38
35
  end
39
36
  end
37
+
38
+ def variable_name_infix(infix)
39
+ infix.capitalize.gsub(/(\d+)/) {|n| n.to_i.to_roman.upcase}.
40
+ gsub(/[^a-zA-Z]/, '').gsub(/\s+/, '')
41
+ end
42
+
43
+ VOICE_INDEX = {
44
+ '1' => 'One',
45
+ '2' => 'Two',
46
+ '3' => 'Three',
47
+ '4' => 'Four'
48
+ }
49
+
50
+ def voice_variable_name_infix(infix)
51
+ infix.capitalize.gsub(/(\d+)/) {|n| VOICE_INDEX[n]}
52
+ end
53
+
54
+ def variable_name(opts)
55
+ varname = "%s/%s/%s" % [
56
+ opts[:movement],
57
+ opts[:part],
58
+ opts[:stream]
59
+ ]
60
+
61
+ varname << "/#{opts[:voice]}" if opts[:voice]
62
+ varname << "/#{opts[:idx]}" if opts[:idx]
63
+
64
+ "\"#{varname}\""
65
+ end
40
66
  end
41
67
  end
42
68
 
43
- LY_LIB_DIR = File.join(File.dirname(__FILE__), 'rendering')
69
+ LY_LIB_DIR = File.join(File.dirname(__FILE__), 'ly_lib')
@@ -2,16 +2,63 @@ module Lydown::Rendering
2
2
  class Command < Base
3
3
  include Notes
4
4
 
5
+ COMMAND_ALIGNMENT = {
6
+ '<' => '\\right-align',
7
+ '>' => '\\left-align',
8
+ '|' => '\\center-align'
9
+ }
10
+
5
11
  def translate
12
+ key = @event[:key]
13
+ if key =~ /([\<\>\|])([a-zA-Z0-9]+)/
14
+ @event[:alignment] = COMMAND_ALIGNMENT[$1]
15
+ key = $2
16
+ end
17
+ # Is there a command handler
18
+ if respond_to?("cmd_#{key}".to_sym)
19
+ return send("cmd_#{key}".to_sym)
20
+ end
21
+
6
22
  if @context['process/duration_macro']
7
23
  add_macro_event(@event[:raw] || cmd_to_lydown(@event))
8
24
  else
9
- once = @event[:once] ? '\once ' : ''
10
- cmd = "#{once}\\#{@event[:key]} #{(@event[:arguments] || []).join(' ')} "
25
+ arguments = (@event[:arguments] || []).map do |a|
26
+ format_argument(@event[:key], a)
27
+ end.join(' ')
28
+ if @event[:key] =~ /^\\/
29
+ cmd = format_override_shorthand_command
30
+ else
31
+ cmd = "\\#{@event[:key]} #{arguments} "
32
+ end
33
+ @context.emit(:music, '\once ') if @event[:once]
11
34
  @context.emit(:music, cmd)
12
35
  end
13
36
  end
14
37
 
38
+ def format_override_shorthand_command
39
+ key = @event[:key] =~ /^\\(.+)$/ && $1
40
+ arguments = @event[:arguments].map do |arg|
41
+ case arg
42
+ when /^[0-9\.]+$/
43
+ "##{arg}"
44
+ when /^[tf]$/
45
+ "###{arg}"
46
+ else
47
+ arg
48
+ end
49
+ end
50
+ "\\override #{key} = #{arguments.join(' ')} "
51
+ end
52
+
53
+ def format_argument(command_key, argument)
54
+ case command_key
55
+ when 'tempo', 'mark'
56
+ "\"#{argument}\""
57
+ else
58
+ argument
59
+ end
60
+ end
61
+
15
62
  def cmd_to_lydown(event)
16
63
  cmd = "\\#{event[:key]}"
17
64
  if event[:arguments]
@@ -20,5 +67,35 @@ module Lydown::Rendering
20
67
  end
21
68
  cmd
22
69
  end
70
+
71
+ def cmd_instr
72
+ return unless (@context.render_mode == :score)
73
+ markup = Staff.inline_part_title(
74
+ @context,
75
+ part: @context[:part],
76
+ name: @event[:arguments] && @event[:arguments][0],
77
+ alignment: @event[:alignment]
78
+ )
79
+ @context.emit(:music, markup)
80
+ end
81
+
82
+ def cmd_tempo
83
+ unless @event[:arguments] && @event[:arguments].size == 1
84
+ raise LydownError, "Invalid or missing tempo argument"
85
+ end
86
+
87
+ tempo = @event[:arguments].first
88
+ if tempo =~ /^\((.+)\)$/
89
+ format = @context['options/format']
90
+ return unless (format == :midi) || (format == :mp3)
91
+ tempo = $1
92
+ end
93
+
94
+ @context.emit(:music, "\\tempo #{tempo} ")
95
+ end
96
+
97
+ def cmd_partBreak
98
+ @context.emit(:music, "\\break ") if (@context.render_mode == :part)
99
+ end
23
100
  end
24
101
  end
@@ -1,21 +1,45 @@
1
1
  module Lydown::Rendering
2
2
  module Figures
3
+ BLANK_EXTENDER_START = '<->'
4
+ BLANK_EXTENDER_STOP = '<.>'
5
+ BLANK_EXTENDER = '<_>'
6
+
7
+ EXTENDERS_ON = "\\bassFigureExtendersOn "
8
+ EXTENDERS_OFF = "\\bassFigureExtendersOff "
9
+
3
10
  def add_figures(figures, value)
11
+ # Add fill-in silences to catch up with music stream
4
12
  if @context['process/running_values']
13
+ silence_figures = @event[:tenue] ?
14
+ @context['process/last_figures'] :
15
+ (@context['process/blank_extender_mode'] ? '<_>' : 's')
16
+
5
17
  @context['process/running_values'].each do |v|
6
- silence = "s"
18
+ silence = silence_figures
7
19
  if v != @context['process/last_figures_value']
8
- silence << v
20
+ silence = silence + v
9
21
  @context['process/last_figures_value'] = v
10
22
  end
11
23
  @context.emit(:figures, "#{silence} ")
12
24
  end
13
- @context['process/running_values'] = []
25
+ @context['process/running_values'] = nil
14
26
  end
15
27
 
16
28
  figures = lilypond_figures(figures)
29
+ if figures == BLANK_EXTENDER_START
30
+ @context['process/blank_extender_mode'] = true
31
+ figures = BLANK_EXTENDER
32
+ @context.emit(:figures, EXTENDERS_ON)
33
+ elsif figures == BLANK_EXTENDER_STOP
34
+ @context['process/blank_extender_mode'] = false
35
+ figures = BLANK_EXTENDER
36
+ @event[:figure_extenders_off] = true
37
+ else
38
+ @context['process/last_figures'] = figures
39
+ end
40
+
17
41
  if value != @context['process/last_figures_value']
18
- figures << value
42
+ figures = figures + value
19
43
  @context['process/last_figures_value'] = value
20
44
  end
21
45
 
@@ -49,7 +73,7 @@ module Lydown::Rendering
49
73
  unless next_event
50
74
  # if next figures event is not found, check if there is a tenue, and
51
75
  # add extenders off flag
52
- @event[:figure_extenders_off] = true if @event[:tenue]
76
+ @event[:figure_extenders_off] = @event[:tenue]
53
77
  return
54
78
  end
55
79
 
@@ -80,9 +104,6 @@ module Lydown::Rendering
80
104
  '`' => '\\\\',
81
105
  "'" => "/"
82
106
  }
83
- EXTENDERS_ON = "\\bassFigureExtendersOn "
84
- EXTENDERS_OFF = "\\bassFigureExtendersOff "
85
-
86
107
  HIDDEN_FORMAT = "\\once \\override BassFigure #'implicit = ##t"
87
108
 
88
109
  def next_figures_event
@@ -0,0 +1,7 @@
1
+ module Lydown::Rendering
2
+ class Literal < Base
3
+ def translate
4
+ @context.emit(:music, "#{@event[:content]} ")
5
+ end
6
+ end
7
+ end
@@ -15,5 +15,66 @@ module Lydown::Rendering
15
15
 
16
16
  title
17
17
  end
18
+
19
+ def self.tacet?(context, name)
20
+ context["movements/#{name}/parts"].empty?
21
+ end
22
+
23
+ PAGE_BREAKS = {
24
+ 'before' => {before: true},
25
+ 'after' => {after: true},
26
+ 'before and after' => {before: true, after: true},
27
+ 'blank page before' => {blank_page_before: true}
28
+ }
29
+
30
+ def self.page_breaks(context, opts)
31
+ setting = case context.render_mode
32
+ when :score
33
+ context.get_setting('score/page_break', opts)
34
+ when :part
35
+ part = opts[:part] || context[:part] ||
36
+ (context['options/parts'] ? context['options/parts'][0] : '')
37
+ context.get_setting(:page_break, opts.merge(part: part)) ||
38
+ context.get_setting('parts/page_break', opts)
39
+ else
40
+ {}
41
+ end
42
+
43
+ PAGE_BREAKS[setting] || {}
44
+ end
45
+
46
+ def self.include_files(context, opts)
47
+ (context.get_setting(:includes, opts) || []).map do |fn|
48
+ case File.extname(fn)
49
+ when '.ely'
50
+ Lydown::Templates.render(fn, context)
51
+ else
52
+ "\\include \"#{fn}\""
53
+ end
54
+ end
55
+ end
56
+
57
+ # Groups movements by bookparts. Whenever a movement requires a page break
58
+ # before, a new group is created
59
+ def self.bookparts(context, opts)
60
+ groups = []; current_group = []
61
+ context[:movements].keys.each do |movement|
62
+ breaks = page_breaks(context, opts.merge(movement: movement))
63
+ if breaks[:before] || breaks[:blank_page_before]
64
+ groups << current_group unless current_group.empty?
65
+ current_group = []
66
+ end
67
+ current_group << movement
68
+ end
69
+ groups << current_group unless current_group.empty?
70
+
71
+ groups
72
+ end
73
+
74
+ def self.hide_bar_numbers?(context, opts)
75
+ context.get_setting(:bar_numbers, opts) == 'hide'
76
+ end
77
+
78
+
18
79
  end
19
80
  end