]> git.donarmstrong.com Git - lilypond.git/blob - scm/song.scm
Merge branch 'master' into lilypond/translation
[lilypond.git] / scm / song.scm
1 ;;;; song.scm --- Festival singing mode output
2 ;;;;
3 ;;;; This file is part of LilyPond, the GNU music typesetter.
4 ;;;;
5 ;;;; Copyright (C) 2006, 2007 Brailcom, o.p.s.
6 ;;;; Author: Milan Zamazal <pdm@brailcom.org>
7 ;;;;
8 ;;;; LilyPond is free software: you can redistribute it and/or modify
9 ;;;; it under the terms of the GNU General Public License as published by
10 ;;;; the Free Software Foundation, either version 3 of the License, or
11 ;;;; (at your option) any later version.
12 ;;;;
13 ;;;; LilyPond is distributed in the hope that it will be useful,
14 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 ;;;; GNU General Public License for more details.
17 ;;;;
18 ;;;; You should have received a copy of the GNU General Public License
19 ;;;; along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
20
21
22 (define-module (scm song))
23
24 (use-modules (srfi srfi-1))
25 (use-modules (ice-9 optargs))
26 (use-modules (ice-9 receive))
27
28 (use-modules (lily))
29 (use-modules (scm song-util))
30
31
32 ;;; Configuration
33
34
35 ;; The word to be sung in places where notes are played without lyrics.
36 ;; If it is #f, the places without lyrics are omitted on the output.
37 (define-public *skip-word* "-skip-")
38
39 ;; If true, use syllables in the Festival XML file.
40 ;; If false, use whole words instead; this is necessary in languages like
41 ;; English, were the phonetic form cannot be deduced from syllables well enough.
42 (define-public *syllabify* #f)
43
44 ;; Base Festival octave to which LilyPond notes are mapped.
45 (define-public *base-octave* 5)
46 ;; The resulting base octave is sum of *base-octave* and
47 ;; *base-octave-shift*.  This is done to work around a Festival bug
48 ;; causing Festival to segfault or produce invalid pitch on higher pitches.
49 ;(define *base-octave-shift* -2)
50 (define *base-octave-shift* 0)
51
52 ;; The coeficient by which the notes just before \breath are shortened.
53 (define-public *breathe-shortage* 0.8)
54
55
56 ;;; LilyPond interface
57
58
59 (define-public (output-file music tempo filename)
60   (if *debug*
61       (debug-enable 'backtrace))
62   (ly:message "Writing Festival XML file ~a..." filename)
63   (let ((port (open-output-file filename)))
64     (write-header port tempo)
65     (write-lyrics port music)
66     (write-footer port)
67     (close-port port))
68   #f)
69
70
71 ;;; Utility functions
72
73
74 (define pp-pitch-names '((0 . "c") (1 . "des") (2 . "d") (3 . "es") (4 . "e") (5 . "f")
75                          (6 . "ges") (7 . "g") (8 . "as") (9 . "a") (10 . "bes") (11 . "b")))
76 (define (pp object)
77   (cond
78    ((list? object)
79     (format #f "[~{~a ~}]" (map pp object)))
80    ((skip? object)
81     (format #f "skip(~a)" (skip-duration object)))
82    ((lyrics? object)
83     (format #f "~a(~a)~a" (lyrics-text object) (lyrics-duration object)
84             (if (lyrics-unfinished object) "-" "")))
85    ((note? object)
86     (let ((pitch (ly:pitch-semitones (note-pitch object))))
87       (format #f "~a~a~a~a"
88               (assoc-get (modulo pitch 12) pp-pitch-names)
89               (let ((octave (+ (inexact->exact (floor (/ pitch 12))) 1)))
90                 (cond
91                  ((= octave 0)
92                   "")
93                  ((> octave 0)
94                   (make-uniform-array #\' octave))
95                  ((< octave 0)
96                   (make-uniform-array #\, (- 0 octave)))))
97               (pp-duration (note-duration object))
98               (if (> (note-joined object) 0) "-" ""))))
99    ((rest? object)
100     (format #f "r~a" (pp-duration (rest-duration object))))
101    (else
102     object)))
103
104 (define (pp-duration duration)
105   (set! duration (/ 4 duration))
106   (if (< (abs (- duration (inexact->exact duration))) 0.0001)
107       (inexact->exact duration)
108       (/ (round (* duration 100)) 100)))
109
110 (define-public (warning object-with-origin message . args)
111   (let ((origin (cond
112                  ((not object-with-origin)
113                   #f)
114                  ((note? object-with-origin)
115                   (note-origin object-with-origin))
116                  ((rest? object-with-origin)
117                   (rest-origin object-with-origin))
118                  ((ly:input-location? object-with-origin)
119                   object-with-origin)
120                  ((ly:music? object-with-origin)
121                   (ly:music-property object-with-origin 'origin))
122                  (else
123                   (format #t "Minor programming error: ~a~%" object-with-origin)
124                   #f))))
125     (if origin
126         (ly:input-message origin "***Song Warning***")
127         (format #t "~%***Song Warning***"))
128     (apply ly:message message (map pp args))))
129
130
131 ;;; Analysis functions
132
133
134 (define *default-tempo* #f)
135 (define *tempo-compression* #f)
136
137 (define (duration->number duration)
138   (let* ((log (ly:duration-log duration))
139          (dots (ly:duration-dot-count duration))
140          (factor (ly:duration-factor duration)))
141     (exact->inexact (* (expt 2 (- log)) (+ 1 (/ dots 2)) (/ (car factor) (cdr factor))))))
142
143 (define (tempo->beats music)
144   (let* ((tempo-spec (or (find-child-named music 'MetronomeChangeEvent)
145                          (find-child-named music 'SequentialMusic)))
146          (tempo (cond
147                  ((not tempo-spec)
148                   #f)
149                  ((music-name? tempo-spec 'MetronomeChangeEvent)
150                   (* (ly:music-property tempo-spec 'metronome-count)
151                      (duration->number (ly:music-property tempo-spec 'tempo-unit))))
152                  ((music-name? tempo-spec 'SequentialMusic)
153                   (* (property-value
154                       (find-child tempo-spec (lambda (elt)
155                                                (let ((tempo (music-property? elt 'tempoUnitCount)))
156                                                  (if (pair? tempo)
157                                                      (round (/ (+ (car tempo) (cdr tempo)) 2))
158                                                      tempo)))))
159                      (duration->number
160                       (property-value
161                        (find-child tempo-spec (lambda (elt) (music-property? elt 'tempoUnitDuration)))))))
162                  (else
163                   (format #t "Programming error (tempo->beats): ~a~%" tempo-spec)))))
164     (debug-enable 'backtrace)
165     (if (and tempo (music-name? tempo-spec 'SequentialMusic))
166         (set! *default-tempo* (property-value
167                                (find-child tempo-spec (lambda (elt) (music-property? elt 'tempoWholesPerMinute))))))
168     (if tempo
169         (round (* tempo (expt 2 (+ 2 *base-octave-shift*))))
170         #f)))
171
172 (defstruct music-context
173   music
174   context)
175
176 (define (collect-lyrics-music music)
177   ;; Returns list of music-context instances.
178   (let ((music-context-list '()))
179     (process-music
180      music
181      (lambda (music*)
182        (cond
183         ((music-name? music* 'LyricCombineMusic)
184          (push! (make-music-context #:music music*
185                                               #:context (ly:music-property music* 'associated-context))
186                      music-context-list)
187          #t)
188         ((and (music-name? music* 'ContextSpeccedMusic)
189               (music-property-value? music* 'context-type 'Lyrics)
190               (not (find-child-named music* 'LyricCombineMusic)))
191          (let ((name-node (find-child music* (lambda (node) (music-property? node 'associatedVoice)))))
192            (if name-node
193                (push! (make-music-context #:music music* #:context (property-value name-node))
194                            music-context-list)))
195          #t)
196         (else
197          #f))))
198     (debug "Lyrics contexts" (reverse music-context-list))))
199
200 (defstruct lyrics
201   text
202   duration
203   unfinished
204   ignore-melismata
205   context)
206
207 (defstruct skip
208   duration
209   context)
210
211 (define (get-lyrics music context)
212   ;; Returns list of lyrics and skip instances.
213   (let ((lyrics-list '())
214         (next-ignore-melismata #f)
215         (ignore-melismata #f)
216         (next-current-voice context)
217         (current-voice context))
218     (process-music
219      music
220      (lambda (music)
221        (cond
222         ;; true lyrics
223         ((music-name? music 'EventChord)
224          (let ((lyric-event (find-child-named music 'LyricEvent)))
225            (push! (make-lyrics
226                         #:text (ly:music-property lyric-event 'text)
227                         #:duration (* (duration->number (ly:music-property lyric-event 'duration)) 4)
228                         #:unfinished (and (not *syllabify*) (find-child-named music 'HyphenEvent))
229                         #:ignore-melismata ignore-melismata
230                         #:context current-voice)
231                        lyrics-list))
232          ;; LilyPond delays applying settings
233          (set! ignore-melismata next-ignore-melismata)
234          (set! current-voice next-current-voice)
235          #t)
236         ;; skipping
237         ((music-name? music 'SkipMusic)
238          (push! (make-skip
239                       #:duration (* (duration->number (ly:music-property music 'duration)) 4)
240                       #:context current-voice)
241                      lyrics-list)
242          #t)
243         ;; parameter change
244         ((music-property? music 'ignoreMelismata)
245          (set! next-ignore-melismata (property-value music))
246          #t)
247         ((music-property? music 'associatedVoice)
248          (set! next-current-voice (property-value music))
249          #t)
250         ;; anything else
251         (else
252          #f))))
253     (debug "Raw lyrics" (reverse lyrics-list))))
254
255 (defstruct score-voice
256   context
257   elements ; list of score-* instances
258   )
259
260 (defstruct score-choice
261   lists ; of lists of score-* instances
262   (n-assigned 0) ; number of lists having a verse-block
263   )
264
265 (defstruct score-repetice
266   count ; number of repetitions
267   elements ; list of score-* instances
268   )
269
270 (defstruct score-notes
271   note/rest-list ; list of note and rest instances
272   (verse-block-list '()) ; lyrics attached to notes -- multiple elements are
273                          ; possible for multiple stanzas
274   )
275
276 (defstruct note
277   pitch
278   duration
279   joined ; to the next note
280   origin
281   )
282
283 (defstruct rest
284   duration
285   origin
286   )
287
288 (define (get-notes music)
289   ;; Returns list of score-* instances.
290   (get-notes* music #t))
291
292 (define (get-notes* music autobeaming*)
293   ;; Returns list of score-* instances.
294   (let* ((result-list '())
295          (in-slur 0)
296          (autobeaming autobeaming*)
297          (last-note-spec #f))
298     (process-music
299      music
300      (lambda (music)
301        (cond
302         ;; context change
303         ((music-has-property? music 'context-id)
304          (let ((context (ly:music-property music 'context-id))
305                (children (music-elements music)))
306            (add! (make-score-voice #:context (debug "Changing context" context)
307                                              #:elements (append-map (lambda (elt)
308                                                                       (get-notes* elt autobeaming))
309                                                                     children))
310                       result-list))
311          #t)
312         ;; timing change
313         ((music-property? music 'timeSignatureFraction)
314          (let ((value (property-value music)))
315            (debug "Timing change" value)))
316         ;; simultaneous notes
317         ((music-name? music 'SimultaneousMusic)
318          (let ((simultaneous-lists (map (lambda (child)
319                                           (get-notes* child autobeaming))
320                                         (ly:music-property music 'elements))))
321            (debug "Simultaneous lists" simultaneous-lists)
322            (add! (make-score-choice #:lists simultaneous-lists) result-list))
323          #t)
324         ;; repetice
325         ((music-name? music 'VoltaRepeatedMusic)
326          (let ((repeat-count (ly:music-property music 'repeat-count))
327                (children (music-elements music)))
328            (add! (make-score-repetice #:count repeat-count
329                                                 #:elements (append-map
330                                                             (lambda (elt) (get-notes* elt autobeaming))
331                                                             children))
332                       result-list))
333          #t)
334         ;; a note or rest
335         ((or (music-name? music 'EventChord)
336              (music-name? music 'MultiMeasureRestMusic)) ; 2.10
337          (debug "Simple music event" music)
338          (if *tempo-compression*
339              (set! music (ly:music-compress (ly:music-deep-copy music) *tempo-compression*)))
340          (let ((note (find-child-named music 'NoteEvent))
341                (rest (if (music-name? music 'MultiMeasureRestMusic) ; 2.10
342                          music
343                          (or (find-child-named music 'RestEvent)
344                              (find-child-named music 'MultiMeasureRestEvent) ; 2.8
345                              ))))
346            (cond
347             (note
348              (debug "Note" note)
349              (let* ((pitch (ly:music-property note 'pitch))
350                     (duration (* (duration->number (ly:music-property note 'duration)) 4))
351                     (events (filter identity (list
352                                               (find-child-named music 'SlurEvent)
353                                               (find-child-named music 'ManualMelismaEvent)
354                                               (and (not autobeaming)
355                                                    (find-child-named music 'BeamEvent)))))
356                     (slur-start (length (filter (lambda (e) (music-property-value? e 'span-direction -1))
357                                                 events)))
358                     (slur-end (length (filter (lambda (e) (music-property-value? e 'span-direction 1))
359                                               events))))
360                (set! in-slur (+ in-slur slur-start (- slur-end)))
361                (let ((note-spec (make-note #:pitch pitch #:duration duration #:joined in-slur
362                                                 #:origin (ly:music-property note 'origin)))
363                      (last-result (and (not (null? result-list)) (last result-list))))
364                  (set! last-note-spec note-spec)
365                  (if (and last-result
366                           (score-notes? last-result))
367                      (set-score-notes-note/rest-list!
368                       last-result
369                       (append (score-notes-note/rest-list last-result) (list note-spec)))
370                      (add! (make-score-notes #:note/rest-list (list note-spec)) result-list)))))
371             (rest
372              (debug "Rest" rest)
373              (let* ((duration (* (duration->number (ly:music-property rest 'duration)) 4))
374                     (rest-spec (make-rest #:duration duration
375                                                #:origin (ly:music-property rest 'origin)))
376                     (last-result (and (not (null? result-list)) (last result-list))))
377                (if (and last-result
378                         (score-notes? last-result))
379                    (set-score-notes-note/rest-list! last-result
380                                                          (append (score-notes-note/rest-list last-result)
381                                                                  (list rest-spec)))
382                    (add! (make-score-notes #:note/rest-list (list rest-spec)) result-list))))))
383          #f)
384         ;; autobeaming change
385         ((music-property? music 'autoBeaming)
386          (set! autobeaming (property-value music))
387          #t)
388         ;; melisma change
389         ((music-property? music 'melismaBusy) ; 2.10
390          (let ((change (if (property-value music) 1 -1)))
391            (set! in-slur (+ in-slur change))
392            (if last-note-spec
393                (set-note-joined! last-note-spec (+ (note-joined last-note-spec) change)))))
394         ;; tempo change
395         ((music-property? music 'tempoWholesPerMinute)
396          (set! *tempo-compression* (ly:moment-div *default-tempo* (property-value music))))
397         ;; breathe
398         ((music-name? music 'BreathingEvent)
399          (if last-note-spec
400              (let* ((note-duration (note-duration last-note-spec))
401                     (rest-spec (make-rest #:duration (* note-duration (- 1 *breathe-shortage*))
402                                                #:origin (ly:music-property music 'origin))))
403                (set-note-duration! last-note-spec (* note-duration *breathe-shortage*))
404                (add! (make-score-notes #:note/rest-list (list rest-spec)) result-list))
405              (warning music "\\\\breathe without previous note known")))
406         ;; anything else
407         (else
408          #f))))
409     (debug "Raw notes" result-list)))
410
411 (defstruct verse-block ; lyrics for a given piece of music
412   verse-list
413   (fresh #t) ; if #t, this block hasn't been yet included in the final output
414   )
415
416 (defstruct parallel-blocks ; several parallel blocks (e.g. stanzas)
417   block-list ; list of verse-blocks
418   )
419
420 (defstruct sequential-blocks
421   block-list ; list of verse-blocks
422   )
423
424 (defstruct repeated-blocks
425   block-list ; list of verse-blocks
426   count ; number of repetitions
427   )
428
429 (defstruct verse ;
430   text ; separate text element (syllable or word)
431   notelist/rests ; list of note lists (slurs) and rests
432   (unfinished #f) ; whether to be merged with the following verse
433   )
434
435 (define (find-lyrics-score score-list context accept-default)
436   ;; Returns score-* element of context or #f (if there's no such any).
437   (and (not (null? score-list))
438        (or (find-lyrics-score* (car score-list) context accept-default)
439            (find-lyrics-score (cdr score-list) context accept-default))))
440
441 (define (find-lyrics-score* score context accept-default)
442   (cond
443    ((and (score-voice? score)
444          (equal? (score-voice-context score) context))
445     score)
446    ((score-voice? score)
447     (find-lyrics-score (score-voice-elements score) context #f))
448    ((score-choice? score)
449     (letrec ((lookup (lambda (lists)
450                        (if (null? lists)
451                            #f
452                            (or (find-lyrics-score (car lists) context accept-default)
453                                (lookup (cdr lists)))))))
454       (lookup (score-choice-lists score))))
455    ((score-repetice? score)
456     (if accept-default
457         score
458         (find-lyrics-score (score-repetice-elements score) context accept-default)))
459    ((score-notes? score)
460     (if accept-default
461         score
462         #f))
463    (else
464     (error "Unknown score element" score))))
465
466 (define (insert-lyrics! lyrics/skip-list score-list context)
467   ;; Add verse-block-lists to score-list.
468   ;; Each processed score-notes instance must receive at most one block in each
469   ;; insert-lyrics! call.  (It can get other blocks if more pieces of
470   ;; lyrics are attached to the same score part.)
471   (let ((lyrics-score-list (find-lyrics-score score-list context #f)))
472     (debug "Lyrics+skip list" lyrics/skip-list)
473     (debug "Corresponding score-* list" score-list)
474     (if lyrics-score-list
475         (insert-lyrics*! lyrics/skip-list (list lyrics-score-list) context)
476         (warning #f "Lyrics context not found: ~a" context))))
477
478 (define (insert-lyrics*! lyrics/skip-list score-list context)
479   (debug "Processing lyrics" lyrics/skip-list)
480   (debug "Processing score" score-list)
481   (cond
482    ((and (null? lyrics/skip-list)
483          (null? score-list))
484     #f)
485    ((null? lyrics/skip-list)
486     (warning #f "Extra notes: ~a ~a" context score-list))
487    ((null? score-list)
488     (warning #f "Extra lyrics: ~a ~a" context lyrics/skip-list))
489    (else
490     (let* ((lyrics/skip (car lyrics/skip-list))
491            (lyrics-context ((if (lyrics? lyrics/skip) lyrics-context skip-context) lyrics/skip))
492            (score (car score-list)))
493       (cond
494        ((score-voice? score)
495         (let ((new-context (score-voice-context score)))
496           (if (equal? new-context lyrics-context)
497               (insert-lyrics*! lyrics/skip-list
498                                     (append (score-voice-elements score)
499                                             (if (null? (cdr score-list))
500                                                 '()
501                                                 (list (make-score-voice #:context context
502                                                                              #:elements (cdr score-list)))))
503                                     new-context)
504               (insert-lyrics*! lyrics/skip-list (cdr score-list) context))))
505        ((score-choice? score)
506         (let* ((lists* (score-choice-lists score))
507                (lists lists*)
508                (n-assigned (score-choice-n-assigned score))
509                (n 0)
510                (allow-default #f)
511                (score* #f))
512           (while (and (not score*)
513                       (not (null? lists)))
514             (set! score* (find-lyrics-score (car lists) lyrics-context allow-default))
515             (set! lists (cdr lists))
516             (if (not score*)
517                 (set! n (+ n 1)))
518             (if (and (null? lists)
519                      (not allow-default)
520                      (equal? lyrics-context context))
521                 (begin
522                   (set! allow-default #t)
523                   (set! n 0)
524                   (set! lists (score-choice-lists score)))))
525           (debug "Selected score" score*)
526           (if (and score*
527                    (>= n n-assigned))
528               (begin
529                 (if (> n n-assigned)
530                     (receive (assigned-elts unassigned-elts) (split-at lists* n-assigned)
531                       (set-score-choice-lists! score (append assigned-elts
532                                                                   (list (list-ref lists* n))
533                                                                   (take unassigned-elts (- n n-assigned))
534                                                                   lists))))
535                 (set-score-choice-n-assigned! score (+ n-assigned 1))))
536           (insert-lyrics*! lyrics/skip-list (append (if score* (list score*) '()) (cdr score-list)) context)))
537        ((score-repetice? score)
538         (insert-lyrics*! lyrics/skip-list
539                               (append (score-repetice-elements score) (cdr score-list)) context))
540        ((score-notes? score)
541         ;; This is the only part which actually attaches the processed lyrics.
542         ;; The subsequent calls return verses which we collect into a verse block.
543         ;; We add the block to the score element.
544         (if (equal? lyrics-context context)
545             (set! lyrics/skip-list (really-insert-lyrics! lyrics/skip-list score context)))
546         (insert-lyrics*! lyrics/skip-list (cdr score-list) context))
547        (else
548         (error "Unknown score element in lyrics processing" score)))))))
549
550 (define (really-insert-lyrics! lyrics/skip-list score context)
551   ;; Return new lyrics/skip-list.
552   ;; Score is modified by side effect.
553   (debug "Assigning notes" score)
554   (let ((note-list (score-notes-note/rest-list score))
555         (unfinished-verse #f)
556         (verse-list '()))
557     (while (not (null? note-list))
558       (if (null? lyrics/skip-list)
559           (let ((final-rests '()))
560             (while (and (not (null? note-list))
561                         (rest? (car note-list)))
562               (push! (car note-list) final-rests)
563               (set! note-list (cdr note-list)))
564             (if (not (null? final-rests))
565                 (set! verse-list (append verse-list
566                                          (list (make-verse #:text ""
567                                                                 #:notelist/rests (reverse! final-rests))))))
568             (if (not (null? note-list))
569                 (begin
570                   (warning (car note-list) "Missing lyrics: ~a ~a" context note-list)
571                   (set! note-list '()))))
572           (let ((lyrics/skip (car lyrics/skip-list)))
573             (receive (notelist/rest note-list*) (if (lyrics? lyrics/skip)
574                                                     (consume-lyrics-notes lyrics/skip note-list context)
575                                                     (consume-skip-notes lyrics/skip note-list context))
576               (debug "Consumed notes" (list lyrics/skip notelist/rest))
577               (set! note-list note-list*)
578               (cond
579                ((null? notelist/rest)
580                 #f)
581                ;; Lyrics
582                ((and (lyrics? lyrics/skip)
583                      unfinished-verse)
584                 (set-verse-text!
585                  unfinished-verse
586                  (string-append (verse-text unfinished-verse) (lyrics-text lyrics/skip)))
587                 (set-verse-notelist/rests!
588                  unfinished-verse
589                  (append (verse-notelist/rests unfinished-verse) (list notelist/rest)))
590                 (if (not (lyrics-unfinished lyrics/skip))
591                     (set! unfinished-verse #f)))
592                ((lyrics? lyrics/skip)
593                 (let ((verse (make-verse #:text (if (rest? notelist/rest)
594                                                          ""
595                                                          (lyrics-text lyrics/skip))
596                                               #:notelist/rests (list notelist/rest))))
597                   (add! verse verse-list)
598                   (set! unfinished-verse (if (lyrics-unfinished lyrics/skip) verse #f))))
599                ;; Skip
600                ((skip? lyrics/skip)
601                 (cond
602                  ((rest? notelist/rest)
603                   (if (null? verse-list)
604                       (set! verse-list (list (make-verse #:text ""
605                                                               #:notelist/rests (list notelist/rest))))
606                       (let ((last-verse (last verse-list)))
607                         (set-verse-notelist/rests!
608                          last-verse
609                          (append (verse-notelist/rests last-verse) (list notelist/rest))))))
610                  ((pair? notelist/rest)
611                   (add! (make-verse #:text *skip-word* #:notelist/rests (list notelist/rest))
612                              verse-list))
613                  (else
614                   (error "Unreachable branch reached")))
615                 (set! unfinished-verse #f)))
616               (if (not (rest? notelist/rest))
617                   (set! lyrics/skip-list (cdr lyrics/skip-list)))))))
618     (if unfinished-verse
619         (set-verse-unfinished! unfinished-verse #t))
620     (set-score-notes-verse-block-list!
621      score
622      (append (score-notes-verse-block-list score)
623              (list (make-verse-block #:verse-list verse-list)))))
624   lyrics/skip-list)
625
626 (define (consume-lyrics-notes lyrics note-list context)
627   ;; Returns list of note instances + new note-list.
628   (assert (lyrics? lyrics))
629   (if (and (not (null? note-list))
630            (rest? (car note-list)))
631       (values (car note-list) (cdr note-list))
632       (let ((ignore-melismata (lyrics-ignore-melismata lyrics))
633             (join #t)
634             (consumed '()))
635         (while (and join
636                     (not (null? note-list)))
637           (let ((note (car note-list)))
638             (push! note consumed)
639             (let ((note-slur (note-joined note)))
640               (if (< note-slur 0)
641                   (warning note "Slur underrun"))
642               (set! join (and (not ignore-melismata) (> note-slur 0)))))
643           (set! note-list (cdr note-list)))
644         (if join
645             (warning (safe-car (if (null? note-list) consumed note-list))
646                      "Unfinished slur: ~a ~a" context consumed))
647         (values (reverse consumed) note-list))))
648
649 (define (consume-skip-notes skip note-list context)
650   ;; Returns either note list (skip word defined) or rest instance (no skip word) + new note-list.
651   (assert (skip? skip))
652   (let ((duration (skip-duration skip))
653         (epsilon 0.001)
654         (consumed '()))
655     (while (and (> duration epsilon)
656                 (not (null? note-list)))
657       (let ((note (car note-list)))
658         (assert (note? note))
659         (push! note consumed)
660         (set! duration (- duration (note-duration note))))
661       (set! note-list (cdr note-list)))
662     (set! consumed (reverse! consumed))
663     (cond
664      ((> duration epsilon)
665       (warning (if (null? note-list) (safe-last consumed) (safe-car note-list))
666                     "Excessive skip: ~a ~a ~a ~a" context skip duration consumed))
667      ((< duration (- epsilon))
668       (warning (if (null? note-list) (safe-last consumed) (safe-car note-list))
669                     "Skip misalignment: ~a ~a ~a ~a" context skip duration consumed)))
670     (values (if *skip-word*
671                 consumed
672                 '())
673             note-list)))
674
675 (define (extract-verse-blocks score)
676   ;; Returns list of blocks and parallel blocks.
677   (debug "Extracting verse blocks" score)
678   (cond
679    ((score-voice? score)
680     (append-map extract-verse-blocks (score-voice-elements score)))
681    ((score-choice? score)
682     (list (make-parallel-blocks
683            #:block-list (map (lambda (block-list)
684                                (make-sequential-blocks
685                                 #:block-list (append-map extract-verse-blocks block-list)))
686                              (score-choice-lists score)))))
687    ((score-repetice? score)
688     (list (make-repeated-blocks #:count (score-repetice-count score)
689                                      #:block-list (append-map extract-verse-blocks
690                                                               (score-repetice-elements score)))))
691    ((score-notes? score)
692     (list (make-parallel-blocks #:block-list (score-notes-verse-block-list score))))
693    (else
694     (error "Invalid score element" score))))
695
696 (define (extract-verses score-list)
697   ;; Returns (final) list of verses.
698   ;; The primary purpose of this routine is to build complete stanzas from
699   ;; lists of verse blocks.
700   ;; Extract verse-blocks and process them until no unprocessed stanzas remain.
701   (debug "Final score list" score-list)
702   (let ((verse-block-list (debug "Verse blocks" (append-map extract-verse-blocks score-list))))
703     (letrec ((combine (lambda (lst-1 lst-2)
704                          (debug "Combining lists" (list lst-1 lst-2))
705                          (if (null? lst-2)
706                              lst-1
707                              (let ((diff (- (length lst-1) (length lst-2))))
708                                (if (< diff 0)
709                                    (let ((last-elt (last lst-1)))
710                                      (while (< diff 0)
711                                        (add! last-elt lst-1)
712                                        (set! diff (+ diff 1))))
713                                    (let ((last-elt (last lst-2)))
714                                      (while (> diff 0)
715                                        (add! last-elt lst-2)
716                                        (set! diff (- diff 1)))))
717                                (debug "Combined" (map append lst-1 lst-2))))))
718              (expand* (lambda (block)
719                         (cond
720                          ((parallel-blocks? block)
721                           (append-map (lambda (block) (expand (list block)))
722                                       (parallel-blocks-block-list block)))
723                          ((sequential-blocks? block)
724                           (expand (sequential-blocks-block-list block)))
725                          ((repeated-blocks? block)
726                           ;; Only simple repetice without nested parallel sections is supported.
727                           (let ((count (repeated-blocks-count block))
728                                 (expanded (expand (repeated-blocks-block-list block)))
729                                 (expanded* '()))
730                             (while (not (null? expanded))
731                               (let ((count* count)
732                                     (item '()))
733                                 (while (and (> count* 0) (not (null? expanded)))
734                                   (set! item (append item (car expanded)))
735                                   (set! expanded (cdr expanded))
736                                   (set! count* (- count* 1)))
737                                 (push! item expanded*)))
738                             (reverse expanded*)))
739                          (else
740                           (list (list block))))))
741              (expand (lambda (block-list)
742                        (debug "Expanding list" block-list)
743                        (if (null? block-list)
744                            '()
745                            (debug "Expanded" (combine (expand* (car block-list))
746                                                            (expand (cdr block-list)))))))
747              (merge (lambda (verse-list)
748                       (cond
749                        ((null? verse-list)
750                         '())
751                        ((verse-unfinished (car verse-list))
752                         (let ((verse-1 (first verse-list))
753                               (verse-2 (second verse-list)))
754                           (merge (cons (make-verse #:text (string-append (verse-text verse-1)
755                                                                               (verse-text verse-2))
756                                                         #:notelist/rests (append (verse-notelist/rests verse-1)
757                                                                                  (verse-notelist/rests verse-2))
758                                                         #:unfinished (verse-unfinished verse-2))
759                                        (cddr verse-list)))))
760                        (else
761                         (cons (car verse-list) (merge (cdr verse-list))))))))
762       (debug "Final verses" (merge (append-map (lambda (lst) (append-map verse-block-verse-list lst))
763                                                     (expand verse-block-list)))))))
764
765 (define (handle-music music)
766   ;; Returns list of verses.
767   ;; The main analysis function.
768   (if *debug*
769       (display-scheme-music music))
770   (let ((score-list (debug "Final raw notes" (get-notes music)))
771         (music-context-list (collect-lyrics-music music)))
772     (for-each (lambda (music-context)
773                 (let ((context (music-context-context music-context)))
774                   (set! *tempo-compression* #f)
775                   (insert-lyrics! (get-lyrics (music-context-music music-context) context)
776                                   score-list context)
777                   (debug "Final score list" score-list)))
778               music-context-list)
779     (extract-verses score-list)))
780
781
782 ;;; Output
783
784
785 (define festival-note-mapping '((0 "C") (1 "C#") (2 "D") (3 "D#") (4 "E") (5 "F") (6 "F#")
786                                      (7 "G") (8 "G#") (9 "A") (10 "A#") (11 "B")))
787 (define (festival-pitch pitch)
788   (let* ((semitones (ly:pitch-semitones pitch))
789          (octave (inexact->exact (floor (/ semitones 12))))
790          (tone (modulo semitones 12)))
791     (format #f "~a~a" (car (assoc-get tone festival-note-mapping))
792             (+ octave *base-octave* *base-octave-shift*))))
793
794 (define (write-header port tempo)
795   (let ((beats (or (tempo->beats tempo) 100)))
796     (format port "<?xml version=\"1.0\"?>
797 <!DOCTYPE SINGING PUBLIC \"-//SINGING//DTD SINGING mark up//EN\" \"Singing.v0_1.dtd\" []>
798 <SINGING BPM=\"~d\">
799 " beats)))
800
801 (define (write-footer port)
802   (format port "</SINGING>~%"))
803
804 (define (write-lyrics port music)
805   (let ((rest-dur 0))
806     (for-each (lambda (verse)
807                 (let ((text (verse-text verse))
808                       (note/rest-list (verse-notelist/rests verse)))
809                   (receive (rest-list note-listlist) (partition rest? note/rest-list)
810                     (debug "Rest list" rest-list)
811                     (debug "Note list" note-listlist)
812                     (if (not (null? rest-list))
813                         (set! rest-dur (+ rest-dur (apply + (map rest-duration rest-list)))))
814                     (if (not (null? note-listlist))
815                         (begin
816                           (if (> rest-dur 0)
817                               (begin
818                                 (write-rest-element port rest-dur)
819                                 (set! rest-dur 0)))
820                           (write-lyrics-element port text note-listlist))))))
821               (handle-music music))
822     (if (> rest-dur 0)
823         (write-rest-element port rest-dur))))
824
825 (define (write-lyrics-element port text slur-list)
826   (let ((fmt "~{~{~a~^+~}~^,~}")
827         (transform (lambda (function)
828                      (map (lambda (slur)
829                             (let ((rests (filter rest? slur)))
830                               (if (not (null? rests))
831                                   (begin
832                                     (warning (car rests) "Rests in a slur: ~a" slur)
833                                     (set! slur (remove rest? slur)))))
834                             (map function slur))
835                           slur-list))))
836     (format port "<DURATION BEATS=\"~@?\"><PITCH NOTE=\"~@?\">~a</PITCH></DURATION>~%"
837             fmt (transform note-duration)
838             fmt (transform (compose festival-pitch note-pitch))
839             text)))
840
841 (define (write-rest-element port duration)
842   (format port "<REST BEATS=\"~a\"></REST>~%" duration))