lydown 0.7.2 → 0.9.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.
- 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
|