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