1 ;;; festival.scm --- Festival singing mode output
3 ;; Copyright (C) 2006, 2007 Brailcom, o.p.s.
5 ;; Author: Milan Zamazal <pdm@brailcom.org>
9 ;; This program is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2 of the License, or
12 ;; (at your option) any later version.
14 ;; This program is distributed in the hope that it will be useful, but
15 ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with this program; if not, write to the Free Software
21 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
24 (define-module (scm song))
26 (use-modules (srfi srfi-1))
27 (use-modules (ice-9 optargs))
28 (use-modules (ice-9 receive))
31 (use-modules (scm song-util))
37 ;; The word to be sung in places where notes are played without lyrics.
38 ;; If it is #f, the places without lyrics are omitted on the output.
39 (define-public *skip-word* "-skip-")
41 ;; If true, use syllables in the Festival XML file.
42 ;; If false, use whole words instead; this is necessary in languages like
43 ;; English, were the phonetic form cannot be deduced from syllables well enough.
44 (define-public *syllabify* #f)
46 ;; Base Festival octave to which LilyPond notes are mapped.
47 (define-public *base-octave* 5)
48 ;; The resulting base octave is sum of *base-octave* and
49 ;; *base-octave-shift*. This is done to work around a Festival bug
50 ;; causing Festival to segfault or produce invalid pitch on higher pitches.
51 ;(define *base-octave-shift* -2)
52 (define *base-octave-shift* 0)
54 ;; The coeficient by which the notes just before \breath are shortened.
55 (define-public *breathe-shortage* 0.8)
58 ;;; LilyPond interface
61 (define-public (output-file music tempo filename)
63 (debug-enable 'backtrace))
64 (ly:message "Writing Festival XML file ~a..." filename)
65 (let ((port (open-output-file filename)))
66 (write-header port tempo)
67 (write-lyrics port music)
75 (define pp-pitch-names '((0 . "c") (1 . "des") (2 . "d") (3 . "es") (4 . "e") (5 . "f")
76 (6 . "ges") (7 . "g") (8 . "as") (9 . "a") (10 . "bes") (11 . "b")))
80 (format #f "[~{~a ~}]" (map pp object)))
82 (format #f "skip(~a)" (skip-duration object)))
84 (format #f "~a(~a)~a" (lyrics-text object) (lyrics-duration object)
85 (if (lyrics-unfinished object) "-" "")))
87 (let ((pitch (ly:pitch-semitones (note-pitch object))))
89 (cdr (assoc (modulo pitch 12) pp-pitch-names))
90 (let ((octave (+ (inexact->exact (floor (/ pitch 12))) 1)))
95 (make-uniform-array #\' octave))
97 (make-uniform-array #\, (- 0 octave)))))
98 (pp-duration (note-duration object))
99 (if (> (note-joined object) 0) "-" ""))))
101 (format #f "r~a" (pp-duration (rest-duration object))))
105 (define (pp-duration duration)
106 (set! duration (/ 4 duration))
107 (if (< (abs (- duration (inexact->exact duration))) 0.0001)
108 (inexact->exact duration)
109 (/ (round (* duration 100)) 100)))
111 (define-public (warning object-with-origin message . args)
113 ((not object-with-origin)
115 ((note? object-with-origin)
116 (note-origin object-with-origin))
117 ((rest? object-with-origin)
118 (rest-origin object-with-origin))
119 ((ly:input-location? object-with-origin)
121 ((ly:music? object-with-origin)
122 (ly:music-property object-with-origin 'origin))
124 (format #t "Minor programming error: ~a~%" object-with-origin)
127 (ly:input-message origin "***Song Warning***")
128 (format #t "~%***Song Warning***"))
129 (apply ly:message message (map pp args))))
132 ;;; Analysis functions
135 (define *default-tempo* #f)
136 (define *tempo-compression* #f)
138 (define (duration->number duration)
139 (let* ((log (ly:duration-log duration))
140 (dots (ly:duration-dot-count duration))
141 (factor (ly:duration-factor duration)))
142 (exact->inexact (* (expt 2 (- log)) (+ 1 (/ dots 2)) (/ (car factor) (cdr factor))))))
144 (define (tempo->beats music)
145 (let* ((tempo-spec (or (find-child-named music 'MetronomeChangeEvent)
146 (find-child-named music 'SequentialMusic)))
150 ((music-name? tempo-spec 'MetronomeChangeEvent)
151 (* (ly:music-property tempo-spec 'metronome-count)
152 (duration->number (ly:music-property tempo-spec 'tempo-unit))))
153 ((music-name? tempo-spec 'SequentialMusic)
155 (find-child tempo-spec (lambda (elt) (music-property? elt 'tempoUnitCount))))
158 (find-child tempo-spec (lambda (elt) (music-property? elt 'tempoUnitDuration)))))))
160 (format #t "Programming error (tempo->beats): ~a~%" tempo-spec)))))
161 (debug-enable 'backtrace)
162 (if (and tempo (music-name? tempo-spec 'SequentialMusic))
163 (set! *default-tempo* (property-value
164 (find-child tempo-spec (lambda (elt) (music-property? elt 'tempoWholesPerMinute))))))
166 (round (* tempo (expt 2 (+ 2 *base-octave-shift*))))
169 (defstruct music-context
173 (define (collect-lyrics-music music)
174 ;; Returns list of music-context instances.
175 (let ((music-context-list '()))
180 ((music-name? music* 'LyricCombineMusic)
181 (push! (make-music-context #:music music*
182 #:context (ly:music-property music* 'associated-context))
185 ((and (music-name? music* 'ContextSpeccedMusic)
186 (music-property-value? music* 'context-type 'Lyrics)
187 (not (find-child-named music* 'LyricCombineMusic)))
188 (let ((name-node (find-child music* (lambda (node) (music-property? node 'associatedVoice)))))
190 (push! (make-music-context #:music music* #:context (property-value name-node))
191 music-context-list)))
195 (debug "Lyrics contexts" (reverse music-context-list))))
208 (define (get-lyrics music context)
209 ;; Returns list of lyrics and skip instances.
210 (let ((lyrics-list '())
211 (next-ignore-melismata #f)
212 (ignore-melismata #f)
213 (next-current-voice context)
214 (current-voice context))
220 ((music-name? music 'EventChord)
221 (let ((lyric-event (find-child-named music 'LyricEvent)))
223 #:text (ly:music-property lyric-event 'text)
224 #:duration (* (duration->number (ly:music-property lyric-event 'duration)) 4)
225 #:unfinished (and (not *syllabify*) (find-child-named music 'HyphenEvent))
226 #:ignore-melismata ignore-melismata
227 #:context current-voice)
229 ;; LilyPond delays applying settings
230 (set! ignore-melismata next-ignore-melismata)
231 (set! current-voice next-current-voice)
234 ((music-name? music 'SkipMusic)
236 #:duration (* (duration->number (ly:music-property music 'duration)) 4)
237 #:context current-voice)
241 ((music-property? music 'ignoreMelismata)
242 (set! next-ignore-melismata (property-value music))
244 ((music-property? music 'associatedVoice)
245 (set! next-current-voice (property-value music))
250 (debug "Raw lyrics" (reverse lyrics-list))))
252 (defstruct score-voice
254 elements ; list of score-* instances
257 (defstruct score-choice
258 lists ; of lists of score-* instances
259 (n-assigned 0) ; number of lists having a verse-block
262 (defstruct score-repetice
263 count ; number of repetitions
264 elements ; list of score-* instances
267 (defstruct score-notes
268 note/rest-list ; list of note and rest instances
269 (verse-block-list '()) ; lyrics attached to notes -- multiple elements are
270 ; possible for multiple stanzas
276 joined ; to the next note
285 (define (get-notes music)
286 ;; Returns list of score-* instances.
287 (get-notes* music #t))
289 (define (get-notes* music autobeaming*)
290 ;; Returns list of score-* instances.
291 (let* ((result-list '())
293 (autobeaming autobeaming*)
300 ((music-has-property? music 'context-id)
301 (let ((context (ly:music-property music 'context-id))
302 (children (music-elements music)))
303 (add! (make-score-voice #:context (debug "Changing context" context)
304 #:elements (append-map (lambda (elt)
305 (get-notes* elt autobeaming))
310 ((music-property? music 'timeSignatureFraction)
311 (let ((value (property-value music)))
312 (debug "Timing change" value)))
313 ;; simultaneous notes
314 ((music-name? music 'SimultaneousMusic)
315 (let ((simultaneous-lists (map (lambda (child)
316 (get-notes* child autobeaming))
317 (ly:music-property music 'elements))))
318 (debug "Simultaneous lists" simultaneous-lists)
319 (add! (make-score-choice #:lists simultaneous-lists) result-list))
322 ((music-name? music 'VoltaRepeatedMusic)
323 (let ((repeat-count (ly:music-property music 'repeat-count))
324 (children (music-elements music)))
325 (add! (make-score-repetice #:count repeat-count
326 #:elements (append-map
327 (lambda (elt) (get-notes* elt autobeaming))
332 ((or (music-name? music 'EventChord)
333 (music-name? music 'MultiMeasureRestMusic)) ; 2.10
334 (debug "Simple music event" music)
335 (if *tempo-compression*
336 (set! music (ly:music-compress (ly:music-deep-copy music) *tempo-compression*)))
337 (let ((note (find-child-named music 'NoteEvent))
338 (rest (if (music-name? music 'MultiMeasureRestMusic) ; 2.10
340 (or (find-child-named music 'RestEvent)
341 (find-child-named music 'MultiMeasureRestEvent) ; 2.8
346 (let* ((pitch (ly:music-property note 'pitch))
347 (duration (* (duration->number (ly:music-property note 'duration)) 4))
348 (events (filter identity (list
349 (find-child-named music 'SlurEvent)
350 (find-child-named music 'ManualMelismaEvent)
351 (and (not autobeaming)
352 (find-child-named music 'BeamEvent)))))
353 (slur-start (length (filter (lambda (e) (music-property-value? e 'span-direction -1))
355 (slur-end (length (filter (lambda (e) (music-property-value? e 'span-direction 1))
357 (set! in-slur (+ in-slur slur-start (- slur-end)))
358 (let ((note-spec (make-note #:pitch pitch #:duration duration #:joined in-slur
359 #:origin (ly:music-property note 'origin)))
360 (last-result (and (not (null? result-list)) (last result-list))))
361 (set! last-note-spec note-spec)
363 (score-notes? last-result))
364 (set-score-notes-note/rest-list!
366 (append (score-notes-note/rest-list last-result) (list note-spec)))
367 (add! (make-score-notes #:note/rest-list (list note-spec)) result-list)))))
370 (let* ((duration (* (duration->number (ly:music-property rest 'duration)) 4))
371 (rest-spec (make-rest #:duration duration
372 #:origin (ly:music-property rest 'origin)))
373 (last-result (and (not (null? result-list)) (last result-list))))
375 (score-notes? last-result))
376 (set-score-notes-note/rest-list! last-result
377 (append (score-notes-note/rest-list last-result)
379 (add! (make-score-notes #:note/rest-list (list rest-spec)) result-list))))))
381 ;; autobeaming change
382 ((music-property? music 'autoBeaming)
383 (set! autobeaming (property-value music))
386 ((music-property? music 'melismaBusy) ; 2.10
387 (let ((change (if (property-value music) 1 -1)))
388 (set! in-slur (+ in-slur change))
390 (set-note-joined! last-note-spec (+ (note-joined last-note-spec) change)))))
392 ((music-property? music 'tempoWholesPerMinute)
393 (set! *tempo-compression* (ly:moment-div *default-tempo* (property-value music))))
395 ((music-name? music 'BreathingEvent)
397 (let* ((note-duration (note-duration last-note-spec))
398 (rest-spec (make-rest #:duration (* note-duration (- 1 *breathe-shortage*))
399 #:origin (ly:music-property music 'origin))))
400 (set-note-duration! last-note-spec (* note-duration *breathe-shortage*))
401 (add! (make-score-notes #:note/rest-list (list rest-spec)) result-list))
402 (warning music "\\\\breathe without previous note known")))
406 (debug "Raw notes" result-list)))
408 (defstruct verse-block ; lyrics for a given piece of music
410 (fresh #t) ; if #t, this block hasn't been yet included in the final output
413 (defstruct parallel-blocks ; several parallel blocks (e.g. stanzas)
414 block-list ; list of verse-blocks
417 (defstruct sequential-blocks
418 block-list ; list of verse-blocks
421 (defstruct repeated-blocks
422 block-list ; list of verse-blocks
423 count ; number of repetitions
427 text ; separate text element (syllable or word)
428 notelist/rests ; list of note lists (slurs) and rests
429 (unfinished #f) ; whether to be merged with the following verse
432 (define (find-lyrics-score score-list context accept-default)
433 ;; Returns score-* element of context or #f (if there's no such any).
434 (and (not (null? score-list))
435 (or (find-lyrics-score* (car score-list) context accept-default)
436 (find-lyrics-score (cdr score-list) context accept-default))))
438 (define (find-lyrics-score* score context accept-default)
440 ((and (score-voice? score)
441 (equal? (score-voice-context score) context))
443 ((score-voice? score)
444 (find-lyrics-score (score-voice-elements score) context #f))
445 ((score-choice? score)
446 (letrec ((lookup (lambda (lists)
449 (or (find-lyrics-score (car lists) context accept-default)
450 (lookup (cdr lists)))))))
451 (lookup (score-choice-lists score))))
452 ((score-repetice? score)
455 (find-lyrics-score (score-repetice-elements score) context accept-default)))
456 ((score-notes? score)
461 (error "Unknown score element" score))))
463 (define (insert-lyrics! lyrics/skip-list score-list context)
464 ;; Add verse-block-lists to score-list.
465 ;; Each processed score-notes instance must receive at most one block in each
466 ;; insert-lyrics! call. (It can get other blocks if more pieces of
467 ;; lyrics are attached to the same score part.)
468 (let ((lyrics-score-list (find-lyrics-score score-list context #f)))
469 (debug "Lyrics+skip list" lyrics/skip-list)
470 (debug "Corresponding score-* list" score-list)
471 (if lyrics-score-list
472 (insert-lyrics*! lyrics/skip-list (list lyrics-score-list) context)
473 (warning #f "Lyrics context not found: ~a" context))))
475 (define (insert-lyrics*! lyrics/skip-list score-list context)
476 (debug "Processing lyrics" lyrics/skip-list)
477 (debug "Processing score" score-list)
479 ((and (null? lyrics/skip-list)
482 ((null? lyrics/skip-list)
483 (warning #f "Extra notes: ~a ~a" context score-list))
485 (warning #f "Extra lyrics: ~a ~a" context lyrics/skip-list))
487 (let* ((lyrics/skip (car lyrics/skip-list))
488 (lyrics-context ((if (lyrics? lyrics/skip) lyrics-context skip-context) lyrics/skip))
489 (score (car score-list)))
491 ((score-voice? score)
492 (let ((new-context (score-voice-context score)))
493 (if (equal? new-context lyrics-context)
494 (insert-lyrics*! lyrics/skip-list
495 (append (score-voice-elements score)
496 (if (null? (cdr score-list))
498 (list (make-score-voice #:context context
499 #:elements (cdr score-list)))))
501 (insert-lyrics*! lyrics/skip-list (cdr score-list) context))))
502 ((score-choice? score)
503 (let* ((lists* (score-choice-lists score))
505 (n-assigned (score-choice-n-assigned score))
509 (while (and (not score*)
511 (set! score* (find-lyrics-score (car lists) lyrics-context allow-default))
512 (set! lists (cdr lists))
515 (if (and (null? lists)
517 (equal? lyrics-context context))
519 (set! allow-default #t)
521 (set! lists (score-choice-lists score)))))
522 (debug "Selected score" score*)
527 (receive (assigned-elts unassigned-elts) (split-at lists* n-assigned)
528 (set-score-choice-lists! score (append assigned-elts
529 (list (list-ref lists* n))
530 (take unassigned-elts (- n n-assigned))
532 (set-score-choice-n-assigned! score (+ n-assigned 1))))
533 (insert-lyrics*! lyrics/skip-list (append (if score* (list score*) '()) (cdr score-list)) context)))
534 ((score-repetice? score)
535 (insert-lyrics*! lyrics/skip-list
536 (append (score-repetice-elements score) (cdr score-list)) context))
537 ((score-notes? score)
538 ;; This is the only part which actually attaches the processed lyrics.
539 ;; The subsequent calls return verses which we collect into a verse block.
540 ;; We add the block to the score element.
541 (if (equal? lyrics-context context)
542 (set! lyrics/skip-list (really-insert-lyrics! lyrics/skip-list score context)))
543 (insert-lyrics*! lyrics/skip-list (cdr score-list) context))
545 (error "Unknown score element in lyrics processing" score)))))))
547 (define (really-insert-lyrics! lyrics/skip-list score context)
548 ;; Return new lyrics/skip-list.
549 ;; Score is modified by side effect.
550 (debug "Assigning notes" score)
551 (let ((note-list (score-notes-note/rest-list score))
552 (unfinished-verse #f)
554 (while (not (null? note-list))
555 (if (null? lyrics/skip-list)
556 (let ((final-rests '()))
557 (while (and (not (null? note-list))
558 (rest? (car note-list)))
559 (push! (car note-list) final-rests)
560 (set! note-list (cdr note-list)))
561 (if (not (null? final-rests))
562 (set! verse-list (append verse-list
563 (list (make-verse #:text ""
564 #:notelist/rests (reverse! final-rests))))))
565 (if (not (null? note-list))
567 (warning (car note-list) "Missing lyrics: ~a ~a" context note-list)
568 (set! note-list '()))))
569 (let ((lyrics/skip (car lyrics/skip-list)))
570 (receive (notelist/rest note-list*) (if (lyrics? lyrics/skip)
571 (consume-lyrics-notes lyrics/skip note-list context)
572 (consume-skip-notes lyrics/skip note-list context))
573 (debug "Consumed notes" (list lyrics/skip notelist/rest))
574 (set! note-list note-list*)
576 ((null? notelist/rest)
579 ((and (lyrics? lyrics/skip)
583 (string-append (verse-text unfinished-verse) (lyrics-text lyrics/skip)))
584 (set-verse-notelist/rests!
586 (append (verse-notelist/rests unfinished-verse) (list notelist/rest)))
587 (if (not (lyrics-unfinished lyrics/skip))
588 (set! unfinished-verse #f)))
589 ((lyrics? lyrics/skip)
590 (let ((verse (make-verse #:text (if (rest? notelist/rest)
592 (lyrics-text lyrics/skip))
593 #:notelist/rests (list notelist/rest))))
594 (add! verse verse-list)
595 (set! unfinished-verse (if (lyrics-unfinished lyrics/skip) verse #f))))
599 ((rest? notelist/rest)
600 (if (null? verse-list)
601 (set! verse-list (list (make-verse #:text ""
602 #:notelist/rests (list notelist/rest))))
603 (let ((last-verse (last verse-list)))
604 (set-verse-notelist/rests!
606 (append (verse-notelist/rests last-verse) (list notelist/rest))))))
607 ((pair? notelist/rest)
608 (add! (make-verse #:text *skip-word* #:notelist/rests (list notelist/rest))
611 (error "Unreachable branch reached")))
612 (set! unfinished-verse #f)))
613 (if (not (rest? notelist/rest))
614 (set! lyrics/skip-list (cdr lyrics/skip-list)))))))
616 (set-verse-unfinished! unfinished-verse #t))
617 (set-score-notes-verse-block-list!
619 (append (score-notes-verse-block-list score)
620 (list (make-verse-block #:verse-list verse-list)))))
623 (define (consume-lyrics-notes lyrics note-list context)
624 ;; Returns list of note instances + new note-list.
625 (assert (lyrics? lyrics))
626 (if (and (not (null? note-list))
627 (rest? (car note-list)))
628 (values (car note-list) (cdr note-list))
629 (let ((ignore-melismata (lyrics-ignore-melismata lyrics))
633 (not (null? note-list)))
634 (let ((note (car note-list)))
635 (push! note consumed)
636 (let ((note-slur (note-joined note)))
638 (warning note "Slur underrun"))
639 (set! join (and (not ignore-melismata) (> note-slur 0)))))
640 (set! note-list (cdr note-list)))
642 (warning (safe-car (if (null? note-list) consumed note-list))
643 "Unfinished slur: ~a ~a" context consumed))
644 (values (reverse consumed) note-list))))
646 (define (consume-skip-notes skip note-list context)
647 ;; Returns either note list (skip word defined) or rest instance (no skip word) + new note-list.
648 (assert (skip? skip))
649 (let ((duration (skip-duration skip))
652 (while (and (> duration epsilon)
653 (not (null? note-list)))
654 (let ((note (car note-list)))
655 (assert (note? note))
656 (push! note consumed)
657 (set! duration (- duration (note-duration note))))
658 (set! note-list (cdr note-list)))
659 (set! consumed (reverse! consumed))
661 ((> duration epsilon)
662 (warning (if (null? note-list) (safe-last consumed) (safe-car note-list))
663 "Excessive skip: ~a ~a ~a ~a" context skip duration consumed))
664 ((< duration (- epsilon))
665 (warning (if (null? note-list) (safe-last consumed) (safe-car note-list))
666 "Skip misalignment: ~a ~a ~a ~a" context skip duration consumed)))
667 (values (if *skip-word*
672 (define (extract-verse-blocks score)
673 ;; Returns list of blocks and parallel blocks.
674 (debug "Extracting verse blocks" score)
676 ((score-voice? score)
677 (append-map extract-verse-blocks (score-voice-elements score)))
678 ((score-choice? score)
679 (list (make-parallel-blocks
680 #:block-list (map (lambda (block-list)
681 (make-sequential-blocks
682 #:block-list (append-map extract-verse-blocks block-list)))
683 (score-choice-lists score)))))
684 ((score-repetice? score)
685 (list (make-repeated-blocks #:count (score-repetice-count score)
686 #:block-list (append-map extract-verse-blocks
687 (score-repetice-elements score)))))
688 ((score-notes? score)
689 (list (make-parallel-blocks #:block-list (score-notes-verse-block-list score))))
691 (error "Invalid score element" score))))
693 (define (extract-verses score-list)
694 ;; Returns (final) list of verses.
695 ;; The primary purpose of this routine is to build complete stanzas from
696 ;; lists of verse blocks.
697 ;; Extract verse-blocks and process them until no unprocessed stanzas remain.
698 (debug "Final score list" score-list)
699 (let ((verse-block-list (debug "Verse blocks" (append-map extract-verse-blocks score-list))))
700 (letrec ((combine (lambda (lst-1 lst-2)
701 (debug "Combining lists" (list lst-1 lst-2))
704 (let ((diff (- (length lst-1) (length lst-2))))
706 (let ((last-elt (last lst-1)))
708 (add! last-elt lst-1)
709 (set! diff (+ diff 1))))
710 (let ((last-elt (last lst-2)))
712 (add! last-elt lst-2)
713 (set! diff (- diff 1)))))
714 (debug "Combined" (map append lst-1 lst-2))))))
715 (expand* (lambda (block)
717 ((parallel-blocks? block)
718 (append-map (lambda (block) (expand (list block)))
719 (parallel-blocks-block-list block)))
720 ((sequential-blocks? block)
721 (expand (sequential-blocks-block-list block)))
722 ((repeated-blocks? block)
723 ;; Only simple repetice without nested parallel sections is supported.
724 (let ((count (repeated-blocks-count block))
725 (expanded (expand (repeated-blocks-block-list block)))
727 (while (not (null? expanded))
730 (while (and (> count* 0) (not (null? expanded)))
731 (set! item (append item (car expanded)))
732 (set! expanded (cdr expanded))
733 (set! count* (- count* 1)))
734 (push! item expanded*)))
735 (reverse expanded*)))
737 (list (list block))))))
738 (expand (lambda (block-list)
739 (debug "Expanding list" block-list)
740 (if (null? block-list)
742 (debug "Expanded" (combine (expand* (car block-list))
743 (expand (cdr block-list)))))))
744 (merge (lambda (verse-list)
748 ((verse-unfinished (car verse-list))
749 (let ((verse-1 (first verse-list))
750 (verse-2 (second verse-list)))
751 (merge (cons (make-verse #:text (string-append (verse-text verse-1)
752 (verse-text verse-2))
753 #:notelist/rests (append (verse-notelist/rests verse-1)
754 (verse-notelist/rests verse-2))
755 #:unfinished (verse-unfinished verse-2))
756 (cddr verse-list)))))
758 (cons (car verse-list) (merge (cdr verse-list))))))))
759 (debug "Final verses" (merge (append-map (lambda (lst) (append-map verse-block-verse-list lst))
760 (expand verse-block-list)))))))
762 (define (handle-music music)
763 ;; Returns list of verses.
764 ;; The main analysis function.
766 (display-scheme-music music))
767 (let ((score-list (debug "Final raw notes" (get-notes music)))
768 (music-context-list (collect-lyrics-music music)))
769 (for-each (lambda (music-context)
770 (let ((context (music-context-context music-context)))
771 (set! *tempo-compression* #f)
772 (insert-lyrics! (get-lyrics (music-context-music music-context) context)
774 (debug "Final score list" score-list)))
776 (extract-verses score-list)))
782 (define festival-note-mapping '((0 "C") (1 "C#") (2 "D") (3 "D#") (4 "E") (5 "F") (6 "F#")
783 (7 "G") (8 "G#") (9 "A") (10 "A#") (11 "B")))
784 (define (festival-pitch pitch)
785 (let* ((semitones (ly:pitch-semitones pitch))
786 (octave (inexact->exact (floor (/ semitones 12))))
787 (tone (modulo semitones 12)))
788 (format #f "~a~a" (cadr (assoc tone festival-note-mapping))
789 (+ octave *base-octave* *base-octave-shift*))))
791 (define (write-header port tempo)
792 (let ((beats (or (tempo->beats tempo) 100)))
793 (format port "<?xml version=\"1.0\"?>
794 <!DOCTYPE SINGING PUBLIC \"-//SINGING//DTD SINGING mark up//EN\" \"Singing.v0_1.dtd\" []>
798 (define (write-footer port)
799 (format port "</SINGING>~%"))
801 (define (write-lyrics port music)
803 (for-each (lambda (verse)
804 (let ((text (verse-text verse))
805 (note/rest-list (verse-notelist/rests verse)))
806 (receive (rest-list note-listlist) (partition rest? note/rest-list)
807 (debug "Rest list" rest-list)
808 (debug "Note list" note-listlist)
809 (if (not (null? rest-list))
810 (set! rest-dur (+ rest-dur (apply + (map rest-duration rest-list)))))
811 (if (not (null? note-listlist))
815 (write-rest-element port rest-dur)
817 (write-lyrics-element port text note-listlist))))))
818 (handle-music music))
820 (write-rest-element port rest-dur))))
822 (define (write-lyrics-element port text slur-list)
823 (let ((fmt "~{~{~a~^+~}~^,~}")
824 (transform (lambda (function)
826 (let ((rests (filter rest? slur)))
827 (if (not (null? rests))
829 (warning (car rests) "Rests in a slur: ~a" slur)
830 (set! slur (remove rest? slur)))))
833 (format port "<DURATION BEATS=\"~@?\"><PITCH NOTE=\"~@?\">~a</PITCH></DURATION>~%"
834 fmt (transform note-duration)
835 fmt (transform (compose festival-pitch note-pitch))
838 (define (write-rest-element port duration)
839 (format port "<REST BEATS=\"~a\"></REST>~%" duration))