lydown 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +159 -2
- data/lib/lydown.rb +8 -2
- data/lib/lydown/cache.rb +54 -0
- data/lib/lydown/cli.rb +1 -0
- data/lib/lydown/cli/commands.rb +27 -9
- data/lib/lydown/cli/compiler.rb +218 -54
- data/lib/lydown/cli/diff.rb +1 -1
- data/lib/lydown/cli/proofing.rb +3 -3
- data/lib/lydown/cli/signals.rb +23 -0
- data/lib/lydown/cli/support.rb +23 -1
- data/lib/lydown/core_ext.rb +41 -5
- data/lib/lydown/{rendering/defaults.yml → defaults.yml} +3 -3
- data/lib/lydown/errors.rb +3 -0
- data/lib/lydown/lilypond.rb +73 -31
- data/lib/lydown/ly_lib/lib.ly +297 -0
- data/lib/lydown/parsing.rb +1 -2
- data/lib/lydown/parsing/lydown.treetop +16 -10
- data/lib/lydown/parsing/nodes.rb +29 -5
- data/lib/lydown/rendering.rb +32 -6
- data/lib/lydown/rendering/command.rb +79 -2
- data/lib/lydown/rendering/figures.rb +29 -8
- data/lib/lydown/rendering/literal.rb +7 -0
- data/lib/lydown/rendering/movement.rb +61 -0
- data/lib/lydown/rendering/music.rb +37 -5
- data/lib/lydown/rendering/notes.rb +26 -8
- data/lib/lydown/rendering/settings.rb +41 -13
- data/lib/lydown/rendering/skipping.rb +43 -10
- data/lib/lydown/rendering/staff.rb +72 -16
- data/lib/lydown/templates.rb +8 -2
- data/lib/lydown/templates/lilypond_doc.erb +10 -1
- data/lib/lydown/templates/movement.erb +87 -34
- data/lib/lydown/templates/multi_voice.erb +1 -1
- data/lib/lydown/templates/part.erb +83 -55
- data/lib/lydown/templates/variables.erb +38 -0
- data/lib/lydown/version.rb +1 -1
- data/lib/lydown/work.rb +39 -26
- data/lib/lydown/work_context.rb +252 -14
- metadata +138 -8
- data/lib/lydown/rendering/lib.ly +0 -88
data/lib/lydown/parsing.rb
CHANGED
@@ -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 /
|
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
|
-
|
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 /
|
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
|
-
'\\' [
|
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
|
-
(
|
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
|
-
(
|
134
|
+
(expression_string / expression_longhand) <Note::Expression>
|
129
135
|
end
|
130
136
|
rule silence
|
131
|
-
[
|
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-
|
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>
|
data/lib/lydown/parsing/nodes.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/lydown/rendering.rb
CHANGED
@@ -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
|
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__), '
|
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
|
-
|
10
|
-
|
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 =
|
18
|
+
silence = silence_figures
|
7
19
|
if v != @context['process/last_figures_value']
|
8
|
-
silence
|
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
|
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] =
|
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
|
@@ -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
|