]> git.donarmstrong.com Git - lilypond.git/blob - scm/ly-syntax-constructors.scm
Doc: NR Update information for modern-cautionary
[lilypond.git] / scm / ly-syntax-constructors.scm
1 ;;;; This file is part of LilyPond, the GNU music typesetter.
2 ;;;;
3 ;;;; Copyright (C) 2006--2015 Erik Sandberg <mandolaerik@gmail.com>
4 ;;;;
5 ;;;; LilyPond is free software: you can redistribute it and/or modify
6 ;;;; it under the terms of the GNU General Public License as published by
7 ;;;; the Free Software Foundation, either version 3 of the License, or
8 ;;;; (at your option) any later version.
9 ;;;;
10 ;;;; LilyPond is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 ;;;; GNU General Public License for more details.
14 ;;;;
15 ;;;; You should have received a copy of the GNU General Public License
16 ;;;; along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
17
18 ;; TODO: use separate module for syntax
19 ;; constructors. Also create wrapper around the constructor?
20 (defmacro define-ly-syntax (args . body)
21   `(define ,args ,@body))
22
23 ;; A ly-syntax constructor can access location data as (*location*).
24 ;; This is mainly used for reporting errors and warnings. This
25 ;; function is a syntactic sugar which uses (*location*) to set the
26 ;; origin of the returned music object; this behaviour is usually
27 ;; desired.
28 (defmacro define-ly-syntax-loc (args . body)
29   `(define ,args
30      (let ((m ,(cons 'begin body)))
31        (set! (ly:music-property m 'origin) (*location*))
32        m)))
33
34 (define (music-function-call-error fun m)
35   (let* ((sig (ly:music-function-signature fun))
36          (pred (if (pair? (car sig)) (caar sig) (car sig))))
37     (ly:parser-error
38                      (format #f (_ "~a function cannot return ~a")
39                              (type-name pred)
40                              (value->lily-string m))
41                      (*location*))
42     (and (pair? (car sig)) (cdar sig))))
43
44 ;; Music function: Apply function and check return value.
45 ;; args are in reverse order, rest may specify additional ones
46 ;;
47 ;; If args is not a proper list, an error has been flagged earlier
48 ;; and no fallback value had been available.  In this case,
49 ;; we don't call the function but rather return the general
50 ;; fallback.
51 (define-ly-syntax (music-function fun args . rest)
52   (let* ((sig (ly:music-function-signature fun))
53          (pred (if (pair? (car sig)) (caar sig) (car sig)))
54          (good (proper-list? args))
55          (m (and good (apply (ly:music-function-extract fun)
56                              (reverse! args rest)))))
57     (if (and good (pred m))
58         (begin
59           (if (ly:music? m)
60               (set! (ly:music-property m 'origin) (*location*)))
61           m)
62         (if good
63             (music-function-call-error fun m)
64             (and (pair? (car sig)) (cdar sig))))))
65
66 (define-ly-syntax (argument-error n pred arg)
67   (ly:parser-error
68    (format #f
69            (_ "wrong type for argument ~a.  Expecting ~a, found ~s")
70            n (type-name pred) (music->make-music arg))
71    (*location*)))
72
73 (define-ly-syntax-loc (void-music)
74   (make-music 'Music))
75
76 (define-ly-syntax-loc (sequential-music mlist)
77   (make-sequential-music mlist))
78
79 (define-ly-syntax-loc (simultaneous-music mlist)
80   (make-simultaneous-music mlist))
81
82 (define-ly-syntax-loc (event-chord mlist)
83   (make-music 'EventChord
84               'elements mlist))
85
86 (define-ly-syntax-loc (unrelativable-music mus)
87   (make-music 'UnrelativableMusic
88               'element mus))
89
90 (define-ly-syntax-loc (context-change type id)
91   (make-music 'ContextChange
92               'change-to-type type
93               'change-to-id id))
94
95 (define-ly-syntax (tempo text . rest)
96   (let* ((unit (and (pair? rest)
97                     (car rest)))
98          (count (and unit
99                      (cadr rest)))
100          (range-tempo? (pair? count))
101          (tempo-change (make-music 'TempoChangeEvent
102                                    'origin (*location*)
103                                    'text text
104                                    'tempo-unit unit
105                                    'metronome-count count))
106          (tempo-set
107           (and unit
108                (context-spec-music
109                 (make-property-set 'tempoWholesPerMinute
110                                    (ly:moment-mul
111                                     (ly:make-moment
112                                      (if range-tempo?
113                                          (round (/ (+ (car count) (cdr count))
114                                                    2))
115                                          count)
116                                      1)
117                                     (ly:duration-length unit)))
118                 'Score))))
119
120     (if tempo-set
121         (make-sequential-music (list tempo-change tempo-set))
122         tempo-change)))
123
124 (define-ly-syntax-loc (repeat type num body alts)
125   (make-repeat type num body alts))
126
127 (define (script-to-mmrest-text music)
128   "Extract @code{'direction} and @code{'text} from @var{music}, and transform
129 into a @code{MultiMeasureTextEvent}."
130
131   (if (music-is-of-type? music 'script-event)
132       (make-music 'MultiMeasureTextEvent music)
133       music))
134
135 (define-ly-syntax-loc (multi-measure-rest duration articulations)
136   (make-music 'MultiMeasureRestMusic
137               'articulations (map script-to-mmrest-text articulations)
138               'duration duration))
139
140 (define-ly-syntax-loc (repetition-chord duration articulations)
141   (make-music 'EventChord
142               'duration duration
143               'elements articulations))
144
145 (define-ly-syntax-loc (context-specification type id ops create-new mus)
146   (let ((csm (context-spec-music mus type id)))
147     (set! (ly:music-property csm 'property-operations) ops)
148     (if create-new (set! (ly:music-property csm 'create-new) #t))
149     csm))
150
151 (define-ly-syntax (composed-markup-list commands markups)
152   ;; `markups' being a list of markups, eg (markup1 markup2 markup3),
153   ;; and `commands' a list of commands with their scheme arguments, in reverse order,
154   ;; eg: ((italic) (raise 4) (bold)), maps the commands on each markup argument, eg:
155   ;;  ((bold (raise 4 (italic markup1)))
156   ;;   (bold (raise 4 (italic markup2)))
157   ;;   (bold (raise 4 (italic markup3))))
158
159   (define (compose arg)
160     (fold
161      (lambda (cmd prev) (append cmd (list prev)))
162      arg
163      commands))
164   (let loop ((markups markups) (completed '()))
165     (cond ((null? markups) (reverse! completed))
166           ((markup? (car markups))
167            (loop (cdr markups)
168                  (cons (compose (car markups)) completed)))
169           (else
170            (call-with-values
171                (lambda () (break! markup? markups))
172              (lambda (complex rest)
173                (loop rest
174                      (reverse!
175                       (make-map-markup-commands-markup-list
176                        compose complex) completed))))))))
177
178 (define-ly-syntax (property-operation ctx music-type symbol . args)
179   (let* ((props (case music-type
180                   ((PropertySet) (list 'value (car args)))
181                   ((PropertyUnset) '())
182                   ((OverrideProperty) (list 'grob-value (car args)
183                                             'grob-property-path (if (list? (cadr args))
184                                                                     (cadr args)
185                                                                     (cdr args))
186                                             'pop-first #t))
187                   ((RevertProperty)
188                    (if (list? (car args))
189                        (list 'grob-property-path (car args))
190                        (list 'grob-property-path args)))
191                   (else (ly:error (_ "Invalid property operation ~a") music-type))))
192          (m (apply make-music music-type
193                    'symbol symbol
194                    'origin (*location*)
195                    props)))
196     (make-music 'ContextSpeccedMusic
197                 'element m
198                 'context-type ctx
199                 'origin (*location*))))
200
201 (define (get-first-context-id! mus)
202   "Find the name of a ContextSpeccedMusic, possibly naming it"
203   (let ((id (ly:music-property mus 'context-id)))
204     (if (eq? (ly:music-property mus 'name) 'ContextSpeccedMusic)
205         (if (and (string? id)
206                  (not (string-null? id)))
207             id
208             ;; We may reliably give a new context a unique name, but
209             ;; not an existing one
210             (if (ly:music-property mus 'create-new #f)
211                 (let ((id (get-next-unique-voice-name)))
212                   (set! (ly:music-property mus 'context-id) id)
213                   id)
214                 '()))
215         '())))
216
217 (define unique-counter -1)
218 (define (get-next-unique-voice-name)
219   (set! unique-counter (1+ unique-counter))
220   (call-with-output-string (lambda (p) (format p "uniqueContext~s" unique-counter))))
221
222 (define-ly-syntax-loc (lyric-event text duration)
223   (make-lyric-event text duration))
224
225 (define (lyric-combine-music sync sync-type music loc)
226   ;; CompletizeExtenderEvent is added following the last lyric in MUSIC
227   ;; to signal to the Extender_engraver that any pending extender should
228   ;; be completed if the lyrics end before the associated voice.
229   (append! (ly:music-property music 'elements)
230            (list (make-music 'CompletizeExtenderEvent)))
231   (make-music 'LyricCombineMusic
232               'element music
233               'associated-context sync
234               'associated-context-type sync-type
235               'origin loc))
236
237 (define-ly-syntax (lyric-combine voice typ music)
238   (lyric-combine-music voice typ music (*location*)))
239
240 (define-ly-syntax (add-lyrics music addlyrics-list)
241   (let* ((existing-voice-name (get-first-context-id! music))
242          (voice-name (if (string? existing-voice-name)
243                          existing-voice-name
244                          (get-next-unique-voice-name)))
245          (voice (if (string? existing-voice-name)
246                     music
247                     (make-music 'ContextSpeccedMusic
248                                 'element music
249                                 'context-type 'Voice
250                                 'context-id voice-name
251                                 'origin (ly:music-property music 'origin))))
252          (voice-type (ly:music-property voice 'context-type))
253          (lyricstos (map (lambda (mus)
254                            (let* ((loc (ly:music-property mus 'origin))
255                                   (lyr (lyric-combine-music
256                                         voice-name voice-type mus loc)))
257                              (make-music 'ContextSpeccedMusic
258                                          'create-new #t
259                                          'context-type 'Lyrics
260                                          'element lyr
261                                          'origin loc)))
262                          addlyrics-list)))
263     (make-simultaneous-music (cons voice lyricstos))))