lydown 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 66935404398a57aa09da31ebddd911edae7227fe
4
+ data.tar.gz: 6bc6875c3b2e2187140a91bb586b53411aeb57c3
5
+ SHA512:
6
+ metadata.gz: 28dcdb1e4501356d371f3ff217af806f966a37fe24611fe7f33426ec264d80b197fc609441fef6953b3929d9239436289950e05983d0bbf15f8fc58770663392
7
+ data.tar.gz: 36a98eb309977703065fb6feb0dc5284b2f78d7b241f40606f0098087980e70415258d9df13d30b06854ac40b92a93fec84bdad93cb6fe9acaea565d3d8e76c0
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Sharon Rosner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,357 @@
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
+
3
+ Lydown builds on the ideas put forth by lilypond and makes the following improvements:
4
+
5
+ - a greatly simplified syntax for entering notes, for more rapid note entry and improved legibility.
6
+ - ability to enter lyrics and bass figures interspersed with the music.
7
+ - rhythmic macros for rapid entry of repeated rhythmic patterns.
8
+ - zero lilypond boilerplate code.
9
+ - a sane file/folder structure for creating multi-part, multi-movement works with automatic part extraction.
10
+
11
+ ## Installation
12
+
13
+ Before installing lydown, you'll need to have installed [lilypond](http://lilypond.org/download.html).
14
+
15
+ You can verify that lilypond is correctly installed by running the following command:
16
+
17
+ lilypond --version
18
+
19
+ If everything's ok, you can proceed by installing lydown:
20
+
21
+ gem install lydown
22
+
23
+ and verifying that it too works:
24
+
25
+ lydown --version
26
+
27
+ ## Hello world in lydown
28
+
29
+ // helloworld.lydown
30
+ - key: d major
31
+ - time: 2/4
32
+ 4d\"Hello world!" 6c#bag 3f#gag6f#d 4e
33
+
34
+ And here's the equivalent lilypond code:
35
+
36
+ \version "2.18.2"
37
+ relative c' {
38
+ \key d major
39
+ \time 2/4
40
+ d'4\"Hello world!" cs16 b a g fs32 g a g fs16 d e4
41
+ }
42
+
43
+ ## Compiling the lydown code
44
+
45
+ The lydown command line tool can compile the code into lilypond code, PDF, PNG, or MIDI. The program creates an output file with the same name as the input file and the corresponding extension. Specifiying the -O switch causes the output to be opened immediately.
46
+
47
+ To create a lilypond file:
48
+
49
+ lydown -O --ly helloworld.lydown
50
+
51
+ To create a PDF file:
52
+
53
+ lydown -O --pdf helloworld.lydown
54
+
55
+ To create a PNG file:
56
+
57
+ lydown -O --png helloworld.lydown
58
+
59
+ To create a MIDI file:
60
+
61
+ lydown -O --midi helloworld.lydown
62
+
63
+ ## The lydown syntax
64
+
65
+ 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:
66
+
67
+ - musical context (staves, parts, etc) is implicit rather than explicit.
68
+ - whitespace between notes is optional.
69
+ - durations come before notes.
70
+ - duration macros allow rapid entry of repeated rhythmic patterns (such as dotted rhythm).
71
+
72
+ It must be stressed that lydown is made to process music that is relatively simple, and it was designed to support the processing of baroque music in particular. Therefore, a lot of stuff that is possible with plain lilypond would not be possible with lydown.
73
+
74
+ For the sake of this tutorial, some familiarity with the concepts and syntax of lilypond is presumed. The lydown code will be shown alongside its lilypond equivalent.
75
+
76
+ ### Notes and durations
77
+
78
+ In lydown, durations are entered before the note to which they refer, and they stay valid for subsequent notes, until a new value is entered:
79
+
80
+ 4c8de2f => c4 d8 e f2
81
+
82
+ In addition to the usual values (1, 2, 4, 8, 16, 32 etc), lydown adds two shortcuts for commonly used values: 6 for 16th notes and 3 for 32th notes:
83
+
84
+ 8c6de3fefefede2f => c8 d16 e f32 e f e f e d e f2
85
+
86
+ Augmentation dots are entered like in lilypond:
87
+
88
+ 8.c6d 8.e6f 2g => c8. d16 e8. f16 g2
89
+ 8..g 3g 4c => g8.. g32 c4
90
+
91
+ ### Rests
92
+
93
+ Normal rests are written [like in lilypond](http://www.lilypond.org/doc/v2.18/Documentation/notation/writing-rests#rests):
94
+
95
+ 4ce2r => c4 e r2
96
+
97
+ Full bar rests are [similar to lilypond](http://www.lilypond.org/doc/v2.18/Documentation/notation/writing-rests#full-measure-rests), except there's no need to enter the rest value (it is implicit in the time signature):
98
+
99
+ - time: 3/4
100
+ // 4 bar rest in the middle
101
+ 2c4e R*4 2.g
102
+
103
+ ### Accidentals
104
+
105
+ Accidentals are entered using + and -
106
+
107
+ 8cgb-c2a => c8 g bb c a2
108
+
109
+ In lydown notes follow the key signature by default:
110
+
111
+ - key: g major
112
+ 8g6fedcba2g => g8 fs16 e d c b a g2
113
+
114
+ The accidental mode can be changed by specifiying the manual accidental mode:
115
+
116
+ - key: g major
117
+ - accidentals: manual
118
+ 8g6f+edcba2g
119
+
120
+ In the default, automatic mode, when deviating from the key signature the accidental must be repeated for each note, regardless of barlines.
121
+
122
+ In the [same manner as lilypond](http://www.lilypond.org/doc/v2.18/Documentation/notation/writing-pitches#accidentals), accidentals can be forced by following the note name and accidental with a ! (for a reminder), or ? (for a cautionary accidental in parentheses):
123
+
124
+ cc+c+!cc? => c cs cs! c c?
125
+
126
+ A ficta accidental (an non-original accidental that appears above the staff) can be entered using the ^ symbol after the accidental:
127
+
128
+ cdef+^g
129
+
130
+ ### Octaves
131
+
132
+ Like in lilypond's [relative mode](http://www.lilypond.org/doc/v2.18/Documentation/notation/writing-pitches#relative-octave-entry), lydown uses ' and , for moving between octaves. The starting point is always c (that is c°).
133
+
134
+ ### Barlines
135
+
136
+ Just like in lilypond, barlines are taken care of automatically according to the time signature. Final bar lines and repeat bar lines can be entered explicitly by using shorthand syntax:
137
+
138
+ |: cege :|: cfaf :|
139
+
140
+ When entering unmetered music, an invisible barline can be added in order to provide line breaks:
141
+
142
+ -time: unmetered
143
+ cdef ?| gag
144
+
145
+ ### Beams, slurs and ties
146
+
147
+ Lydown uses automatic beaming as the default, except in the case of vocal parts (see [document settings](#settings)). Auto beaming can be
148
+
149
+ Beaming and sluring is similar to lilypond, except the beam/slur start comes before the note:
150
+
151
+ 8(cdef)g[6fe]4f => c8( d e f) g f16[ e] f4
152
+
153
+ a regular tie is written just like in lilypond:
154
+
155
+ 4g~6gfed2c => g4 ~ g16 f e d c2
156
+
157
+ Lydown also supports a shortened tie form, where the tied note is not repeated:
158
+
159
+ 4g6&fed2c => g4 ~ g16 f e d c2
160
+
161
+ ### Articulation and expression marks
162
+
163
+ Lilypond [shorthand articulation marks](http://www.lilypond.org/doc/v2.18/Documentation/notation/expressive-marks-attached-to-notes#articulations-and-ornamentations) can be entered after a backslash
164
+
165
+ c\^ e\+ g\_ => c-^ e-+ g-_
166
+
167
+ Common articulation marks can be entered immediately after the note:
168
+
169
+ // tenuto, staccato, staccatissimo
170
+ c_e.g` => c-- e-. g-!
171
+
172
+ Other arbitrary lilypond articulations can be entered after a backslash:
173
+
174
+ c\staccato e\mordent g\turn => c\staccato e\mordent g\turn
175
+
176
+ [Dynamic marks](http://www.lilypond.org/doc/v2.18/Documentation/notation/expressive-marks-attached-to-notes#dynamics) are entered before the note to which they apply:
177
+
178
+ c\f eg
179
+
180
+ \f cege \p cfaf => c\f e g e c\p f a f
181
+
182
+ 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).
183
+
184
+ ### Repeated articulation and rhythmic patterns: macros
185
+
186
+ An important feature of lydown is the macro, which facilitates rapid entry of repeated rhythmic and articulative patterns.
187
+
188
+ A common scenario is repeated articulation, for example a sequence of staccato notes:
189
+
190
+ {.}cdefgabc => c-. d-. e-. f-. g-. a-. b-. c-.
191
+
192
+ A macro can also contain a fully qualified lilypond articulation specifier:
193
+
194
+ {\tenuto}cege => c\tenuto e\tenuto g\tenuto e\tenuto
195
+
196
+ Rhythmic macros can be used for repeated rhythmic patterns. A common scenario is a dotted rhythm:
197
+
198
+ // The _ symbol denotes a note placeholder
199
+ {8._6_}cdefgfed => c8. d16 e8. f16 g8. f16 e8. d16
200
+
201
+ A repeating diminution may also be expressed succintly:
202
+
203
+ // The @ symbol denotes a repeated pitch
204
+ {16_@@@}cfgf => c16 c c c f f f f g g g g f f f f
205
+
206
+ A macro can include both durations and articulation marks:
207
+
208
+ {4_~6@(_._._.)}cdefgfed => c4 ~ c16 d-.( e-. f-.) g4 ~ g16 f-.( e-. d-.)
209
+
210
+ A macro containing durations will remain valid until another duration or duration macro is encountered. A macro containing articulation only will be valid until another duration, macro or empty macro is encountered:
211
+
212
+ 6{.}gg{}aa => g16-. g-. a a
213
+
214
+ ### Named macros
215
+
216
+ Macros can be defined with a name and reused:
217
+
218
+ - macros:
219
+ - dotted: 8._6_
220
+ {dotted}gaba2g{dotted}abcb2a => g8. a16 b8. a16 g2 a8. b16 c8. b16 a2
221
+
222
+ ### Clefs, key and time signatures
223
+
224
+ Clefs are determined automatically by lydown based on the specified part. In case no part is specified, the default clef is a treble clef. The clef values are the same as in [lilypond](http://www.lilypond.org/doc/v2.18/Documentation/notation/displaying-pitches#clef). The clef can be changed at any time with a clef setting:
225
+
226
+ - clef: bass
227
+ 8cdefgabc
228
+ - clef: tenor
229
+ defedcbd
230
+ - clef: bass
231
+ 1c
232
+
233
+ Key and time signatures are entered inline as document settings ([see below](#settings)). The [key](http://www.lilypond.org/doc/v2.18/Documentation/notation/displaying-pitches#key-signature) and [time](http://www.lilypond.org/doc/v2.18/Documentation/notation/displaying-rhythms#time-signature) values follow the lilypond syntax:
234
+
235
+ - key: d major
236
+ - time: 3/4
237
+
238
+ In the case of key signatures, accidentals will follow the lydown syntax:
239
+
240
+ - key: b- major
241
+ - key: f+ minor
242
+
243
+ The default key signature is C major, and the default time signature is 4/4.
244
+
245
+ Key or time signatures can be changed on the fly:
246
+
247
+ - time: 4/4
248
+ 4c e g b
249
+ - time: 3/4
250
+ c e g 2.c
251
+
252
+ ### Pickup bars
253
+
254
+ [Pickup bars](http://www.lilypond.org/doc/v2.18/Documentation/notation/displaying-rhythms#upbeats) (anacrusis, upbeat) are defined with the pickup setting:
255
+
256
+ - time: 3/4
257
+ - pickup: 4
258
+ 4g
259
+ c8cdcb4aaa
260
+ d8dedc4bb
261
+
262
+ ### Inline lyrics
263
+
264
+ Lyrics for vocal parts can be entered on separate lines prefixed by a > symbol:
265
+
266
+ 4c[8de]4fd(4c[8de]2f)
267
+ > Ly-down is the bomb__
268
+
269
+ Text alignment follows the duration, beaming and slurring of the music, just like in lilypond. Sillables are expected to be separated by a dash. Melismas, i.e. a single sillable streched over multiple notes, is signified by one or more underscores.
270
+
271
+ ### Stream switching
272
+
273
+ Lyrics can be entered in a block, before or after musical notation, by switching streams:
274
+
275
+ 8ccg'gaa4g
276
+ 8ffeedd4c
277
+ =lyrics
278
+ Twin-kle twin-kle lit-tle star,
279
+ How I won-der what you are.
280
+ =music
281
+ 8g'gffeed4
282
+ ...
283
+
284
+ ### Figured bass
285
+
286
+ Figured bass is entered inline, following notes or even between notes, when
287
+ multiple figures align with a single note.
288
+
289
+ ## Multiple parts
290
+
291
+ Multiple parts can be entered in the same file by prefixing each part's content with a -part setting:
292
+
293
+ - part: violino1
294
+ 8c'cg'gaa4g
295
+ - part: continuo
296
+ 4cefe
297
+
298
+ ## Multiple movements
299
+
300
+ For multi-movement works, prefix each movement with a -movement setting:
301
+
302
+ - movement: Adagio
303
+ ...
304
+ - movement: Allegro
305
+ ...
306
+
307
+ ## Multiple voices
308
+
309
+ [Multiple voices](http://www.lilypond.org/doc/v2.18/Documentation/notation/multiple-voices#single_002dstaff-polyphony) on the same staff can be easily entered using the following notation:
310
+
311
+ 1: 8egfdeg4f
312
+ 2: 4cded
313
+
314
+ ## Piano scores
315
+
316
+ [Piano/keyboard scores](http://www.lilypond.org/doc/v2.18/Documentation/notation/common-notation-for-keyboards) can be created by using the <code>r/l</code> (right/left) prefixes:
317
+
318
+ r: 8cdeccdec
319
+ l: 4cgcg
320
+
321
+ In order to jump between staves, you can use the special commands <code>\r, \l</code>
322
+
323
+ r: 8<e'c'> \l g,f+g \r <g'' c'> \l e,,d+e \r
324
+ l: 1s
325
+
326
+ ## multi-part scores and part extraction
327
+
328
+ As the example above shows, lydown supports multiple parts in the same file, but parts can also be written in separate files. This is useful for long pieces or those with a large number of parts.
329
+
330
+ For this we create a main file which defines the score structure:
331
+
332
+ // score.lydown
333
+ - score:
334
+ - [violino1, violino2]
335
+ - viola
336
+ - violoncello
337
+ - time: 4/4
338
+ - key: c major
339
+
340
+ Then we enter the music in a separate file for each part:
341
+
342
+ // violino1.lydown
343
+ 8c''cg'gaa4g 8ffeedd4c
344
+
345
+ And so forth.
346
+
347
+ To compile the score, we use the following command:
348
+
349
+ lydown -o -s score.lydown
350
+
351
+ To extract a specific part, we use the -p switch:
352
+
353
+ lydown -o -p violino1 score.lydown
354
+
355
+ ## Adding a front cover
356
+
357
+ 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
data/bin/lydown ADDED
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'lydown'
5
+ require 'lydown/version'
6
+ require 'optparse'
7
+
8
+ trap("INT") {exit}
9
+ trap("TERM") {exit}
10
+
11
+ help = <<HELP
12
+ Lydown is not lilypond
13
+ HELP
14
+
15
+ $options = {'format' => 'pdf'}
16
+ opts = OptionParser.new do |opts|
17
+ opts.banner = help
18
+
19
+ opts.on("-v", "--version", "Display current version") do
20
+ puts "lydown " + Lydown::VERSION
21
+ exit 0
22
+ end
23
+
24
+ opts.on("-p", "--parts PART", "Parts (comma-separated)") do |v|
25
+ $options["parts"] = v.split(',')
26
+ end
27
+
28
+ opts.on("-m", "--mvts MVT", "Movements (comma-separated)") do |v|
29
+ $options["movements"] = v.split(',')
30
+ end
31
+
32
+ opts.on("--no-score", "Do not generate secore") do
33
+ $options["no_score"] = true
34
+ end
35
+
36
+ opts.on("-s", "--score", "Generate only score, no parts") do
37
+ $options["score_only"] = true
38
+ end
39
+
40
+ opts.on("-V", "--vocal", "Generate only vocal score") do
41
+ $options["vocal_only"] = true
42
+ end
43
+
44
+ opts.on("--ly", "Generate Lilypond file") do
45
+ $options["format"] = 'ly'
46
+ end
47
+
48
+ opts.on("--pdf", "Generate PDF file") do
49
+ $options["format"] = 'pdf'
50
+ end
51
+
52
+ opts.on("--png", "Generate PNG file") do
53
+ $options["format"] = 'png'
54
+ end
55
+
56
+ opts.on("-M", "--midi", "Generate midi file") do
57
+ $options["format"] = 'midi'
58
+ $options["score_only"] = true # implied
59
+ end
60
+
61
+ opts.on("-O", "--open", "Open PDF/Midi file after processing") do
62
+ $options["open_target"] = true
63
+ end
64
+
65
+ opts.on("-o", "--output FILE", "Filename for output") do |v|
66
+ $options["output_filename"] = v
67
+ end
68
+
69
+ opts.on("-W", "--work", "Create new work") do
70
+ $options["gen"] = :work
71
+ end
72
+ end
73
+
74
+ opts.parse!
75
+
76
+ if $options["gen"]
77
+ # Ripple.generate($options["gen"], ARGV)
78
+ exit
79
+ end
80
+
81
+ source = ''
82
+ if ARGV[0].nil? || ARGV[0] == '-'
83
+ # read source from stdin
84
+ source = STDIN.read
85
+
86
+ # the output defaults to a file named lydown expect if the format is ly.
87
+ # In that case the output will be sent to STDOUT.
88
+ $options["output_filename"] ||= 'lydown' unless $options["format"] == 'ly'
89
+ else
90
+ filename = ARGV[0]
91
+ $options['source_filename'] = filename
92
+ if (filename !~ /\.ld$/) and File.file?(filename + ".ld")
93
+ filename += ".ld"
94
+ end
95
+
96
+ $options["output_filename"] ||= (filename =~ /^(.+)\.ld$/) ? $1 : filename
97
+ end
98
+
99
+ # translate lydown code to lilypond code
100
+ ly = ''
101
+ begin
102
+ work = Lydown::Work.new(path: $options['source_filename'], nice_error: true)
103
+ # work.process(lydown)
104
+ ly = work.to_lilypond(mode: :score)
105
+ rescue LydownError => e
106
+ STDERR.puts e.message
107
+ exit 1
108
+ rescue => e
109
+ STDERR.puts "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
110
+ exit 1
111
+ end
112
+
113
+ if $options['format'] == 'ly'
114
+ unless $options["output_filename"]
115
+ puts ly
116
+ exit
117
+ else
118
+ begin
119
+ File.open($options["output_filename"] + '.ly', 'w+') do |f|
120
+ f.write ly
121
+ end
122
+ exit
123
+ rescue => e
124
+ STDERR.puts "#{e.class}: #{e.message}"
125
+ exit 1
126
+ end
127
+ end
128
+ end
129
+
130
+ # compile lilypond code
131
+ begin
132
+ opts = {
133
+ output_filename: $options['output_filename'],
134
+ format: $options['format'],
135
+ mode: :score
136
+ }
137
+ Lydown::Lilypond.compile(ly, opts)
138
+ rescue LydownError => e
139
+ STDERR.puts e.message
140
+ exit 1
141
+ rescue => e
142
+ STDERR.puts "#{e.class}: #{e.message}"
143
+ exit 1
144
+ end
145
+
146
+ if $options['open_target']
147
+ filename = "#{$options['output_filename']}.#{$options['format']}"
148
+ system("open #{filename}")
149
+ end
@@ -0,0 +1,121 @@
1
+ class Hash
2
+ # Merges self with another hash, recursively.
3
+ #
4
+ # This code was lovingly stolen from some random gem:
5
+ # http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
6
+ #
7
+ # Thanks to whoever made it.
8
+ def deep_merge(hash)
9
+ target = Marshal.load(Marshal.dump(self))
10
+ target.deep_merge!(hash)
11
+ end
12
+
13
+ def deep_merge!(hash)
14
+ hash.keys.each do |key|
15
+ if hash[key].is_a? Hash and self[key].is_a? Hash
16
+ self[key] = self[key].deep_merge!(hash[key])
17
+ next
18
+ end
19
+
20
+ self[key] = hash[key]
21
+ end
22
+
23
+ self.deep = true
24
+ self
25
+ end
26
+
27
+ def deep_clone
28
+ dest = {}.deep!
29
+ each do |k, v|
30
+ dest[k] = case v
31
+ when Hash
32
+ v.deep_clone
33
+ when Array
34
+ v.clone
35
+ else
36
+ v
37
+ end
38
+ end
39
+ dest
40
+ end
41
+
42
+ def lookup(path)
43
+ path.split("/").inject(self) {|m,i| m[i].nil? ? (return nil) : m[i]}
44
+ end
45
+
46
+ def set(path, value)
47
+ leafs = path.split("/")
48
+ k = leafs.pop
49
+ h = leafs.inject(self) {|m, i| m[i].is_a?(Hash) ? m[i] : (m[i] = {})}
50
+ h[k] = value
51
+ end
52
+
53
+ attr_accessor :deep
54
+
55
+ alias_method :old_get, :[]
56
+ def [](k)
57
+ if @deep && k.is_a?(String) && k =~ /\//
58
+ lookup(k)
59
+ elsif @deep && k.is_a?(Symbol)
60
+ old_get(k.to_s)
61
+ else
62
+ old_get(k)
63
+ end
64
+ end
65
+
66
+ alias_method :old_set, :[]=
67
+ def []=(k, v)
68
+ if @deep && k.is_a?(String) && k =~ /\//
69
+ set(k, v)
70
+ elsif @deep && k.is_a?(Symbol)
71
+ old_set(k.to_s, v)
72
+ else
73
+ old_set(k, v)
74
+ end
75
+ end
76
+
77
+ alias_method :old_merge, :merge
78
+ def merge(hash)
79
+ if @deep || hash.deep
80
+ deep_merge(hash)
81
+ else
82
+ old_merge(hash)
83
+ end
84
+ end
85
+
86
+ alias_method :old_merge!, :merge!
87
+ def merge!(hash)
88
+ if @deep || hash.deep
89
+ deep_merge!(hash)
90
+ else
91
+ old_merge!(hash)
92
+ end
93
+ end
94
+
95
+ def deep!
96
+ @deep = true
97
+ self
98
+ end
99
+ end
100
+
101
+ class String
102
+ def titlize(all_capitals = false)
103
+ all_capitals ?
104
+ self.gsub("-", " ").gsub(/\b('?[a-z])/) {$1.capitalize} :
105
+ self.gsub("-", " ").capitalize
106
+ end
107
+
108
+ def camelize
109
+ split('_').collect(&:capitalize).join
110
+ end
111
+ end
112
+
113
+ class Fixnum
114
+ ROMAN = %w[0 I II III IV V VI VII VIII IX X XI XII XIII XIV XV XVI XVII XVIII XIX
115
+ XX XXI XXII XXIII XXIV XXV XXVI XXVII XXVIII XXIX XXX]
116
+
117
+ def to_roman
118
+ ROMAN[self]
119
+ end
120
+ end
121
+
@@ -0,0 +1,2 @@
1
+ class LydownError < RuntimeError
2
+ end
@@ -0,0 +1,41 @@
1
+ require 'lydown/errors'
2
+ require 'tmpdir'
3
+
4
+ module Lydown
5
+ module Lilypond
6
+ class << self
7
+ def tmpdir
8
+ @tmpdir ||= Dir.mktmpdir
9
+ end
10
+
11
+ def compile(source, opts = {})
12
+ opts[:output_filename] ||= 'lydown'
13
+
14
+ ly_path = File.join(tmpdir, File.basename(opts[:output_filename]))
15
+ File.open(ly_path, 'w+') do |f|
16
+ f << source
17
+ end
18
+
19
+ # Run lilypond, pipe source into its STDIN, and capture its STDERR
20
+ cmd = 'lilypond -lERROR '
21
+ cmd << "-o #{File.dirname(opts[:output_filename])} "
22
+ cmd << "--#{opts[:format]} " if opts[:format]
23
+ # cmd << '-s - 2>&1'
24
+ cmd << "-s #{ly_path} 2>&1"
25
+
26
+ err_info = ''
27
+ IO.popen(cmd, 'r+') do |f|
28
+ f.puts source
29
+ f.close_write
30
+ err_info = f.read
31
+ f.close
32
+ end
33
+ unless $?.success?
34
+ err_info = err_info.lines[0, 3].join
35
+ raise LydownError, "Lilypond compilation failed:\n#{err_info}"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+