lydown 0.12.4 → 0.14.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.
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