lydown 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +159 -2
  3. data/lib/lydown.rb +8 -2
  4. data/lib/lydown/cache.rb +54 -0
  5. data/lib/lydown/cli.rb +1 -0
  6. data/lib/lydown/cli/commands.rb +27 -9
  7. data/lib/lydown/cli/compiler.rb +218 -54
  8. data/lib/lydown/cli/diff.rb +1 -1
  9. data/lib/lydown/cli/proofing.rb +3 -3
  10. data/lib/lydown/cli/signals.rb +23 -0
  11. data/lib/lydown/cli/support.rb +23 -1
  12. data/lib/lydown/core_ext.rb +41 -5
  13. data/lib/lydown/{rendering/defaults.yml → defaults.yml} +3 -3
  14. data/lib/lydown/errors.rb +3 -0
  15. data/lib/lydown/lilypond.rb +73 -31
  16. data/lib/lydown/ly_lib/lib.ly +297 -0
  17. data/lib/lydown/parsing.rb +1 -2
  18. data/lib/lydown/parsing/lydown.treetop +16 -10
  19. data/lib/lydown/parsing/nodes.rb +29 -5
  20. data/lib/lydown/rendering.rb +32 -6
  21. data/lib/lydown/rendering/command.rb +79 -2
  22. data/lib/lydown/rendering/figures.rb +29 -8
  23. data/lib/lydown/rendering/literal.rb +7 -0
  24. data/lib/lydown/rendering/movement.rb +61 -0
  25. data/lib/lydown/rendering/music.rb +37 -5
  26. data/lib/lydown/rendering/notes.rb +26 -8
  27. data/lib/lydown/rendering/settings.rb +41 -13
  28. data/lib/lydown/rendering/skipping.rb +43 -10
  29. data/lib/lydown/rendering/staff.rb +72 -16
  30. data/lib/lydown/templates.rb +8 -2
  31. data/lib/lydown/templates/lilypond_doc.erb +10 -1
  32. data/lib/lydown/templates/movement.erb +87 -34
  33. data/lib/lydown/templates/multi_voice.erb +1 -1
  34. data/lib/lydown/templates/part.erb +83 -55
  35. data/lib/lydown/templates/variables.erb +38 -0
  36. data/lib/lydown/version.rb +1 -1
  37. data/lib/lydown/work.rb +39 -26
  38. data/lib/lydown/work_context.rb +252 -14
  39. metadata +138 -8
  40. data/lib/lydown/rendering/lib.ly +0 -88
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9597286c88009e270c73fae97a968f1d946c52e2
4
- data.tar.gz: d360df9c114bfc5fefcaf600832e549c42997232
3
+ metadata.gz: 8609e6ea7dff78ec0b444ea4aeecb7156f24742b
4
+ data.tar.gz: 523985930d61e49ddbbffb093e2af2b256862ba1
5
5
  SHA512:
6
- metadata.gz: 603853150a584ee4951388f1565bee07aac65e36639a703d10944de9b011f6530243a9f25fe2abe037f35ce84d33c3bc11a7139fbe7ea16144c4c49662a2dcb2
7
- data.tar.gz: 04c071237d29bdaaa0fb78becd234ae5da75fc1321cafd0901fd879b472a09aaf01713c858b9a233a29243d4fa1b62b19cd1f768f85c261eadb6b84d855e2d2a
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 marks can be entered as a string following a backslash (for placing it under the note) or a forward slash (for placing it above the note).
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. Lydown includes a
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'
@@ -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
@@ -10,3 +10,4 @@ require 'lydown/cli/diff'
10
10
  require 'lydown/cli/translation'
11
11
  require 'lydown/cli/output'
12
12
  require 'lydown/cli/commands'
13
+ require 'lydown/cli/signals'
@@ -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
- [:png, :ly, :midi].each {|f| opts[:format] = f.to_s if opts[f]}
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] || !opts[:parts]
54
- parts = opts[:parts]
55
- parts.each do |p|
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
 
@@ -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
- def output_filename(opts)
4
- fn = opts[:output_filename]
5
- if opts[:movements] && opts[:movements].size == 1
6
- fn << "-#{opts[:movements].first}"
7
- end
8
- if opts[:parts] && opts[:parts].size == 1
9
- fn << "-#{opts[:parts].first}"
10
- end
11
- fn
12
- end
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
- # translate lydown code to lilypond code
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
- opts[:output_target] = output_filename(opts)
34
-
35
- if opts[:format] == 'ly'
36
- unless opts[:output_target]
37
- STDOUT << ly_code
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
- begin
40
- File.open(opts[:output_target] + '.ly', 'w+') do |f|
41
- f.write ly_code
42
- end
43
- rescue => e
44
- $stderr.puts "#{e.class}: #{e.message}"
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
- t2 = Time.now
51
- $stderr.puts "Elapsed: #{'%.1f' % [t2-t1]}s"
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 = opts.deep_clone
56
- begin
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
- if opts[:open_target]
67
- filename = "#{opts[:output_target]}.#{opts[:format]}"
68
-
69
- unless File.file?(filename)
70
- filename = "#{opts[:output_target]}-page1.#{opts[:format]}"
71
- end
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
- # Mac OSX specific probably
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