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