lydown 0.12.4 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -1
  3. data/bin/lydown +2 -0
  4. data/lib/lydown.rb +6 -2
  5. data/lib/lydown/cache.rb +5 -1
  6. data/lib/lydown/cli.rb +1 -1
  7. data/lib/lydown/cli/commands.rb +7 -11
  8. data/lib/lydown/cli/compiler.rb +5 -0
  9. data/lib/lydown/cli/proofing.rb +2 -2
  10. data/lib/lydown/cli/repl.rb +1 -1
  11. data/lib/lydown/cli/support.rb +0 -33
  12. data/lib/lydown/defaults.yml +50 -8
  13. data/lib/lydown/inverso.rb +84 -0
  14. data/lib/lydown/lilypond.rb +76 -10
  15. data/lib/lydown/ly_lib/lib.ly +140 -127
  16. data/lib/lydown/parsing/lydown.treetop +25 -12
  17. data/lib/lydown/parsing/nodes.rb +55 -19
  18. data/lib/lydown/rendering.rb +72 -1
  19. data/lib/lydown/rendering/base.rb +21 -0
  20. data/lib/lydown/rendering/command.rb +53 -0
  21. data/lib/lydown/rendering/layout.rb +83 -0
  22. data/lib/lydown/rendering/lyrics.rb +1 -1
  23. data/lib/lydown/rendering/markup.rb +23 -0
  24. data/lib/lydown/rendering/movement.rb +7 -4
  25. data/lib/lydown/rendering/music.rb +35 -29
  26. data/lib/lydown/rendering/notes.rb +75 -41
  27. data/lib/lydown/rendering/repeats.rb +27 -0
  28. data/lib/lydown/rendering/settings.rb +36 -9
  29. data/lib/lydown/rendering/skipping.rb +10 -2
  30. data/lib/lydown/rendering/staff.rb +38 -31
  31. data/lib/lydown/rendering/voices.rb +1 -1
  32. data/lib/lydown/templates.rb +8 -8
  33. data/lib/lydown/templates/layout.rb +40 -0
  34. data/lib/lydown/templates/lilypond_doc.rb +95 -0
  35. data/lib/lydown/templates/movement.rb +188 -0
  36. data/lib/lydown/templates/multi_voice.rb +25 -0
  37. data/lib/lydown/templates/part.rb +146 -0
  38. data/lib/lydown/templates/variables.rb +43 -0
  39. data/lib/lydown/translation/ripple.rb +1 -1
  40. data/lib/lydown/translation/ripple/nodes.rb +51 -2
  41. data/lib/lydown/translation/ripple/ripple.treetop +87 -10
  42. data/lib/lydown/version.rb +1 -1
  43. data/lib/lydown/work.rb +19 -2
  44. data/lib/lydown/work_context.rb +10 -2
  45. metadata +12 -8
  46. data/lib/lydown/cli/installer.rb +0 -175
  47. data/lib/lydown/templates/lilypond_doc.erb +0 -34
  48. data/lib/lydown/templates/movement.erb +0 -118
  49. data/lib/lydown/templates/multi_voice.erb +0 -16
  50. data/lib/lydown/templates/part.erb +0 -118
  51. data/lib/lydown/templates/variables.erb +0 -43
@@ -1,5 +1,6 @@
1
1
  require 'lydown/errors'
2
2
  require 'lydown/cli/output'
3
+ require 'lydown/cli/signals'
3
4
 
4
5
  require 'tempfile'
5
6
  require 'fileutils'
@@ -8,6 +9,49 @@ require 'open3'
8
9
  module Lydown
9
10
  module Lilypond
10
11
  class << self
12
+ MINIMAL_LILYPOND_VERSION = Gem::Version.new('2.18')
13
+
14
+ def lilypond_path
15
+ # detect lyp-installed lilypond
16
+ # path = `lyp which lilypond`.lines.last rescue nil
17
+ # return path.chomp if path
18
+ #
19
+ # path = `which lilypond` rescue nil
20
+ # return path.chomp if path && !path.empty?
21
+
22
+ return 'lilypond'
23
+ end
24
+
25
+ # detect the lilypond version. If lilypond is not found, or the version is
26
+ # less than the minimal supported version, display an error message.
27
+ def detect_lilypond_version(exit_on_error)
28
+ version = nil
29
+ if path = lilypond_path
30
+ msg = `#{lilypond_path} --version`
31
+ if msg.lines.first =~ /LilyPond ([\d\.]+)/
32
+ version = $1
33
+ end
34
+ end
35
+
36
+ unless version && Gem::Version.new(version) >= MINIMAL_LILYPOND_VERSION
37
+ display_lilypond_version_error_msg(version)
38
+ exit!(1) if exit_on_error
39
+ version = nil
40
+ end
41
+ version
42
+ rescue => e
43
+ display_lilypond_version_error_msg(nil)
44
+ exit!(1) if exit_on_error
45
+ end
46
+
47
+ def display_lilypond_version_error_msg(version)
48
+ if version
49
+ STDERR.puts "ERROR: The installed lilypond (version #{version}) is too old."
50
+ else
51
+ STDERR.puts "ERROR: No copy of lilypond found."
52
+ end
53
+ end
54
+
11
55
  def tmpdir
12
56
  @tmpdir ||= Dir.mktmpdir
13
57
  end
@@ -67,14 +111,30 @@ module Lydown
67
111
  end
68
112
  end
69
113
 
114
+ # In order to be able to run the lyp lilypond wrapper (if present),
115
+ # we need to reset the environment variables set by bundler when running
116
+ # lydown.
117
+ RESET_ENV = {
118
+ "_ORIGINAL_GEM_PATH" => "",
119
+ "BUNDLE_GEMFILE" => "",
120
+ "BUNDLE_BIN_PATH" => "",
121
+ "RUBYOPT" => "",
122
+ "RUBYLIB" => ""
123
+ }
124
+
70
125
  # Run lilypond, pipe source into its STDIN, and capture its STDERR
71
126
  def invoke(source, opts = {})
72
- cmd = format_cmd(opts)
127
+ tmp_source_path = Tempfile.new('lydown').path
128
+ File.open(tmp_source_path, 'w') {|f| f << source}
129
+
130
+ cmd = format_cmd(opts, tmp_source_path)
131
+ puts cmd if opts[:verbose]
73
132
 
74
133
  err_info = ''
75
134
  exit_value = nil
76
- Open3.popen2e(cmd) do |input, output, wait_thr|
77
- err_info = exec(wait_thr, input, output, source, opts)
135
+
136
+ Open3.popen2e(RESET_ENV, cmd) do |input, output, wait_thr|
137
+ err_info = exec(wait_thr, input, output, opts)
78
138
  exit_value = wait_thr.value
79
139
  end
80
140
  if exit_value != 0
@@ -87,23 +147,23 @@ module Lydown
87
147
  end
88
148
  end
89
149
 
90
- def format_cmd(opts)
150
+ def format_cmd(opts, source_path)
91
151
  format = opts[:format]
92
152
  format = nil if (format == :midi) || (format == :mp3)
93
153
 
94
- cmd = 'lilypond '
95
- cmd << "-dbackend=eps " if opts[:mode] == :proof
154
+ cmd = "#{lilypond_path} "
155
+ # cmd << "-dbackend=eps " if opts[:mode] == :proof
96
156
  cmd << "-o #{opts[:output_filename]} "
97
157
  cmd << "-dno-point-and-click "
98
158
  cmd << "--#{opts[:format]} " if format
99
- cmd << ' - '
159
+ cmd << "-V " if opts[:verbose]
160
+ cmd << source_path
100
161
 
101
162
  cmd
102
163
  end
103
164
 
104
- def exec(wait_thr, input, output, source, opts)
165
+ def exec(wait_thr, input, output, opts)
105
166
  Lydown::CLI.register_abortable_process(wait_thr.pid)
106
- input.puts source
107
167
  input.close_write
108
168
  err_info = read_lilypond_progress(output, opts)
109
169
  output.close
@@ -128,7 +188,7 @@ module Lydown
128
188
 
129
189
  def read_lilypond_progress(f, opts)
130
190
  info = ''
131
- unless opts[:no_progress_bar]
191
+ if !opts[:verbose] && !opts[:no_progress_bar]
132
192
  Lydown::CLI::show_progress('Compile', STATUS_TOTAL) do |bar|
133
193
  while !f.eof?
134
194
  line = f.gets
@@ -140,6 +200,12 @@ module Lydown
140
200
  end
141
201
  bar.progress = STATUS_TOTAL
142
202
  end
203
+ elsif opts[:verbose]
204
+ while !f.eof?
205
+ line = f.gets
206
+ STDERR.puts line
207
+ info += line
208
+ end
143
209
  else
144
210
  info = f.read
145
211
  end
@@ -1,53 +1,53 @@
1
- \header {
2
- tagline = ##f
3
- }
1
+ #(define-markup-command (mm-feed layout props amount) (number?)
2
+ (let ((o-s (ly:output-def-lookup layout 'output-scale)))
3
+ (ly:make-stencil "" '(0 . 0) (cons 0 (abs (/ amount o-s))))))
4
4
 
5
5
  segno = {
6
- \once \override Score.RehearsalMark #'font-size = #-2
6
+ \once \override Score.RehearsalMark.font-size = #-2
7
7
  \mark \markup { \musicglyph #"scripts.segno" }
8
8
  }
9
9
 
10
10
  segnobottom = {
11
- \once \override Score.RehearsalMark #'direction = #DOWN
12
- \once \override Score.RehearsalMark #'font-size = #-2
11
+ \once \override Score.RehearsalMark.direction = #DOWN
12
+ \once \override Score.RehearsalMark.font-size = #-2
13
13
  \mark \markup { \musicglyph #"scripts.segno" }
14
14
  }
15
15
 
16
16
  dalsegno = {
17
- \once \override Score.RehearsalMark #'break-visibility = #begin-of-line-invisible
18
- \once \override Score.RehearsalMark #'direction = #DOWN
19
- \once \override Score.RehearsalMark #'self-alignment-X = #RIGHT
20
- \once \override Score.RehearsalMark #'font-size = #-2
17
+ \once \override Score.RehearsalMark.break-visibility = #begin-of-line-invisible
18
+ \once \override Score.RehearsalMark.direction = #DOWN
19
+ \once \override Score.RehearsalMark.self-alignment-X = #RIGHT
20
+ \once \override Score.RehearsalMark.font-size = #-2
21
21
  \mark \markup { \fontsize #2 {"dal segno "} \musicglyph #"scripts.segno" }
22
22
  }
23
23
 
24
24
  dacapo = {
25
- \once \override Score.RehearsalMark #'break-visibility = #begin-of-line-invisible
26
- \once \override Score.RehearsalMark #'direction = #DOWN
27
- \once \override Score.RehearsalMark #'self-alignment-X = #RIGHT
25
+ \once \override Score.RehearsalMark.break-visibility = #begin-of-line-invisible
26
+ \once \override Score.RehearsalMark.direction = #DOWN
27
+ \once \override Score.RehearsalMark.self-alignment-X = #RIGHT
28
28
  \mark \markup {\bold {\italic {"Da capo"}}}
29
29
  }
30
30
 
31
31
  dalsegnoadlib = {
32
- \once \override Score.RehearsalMark #'direction = #DOWN
33
- \once \override Score.RehearsalMark #'self-alignment-X = #LEFT
34
- \once \override Score.RehearsalMark #'font-size = #-2
32
+ \once \override Score.RehearsalMark.direction = #DOWN
33
+ \once \override Score.RehearsalMark.self-alignment-X = #LEFT
34
+ \once \override Score.RehearsalMark.font-size = #-2
35
35
  \mark \markup { \musicglyph #"scripts.segno" ad lib }
36
36
  }
37
37
 
38
38
  finedellaparteprima = {
39
- \once \override Score.RehearsalMark #'break-visibility = #begin-of-line-invisible
40
- \once \override Score.RehearsalMark #'direction = #DOWN
41
- \once \override Score.RehearsalMark #'self-alignment-X = #RIGHT
39
+ \once \override Score.RehearsalMark.break-visibility = #begin-of-line-invisible
40
+ \once \override Score.RehearsalMark.direction = #DOWN
41
+ \once \override Score.RehearsalMark.self-alignment-X = #RIGHT
42
42
  \mark \markup {\bold {\italic {"Fine della parte prima"}}}
43
43
  }
44
44
 
45
45
  padbarlinebefore = {
46
- \once \override Staff.BarLine #'extra-spacing-width = #'(-2 . 0)
46
+ \once \override Staff.BarLine.extra-spacing-width = #'(-2 . 0)
47
47
  }
48
48
 
49
49
  padbarlineafter = {
50
- \once \override Staff.BarLine #'extra-spacing-width = #'(0 . 2)
50
+ \once \override Staff.BarLine.extra-spacing-width = #'(0 . 2)
51
51
  }
52
52
 
53
53
  editF = \markup { \center-align \concat { \bold { \italic ( }
@@ -63,7 +63,8 @@ fort = \markup { \center-align \bold { \italic fort }}
63
63
  ten = \markup { \italic ten. }
64
64
 
65
65
  ficta = {
66
- \once \override AccidentalSuggestion #'avoid-slur = #'outside
66
+ \once \override AccidentalSuggestion.avoid-slur = #'outside
67
+ \once \override AccidentalSuggestion.parenthesized = ##t
67
68
  \once \set suggestAccidentals = ##t
68
69
  }
69
70
 
@@ -84,99 +85,6 @@ ficta = {
84
85
  #f))))
85
86
 
86
87
 
87
- \layout {
88
- #(layout-set-staff-size 17)
89
- indent = 0\cm
90
-
91
- \context {
92
- \Score
93
- \override InstrumentName #'self-alignment-X = #right
94
- \override InstrumentName #'padding = 0.6
95
-
96
- \override BarNumber #'padding = 1.5
97
-
98
- %make note stems a bit thicker
99
- \override Stem.thickness = #2
100
-
101
- % slurs and ties are a bit curvier and thicker
102
- % ties are also a bit more distant from note heads
103
- % all that with a bit of randomness
104
- \override Slur.eccentricity = #(lambda (grob) (* 0.05 (random:normal)))
105
- \override Slur.height-limit = #(lambda (grob) (+ 2.8 (* 0.2 (random:normal))))
106
- \override Slur.thickness = #(lambda (grob) (+ 2.9 (* 0.1 (random:normal))))
107
- \override Slur.ratio = #(lambda (grob) (+ 0.3 (* 0.05 (random:normal))))
108
-
109
- \override Tie.thickness = #(lambda (grob) (+ 2.9 (* 0.1 (random:normal))))
110
- \override Tie.ratio = #(lambda (grob) (+ 0.3 (* 0.05 (random:normal))))
111
- \override Tie #'details #'note-head-gap = #(lambda (grob) (+ 0.5 (* 0.1 (random:normal))))
112
-
113
- \override Beam.beam-thickness = #(lambda (grob) (+ 0.55 (* 0.02 (random:normal))))
114
- \override Beam.length-fraction = #1.15
115
-
116
- % \remove "Bar_number_engraver"
117
- }
118
-
119
- \context {
120
- \Staff
121
- \override StaffSymbol.color = #(rgb-color 0.25 0.2 0.2)
122
-
123
- }
124
-
125
- \context {
126
- \Lyrics
127
- % candidates: Georgia, Hoefler Text, Hoefler Text Italic,
128
- %
129
- \override LyricText #'font-name = #"Hoefler Text"
130
- % \override LyricText #'font-size = #3
131
- }
132
-
133
- \context {
134
- \override MarkupText #'font-name = #"Hoefler Text"
135
- }
136
- }
137
-
138
- \paper {
139
- % #(set-default-paper-size "A5")
140
-
141
- % system-system-spacing #'basic-distance = #17
142
-
143
- top-margin = 1.4\cm
144
- bottom-margin = 1.4\cm
145
- two-sided = ##t
146
- inner-margin = 1.4\cm
147
- outer-margin = 2\cm
148
-
149
- markup-system-spacing #'padding = #3
150
- markup-system-spacing #'stretchability = #10
151
- score-markup-spacing #'padding = #7
152
- top-system-spacing #'padding = #3
153
- top-markup-spacing #'padding = #3
154
- system-system-spacing #'minimum-distance = #9
155
- system-system-spacing #'stretchability = #15
156
-
157
-
158
- % ragged-last-bottom = ##t
159
- ragged-bottom = ##t
160
-
161
- print-first-page-number = ##f
162
-
163
- oddHeaderMarkup = \markup
164
- \fill-line {
165
- %% force the header to take some space, otherwise the
166
- %% page layout becomes a complete mess.
167
- " "
168
- % \on-the-fly #not-first-page {
169
- \on-the-fly #print-page-number-check-first \fromproperty #'page:page-number-string
170
- % }
171
- }
172
-
173
- evenHeaderMarkup = \markup
174
- \fill-line {
175
- \on-the-fly #print-page-number-check-first \fromproperty #'page:page-number-string
176
- " "
177
- }
178
-
179
- }
180
88
 
181
89
  % trill = #(make-articulation "stopped")
182
90
  trillSharp = #(make-articulation "trillSharp")
@@ -277,21 +185,126 @@ prallupbefore = {
277
185
  (markup #:fontsize 0 fig-markup)
278
186
  empty-markup)))
279
187
 
280
- \layout {
188
+ \layout {
281
189
  \context {
282
190
  \FiguredBass
283
191
  figuredBassFormatter = #better-format-bass-figure
284
- % \override BassFigure #'font-size = #-1
285
- % \override BassFigure #'font-name = #"Georgia"
192
+ % \override BassFigure.font-size = #-1
193
+ % \override BassFigure.font-name = #"Georgia"
286
194
  }
287
195
 
288
- % \context {
289
- % \StaffGroup
290
- % \override StaffGrouper.staff-staff-spacing =
291
- % #'((basic-distance . 10)
292
- % (minimum-distance . 7)
293
- % (padding . 0)
294
- % (stretchability . 7))
295
- %
296
- % }
297
196
  }
197
+
198
+ #(define-markup-command (when-property layout props symbol markp) (symbol?
199
+ markup?)
200
+ (if (chain-assoc-get symbol props)
201
+ (interpret-markup layout props markp)
202
+ (ly:make-stencil empty-stencil)))
203
+
204
+ #(define-markup-command (apply-fromproperty layout props fn symbol)
205
+ (procedure? symbol?)
206
+ (let ((m (chain-assoc-get symbol props)))
207
+ (if (markup? m)
208
+ (interpret-markup layout props (fn m))
209
+ empty-stencil)))
210
+
211
+ \header { tagline = ##f } % no tagline
212
+
213
+ setVerticalMargins = #(define-scheme-function
214
+ (parser location top-staff top-markup bottom) (number? number? number?)
215
+ #{
216
+ \paper {
217
+ top-system-spacing.basic-distance = #top-staff
218
+ top-system-spacing.minimum-distance = #top-staff
219
+ top-system-spacing.padding = -100 % negative padding to ignore skyline
220
+ top-system-spacing.stretchability = 0 % fixed position
221
+
222
+ top-markup-spacing.basic-distance = #top-markup
223
+ top-markup-spacing.minimum-distance = #top-markup
224
+ top-markup-spacing.padding = -100 % negative padding to ignore skyline
225
+ top-markup-spacing.stretchability = 0 % fixed position
226
+
227
+ last-bottom-spacing.basic-distance = #bottom
228
+ last-bottom-spacing.minimum-distance = #bottom
229
+ last-bottom-spacing.padding = -100 % negative padding to ignore skyline
230
+ last-bottom-spacing.stretchability = 0 % fixed position
231
+ }
232
+ #}
233
+ )
234
+
235
+ halfBarline = {
236
+ \once \hide Score.SpanBar
237
+ \once \override Score.BarLine.bar-extent = #'(-1 . 1)
238
+ \noBreak
239
+ \bar "|"
240
+ }
241
+
242
+ scoreMode = #(define-void-function (parser location music) (scheme?)
243
+ (if (eq? lydown:render-mode 'score)
244
+ #{ #music #}
245
+ ))
246
+
247
+ partMode = #(define-music-function (parser location music) (ly:music?)
248
+ (if (eq? lydown:render-mode 'part)
249
+ #{ #music #}
250
+ #{ #}
251
+ ))
252
+
253
+ #(define lydown:score-mode (eq? lydown:render-mode 'score))
254
+ #(define lydown:part-mode (eq? lydown:render-mode 'part))
255
+
256
+ \layout {
257
+ % The OrchestraGroup is used in score mode as a wrapping context for staff
258
+ % groups. It is based on the ChoirStaff in order to allow individual span
259
+ % bars.
260
+ \context {
261
+ \ChoirStaff
262
+ \name "OrchestraGroup"
263
+ \accepts StaffGroup
264
+ systemStartDelimiter = #'SystemStartBar
265
+ }
266
+
267
+ \context {
268
+ \Score
269
+ \accepts OrchestraGroup
270
+ }
271
+ }
272
+
273
+ tacetScore = {
274
+ \new Staff \with {
275
+ \override StaffSymbol.staff-space = 0.001
276
+ \override StaffSymbol.line-count = 1
277
+ \override StaffSymbol.thickness = 0
278
+ \override StaffSymbol.color = #(rgb-color 1 1 1)
279
+ \remove "Time_signature_engraver"
280
+ \remove "Clef_engraver"
281
+ }
282
+ { s4 }
283
+ }
284
+
285
+ % Shaping commands
286
+
287
+ sR = #(define-music-function (parser location r) (number?)
288
+ #{ \once \override Slur.ratio = #r #}
289
+ )
290
+ sE = #(define-music-function (parser location e) (number?)
291
+ #{ \once \override Slur.eccentricity = #e #}
292
+ )
293
+
294
+ sHL = #(define-music-function (parser location l) (number?)
295
+ #{ \once \override Slur.height-limit = #l #}
296
+ )
297
+
298
+ sP = #(define-music-function (parser location p1 p2) (number? number?)
299
+ #{ \once \override Slur.positions = #(cons p1 p2) #}
300
+ )
301
+
302
+ #(define (convert-shape-coords s)
303
+ (map (lambda (v) (if (eq? v #f) '(0 . 0) v)) s)
304
+ )
305
+
306
+ sS = #(define-music-function (parser location s) (scheme?)
307
+ #{ \once \shape #(convert-shape-coords s) Slur #}
308
+ )
309
+
310
+ knee = \once \override Beam.auto-knee-gap = #0