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
@@ -0,0 +1,100 @@
|
|
1
|
+
grammar Ripple
|
2
|
+
include Lydown::Translation::Ripple
|
3
|
+
|
4
|
+
rule events
|
5
|
+
white_space? event* <Root>
|
6
|
+
end
|
7
|
+
|
8
|
+
rule white_space
|
9
|
+
[ \t\n]+
|
10
|
+
end
|
11
|
+
|
12
|
+
rule event
|
13
|
+
(relative_cmd / curly_brace / key_signature / time_signature /
|
14
|
+
full_rest / note / phrasing / named_macro / stop_macro / command) white_space?
|
15
|
+
end
|
16
|
+
|
17
|
+
rule relative_cmd
|
18
|
+
'\relative' [\s]+ relative_cmd_note
|
19
|
+
end
|
20
|
+
|
21
|
+
rule relative_cmd_note
|
22
|
+
[a-g] accidental? octave* <RelativeCommand>
|
23
|
+
end
|
24
|
+
|
25
|
+
rule curly_brace
|
26
|
+
[\{\}]
|
27
|
+
end
|
28
|
+
|
29
|
+
rule key_signature
|
30
|
+
'\key' [\s]+ note_head [\s+] key_mode <KeySignature>
|
31
|
+
end
|
32
|
+
|
33
|
+
rule key_mode
|
34
|
+
'\major' / '\minor' <KeySignature::Mode>
|
35
|
+
end
|
36
|
+
|
37
|
+
rule note
|
38
|
+
note_head duration? expression* <Note>
|
39
|
+
end
|
40
|
+
|
41
|
+
rule note_head
|
42
|
+
[a-gr] accidental octave* accidental_flag? <Note::Head>
|
43
|
+
end
|
44
|
+
|
45
|
+
rule octave
|
46
|
+
[,']+
|
47
|
+
end
|
48
|
+
|
49
|
+
rule accidental
|
50
|
+
('b' / 's' / 'es')*
|
51
|
+
end
|
52
|
+
|
53
|
+
rule accidental_flag
|
54
|
+
[\!\?]+
|
55
|
+
end
|
56
|
+
|
57
|
+
rule duration
|
58
|
+
[0-9]+ '.'* <Note::Duration>
|
59
|
+
end
|
60
|
+
|
61
|
+
rule expression
|
62
|
+
'\\' [a-zA-Z]+ <Note::Expression>
|
63
|
+
end
|
64
|
+
|
65
|
+
rule time_signature
|
66
|
+
'\time' [\s]+ time_signature_content
|
67
|
+
end
|
68
|
+
|
69
|
+
rule time_signature_content
|
70
|
+
[0-9\/]+ <TimeSignature>
|
71
|
+
end
|
72
|
+
|
73
|
+
rule full_rest
|
74
|
+
'R' [0-9]+ ('*' [0-9]+)? <FullRest>
|
75
|
+
end
|
76
|
+
|
77
|
+
rule phrasing
|
78
|
+
[\(\)\[\]\~] <Phrasing>
|
79
|
+
end
|
80
|
+
|
81
|
+
rule named_macro
|
82
|
+
'$' [a-z0-9]+ <NamedMacro>
|
83
|
+
end
|
84
|
+
|
85
|
+
rule stop_macro
|
86
|
+
'$$' <NamedMacro::Stop>
|
87
|
+
end
|
88
|
+
|
89
|
+
rule command
|
90
|
+
(clef_command / simple_command)
|
91
|
+
end
|
92
|
+
|
93
|
+
rule simple_command
|
94
|
+
'\\' [a-zA-Z]+ <Command>
|
95
|
+
end
|
96
|
+
|
97
|
+
rule clef_command
|
98
|
+
'\clef ' [a-z0-9_]+ <Command>
|
99
|
+
end
|
100
|
+
end
|
data/lib/lydown/version.rb
CHANGED
data/lib/lydown/work.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
require 'lydown/core_ext'
|
2
1
|
require 'lydown/templates'
|
3
|
-
|
4
|
-
require '
|
2
|
+
require 'lydown/cli/output'
|
3
|
+
require 'parallel'
|
5
4
|
|
6
5
|
module Lydown
|
7
6
|
# Work is a virtual lilypond document. It can contain multiple movements,
|
@@ -16,81 +15,13 @@ module Lydown
|
|
16
15
|
attr_accessor :context
|
17
16
|
|
18
17
|
def initialize(opts = {})
|
19
|
-
@context =
|
20
|
-
reset_context(:work)
|
21
|
-
@context[:options] = opts.deep_clone
|
18
|
+
@context = WorkContext.new(opts)
|
22
19
|
|
23
20
|
process_work_files if opts[:path]
|
24
21
|
end
|
25
|
-
|
26
|
-
def
|
27
|
-
|
28
|
-
when :work
|
29
|
-
@context[:time] = '4/4'
|
30
|
-
@context[:tempo] = nil
|
31
|
-
@context[:cadenza_mode] = nil
|
32
|
-
@context[:key] = 'c major'
|
33
|
-
@context[:pickup] = nil
|
34
|
-
@context[:beaming] = nil
|
35
|
-
@context[:end_barline] = nil
|
36
|
-
@context[:part] = nil
|
37
|
-
when :movement
|
38
|
-
@context[:part] = nil
|
39
|
-
end
|
40
|
-
if @context['process/tuplet_mode']
|
41
|
-
Lydown::Rendering::TupletDuration.emit_tuplet_end(self)
|
42
|
-
end
|
43
|
-
if @context['process/grace_mode']
|
44
|
-
Lydown::Rendering::Grace.emit_grace_end(self)
|
45
|
-
end
|
46
|
-
|
47
|
-
if @context['process/voice_selector']
|
48
|
-
Lydown::Rendering::VoiceSelect.render_voices(self)
|
49
|
-
end
|
50
|
-
|
51
|
-
Lydown::Rendering::Notes.cleanup_duration_macro(self)
|
52
|
-
|
53
|
-
# reset processing variables
|
54
|
-
@context['process'] = {
|
55
|
-
'duration_values' => ['4'],
|
56
|
-
'running_values' => []
|
57
|
-
}
|
58
|
-
end
|
59
|
-
|
60
|
-
# Used to bind to instance when rendering templates
|
61
|
-
def template_binding(locals = {})
|
62
|
-
b = binding
|
63
|
-
locals.each {|k, v| b.local_variable_set(k.to_sym, v)}
|
64
|
-
b
|
65
|
-
end
|
66
|
-
|
67
|
-
# translate a lydown stream into lilypond
|
68
|
-
def process(lydown_stream, opts = {})
|
69
|
-
lydown_stream.each_with_index do |e, idx|
|
70
|
-
if e[:type]
|
71
|
-
Lydown::Rendering.translate(self, e, lydown_stream, idx)
|
72
|
-
else
|
73
|
-
raise LydownError, "Invalid lydown stream event: #{e.inspect}"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
reset_context(:part) unless opts[:no_reset]
|
77
|
-
end
|
78
|
-
|
79
|
-
def emit(path, *content)
|
80
|
-
stream = current_stream(path)
|
81
|
-
|
82
|
-
content.each {|c| stream << c}
|
83
|
-
end
|
84
|
-
|
85
|
-
def current_stream(subpath)
|
86
|
-
if @context['process/voice_selector']
|
87
|
-
path = "process/voices/#{@context['process/voice_selector']}/#{subpath}"
|
88
|
-
else
|
89
|
-
movement = @context[:movement]
|
90
|
-
part = @context[:part]
|
91
|
-
path = "movements/#{movement}/parts/#{part}/#{subpath}"
|
92
|
-
end
|
93
|
-
@context[path] ||= (subpath == :settings) ? {} : ''
|
22
|
+
|
23
|
+
def translate(stream)
|
24
|
+
@context.translate(stream)
|
94
25
|
end
|
95
26
|
|
96
27
|
def to_lilypond(opts = {})
|
@@ -105,161 +36,176 @@ module Lydown
|
|
105
36
|
else
|
106
37
|
@original_context = @context
|
107
38
|
begin
|
108
|
-
|
109
|
-
|
39
|
+
filtered = @context.filter(opts)
|
40
|
+
filtered.extend(TemplateBinding)
|
41
|
+
Lydown::Templates.render(:lilypond_doc, filtered)
|
110
42
|
ensure
|
111
43
|
@context = @original_context
|
112
44
|
end
|
113
45
|
end
|
114
46
|
end
|
115
47
|
|
116
|
-
def
|
117
|
-
|
48
|
+
def process_work_files
|
49
|
+
path = @context[:options][:path]
|
50
|
+
path += '.ld' if File.file?(path + '.ld')
|
118
51
|
|
119
|
-
if
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
52
|
+
if File.file?(path)
|
53
|
+
process_file(path)
|
54
|
+
elsif File.directory?(path)
|
55
|
+
process_directory(path)
|
56
|
+
else
|
57
|
+
raise LydownError, "Could not read #{path}"
|
125
58
|
end
|
59
|
+
end
|
126
60
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
61
|
+
def process_file(path, prefix = [], opts = {})
|
62
|
+
$stderr.puts path
|
63
|
+
content = IO.read(path)
|
64
|
+
stream = LydownParser.parse(content, {
|
65
|
+
filename: File.expand_path(path),
|
66
|
+
source: content,
|
67
|
+
proof_mode: @context['options/proof_mode']
|
68
|
+
})
|
69
|
+
|
70
|
+
if opts[:line_range]
|
71
|
+
Lydown::Rendering.insert_skip_markers(stream, opts[:line_range])
|
132
72
|
end
|
73
|
+
|
74
|
+
@context.translate(prefix + stream)
|
75
|
+
end
|
76
|
+
|
77
|
+
DEFAULT_BASENAMES = %w{work movement}
|
133
78
|
|
134
|
-
|
135
|
-
|
79
|
+
def process_directory(path)
|
80
|
+
state = {
|
81
|
+
streams: {},
|
82
|
+
movements: Hash.new {|h, k| h[k] = {}},
|
83
|
+
current_movement: nil,
|
84
|
+
part_filter: @context[:options][:parts],
|
85
|
+
mvmt_filter: @context[:options][:movements]
|
86
|
+
}
|
87
|
+
|
88
|
+
read_directory(path, true, state)
|
89
|
+
parse_directory_files(state)
|
90
|
+
translate_directory_streams(state)
|
91
|
+
end
|
92
|
+
|
93
|
+
def read_directory(path, recursive, state)
|
94
|
+
Dir["#{path}/*"].entries.sort.each do |entry|
|
95
|
+
handle_directory_entry(entry, recursive, state)
|
136
96
|
end
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
97
|
+
end
|
98
|
+
|
99
|
+
def handle_directory_entry(entry, recursive, state)
|
100
|
+
if File.file?(entry) && (entry =~ /\.ld$/)
|
101
|
+
part = File.basename(entry, '.*')
|
102
|
+
if part == 'work'
|
103
|
+
state[:streams][entry] = nil
|
104
|
+
state[:movements][nil][:work] = entry
|
105
|
+
elsif part == 'movement'
|
106
|
+
state[:streams][entry] = nil
|
107
|
+
state[:movements][state[:current_movement]][:movement] = entry
|
108
|
+
elsif !skip_part?(part, state)
|
109
|
+
state[:streams][entry] = nil
|
110
|
+
state[:movements][state[:current_movement]][part] = entry
|
141
111
|
end
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
112
|
+
elsif File.directory?(entry) && recursive
|
113
|
+
movement = File.basename(entry)
|
114
|
+
unless skip_movement?(movement, state)
|
115
|
+
state[:current_movement] = movement
|
116
|
+
read_directory(entry, false, state)
|
147
117
|
end
|
148
118
|
end
|
149
|
-
|
150
|
-
filtered
|
151
119
|
end
|
152
|
-
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
Lydown::Lilypond.compile(code, opts)
|
120
|
+
|
121
|
+
def skip_part?(part, state)
|
122
|
+
DEFAULT_BASENAMES.include?(part) ||
|
123
|
+
state[:part_filter] && !state[:part_filter].include?(part)
|
157
124
|
end
|
158
|
-
|
159
|
-
def
|
160
|
-
|
125
|
+
|
126
|
+
def skip_movement?(mvmt, state)
|
127
|
+
state[:mvmt_filter] && !state[:mvmt_filter].include?(mvmt)
|
161
128
|
end
|
162
129
|
|
163
|
-
|
164
|
-
|
165
|
-
|
130
|
+
PARALLEL_PARSE_OPTIONS = {
|
131
|
+
progress: {
|
132
|
+
title: 'Parse',
|
133
|
+
format: Lydown::CLI::PROGRESS_FORMAT
|
134
|
+
}
|
135
|
+
}
|
166
136
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
@context
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
137
|
+
PARALLEL_PROCESS_OPTIONS = {
|
138
|
+
progress: {
|
139
|
+
title: 'Process',
|
140
|
+
format: Lydown::CLI::PROGRESS_FORMAT
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
def parse_directory_files(state)
|
145
|
+
streams = state[:streams]
|
146
|
+
proof_mode = @context['options/proof_mode']
|
147
|
+
paths = streams.keys
|
148
|
+
|
149
|
+
processed_streams = Parallel.map(paths, PARALLEL_PARSE_OPTIONS) do |path|
|
150
|
+
content = IO.read(path)
|
151
|
+
LydownParser.parse(content, {
|
152
|
+
filename: File.expand_path(path),
|
153
|
+
source: content,
|
154
|
+
proof_mode: proof_mode,
|
155
|
+
no_progress: true
|
156
|
+
})
|
185
157
|
end
|
158
|
+
processed_streams.each_with_index {|s, idx| streams[paths[idx]] = s}
|
186
159
|
end
|
187
160
|
|
188
|
-
def
|
189
|
-
|
190
|
-
path =
|
191
|
-
|
192
|
-
|
193
|
-
settings[:pickup] = @context[:pickup]
|
194
|
-
settings[:key] = @context[:key]
|
195
|
-
settings[:tempo] = @context[:tempo]
|
161
|
+
def translate_directory_streams(state)
|
162
|
+
# Process work file
|
163
|
+
if path = state[:movements][nil][:work]
|
164
|
+
@context.translate state[:streams][path]
|
165
|
+
end
|
196
166
|
|
197
|
-
|
167
|
+
translate_movement_files(state)
|
198
168
|
end
|
199
|
-
|
200
|
-
def
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
else
|
209
|
-
raise LydownError, "Could not read #{path}"
|
169
|
+
|
170
|
+
def translate_movement_files(state)
|
171
|
+
stream_entries = prepare_work_stream_array(state)
|
172
|
+
processed_contexts = Parallel.map(stream_entries, PARALLEL_PROCESS_OPTIONS) do |entry|
|
173
|
+
mvmt_stream, stream = *entry
|
174
|
+
ctx = @context.clone_for_translation
|
175
|
+
ctx.translate(mvmt_stream)
|
176
|
+
ctx.translate(stream)
|
177
|
+
ctx
|
210
178
|
end
|
179
|
+
|
180
|
+
processed_contexts.each {|ctx| @context.merge_movements(ctx)}
|
211
181
|
end
|
182
|
+
|
183
|
+
# An array containing entries for each file/stream to be translated.
|
184
|
+
# Each entry in this array is in the form [mvmt_stream, stream]
|
185
|
+
def prepare_work_stream_array(state)
|
186
|
+
streams = state[:streams]
|
187
|
+
movements = state[:movements]
|
188
|
+
line_range = @context[:options][:line_range]
|
189
|
+
entries = []
|
190
|
+
|
191
|
+
movements.each do |mvmt, mvmt_files|
|
192
|
+
# Construct movement stream, a prefix for the part stream
|
193
|
+
mvmt_stream = [{type: :setting, key: 'movement', value: mvmt}]
|
194
|
+
if path = movements[mvmt][:movement]
|
195
|
+
mvmt_stream += streams[path]
|
196
|
+
end
|
212
197
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
# process movement specific code
|
220
|
-
process_lydown_file(File.join(path, 'movement.ld'))
|
221
|
-
|
222
|
-
part_filter = @context[:options][:parts]
|
223
|
-
mvmt_filter = @context[:options][:movements]
|
224
|
-
|
225
|
-
# Iterate over sorted directory entries
|
226
|
-
Dir["#{path}/*"].entries.sort.each do |entry|
|
227
|
-
if File.file?(entry) && (entry =~ /\.ld$/)
|
228
|
-
part = File.basename(entry, '.*')
|
229
|
-
skip = part_filter && !part_filter.include?(part)
|
230
|
-
unless DEFAULT_BASENAMES.include?(part) || skip
|
231
|
-
preserve_context do
|
232
|
-
process_lydown_file(entry, [
|
233
|
-
{type: :setting, key: 'part', value: part}
|
234
|
-
], line_range: @context[:options][:line_range])
|
235
|
-
end
|
236
|
-
end
|
237
|
-
elsif File.directory?(entry) && recursive
|
238
|
-
movement = File.basename(entry)
|
239
|
-
skip = mvmt_filter && !mvmt_filter.include?(movement)
|
240
|
-
unless skip
|
241
|
-
process([{type: :setting, key: 'movement', value: movement}])
|
242
|
-
process_directory(entry, false)
|
243
|
-
end
|
198
|
+
mvmt_files.each do |part, path|
|
199
|
+
unless part.is_a?(Symbol)
|
200
|
+
stream = streams[path]
|
201
|
+
Lydown::Rendering.insert_skip_markers(stream, line_range) if line_range
|
202
|
+
stream.unshift({type: :setting, key: 'part', value: part})
|
203
|
+
entries << [mvmt_stream, stream]
|
244
204
|
end
|
245
205
|
end
|
246
206
|
end
|
207
|
+
entries
|
247
208
|
end
|
248
209
|
|
249
|
-
def process_lydown_file(path, prefix = [], opts = {})
|
250
|
-
return unless File.file?(path)
|
251
|
-
|
252
|
-
content = IO.read(path)
|
253
|
-
stream = LydownParser.parse(content, {
|
254
|
-
filename: File.expand_path(path),
|
255
|
-
source: content
|
256
|
-
})
|
257
|
-
|
258
|
-
if opts[:line_range]
|
259
|
-
Lydown::Rendering.insert_skip_markers(stream, opts[:line_range])
|
260
|
-
end
|
261
|
-
|
262
|
-
process(prefix + stream)
|
263
|
-
end
|
264
210
|
end
|
265
211
|
end
|