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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8609e6ea7dff78ec0b444ea4aeecb7156f24742b
|
4
|
+
data.tar.gz: 523985930d61e49ddbbffb093e2af2b256862ba1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 825b276f2582fd0bb3cbaed37e1a579978f59da333afffe39f203667e948ce467a6154d65fea6df28cb07218a9f1671aa95257b84e08c8030efdf7863b19bef7
|
7
|
+
data.tar.gz: b676b5b4f22cb575968435084d3e5ecb1a3759c3dd1d4a3e9b3df5fa81b57af529e92f2d4d60f91bc1e0ad93918a3e140e31f239441a4296d4c38bbe2ca5e9b7
|
data/README.md
CHANGED
@@ -1,5 +1,32 @@
|
|
1
1
|
Lydown is a language and compiler for creating music scores, parts and snippets. The lydown code is compiled to [lilypond](http://lilypond.org/) code and then compiled to PDF, PNG or MIDI files.
|
2
2
|
|
3
|
+
- [About](#)
|
4
|
+
- [Installation](#)
|
5
|
+
- [Hello world in lydown](#)
|
6
|
+
- [Compiling the lydown code](#)
|
7
|
+
- [Proofing mode](#)
|
8
|
+
- [The lydown syntax](#)
|
9
|
+
- [Notes and durations](#)
|
10
|
+
- [Accidentals](#)
|
11
|
+
- [Octaves](#)
|
12
|
+
- [Barlines](#)
|
13
|
+
- [Rests](#)
|
14
|
+
- [Beams, slurs and ties](#)
|
15
|
+
- [Articulation and expression marks](#)
|
16
|
+
- [Repeated articulation and rhythmic patterns: macros](#)
|
17
|
+
- [Named macros](#)
|
18
|
+
- [Clefs, key and time signatures](#)
|
19
|
+
- [Pickup bars](#)
|
20
|
+
- [Lilypond Commands and inline settings](#)
|
21
|
+
- [Inline lyrics](#)
|
22
|
+
- [Stream switching](#)
|
23
|
+
- [Figured bass](#)
|
24
|
+
- [Multiple parts](#)
|
25
|
+
- [Multiple movements](#)
|
26
|
+
- [Multiple voices](#)
|
27
|
+
|
28
|
+
## About
|
29
|
+
|
3
30
|
Lydown builds on the ideas put forth by lilypond and makes the following improvements:
|
4
31
|
|
5
32
|
- a greatly simplified syntax for entering notes, for more rapid note entry and improved legibility.
|
@@ -60,6 +87,22 @@ To create a MIDI file:
|
|
60
87
|
|
61
88
|
lydown -O --midi helloworld.lydown
|
62
89
|
|
90
|
+
|
91
|
+
## Proofing mode
|
92
|
+
|
93
|
+
Lydown can be run in a special proofing mode designed to minimize the turnaround time between editing and viewing the result when entering new music or when editing existing music. To run lydown in proofing mode, use the proof subcommand:
|
94
|
+
|
95
|
+
lydown proof
|
96
|
+
|
97
|
+
Lydown will then start to monitor all subdirectories and files in the current directory, and compile them whenever they're changed. Lydown will detect the lines that have changed and insert skip markers in order to speed up compilation and typeset only the music that has actually changed. In addition, notes in the bars that changed will be colored red.
|
98
|
+
|
99
|
+
In proof mode, lydown will generate parts and not a score. To include another part for reference along with the part that changed, you can use the include_parts parameter:
|
100
|
+
|
101
|
+
lydown proof --include_parts continuo # or:
|
102
|
+
lydown proof -i continuo
|
103
|
+
|
104
|
+
This is useful when editing baroque music for example, or when any other part can give context and aid in verifying the music.
|
105
|
+
|
63
106
|
## The lydown syntax
|
64
107
|
|
65
108
|
The lydown syntax is designed for faster note entry, better legibility and minimal boilerplate. The lydown syntax takes the basic ideas put forth by lilypond and simplifies them, with the following main differences:
|
@@ -204,7 +247,15 @@ Other arbitrary lilypond articulations can be entered after a backslash:
|
|
204
247
|
|
205
248
|
\f cege \p cfaf => c\f e g e c\p f a f
|
206
249
|
|
207
|
-
Arbitrary expression
|
250
|
+
Arbitrary expression markup can be entered as a string following a backslash. Lydown supports basic markdown syntax for formatting the expression:
|
251
|
+
|
252
|
+
c\"hello" // displayed above the note
|
253
|
+
c\_"hello" // displayed below the note
|
254
|
+
c\<"hello" // right-aligned
|
255
|
+
c\|"hello" // centered
|
256
|
+
c\_|"hello" // centered, below the note
|
257
|
+
c\"_hello_" // italic
|
258
|
+
c\"__hello__" // bold
|
208
259
|
|
209
260
|
### Repeated articulation and rhythmic patterns: macros
|
210
261
|
|
@@ -351,6 +402,19 @@ Multiple lyrics stanzas can be written by including the stanza number in parens:
|
|
351
402
|
Figured bass is entered inline, following notes or even between notes, when
|
352
403
|
multiple figures align with a single note.
|
353
404
|
|
405
|
+
c<6>de<5>c // figures are automatically rhythmically aligned
|
406
|
+
e<7`> // barred figure
|
407
|
+
f<6+> // sharp 6th
|
408
|
+
g<6-> // flat 6th
|
409
|
+
g<6!> // natural sixth
|
410
|
+
a<h> // natural third
|
411
|
+
a<#> // sharp third
|
412
|
+
a<b> // flat third
|
413
|
+
b<7#>b<6_> // extender (tenue) line on sharp third
|
414
|
+
1c2<6><7-> // use durations to change figures over a long note
|
415
|
+
1c2<><7-> // <> is an empty figure
|
416
|
+
c<->dec<.> // extender (tenue) line without figures over the four notes
|
417
|
+
|
354
418
|
## Multiple parts
|
355
419
|
|
356
420
|
Multiple parts can be entered in the same file by prefixing each part's content with a -part setting:
|
@@ -422,6 +486,99 @@ To extract a specific part, we use the -p switch:
|
|
422
486
|
|
423
487
|
lydown -o -p violino1 score.lydown
|
424
488
|
|
489
|
+
## Colla parte and part sources
|
490
|
+
|
491
|
+
lydown provides two ways to reuse a part's music in another part. The first is by defining a part source:
|
492
|
+
|
493
|
+
- parts:
|
494
|
+
- violino1:
|
495
|
+
- source: soprano
|
496
|
+
|
497
|
+
When dealing situations such as a choral being doubled by many instruments, a more convenient approach would be the <code>colla_parte</code> setting:
|
498
|
+
|
499
|
+
- colla_parte:
|
500
|
+
- soprano: violino1, flute1, flute2, oboe1
|
501
|
+
- alto: violino2, oboe2
|
502
|
+
- tenor: viola, gamba1
|
503
|
+
- basso: gamba2
|
504
|
+
|
505
|
+
It is important to remember that the <code>colla_parte</code> setting is the opposite of the part source. Instead of defining a source for a part, it defines the parts that follow the source part.
|
506
|
+
|
507
|
+
## Including another part when extracting parts
|
508
|
+
|
509
|
+
When extracing parts, some cases, such as recitatives, require displaying another part together with the part to be extracted. Parts can be included in extracted parts using the <code>include_parts</code> setting:
|
510
|
+
|
511
|
+
- parts:
|
512
|
+
- continuo:
|
513
|
+
- include_parts: tenore
|
514
|
+
|
515
|
+
multiple parts can be specified by comma separating them.
|
516
|
+
|
517
|
+
## Mode specific code
|
518
|
+
|
519
|
+
The <code>\mode</code> and <code>\nomode</code> commands can be used to render code for specific modes. This can be useful when the extracted part should display a different music than the score. The mode command causes anything after it to be rendered only when the rendering mode matches the specified mode.
|
520
|
+
|
521
|
+
\mode:score 4cege \mode:part 4cded \mode:none 1c
|
522
|
+
|
523
|
+
The example above will be render as <code>4cege1c</code> when in score mode, and as <code>4cded1c</code> in the extracted part.
|
524
|
+
|
525
|
+
To cancel a mode, use either <code>\mode:none</code> or <code>\nomode</code>
|
526
|
+
|
527
|
+
## Staff and system appearance
|
528
|
+
|
529
|
+
Numerous settings can be used to control the way staves and systems are displayed. Normally, lydown will follow standard conventions when putting together a score: the different parts will be ordered correctly (e.g. flutes, then oboes, then strings, then vocal parts, then continuo), and braced/bracketed according to convention.
|
530
|
+
|
531
|
+
When a single movement switches between different ensembles, for example a recitativo secco followed by a recitativo accompagnato, empty staves may be hidden using setting <code>empty_staves</code> setting:
|
532
|
+
|
533
|
+
- empty_staves: hide
|
534
|
+
|
535
|
+
Normally, instrument names are shown according to convention, to the left of each staff in the first system. To hide the instrument names on the first system, use the <code>instrument_names</code> setting:
|
536
|
+
|
537
|
+
- instrument_names: hide
|
538
|
+
|
539
|
+
The <code>instrument_names</code> setting can also be set to <inline>, in which case the instrument name will be displayed above the beginning of each staff in the first system.
|
540
|
+
|
541
|
+
To show the instrument name inline at any point in the music, use the <code>\instr</code> command:
|
542
|
+
|
543
|
+
- part: flute
|
544
|
+
R*4
|
545
|
+
// when no parameter is given, the part name is used,
|
546
|
+
// and engraved capitalized with numbers formatted to roman numerals
|
547
|
+
\instr 4c8eg2c
|
548
|
+
...
|
549
|
+
// when the instrument name is specifed, it is used in place of the
|
550
|
+
// part name:
|
551
|
+
\instr:"Flute II" 4c8eg2c
|
552
|
+
|
553
|
+
The <code>\instr</code> command can also accept an alignment modifier:
|
554
|
+
|
555
|
+
\<instr // right-aligned
|
556
|
+
\>instr // left-aligned (default)
|
557
|
+
\|instr // centered
|
558
|
+
|
559
|
+
Another setting which controls the display of instrument names is <code>instrument_name_style</code> which can be set to <code>normal</code> or <code>smallcaps</code>, in which case the instrument name will be engraved using small caps.
|
560
|
+
|
561
|
+
## Rendering MIDI && mp3 files
|
562
|
+
|
563
|
+
In order to render the source code into MIDI, the midi format should be specified:
|
564
|
+
|
565
|
+
lydown --midi score.ld
|
566
|
+
|
567
|
+
The playback tempo can be specified either using the <code>midi_tempo</code> setting:
|
568
|
+
|
569
|
+
- midi_tempo: 4=96
|
570
|
+
|
571
|
+
Or inline using the <code>tempo</code> command, putting the tempo in parens:
|
572
|
+
|
573
|
+
\tempo:(4=120)
|
574
|
+
cdef
|
575
|
+
/tempo:(4=54)
|
576
|
+
gabc
|
577
|
+
|
578
|
+
Rendering of mp3 files requires both [timidity](http://timidity.sourceforge.net/) and [LAME](http://lame.sourceforge.net/) to be installed. The mp3 format needs to be
|
579
|
+
|
425
580
|
## Adding a front cover
|
426
581
|
|
427
|
-
When creating professional scores and parts, it is customary to add a cover page, with the title of the piece, the composer's name and other general information.
|
582
|
+
When creating professional scores and parts, it is customary to add a cover page, with the title of the piece, the composer's name and other general information.
|
583
|
+
|
584
|
+
|
data/lib/lydown.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
-
module Lydown; end
|
2
|
-
|
3
1
|
require 'lydown/core_ext'
|
2
|
+
require 'lydown/cache'
|
3
|
+
|
4
|
+
module Lydown
|
5
|
+
require 'yaml'
|
6
|
+
DEFAULTS = YAML.load(IO.read(File.join(File.dirname(__FILE__),
|
7
|
+
'lydown/defaults.yml'))).deep!
|
8
|
+
end
|
9
|
+
|
4
10
|
require 'lydown/errors'
|
5
11
|
require 'lydown/parsing'
|
6
12
|
require 'lydown/templates'
|
data/lib/lydown/cache.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
require 'msgpack'
|
3
|
+
|
4
|
+
module Cache
|
5
|
+
class << self
|
6
|
+
def disable!
|
7
|
+
@disabled = true
|
8
|
+
end
|
9
|
+
|
10
|
+
def enable!
|
11
|
+
@disabled = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def hit(*params)
|
15
|
+
return yield if @disabled
|
16
|
+
|
17
|
+
cache_key = calculate_hash(*params)
|
18
|
+
if result = get(cache_key)
|
19
|
+
result
|
20
|
+
else
|
21
|
+
result = yield
|
22
|
+
set(cache_key, result)
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def calculate_hash(*params)
|
28
|
+
params.map do |p|
|
29
|
+
Digest::MD5.hexdigest(p.is_a?(String) ? p : p.to_msgpack)
|
30
|
+
end.join('-')
|
31
|
+
end
|
32
|
+
|
33
|
+
def get(cache_key)
|
34
|
+
fn = filename(cache_key)
|
35
|
+
if File.file?(fn)
|
36
|
+
Marshal.load(IO.read(fn))
|
37
|
+
else
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
rescue
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def set(cache_key, value)
|
45
|
+
File.open(filename(cache_key), 'w+') do |f|
|
46
|
+
f << Marshal.dump(value)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def filename(cache_key)
|
51
|
+
"/tmp/lydown-cache-#{cache_key}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/lydown/cli.rb
CHANGED
data/lib/lydown/cli/commands.rb
CHANGED
@@ -12,11 +12,12 @@ module Lydown::CLI
|
|
12
12
|
|
13
13
|
desc "compile [PATH]", "compile the lydown source at PATH"
|
14
14
|
method_option :format, aliases: '-f',
|
15
|
-
default: 'pdf', desc: 'Set output format (pdf/png/ly/midi)',
|
16
|
-
enum: %w{pdf png ly midi}
|
15
|
+
default: 'pdf', desc: 'Set output format (pdf/png/ly/midi/mp3)',
|
16
|
+
enum: %w{pdf png ly midi mp3}
|
17
17
|
method_option :png, type: :boolean, desc: 'Set PNG output format'
|
18
18
|
method_option :ly, type: :boolean, desc: 'Set Lilypond output format'
|
19
19
|
method_option :midi, type: :boolean, desc: 'Set MIDI output format'
|
20
|
+
method_option :mp3, type: :boolean, desc: 'Set MP3 output format'
|
20
21
|
|
21
22
|
method_option :parts, aliases: '-p',
|
22
23
|
desc: 'Compile only the specified parts (comma separated)'
|
@@ -30,31 +31,43 @@ module Lydown::CLI
|
|
30
31
|
desc: 'Set output path'
|
31
32
|
method_option :open_target, type: :boolean, aliases: '-O',
|
32
33
|
desc: 'Open output file after compilation'
|
34
|
+
method_option :separate, type: :boolean, aliases: '-S',
|
35
|
+
desc: 'Create separate file for each movement'
|
33
36
|
method_option :verbose, type: :boolean
|
34
37
|
def compile(*args)
|
35
38
|
require 'lydown'
|
36
39
|
|
37
40
|
opts = Lydown::CLI::Support.copy_options(options)
|
38
41
|
opts[:path] = args.first || '.'
|
39
|
-
Lydown::CLI::Support.detect_filename(opts)
|
40
42
|
|
41
43
|
# Set format based on direct flag
|
42
|
-
[:
|
44
|
+
opts[:format] = opts[:format].to_sym if opts[:format]
|
45
|
+
[:png, :ly, :midi, :mp3].each {|f| opts[:format] = f if opts[f]}
|
43
46
|
|
44
47
|
opts[:parts] = opts[:parts].split(',') if opts[:parts]
|
45
48
|
opts[:movements] = opts[:movements].split(',') if opts[:movements]
|
46
49
|
|
50
|
+
# Detect work directory
|
51
|
+
Lydown::CLI::Support.detect_work_directory(opts)
|
52
|
+
Lydown::CLI::Support.detect_filename(opts)
|
53
|
+
|
54
|
+
p opts
|
55
|
+
|
56
|
+
if (opts[:format] == :midi) || (opts[:format] == :mp3)
|
57
|
+
opts[:score_only] = true
|
58
|
+
opts[:parts_only] = false
|
59
|
+
end
|
60
|
+
|
47
61
|
# compile score
|
48
62
|
unless opts[:parts_only] || opts[:parts]
|
63
|
+
$stderr.puts "Processing score..."
|
49
64
|
Lydown::CLI::Compiler.process(opts.merge(mode: :score, parts: nil))
|
50
65
|
end
|
51
66
|
|
52
67
|
# compile parts
|
53
|
-
unless opts[:score_only]
|
54
|
-
|
55
|
-
|
56
|
-
Lydown::CLI::Compiler.process(opts.merge(mode: :part, parts: p))
|
57
|
-
end
|
68
|
+
unless opts[:score_only]
|
69
|
+
$stderr.puts "Processing parts..."
|
70
|
+
Lydown::CLI::Compiler.process(opts.merge(mode: :part))
|
58
71
|
end
|
59
72
|
end
|
60
73
|
|
@@ -62,15 +75,20 @@ module Lydown::CLI
|
|
62
75
|
method_option :format, aliases: '-f',
|
63
76
|
default: 'pdf', desc: 'Set output format (pdf/png/ly)',
|
64
77
|
enum: %w{pdf png ly}
|
78
|
+
method_option :include_parts, aliases: '-i', desc: 'Include parts (comma separated)'
|
65
79
|
def proof(*args)
|
66
80
|
require 'lydown'
|
67
81
|
|
68
82
|
opts = Lydown::CLI::Support.copy_options(options)
|
69
83
|
opts[:path] = args.first || '.'
|
84
|
+
|
70
85
|
opts[:proof_mode] = true
|
86
|
+
opts[:include_parts] = opts[:include_parts] && opts[:include_parts].split(',')
|
71
87
|
opts[:open_target] = true
|
72
88
|
|
89
|
+
Lydown::CLI::Support.detect_work_directory(opts)
|
73
90
|
Lydown::CLI::Support.detect_filename(opts)
|
91
|
+
|
74
92
|
Lydown::CLI::Proofing.start_proofing(opts)
|
75
93
|
end
|
76
94
|
|
data/lib/lydown/cli/compiler.rb
CHANGED
@@ -1,78 +1,242 @@
|
|
1
|
+
require 'lydown/cli/output'
|
2
|
+
require 'combine_pdf'
|
3
|
+
|
1
4
|
module Lydown::CLI::Compiler
|
2
5
|
class << self
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
PARALLEL_COMPILE_OPTIONS = {
|
7
|
+
progress: {
|
8
|
+
title: 'Compile',
|
9
|
+
format: Lydown::CLI::PROGRESS_FORMAT
|
10
|
+
}
|
11
|
+
}
|
12
|
+
|
13
|
+
PARALLEL_COLLATE_OPTIONS = {
|
14
|
+
progress: {
|
15
|
+
title: 'Collate',
|
16
|
+
format: Lydown::CLI::PROGRESS_FORMAT
|
17
|
+
}
|
18
|
+
}
|
13
19
|
|
14
20
|
def process(opts)
|
15
21
|
t1 = Time.now
|
22
|
+
|
16
23
|
opts = opts.deep_clone
|
17
|
-
|
18
|
-
ly_code = ''
|
19
|
-
begin
|
20
|
-
opts[:path] = opts[:source_filename]
|
21
|
-
opts[:nice_error] = true
|
22
|
-
work = Lydown::Work.new(opts)
|
23
|
-
ly_code = work.to_lilypond(opts)
|
24
|
-
rescue LydownError => e
|
25
|
-
$stderr.puts e.message
|
26
|
-
$stderr.puts e.backtrace.join("\n")
|
27
|
-
exit 1
|
28
|
-
rescue => e
|
29
|
-
$stderr.puts "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
30
|
-
exit 1
|
31
|
-
end
|
24
|
+
work = create_work_from_opts(opts)
|
32
25
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
26
|
+
jobs = create_jobs_from_opts(work, opts)
|
27
|
+
process_jobs(work, jobs, opts)
|
28
|
+
|
29
|
+
t2 = Time.now
|
30
|
+
$stderr.puts "Elapsed: #{'%.1f' % [t2-t1]}s"
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_jobs_from_opts(work, opts)
|
34
|
+
jobs = {compile: [], collate: {}}
|
35
|
+
case opts[:mode]
|
36
|
+
when :score
|
37
|
+
if opts[:separate]
|
38
|
+
# If separate flag is specified, compile each movement separately
|
39
|
+
work.context[:movements].keys.each do |m|
|
40
|
+
add_compile_job(jobs, work, opts.merge(movements: [m]))
|
41
|
+
end
|
42
|
+
else
|
43
|
+
add_compile_job(jobs, work, opts)
|
44
|
+
end
|
45
|
+
when :part
|
46
|
+
# If parts are not specified, compile all extractable parts
|
47
|
+
parts = opts[:parts] || work.context.part_list_for_extraction(opts)
|
48
|
+
|
49
|
+
parts.each {|p| add_compile_job(jobs, work, opts.merge(parts: p))}
|
50
|
+
when :proof
|
51
|
+
add_compile_job(jobs, work, opts)
|
52
|
+
end
|
53
|
+
jobs
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_compile_job(jobs, work, opts)
|
57
|
+
if opts[:separate] || (opts[:format] != :pdf)
|
58
|
+
jobs[:compile] << opts
|
59
|
+
else
|
60
|
+
# For now we disable dividing into bookparts, as we have a problem
|
61
|
+
# With page numbers (each bookpart will start at 1).
|
62
|
+
return jobs[:compile] << opts
|
63
|
+
|
64
|
+
bookparts = Lydown::Rendering::Movement.bookparts(
|
65
|
+
work.context, opts.merge(part: opts[:parts])
|
66
|
+
)
|
67
|
+
if bookparts.size == 1
|
68
|
+
jobs[:compile] << opts
|
38
69
|
else
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
70
|
+
# If compiling pdfs, compilation can be sped up by breaking
|
71
|
+
# the work into bookparts (on page break boundaries), compiling
|
72
|
+
# them in parallel, and then collating the files into a single PDF.
|
73
|
+
|
74
|
+
bookpart_files = []
|
75
|
+
bookparts.each do |movements|
|
76
|
+
tmp_target = Tempfile.new('lydown').path
|
77
|
+
bookpart_files << tmp_target
|
78
|
+
jobs[:compile] << opts.merge(
|
79
|
+
output_target: tmp_target, temp: true, open_target: false,
|
80
|
+
movements: movements
|
81
|
+
)
|
45
82
|
end
|
83
|
+
jobs[:collate][output_filename(opts)] = bookpart_files
|
46
84
|
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def process_jobs(work, jobs, opts)
|
89
|
+
if jobs[:compile].size == 1
|
90
|
+
run_compile_job(work, jobs[:compile][0])
|
91
|
+
else
|
92
|
+
Parallel.each(jobs[:compile], PARALLEL_COMPILE_OPTIONS.clone) do |job|
|
93
|
+
job = job.deep_clone
|
94
|
+
job[:no_progress_bar] = true
|
95
|
+
run_compile_job(work, job)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
collate_pdfs(jobs[:collate], opts) unless jobs[:collate].empty?
|
100
|
+
end
|
101
|
+
|
102
|
+
def collate_pdfs(collate_map, opts)
|
103
|
+
if collate_map.size == 1
|
104
|
+
collate_bookparts_into_pdf(collate_map.keys[0], collate_map.values[0], opts)
|
105
|
+
else
|
106
|
+
Parallel.each(collate_map, PARALLEL_COLLATE_OPTIONS.clone) do |fn, tempfiles|
|
107
|
+
collate_bookparts_into_pdf(fn, tempfiles, opts)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def collate_bookparts_into_pdf(output_filename, source_filenames, opts)
|
113
|
+
pdf = CombinePDF.new
|
114
|
+
source_filenames.each do |fn|
|
115
|
+
pdf << CombinePDF.load(fn + ".pdf")
|
116
|
+
end
|
117
|
+
pdf.save output_filename + ".pdf"
|
118
|
+
|
119
|
+
system("open #{output_filename}.pdf") if opts[:open_target]
|
120
|
+
end
|
121
|
+
|
122
|
+
def run_compile_job(work, opts)
|
123
|
+
ly_code = work.to_lilypond(opts)
|
124
|
+
opts[:output_target] = output_filename(opts) unless opts[:temp]
|
125
|
+
|
126
|
+
if opts[:format] == :ly
|
127
|
+
process_ly_target(ly_code, opts)
|
47
128
|
else
|
48
129
|
compile(ly_code, opts)
|
49
130
|
end
|
50
|
-
|
51
|
-
|
131
|
+
rescue => e
|
132
|
+
if e.is_a?(LydownError)
|
133
|
+
$stderr.puts e.message
|
134
|
+
else
|
135
|
+
$stderr.puts "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
136
|
+
end
|
137
|
+
if opts[:proof_mode]
|
138
|
+
`osascript -e beep`
|
139
|
+
return
|
140
|
+
else
|
141
|
+
exit(1)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def process_ly_target(ly_code, opts)
|
146
|
+
unless opts[:output_target]
|
147
|
+
STDOUT << ly_code
|
148
|
+
else
|
149
|
+
File.open(opts[:output_target] + '.ly', 'w+') do |f|
|
150
|
+
f.write ly_code
|
151
|
+
end
|
152
|
+
open_target(opts) if opts[:open_target]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def output_filename(opts)
|
157
|
+
fn = opts[:output_filename].dup
|
158
|
+
if opts[:movements] && opts[:movements].size == 1
|
159
|
+
fn << "-#{opts[:movements].first}"
|
160
|
+
end
|
161
|
+
if opts[:parts] && (opts[:parts].is_a?(String) || (opts[:parts].size == 1))
|
162
|
+
part_name = opts[:parts].is_a?(String) ? opts[:parts] : opts[:parts].first
|
163
|
+
fn << "-#{part_name}"
|
164
|
+
elsif (opts[:mode] == :score) && !([:midi, :mp3].include? opts[:format])
|
165
|
+
# Don't add score postfix for midi or mp3 compilation
|
166
|
+
fn << '-score'
|
167
|
+
end
|
168
|
+
fn
|
169
|
+
end
|
170
|
+
|
171
|
+
def create_work_from_opts(opts)
|
172
|
+
opts[:path] = opts[:source_filename]
|
173
|
+
opts[:nice_error] = true
|
174
|
+
Lydown::Work.new(opts)
|
52
175
|
end
|
53
176
|
|
54
177
|
def compile(ly_code, opts)
|
55
|
-
opts
|
56
|
-
|
57
|
-
Lydown::Lilypond.compile(ly_code, opts)
|
58
|
-
rescue LydownError => e
|
59
|
-
$stderr.puts e.message
|
60
|
-
$stderr.puts e.backtrace.join("\n")
|
61
|
-
rescue => e
|
62
|
-
$stderr.puts "#{e.class}: #{e.message}"
|
63
|
-
$stderr.puts e.backtrace.join("\n")
|
178
|
+
if opts[:format] == 'mp3'
|
179
|
+
return compile_mp3(ly_code, opts)
|
64
180
|
end
|
181
|
+
|
182
|
+
opts = opts.deep_clone
|
183
|
+
Lydown::Lilypond.compile(ly_code, opts)
|
184
|
+
open_target(opts) if opts[:open_target]
|
65
185
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
186
|
+
rescue CompilationAbortError
|
187
|
+
$stderr.puts "Compilation aborted"
|
188
|
+
rescue LydownError => e
|
189
|
+
$stderr.puts e.message
|
190
|
+
$stderr.puts e.backtrace.join("\n")
|
191
|
+
rescue => e
|
192
|
+
$stderr.puts "#{e.class}: #{e.message}"
|
193
|
+
$stderr.puts e.backtrace.join("\n")
|
194
|
+
end
|
195
|
+
|
196
|
+
def compile_mp3(ly_code, opts)
|
197
|
+
opts2 = opts.merge(format: 'midi').deep_clone
|
198
|
+
Lydown::Lilypond.compile(ly_code, opts2)
|
72
199
|
|
73
|
-
|
200
|
+
midi_filename = "#{opts[:output_target]}.midi"
|
201
|
+
mp3_filename = "#{opts[:output_target]}.mp3"
|
202
|
+
cmd = "timidity -Ow -o - #{midi_filename} | lame - #{mp3_filename}"
|
203
|
+
$stderr.puts "Converting to MP3..."
|
204
|
+
Open3.popen2e(cmd) do |input, output, wait_thr|
|
205
|
+
input.close
|
206
|
+
output.read
|
207
|
+
output.close
|
208
|
+
end
|
209
|
+
open_target(opts) if opts[:open_target]
|
210
|
+
rescue CompilationAbortError
|
211
|
+
$stderr.puts "Compilation aborted"
|
212
|
+
end
|
213
|
+
|
214
|
+
def open_target(opts)
|
215
|
+
filename = "#{opts[:output_target]}.#{opts[:format]}"
|
216
|
+
|
217
|
+
unless File.file?(filename)
|
218
|
+
filename2 = "#{opts[:output_target]}-page1.#{opts[:format]}"
|
219
|
+
unless File.file?(filename2)
|
220
|
+
raise "Could not find target file #{filename}"
|
221
|
+
else
|
222
|
+
filename = filename2
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
if opts[:format] == :midi
|
227
|
+
open_midi_target(filename)
|
228
|
+
else
|
74
229
|
system("open #{filename}")
|
75
230
|
end
|
76
231
|
end
|
232
|
+
|
233
|
+
def open_midi_target(filename)
|
234
|
+
$stderr << "Playing #{filename}..."
|
235
|
+
Open3.popen2e("timidity #{filename}") do |input, output, wait_thr|
|
236
|
+
input.close
|
237
|
+
output.read
|
238
|
+
output.close
|
239
|
+
end
|
240
|
+
end
|
77
241
|
end
|
78
242
|
end
|