forked from openlilylib/gridly
-
Notifications
You must be signed in to change notification settings - Fork 0
/
__main__.ily
503 lines (463 loc) · 18.6 KB
/
__main__.ily
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
\version "2.18.2"
%% gridly - simple segmented grid for LilyPond
%% Copyright (C) 2015 - Matteo Ceccarello
%%
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%
%%% Gridly
%%% ======
%%%
%%% A simple "segmented grid" framework.
%%%
%%% For documentation take a look at the README and at the contents of
%%% the `example` folder.
%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#(use-modules (oop goops))
#(use-modules (ice-9 regex))
#(define-class <cell> ()
(music #:init-keyword #:music
#:getter cell:music)
(lyrics #:init-keyword #:lyrics
#:getter cell:lyrics)
(opening #:init-keyword #:opening
#:getter cell:opening)
(opening-lyrics #:init-keyword #:opening-lyrics
#:getter cell:opening-lyrics)
(closing #:init-keyword #:closing
#:getter cell:closing)
(closing-lyrics #:init-keyword #:closing-lyrics
#:getter cell:closing-lyrics)
(barNumber #:init-keyword #:barNumber
#:getter cell:barNumber)
(transposeKey #:init-keyword #:transposeKey
#:getter cell:transposeKey))
%%% Some utility functions
#(define (check-coords part segment)
(cond
;; Check segment
((not (integer? segment))
(ly:error "Segment must be an integer, was ~a" segment))
((> 1 segment)
(ly:error "Segment must be > 1, was" segment))
((< (hash-ref music-grid-meta #:segments) segment)
(ly:error "Segment must be less than ~a, was ~a"
(hash-ref music-grid-meta #:segments) segment))
;; Check part
((not (string? part))
(ly:error "Part must be a string"))
((not (member part (hash-ref music-grid-meta #:parts)))
(ly:error "Part must be defined in \\gridInit: ~a" part))
(#t #t)))
#(define (check-grid)
(if (and music-grid music-grid-meta)
#t
(ly:error "You must first call \\initMusicGrid")))
#(define (display-spaces num-spaces)
(for-each (lambda (x) (display " ")) (iota num-spaces)))
#(define (get-music-cell part segment)
(check-coords part segment)
(hash-ref music-grid (cons part segment)))
#(define (check-durations segment strict)
(let* ((durations (map
(lambda (part)
(let ((cell (get-music-cell part segment)))
(cons part
(if cell
(ly:moment-main (ly:music-length
(cell:music cell)))
#f))))
(hash-ref music-grid-meta #:parts)))
(defined-durations (filter cdr durations))
(reference-duration (if (null? defined-durations)
#f
(cdar defined-durations))))
(if reference-duration
(for-each
(lambda (d-pair)
(if (not (equal? reference-duration (cdr d-pair)))
(let ((msg-args
(list "Expected length of ~a for ~a:~a, got ~a"
reference-duration (car d-pair) segment (cdr d-pair))))
(if strict
(apply ly:error msg-args)
(apply ly:warning msg-args)))))
defined-durations))))
gridDisplay =
#(define-void-function
(parser location) ()
(let* ((num-segments (hash-ref music-grid-meta #:segments))
(segments (map (lambda (x) (+ 1 x)) (iota num-segments)))
(parts (hash-ref music-grid-meta #:parts)))
(newline)
(display "=== Music grid ===")
(newline)
(let ((longest-name (reduce max 0
(map string-length parts)))
(table-spacing (reduce max 0
(map (lambda (seg)
(string-length (number->string seg)))
segments))))
(display-spaces longest-name)
(for-each
(lambda (x)
(let ((seg-str (number->string x)))
(display-spaces (+ 1 (- table-spacing (string-length seg-str))))
(display seg-str)))
segments)
(for-each
(lambda (part)
(newline)
(display part)
(display-spaces (- longest-name (string-length part)))
(for-each
(lambda (seg)
;(display-spaces (string-length (number->string seg)))
(display-spaces table-spacing)
(if (hash-ref music-grid (cons part seg))
(display "o")
(display "-")))
segments))
parts))
(newline)
(newline)))
gridCheck =
#(define-void-function
(parser location) ()
(for-each
(lambda (segment)
(check-durations segment #f))
(map (lambda (x) (+ 1 x))
(iota (hash-ref music-grid-meta #:segments)))))
%%% Grid initialization
gridInit =
#(define-void-function
(parser location segments parts) (number? list?)
(if music-grid
(ly:debug "Music grid already initialized, skipping initialization")
(set! music-grid (make-hash-table)))
(if music-grid-meta
(ly:debug
"Music grid metadata already initialized, skipping initialization")
(begin
(set! music-grid-meta (make-hash-table))
(hash-set! music-grid-meta #:segments segments)
(hash-set! music-grid-meta #:parts (cons "<template>" parts)))))
%%% Grid manipulation
#(define (ctx-mod-or-music? arg)
(or (ly:context-mod? arg) (ly:music? arg)))
#(define (context-mod->alist ctx-mod)
(let ((props '()))
(if ctx-mod
(for-each
(lambda (mod)
(set! props
(assoc-set! props
(cadr mod) (caddr mod))))
(ly:get-context-mods ctx-mod)))
props))
gridPutMusic =
#(define-void-function
(parser location part segment ctx-mod-or-music)
(string? number? ctx-mod-or-music?)
(check-grid)
(check-coords part segment)
(let* ((ctx-mod (if (ly:music? ctx-mod-or-music)
#{ \with { music = $ctx-mod-or-music } #}
ctx-mod-or-music))
(props (context-mod->alist ctx-mod))
(key (cons part segment))
;; This closure will look in the `props' alist for the given
;; symbol, returning the associated value. If the symbol is
;; not in the alist, then a default value is looked up in
;; the corresponding `<template>' segment. If even there a
;; default value is not found, `default'
(props-get (lambda (sym last-default)
(let ((res (assoc-ref props sym)))
(if res
res
(let ((cell-template
(get-music-cell "<template>" segment)))
(if cell-template
(slot-ref cell-template sym)
last-default)))))))
(if (not (ly:music? (assoc-ref props 'music)))
(begin
(ly:input-message
location "No music defined for ~a:~a"
part segment)
(ly:error "The `music' argument is mandatory"))
(let ((value (make <cell>
#:music (ly:assoc-get 'music props #f #t)
#:lyrics (props-get 'lyrics #f)
#:opening (props-get 'opening #{ #})
#:opening-lyrics (props-get 'opening-lyrics #f)
#:closing (props-get 'closing #{ #})
#:closing-lyrics (props-get 'closing-lyrics #f)
#:barNumber (props-get 'barNumber #f)
#:transposeKey (props-get 'transposeKey #f))))
(hash-set! music-grid key value)))))
gridSetSegmentTemplate =
#(define-void-function
(parser location segment ctx-mod-or-music)
(number? ctx-mod-or-music?)
(if (get-music-cell "<template>" segment)
(ly:debug "Skipping setting of <template>:~a, already set" segment)
#{
\gridPutMusic "<template>" $segment $ctx-mod-or-music
#}))
#(define (segment-selector? x)
(or (pair? x)
(integer? x)
(equal? 'all x)))
#(define (get-cell-range part start-end)
(check-grid)
(let ((start (cond ((equal? 'all start-end)
1)
((pair? start-end)
(car start-end))
((integer? start-end)
start-end)))
(end (cond ((equal? 'all start-end)
(hash-ref music-grid-meta #:segments))
((pair? start-end)
(cdr start-end))
((integer? start-end)
start-end))))
(check-coords part start)
(check-coords part end)
(let* ((segments (map (lambda (x) (+ x start)) (iota (+ 1 (- end start)))))
(elems
(map (lambda (i)
(let ((cell (get-music-cell part i)))
(cond
;; The cell is defined an populated with music
(cell cell)
;; The cell is not defined, but its template is
;; defined. Hence we use the default values provided
;; by the template, except for the lyrics, since
;; there are no notes in this dummy cell.
((get-music-cell "<template>" i)
(make <cell>
#:lyrics #{ #}
#:opening (cell:opening
(get-music-cell "<template>" i))
#:opening-lyrics #{ #}
#:closing (cell:closing
(get-music-cell "<template>" i))
#:closing-lyrics #{ #}
#:music (cell:music
(get-music-cell "<template>" i))
#:barNumber (cell:barNumber
(get-music-cell "<template>" i))
#:transposeKey (cell:transposeKey
(get-music-cell "<template>" i))))
;; Neither the cell nor the template are
;; defined. Throw an error.
(#t (ly:error
"Segment '~a' of part '~a' is still empty and its template is not defined"
i part)))))
segments)))
elems)))
gridGetRange =
#(define-music-function (part start-end)(string? segment-selector?)
(gridSetRange start-end)
(gridGetMusic part))
gridSetRange =
#(define-void-function
(parser location start-end) (segment-selector?)
#{ \setOption gridly.segment-range #start-end #})
#(define (prepend-barcheck music barnumber)
(let ((barcheck #{ \barNumberCheck $barnumber #}))
(make-music
'SequentialMusic
'elements
(list
barcheck
music))))
#(define (transpose-music music transpose-key)
(if transpose-key
#{ \transpose $transpose-key #(ly:make-pitch -1 0 0) { $music } #}
music))
gridGetMusic =
#(define-music-function
(parser location part) (string?)
(let* ((cells (get-cell-range part #{ \getOption gridly.segment-range #}))
(music (map cell:music cells))
(transpose-keys (map cell:transposeKey cells))
(barnumbers (map cell:barNumber cells))
(barnumber-start (cell:barNumber (car cells)))
(barnum-set-expr
(if barnumber-start
(list #{ \set Score.currentBarNumber = $barnumber-start #})
(list #{ #})))
(music (map (lambda (m b)
(if b (prepend-barcheck m b) m))
music barnumbers))
(music (map transpose-music music transpose-keys))
(opening (list
(transpose-music
(cell:opening (car cells))
(cell:transposeKey (car cells)))))
(closing (list
(transpose-music
(cell:closing (car (last-pair cells)))
(cell:transposeKey (car (last-pair cells)))))))
(make-music
'SequentialMusic
'elements (append opening barnum-set-expr music closing))))
gridGetLyrics =
#(define-music-function
(parser location part) (string?)
(let* ((cells (get-cell-range part #{ \getOption gridly.segment-range #}))
(lyrics (map cell:lyrics cells))
(opening-lyrics (let ((maybe-lyrics (cell:opening-lyrics (car cells))))
(if maybe-lyrics
(list maybe-lyrics)
'())))
(closing-lyrics (let ((maybe-lyrics (cell:closing-lyrics (car (last-pair cells)))))
(if maybe-lyrics
(list maybe-lyrics)
'()))))
(if (member #f lyrics)
(ly:error "A segment is missing lyrics!")
(make-music
'SequentialMusic
'elements (append opening-lyrics lyrics closing-lyrics)))))
#(define (format-cell-file-name parser part segment)
(let* ((max-segment-str-len (string-length
(number->string
(hash-ref music-grid-meta #:segments))))
(segment-format-str (string-append "~"
(number->string max-segment-str-len)
",,,'0@a"))
(segment-str (format segment-format-str segment)))
(format "~a-~a-~a"
(ly:parser-output-name parser)
part
segment-str)))
gridCompileCell =
#(define-void-function
(parser location part segment)
(string? number?)
(check-grid)
(check-coords part segment)
(if (this-file-compiled? parser location)
(let ((cache-segment #{ \getOption gridly.segment-range #}))
(ly:message "Compiling test file")
(if (not (get-music-cell part segment))
(ly:error "There is no music cell for ~a:~a"
part segment))
(check-durations segment #f)
#{ \setOption gridly.segment-range $segment #}
(let* ((name (ly:format "~a-~a" part segment))
(lyrics (let ((maybe-lyrics (cell:lyrics
(get-music-cell part segment))))
(if maybe-lyrics
#{ \new Lyrics \lyricsto $name { \gridGetLyrics $part } #}
#{ #})))
(book
#{
\book {
\score {
<<
\new Staff \new Voice = $name {
\gridGetMusic $part
}
$lyrics
>>
\midi{}
\layout{}
}
}
#}))
(ly:book-process book
#{ \paper {} #}
#{ \layout {} #}
(format-cell-file-name
parser
part
segment))
#{ \setOption gridly.segment-range #cache-segment #}))))
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Deprecated functions
gridTest =
#(define-void-function
(parser location part segment)
(string? number?)
(ly:input-warning
location
(string-append
"\n\tFunction `~a' is deprecated in favor of `~a' and"
"\n\twill be removed in a future release."
"\n\tPlease replace the former with the latter.")
"gridTest" "gridCompileCell")
((ly:music-function-extract gridCompileCell) parser location part segment))
gridSetStructure =
#(define-void-function
(parser location segment ctx-mod music)
(number? (ly:context-mod? #{ \with{} #}) ly:music?)
(ly:input-warning
location
(string-append
"\n\tFunction `~a' is deprecated in favor of `~a' and"
"\n\twill be removed in a future release."
"\n\tPlease replace the former with the latter.")
"gridSetStructure" "gridSetSegmentTemplate")
((ly:music-function-extract gridSetSegmentTemplate)
parser location segment ctx-mod music))
gridGetStructure =
#(define-music-function
(parser location) ()
(ly:input-warning
location
(string-append
"\n\tThe function `gridGetStructure' is deprecated and is"
"\n\tno longer part of the public interface of GridLY."
"\n\tIt will be removed in a future release."
"\n\tIf you are using this function to retrieve marks and"
"\n\tand tempo changes, please put them in a dedicated part,"
"\n\tnamed for instance `marks'"))
#{
\gridGetMusic "<template>"
#})
gridPutMusicDepr =
#(define-void-function
(parser location part segment ctx-mod music)
(string? number? (ly:context-mod?) ly:music?)
(ly:input-warning
location
"This function is deprecated, use `gridPutMusic' instead")
(if ctx-mod
(let ((context (ly:make-context-mod
(append
(ly:get-context-mods #{ \with { music = $music } #})
(ly:get-context-mods ctx-mod)))))
#{ \gridPutMusic $part $segment $context #})
#{ \gridPutMusic $part $segment $music #}))
gridSetSegmentTemplateDepr =
#(define-void-function
(parser location segment ctx-mod music)
(number? (ly:context-mod? #{ \with{} #}) ly:music?)
(ly:input-warning
location
"This function is deprecated, use `setSegmentTemplate' instead")
(let ((context (ly:make-context-mod
(append
(ly:get-context-mods #{ \with { music = $music } #})
(ly:get-context-mods ctx-mod)))))
#{
\gridSetSegmentTemplate $segment $context
#}))