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