lydown 0.7.2 → 0.9.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 +25 -14
- data/bin/lydown +1 -122
- data/lib/lydown.rb +3 -3
- data/lib/lydown/cli.rb +4 -0
- data/lib/lydown/cli/commands.rb +98 -0
- data/lib/lydown/cli/compiler.rb +11 -14
- data/lib/lydown/cli/diff.rb +3 -3
- data/lib/lydown/cli/output.rb +18 -0
- data/lib/lydown/cli/proofing.rb +8 -6
- data/lib/lydown/cli/support.rb +34 -0
- data/lib/lydown/cli/translation.rb +73 -0
- data/lib/lydown/core_ext.rb +1 -1
- data/lib/lydown/lilypond.rb +49 -11
- data/lib/lydown/parsing.rb +37 -6
- data/lib/lydown/parsing/nodes.rb +57 -56
- data/lib/lydown/rendering.rb +0 -9
- data/lib/lydown/rendering/base.rb +2 -2
- data/lib/lydown/rendering/command.rb +2 -2
- data/lib/lydown/rendering/comments.rb +1 -1
- data/lib/lydown/rendering/figures.rb +14 -14
- data/lib/lydown/rendering/lib.ly +22 -0
- data/lib/lydown/rendering/lyrics.rb +2 -2
- data/lib/lydown/rendering/movement.rb +2 -2
- data/lib/lydown/rendering/music.rb +46 -46
- data/lib/lydown/rendering/notes.rb +149 -70
- data/lib/lydown/rendering/settings.rb +21 -16
- data/lib/lydown/rendering/skipping.rb +1 -1
- data/lib/lydown/rendering/source_ref.rb +4 -4
- data/lib/lydown/rendering/staff.rb +8 -4
- data/lib/lydown/rendering/voices.rb +9 -9
- data/lib/lydown/templates.rb +9 -0
- data/lib/lydown/templates/lilypond_doc.erb +1 -1
- data/lib/lydown/templates/movement.erb +9 -1
- data/lib/lydown/templates/part.erb +12 -3
- data/lib/lydown/translation.rb +17 -0
- data/lib/lydown/translation/ripple.rb +30 -0
- data/lib/lydown/translation/ripple/nodes.rb +191 -0
- data/lib/lydown/translation/ripple/ripple.treetop +100 -0
- data/lib/lydown/version.rb +1 -1
- data/lib/lydown/work.rb +144 -198
- data/lib/lydown/work_context.rb +152 -0
- metadata +11 -2
@@ -4,7 +4,8 @@ module Lydown::Rendering
|
|
4
4
|
|
5
5
|
SETTING_KEYS = [
|
6
6
|
'key', 'time', 'pickup', 'clef', 'part', 'movement', 'tempo',
|
7
|
-
'accidentals', 'beams', 'end_barline', 'macros', 'empty_staves'
|
7
|
+
'accidentals', 'beams', 'end_barline', 'macros', 'empty_staves',
|
8
|
+
'midi_tempo'
|
8
9
|
]
|
9
10
|
|
10
11
|
RENDERABLE_SETTING_KEYS = [
|
@@ -19,34 +20,38 @@ module Lydown::Rendering
|
|
19
20
|
|
20
21
|
def translate
|
21
22
|
# if setting while doing a macro, insert it into the current macro group
|
22
|
-
if @
|
23
|
+
if @context['process/duration_macro'] && @event[:raw]
|
23
24
|
return add_macro_event(@event[:raw])
|
24
25
|
end
|
25
26
|
|
26
27
|
key = @event[:key]
|
27
28
|
value = @event[:value]
|
28
29
|
level = @event[:level] || 0
|
29
|
-
|
30
|
+
|
30
31
|
unless (level > 0) || SETTING_KEYS.include?(key)
|
31
32
|
raise LydownError, "Invalid setting (#{key})"
|
32
33
|
end
|
33
34
|
|
34
35
|
if level == 0
|
35
36
|
value = check_setting_value(key, value)
|
36
|
-
@
|
37
|
+
@context[key] = value
|
37
38
|
case key
|
38
39
|
when 'part'
|
39
|
-
@
|
40
|
+
@context.set_part_context(value)
|
40
41
|
|
41
42
|
# when changing parts we repeat the last set time and key signature
|
42
|
-
render_setting('time', @
|
43
|
+
render_setting('time', @context[:time]) unless @context[:time] == '4/4'
|
43
44
|
|
44
|
-
key = @
|
45
|
+
key = @context[:key]
|
45
46
|
render_setting('key', key) unless key == 'c major'
|
46
47
|
|
47
|
-
@
|
48
|
+
@context.reset(:part)
|
48
49
|
when 'movement'
|
49
|
-
@
|
50
|
+
@context.reset(:movement)
|
51
|
+
when 'midi_tempo'
|
52
|
+
movement = @context[:movement]
|
53
|
+
path = "movements/#{movement}/settings/midi_tempo"
|
54
|
+
@context[path] = value
|
50
55
|
end
|
51
56
|
|
52
57
|
if RENDERABLE_SETTING_KEYS.include?(key)
|
@@ -56,14 +61,14 @@ module Lydown::Rendering
|
|
56
61
|
# nested settings
|
57
62
|
l, path = 0, ''
|
58
63
|
while l < level
|
59
|
-
path << "#{@
|
64
|
+
path << "#{@context['process/setting_levels'][l]}/"; l += 1
|
60
65
|
end
|
61
66
|
path << key
|
62
|
-
@
|
67
|
+
@context[path] = value
|
63
68
|
end
|
64
69
|
|
65
|
-
@
|
66
|
-
@
|
70
|
+
@context['process/setting_levels'] ||= {}
|
71
|
+
@context['process/setting_levels'][level] = key
|
67
72
|
end
|
68
73
|
|
69
74
|
def check_setting_value(key, value)
|
@@ -85,9 +90,9 @@ module Lydown::Rendering
|
|
85
90
|
setting = ""
|
86
91
|
case key
|
87
92
|
when 'time'
|
88
|
-
cadenza_mode = @
|
93
|
+
cadenza_mode = @context[:cadenza_mode]
|
89
94
|
should_cadence = value == 'unmetered'
|
90
|
-
@
|
95
|
+
@context[:cadenza_mode] = should_cadence
|
91
96
|
|
92
97
|
if should_cadence && !cadenza_mode
|
93
98
|
setting = "\\cadenzaOn "
|
@@ -119,7 +124,7 @@ module Lydown::Rendering
|
|
119
124
|
setting = "\\#{key} #{value} "
|
120
125
|
end
|
121
126
|
|
122
|
-
@
|
127
|
+
@context.emit(:music, setting)
|
123
128
|
end
|
124
129
|
end
|
125
130
|
end
|
@@ -37,7 +37,7 @@ module Lydown::Rendering
|
|
37
37
|
# returns index of first event at or after specified line
|
38
38
|
def find_line_idx(stream, line, last = false)
|
39
39
|
if last
|
40
|
-
stream.reverse_each do |e|
|
40
|
+
stream.reverse_each do |e|
|
41
41
|
return stream.index(e) if e[:line] && e[:line] <= line
|
42
42
|
end
|
43
43
|
nil
|
@@ -3,12 +3,12 @@ module Lydown::Rendering
|
|
3
3
|
# for notes in a macro group
|
4
4
|
class SourceRef < Base
|
5
5
|
def translate
|
6
|
-
return # unless @
|
6
|
+
return # unless @context['options/proof_mode']
|
7
7
|
|
8
8
|
fn = @event[:filename]
|
9
|
-
if fn && fn != @
|
10
|
-
@
|
11
|
-
@
|
9
|
+
if fn && fn != @context['process/last_filename']
|
10
|
+
@context['process/last_filename'] = fn
|
11
|
+
@context.emit(@event[:stream] || :music, "%{::#{File.expand_path(fn)}%} ")
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Lydown::Rendering
|
2
2
|
module Staff
|
3
|
-
def self.staff_groups(
|
4
|
-
model =
|
3
|
+
def self.staff_groups(context, movement, parts)
|
4
|
+
model = context['score/order'] || movement['score/order'] || DEFAULTS['score/order']
|
5
5
|
parts_copy = parts.clone
|
6
6
|
|
7
7
|
groups = []
|
@@ -59,6 +59,10 @@ module Lydown::Rendering
|
|
59
59
|
DEFAULTS["parts/#{part}/clef"]
|
60
60
|
end
|
61
61
|
|
62
|
+
def self.midi_instrument(part)
|
63
|
+
DEFAULTS["parts/#{part}/midi_instrument"]
|
64
|
+
end
|
65
|
+
|
62
66
|
def self.beaming_mode(part)
|
63
67
|
beaming = DEFAULTS["parts/#{part}/beaming"]
|
64
68
|
return nil if beaming.nil?
|
@@ -75,8 +79,8 @@ module Lydown::Rendering
|
|
75
79
|
|
76
80
|
DEFAULT_END_BARLINE = '|.'
|
77
81
|
|
78
|
-
def self.end_barline(
|
79
|
-
barline = movement['end_barline'] ||
|
82
|
+
def self.end_barline(context, movement)
|
83
|
+
barline = movement['end_barline'] || context['end_barline'] || DEFAULT_END_BARLINE
|
80
84
|
barline == 'none' ? nil : barline
|
81
85
|
end
|
82
86
|
end
|
@@ -2,30 +2,30 @@ module Lydown::Rendering
|
|
2
2
|
class VoiceSelect < Base
|
3
3
|
def translate
|
4
4
|
if @event[:voice]
|
5
|
-
@
|
5
|
+
@context['process/voice_selector'] = "voice#{@event[:voice]}"
|
6
6
|
else
|
7
|
-
self.class.render_voices(@
|
7
|
+
self.class.render_voices(@context)
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
def self.render_voices(
|
12
|
-
|
11
|
+
def self.render_voices(context)
|
12
|
+
context['process/voice_selector'] = nil
|
13
13
|
|
14
|
-
music = Lydown::Templates.render(:multi_voice,
|
14
|
+
music = Lydown::Templates.render(:multi_voice, context, part: context[:part])
|
15
15
|
|
16
|
-
|
16
|
+
context.emit(:music, music)
|
17
17
|
|
18
|
-
|
18
|
+
context['process/voices'].each_value do |stream|
|
19
19
|
if stream['lyrics']
|
20
20
|
stream['lyrics'].each do |voice, lyrics_stream|
|
21
21
|
lyrics_stream.each do |idx, content|
|
22
|
-
|
22
|
+
context.emit("lyrics/#{voice}/#{idx}", content)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
|
28
|
+
context['process/voices'] = nil
|
29
29
|
end
|
30
30
|
|
31
31
|
VOICE_COMMANDS = {
|
data/lib/lydown/templates.rb
CHANGED
@@ -19,4 +19,13 @@ module Lydown
|
|
19
19
|
ERB.new IO.read(File.join(TEMPLATES_DIR, "#{name}.erb")), 0, '<>'
|
20
20
|
end
|
21
21
|
end
|
22
|
+
|
23
|
+
module TemplateBinding
|
24
|
+
# Used to bind to instance when rendering templates
|
25
|
+
def template_binding(locals = {})
|
26
|
+
b = binding
|
27
|
+
locals.each {|k, v| b.local_variable_set(k.to_sym, v)}
|
28
|
+
b
|
29
|
+
end
|
30
|
+
end
|
22
31
|
end
|
@@ -13,12 +13,16 @@
|
|
13
13
|
score_mode = self['render_opts/mode'] == :score
|
14
14
|
|
15
15
|
part['settings'] ||= {
|
16
|
-
'pickup' =>
|
17
|
-
'key' =>
|
18
|
-
'tempo' =>
|
16
|
+
'pickup' => self[:pickup],
|
17
|
+
'key' => self[:key],
|
18
|
+
'tempo' => self[:tempo]
|
19
19
|
}.deep!
|
20
20
|
|
21
21
|
clef = Lydown::Rendering::Staff.clef(name)
|
22
|
+
|
23
|
+
midi_mode = self['render_opts']['format'] == 'midi'
|
24
|
+
midi_instrument = midi_mode && Lydown::Rendering::Staff.midi_instrument(name)
|
25
|
+
|
22
26
|
beaming_mode = Lydown::Rendering::Staff.beaming_mode(name)
|
23
27
|
partial = part['settings'][:pickup] ? "\\partial #{part['settings'][:pickup]}" : ""
|
24
28
|
|
@@ -38,6 +42,11 @@
|
|
38
42
|
<% end %>
|
39
43
|
|
40
44
|
<% if score_mode %>\set Staff.instrumentName = #"<%= title %>"<% end %>
|
45
|
+
|
46
|
+
<% if midi_instrument %>
|
47
|
+
\set Staff.midiInstrument = #"<%= midi_instrument %>"
|
48
|
+
<% end %>
|
49
|
+
|
41
50
|
\relative c {
|
42
51
|
<% if clef %>
|
43
52
|
\clef "<%= clef %>"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Lydown
|
2
|
+
module Translation
|
3
|
+
def self.process(source)
|
4
|
+
output = ''
|
5
|
+
if source[:ripple]
|
6
|
+
output << RippleParser.translate(source[:ripple], source)
|
7
|
+
end
|
8
|
+
if source[:lyrics]
|
9
|
+
output << "\n=lyrics\n#{source[:lyrics]}"
|
10
|
+
end
|
11
|
+
output
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'lydown/translation/ripple'
|
17
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'polyglot'
|
2
|
+
require 'treetop'
|
3
|
+
|
4
|
+
require 'lydown/translation/ripple/nodes'
|
5
|
+
require 'lydown/translation/ripple/ripple.treetop'
|
6
|
+
|
7
|
+
class RippleParser
|
8
|
+
def self.parse(source, opts = {})
|
9
|
+
parser = self.new
|
10
|
+
ast = parser.parse(source)
|
11
|
+
unless ast
|
12
|
+
error_msg = format_parser_error(source, parser, opts)
|
13
|
+
$stderr.puts error_msg
|
14
|
+
raise LydownError, error_msg
|
15
|
+
end
|
16
|
+
ast
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.translate(source, opts)
|
20
|
+
ast = parse(source)
|
21
|
+
stream = ''
|
22
|
+
ast.translate(stream, opts.merge(source: source))
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.format_parser_error(source, parser, opts)
|
26
|
+
msg = "#{parser.failure_reason} at #{parser.failure_line}:#{parser.failure_column}\n"
|
27
|
+
msg << " #{source.lines[parser.failure_line - 1].chomp}\n #{' ' * parser.failure_column}^"
|
28
|
+
msg
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
module Lydown::Translation::Ripple
|
2
|
+
class Root < Treetop::Runtime::SyntaxNode
|
3
|
+
def _translate(element, stream, opts)
|
4
|
+
if element.elements
|
5
|
+
element.elements.each do |e|
|
6
|
+
e.respond_to?(:translate) ?
|
7
|
+
e.translate(stream, opts) :
|
8
|
+
_translate(e, stream, opts)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
stream
|
12
|
+
end
|
13
|
+
|
14
|
+
def translate(stream = [], opts = {})
|
15
|
+
_translate(self, stream, opts)
|
16
|
+
stream
|
17
|
+
end
|
18
|
+
|
19
|
+
def source_line(opts)
|
20
|
+
if opts[:source]
|
21
|
+
line, column = opts[:source].find_line_and_column(interval.first)
|
22
|
+
line
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_line_break(stream, opts)
|
29
|
+
line = source_line(opts)
|
30
|
+
if line != opts[:last_line]
|
31
|
+
unless stream.empty? || (stream[-1] == "\n")
|
32
|
+
# remove whitespace at end of line
|
33
|
+
stream.gsub!(/\s+$/, '')
|
34
|
+
stream << "\n"
|
35
|
+
end
|
36
|
+
opts[:last_line] = line
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class RelativeCommand < Root
|
42
|
+
def translate(stream, opts)
|
43
|
+
opts[:relative_start_octave] = text_value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Note < Root
|
48
|
+
def translate(stream, opts)
|
49
|
+
check_line_break(stream, opts)
|
50
|
+
note = {expressions: []}
|
51
|
+
_translate(self, note, opts)
|
52
|
+
expressions = note[:expressions].join
|
53
|
+
expressions << ' ' unless expressions.empty?
|
54
|
+
|
55
|
+
unless opts[:first_note] || note[:head] =~ /^[rs]/
|
56
|
+
octave = Lydown::Rendering::Octaves.absolute_octave(
|
57
|
+
note[:head], opts[:relative_start_octave] || 'c'
|
58
|
+
)
|
59
|
+
if note[:head] =~ /[',]+$/
|
60
|
+
note[:head].gsub!(/[',]+/, octave)
|
61
|
+
else
|
62
|
+
note[:head] << octave
|
63
|
+
end
|
64
|
+
opts[:first_note] = note[:head]
|
65
|
+
end
|
66
|
+
|
67
|
+
if opts[:post_macro_value]
|
68
|
+
note[:duration] ||= opts[:post_macro_value]
|
69
|
+
opts[:post_macro_value] = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
stream << "%s%s%s" % [
|
73
|
+
note[:duration],
|
74
|
+
note[:head],
|
75
|
+
expressions
|
76
|
+
]
|
77
|
+
end
|
78
|
+
|
79
|
+
class Head < Root
|
80
|
+
def translate(note, opts)
|
81
|
+
head = text_value.dup
|
82
|
+
head.gsub!(/(?<=.{1})b/, '-')
|
83
|
+
head.gsub!(/(?<=.{1})es/, '-')
|
84
|
+
head.gsub!(/(?<=.{1})s/, '+')
|
85
|
+
head.sub!(/([a-g][\+\-]*)/) do |m|
|
86
|
+
Lydown::Rendering::Accidentals.chromatic_to_diatonic(
|
87
|
+
m, opts[:key] || 'c major'
|
88
|
+
)
|
89
|
+
end
|
90
|
+
note[:head] = head
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Duration < Root
|
95
|
+
def translate(note, opts)
|
96
|
+
note[:duration] = text_value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Expression < Root
|
101
|
+
def translate(note, opts)
|
102
|
+
note[:expressions] << text_value
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class KeySignature < Root
|
108
|
+
def translate(stream, opts)
|
109
|
+
signature = {}
|
110
|
+
_translate(self, signature, opts)
|
111
|
+
opts[:key] = "#{signature[:head]} #{signature[:mode]}"
|
112
|
+
stream << "- key: #{opts[:key]}\n"
|
113
|
+
end
|
114
|
+
|
115
|
+
class Mode < Root
|
116
|
+
def translate(signature, opts)
|
117
|
+
if text_value =~ /(major|minor)/
|
118
|
+
signature[:mode] = $1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class TimeSignature < Root
|
125
|
+
def translate(stream, opts)
|
126
|
+
opts[:time] = text_value
|
127
|
+
stream << "- time: #{text_value}\n"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class FullRest < Root
|
132
|
+
def translate(stream, opts)
|
133
|
+
check_line_break(stream, opts)
|
134
|
+
if text_value =~ /R[\d]+\*(\d+)/
|
135
|
+
stream << "R*#{$1} "
|
136
|
+
else
|
137
|
+
stream << "R "
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class Phrasing < Root
|
143
|
+
def translate(stream, opts)
|
144
|
+
check_line_break(stream, opts)
|
145
|
+
stream << text_value
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class NamedMacro < Root
|
150
|
+
def translate(stream, opts)
|
151
|
+
check_line_break(stream, opts)
|
152
|
+
macro_name = text_value.sub('$', '')
|
153
|
+
if opts[:macros][macro_name]
|
154
|
+
macro = translate_macro(opts[:macros][macro_name])
|
155
|
+
stream << "{#{macro}}"
|
156
|
+
opts[:current_macro] = macro
|
157
|
+
opts[:post_macro_value] = nil
|
158
|
+
else
|
159
|
+
raise LydownError, "Could not find named macro #{macro_name}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
PLACE_HOLDERS = {
|
164
|
+
'#' => '_',
|
165
|
+
'@' => '@',
|
166
|
+
'r' => 'r'
|
167
|
+
}
|
168
|
+
|
169
|
+
def translate_macro(macro)
|
170
|
+
macro.gsub(/([r#@])([0-9\.]*)/) {|m| "#{$2}#{PLACE_HOLDERS[$1]}"}.
|
171
|
+
gsub(' ', '')
|
172
|
+
end
|
173
|
+
|
174
|
+
class Stop < Root
|
175
|
+
def translate(stream, opts)
|
176
|
+
if opts[:current_macro]
|
177
|
+
if opts[:current_macro] =~ /([0-9]+\.*)[^0-9]*$/
|
178
|
+
opts[:post_macro_value] = $1
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
class Command < Root
|
186
|
+
def translate(stream, opts)
|
187
|
+
cmd = text_value.gsub(' ', ':')
|
188
|
+
stream << " #{cmd} "
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|