]> git.donarmstrong.com Git - lilypond.git/blob - ly/articulate.ly
Include and document the Articulate script by Peter Chubb.
[lilypond.git] / ly / articulate.ly
1 %
2 % Copyright (C) 2008, 2009, 2010, 2011 NICTA
3 % Author: Peter Chubb <peter.chubb AT nicta.com.au>
4 % $Id: articulate.ly,v 1.7 2011-03-24 00:40:00 peterc Exp $
5 %
6 %
7 %  This program is free software; you can redistribute it and/or modify
8 %  it under the terms of the GNU General Public License, version 3,
9 %  as published by the Free Software Foundation.
10 %
11 %  WARNING: this file under GPLv3 only, not GPLv3+
12 %
13 %  This program 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.
16 %  See the GNU General Public License for more details.  It is
17 %  available in the Lilypond source tree, or at
18 %  http://www.gnu.org/licenses/gpl-3.0.html
19 %
20 % This script tries to make MIDI output from LilyPond a little more realistic.
21 % It tries to take articulations (slurs, staccato, etc) into account, by
22 % replacing notes  with sequential music of suitably time-scaled note plus
23 % skip.
24 %
25 % It also tries to unfold trills turns etc., and take rallentendo
26 % and accelerando into account.
27 %
28 % As my scheme knowledge is poor (I was teaching myself as I went), there
29 % is much scope for improvement.
30
31 %
32 %%%USAGE
33 % In the \score section do:
34 %  \unfoldRepeats \articulate <<
35 %       all the rest of the score
36 % >>
37 % or use the lilywrap script.
38 %
39 % TO DO (prioritised, the ones that'll make the most difference first)
40 %
41 % * Dynamics.
42 %   * Fix quantisation for dynamics on single note (replace note
43 %     with tied 128th notes?) -- started, needs work.
44 %   * Make \fp do the right thing (loud start, then quiet).
45 %
46 % * Inegalite.  Notes on-beat steal time from notes off-beat.
47 %   Degree of stealing is a parameter: from 1.0 (straight)
48 %   to 1.5 (extreme swing).  Also fix tenuto to use this.
49 %
50 % * add accel (to match rall), and molto rall. I've never seen
51 %   molto accel but some composer somewhere has probably used it.
52 %
53 % * Fermata, and Fermata Lunga
54 % * Add more synonyms for accel and rall: rit ritard stringendo
55 %
56 % * Phrasing.
57 %   * Rall at end of piece
58 %   * Very slight accel into a phrase, rall out of it.
59 %   * Dynamics during a phrase????  unclear how these should be in
60 %     the general case
61 %
62 % * Trill algorithm needs work.
63 %
64 % * Cope with more ornaments/articulations.
65 %    inverted-turns, etc.
66 %   -- accent needs better control of dynamics.
67 %   -- Articulations: mezzo-staccato, portato.
68 %   -- Handling of generic ornaments (in lily, `\stopped'; in
69 %               most early music:  ornament this note (trill, turn
70 %               or mordent as the player wishes))
71 % * Automatic gruppetto at end of trill; better handling of
72 %      initial/final grace notes on trill
73 % * Automatic ornaments.
74 %   * Spot cadences and ornament
75 %   * Look for quaver-dotted note for trills, for example.
76 %   * Fill in steps. (Needs lookahead/lookbehind.)
77 % * `afterturn' -- a turn after the start of a note.
78 % * accidentals for trills and turns
79
80 % CHANGELOG
81 %  * From Iain Nicol: appoggiatura timings were out; add staccatissimo; fix
82 %    trillSpanner endpoints.
83 %  * Also handle Breathing events (by throwing them away).  This isn't ideal;
84 %    one should really shorten the note before a little.  But I don't know
85 %    how to do lookahead in scheme.
86 %  * Also ignore explicit line breaks.
87 %  * Add Mordents (reported by Patrick Karl)
88 %
89
90 #(use-modules (ice-9 debug))
91 #(use-modules (scm display-lily))
92
93 % PARAMETERS
94 % How much to compress notes marked Staccato.  CPE Bach says `as short as
95 % may conveniently be played, as if the keys were too hot to touch'.
96 % Most modern sources say 1/2 the notated length of a note.
97 #(define ac:staccatoFactor '(1 . 2))
98
99 % How much to compress notes marked staccatissimo.
100 #(define ac:staccatissimoFactor '(1 . 4))
101
102 % And tenuto (if we ever implement time stealing, this should be >1.0)
103 #(define ac:tenutoFactor '(1 . 1))
104
105 % How much to articulate normal notes.  CPE Bach says 1/2 (and
106 % staccato should be `as short as may conveniently be played') but this
107 % sounds too short for modern music.  7/8 sounds about right.
108 #(define ac:normalFactor '(7 . 8))
109
110 % How much to slow down for a rall. or a poco rall.
111 % (or speed up for accel or poco accel)
112 #(define ac:rallFactor (ly:make-moment 60 100)) % 40% slowdown
113 #(define ac:pocoRallFactor (ly:make-moment 90 100)) % 10% slowdown
114
115 % The absolute time for a twiddle in a trill, in minutes.
116 % Start with 1/4 seconds == 1/240 minutes
117 #(define ac:maxTwiddleTime (ly:make-moment 1 240))
118
119
120 % Internal variables, don't touch.
121 % (should probably be part of a context somehow)
122
123 % Whether to slur, or not
124 #(define ac:inSlur #f)
125 #(define ac:inPhrasingSlur #f)
126
127 % Whether the current noteevent is in a trill spanner
128 #(define ac:inTrill #f)
129
130 % assume start in C major.  Key change events override this.
131 % Could get from context, but don't know how.
132 #(define ac:current-key (make-music
133           'KeyChangeEvent
134           'tonic
135           (ly:make-pitch -1 0 0)
136           'pitch-alist
137           (list (cons 0 0)
138                 (cons 1 0)
139                 (cons 2 0)
140                 (cons 3 0)
141                 (cons 4 0)
142                 (cons 5 0)
143                 (cons 6 0))))
144
145
146 #(define ac:currentTempo (ly:make-moment 15 1)) % 4 = 60, measured wholes per minute
147 #(define ac:lastTempo ac:currentTempo) % for 'a tempo' or 'tempo I'
148
149 % The duration of the current note.  Start at a crotchet
150 % for no good reason.
151 #(define ac:currentDuration (ly:make-duration 2 0 1 1))
152
153 % Debugging: display a moment plus some text.
154 % Returns its moment argument so can be used in-line.
155 #(define (display-moment  text m)
156   (display text)
157   (display (list (ly:moment-main-numerator m) "/" (ly:moment-main-denominator m)))
158   m
159 )
160
161 % Track tempo (and maybe later, other context properties)
162 % as they change.  Needs to better cope with saving only Tempo I,
163 % otherwise "a tempo" goes back to the tempo before the last change.
164 #(define (ac:adjust-props sym music)
165   (case sym
166    ((tempoWholesPerMinute)
167     (set! ac:currentTempo (ly:music-property music 'value))
168     (set! ac:lastTempo ac:currentTempo)
169   )))
170
171 % Raise note one step in the current diatonic scale.
172 #(define (ac:up note)
173   (let* ((pitch (ly:music-property note 'pitch))
174          (notename (ly:pitch-notename pitch))
175          (new-notename (if (eq? notename 6) 0 (+ 1 notename)))
176          (alterations (ly:music-property ac:current-key 'pitch-alist))
177          (new-alteration (cdr (assq new-notename alterations)))
178          (new-octave (if (eq? new-notename 0) (+ 1 (ly:pitch-octave pitch))
179                       (ly:pitch-octave pitch)))
180        )
181    (set! (ly:music-property note 'pitch)(ly:make-pitch new-octave new-notename new-alteration))))
182
183
184 % Lower note one step in the current diatonic scale.
185 #(define (ac:down note)
186   (begin  (let* ((pitch (ly:music-property note 'pitch))
187          (notename (ly:pitch-notename pitch))
188          (new-notename (if (eq? notename 0) 6 (- notename 1)))
189          (alterations (ly:music-property ac:current-key 'pitch-alist))
190          (new-alteration (cdr (assq new-notename alterations)))
191          (new-octave (if (eq? new-notename 6) (- (ly:pitch-octave pitch) 1)
192                       (ly:pitch-octave pitch)))
193        )
194    (set! (ly:music-property note 'pitch)(ly:make-pitch new-octave new-notename new-alteration))))
195 )
196
197 % Shorten a note, and save the note's original duration in ac:currentDuration
198 #(define (ac:articulate-one-note m fraction)
199   "Replace m with m*fraction"
200   (if  (eq? 'NoteEvent (ly:music-property m 'name))
201    (let*
202     ((dur (ly:music-property m 'duration))
203      (l (ly:duration-log dur))
204      (d (ly:duration-dot-count dur))
205      (factor (ly:duration-factor dur))
206      (num (car fraction))
207      (denom (cdr fraction)))
208     (begin
209      (set! ac:currentDuration dur)
210      (set! (ly:music-property m 'duration)
211       (ly:make-duration l d
212        (* num (car factor))
213        (* denom (cdr factor))))))
214    m))
215
216 % helper routine to set duration.
217 #(define (ac:setduration music duration)
218   "Set a note's duration."
219   (let ((eventtype (ly:music-property music 'name)))
220    (if
221     (or
222      (eq? eventtype 'NoteEvent)
223      (eq? eventtype 'RestEvent)
224      (eq? eventtype 'SkipEvent))
225     (set! (ly:music-property music 'duration) duration))))
226
227 % Add an articulation event to a note.
228 % Used in afterGrace to mark all notes as tenuto, so they're not shortened
229 #(define (ac:add-articulation type music)
230     (music-map (lambda (m)
231                 (if (eq? 'EventChord (ly:music-property m 'name))
232                  (set! (ly:music-property m 'elements)
233                   (append (ly:music-property m 'elements)
234                    (list (make-music 'ArticulationEvent 'articulation-type type)))))
235                 m)
236      music))
237
238 % Convert a long note to an equivalent set of short notes, tied together.
239 % This is needed to get smooth dynamics changes.
240 % Need to deal properly with stuff other than the notes (dynamics, markup etc)
241 % Still experimental, so disabled for now.
242 #(define (ac:to128 music) music)
243
244 #(define (ac:to128_disabled music)
245   (if (or (eq? 'SkipEvent (ly:music-property music 'name))
246         (eq? 'NoteEvent (ly:music-property music 'name)))
247    (let* ((dur (ly:music-property music 'duration))
248           (log2 (ly:duration-log dur))
249          (shiftcount (- 6 log2))
250          (lastm (ly:music-deep-copy (shift-duration-log music shiftcount 0))))
251    (set! (ly:music-property music 'elements)
252     (cons (make-music 'TieEvent) (ly:music-property music 'elements)))
253    (make-sequential-music (list
254                            (make-repeat "unfold" (1- (expt 2 shiftcount))
255                             (make-sequential-music (list music)) '())
256                            lastm)))
257  music))
258
259
260 % absolute time in minutes of a length of music, as a rational number (moment)
261 #(define (ac:abstime music)
262   (ly:moment-div (ly:music-length music) ac:currentTempo))
263
264 % convert absolute time (in minutes) to a moment in the current tempo
265 #(define (ac:abs->mom m)
266   (ly:moment-mul m ac:currentTempo))
267
268
269 % a moment that is ac:maxTwiddletime seconds at the current tempo.
270 #(define (ac:targetTwiddleTime)
271   (ac:abs->mom ac:maxTwiddleTime))
272
273
274 % Nearest twiddletime (in minutes) achievable with power-of-2 divisions of
275 % the original music.  (twiddletime is the time for one pair of notes
276 % in a trill)
277 % If the music has a precomputed twiddletime (e.g., from \afterGrace) use that.
278 #(define (ac:twiddletime music)
279   (let* ((tr (filter (lambda (x)
280                      (and (eq? 'ArticulationEvent (ly:music-property x 'name))
281                       (string= "trill" (ly:music-property x 'articulation-type))))
282               (ly:music-property music 'elements)))
283          (pre-t (if (pair? tr) (ly:music-property (car tr) 'twiddle)
284                  '()))
285          (t (ac:targetTwiddleTime)))
286    (if (ly:moment? pre-t)
287     pre-t
288     (let loop ((len (ly:music-length music)))
289      (if (ly:moment<? t len)
290       (loop (ly:moment-mul len (ly:make-moment 1 2)))
291       len)))))
292
293
294
295 % Note: I'm assuming early music practice of starting on the auxiliary note.
296 % Needs to add gruppetto if it's a long trill (TODO)
297 #(define (ac:trill music)
298   " Replace music with time-compressed repeats of the music,
299     maybe accelerating if the length is longer than a crotchet "
300   (let* ((hemisemidur (ly:make-duration 5 0 1 1))
301          (orig-len  (ly:music-length music))
302          (t (ac:twiddletime music))
303          (uppernote '())
304          (note_moment (ly:moment-mul t (ly:make-moment 1 2)))
305          (c1 (ly:moment-div orig-len note_moment))
306          (c2 (inexact->exact
307               (round (/ (ly:moment-main-numerator c1)
308                       (* 2 (ly:moment-main-denominator c1))))))
309          (count (if (< c2 2) 2 c2)))
310
311    (set! (ly:music-property music 'elements)
312     (filter (lambda (y) (eq? 'NoteEvent (ly:music-property y 'name)))
313      (ly:music-property music 'elements)))
314    (map (lambda (y) (ac:setduration y hemisemidur))
315     (ly:music-property music 'elements))
316    (set! uppernote (ly:music-deep-copy music))
317    (map (lambda (y) (ac:up y))
318     (filter
319      (lambda (z) (eq? 'NoteEvent (ly:music-property z 'name)))
320      (ly:music-property uppernote 'elements)))
321
322    (let* ((trillMusicElements
323           (let loop ((so_far (list uppernote music))
324                      (c count))
325            (if (> c 1)
326             (loop (append (list (ly:music-deep-copy uppernote) (ly:music-deep-copy music)) so_far) (1- c))
327             so_far)))
328           (trillMusic (make-sequential-music trillMusicElements))
329           (newlen (ly:music-length trillMusic))
330           (factor (ly:moment-div  orig-len newlen)))
331     (ly:music-compress trillMusic factor)
332 ; accelerating the music seems to put lily into an infinite loop in
333 ; its layout and midi engines.
334 ;    (let* ((realfactor (exp (* (/ 1.0 count) (log 0.75))))
335 ;          (factor (ly:make-moment (inexact->exact (round (* 1024 realfactor)))
336 ;                   1024)))
337 ;     (ac:accel trillMusic factor))
338  )))
339
340
341
342
343 % If there's an articulation, use it.
344 % If in a slur, use (1 . 1) instead.
345 % Treat phrasing slurs as slurs, but allow explicit articulation.
346 % (Maybe should treat staccato under a phrasing slur as mezzo-staccato?)
347 %
348 % Expect an EventChord.
349 %
350 % trills, turns, ornaments etc.  are also treated as Articulations.
351 % Split into two functions:
352 %  ac:getactions traverses the elements in the EventChord
353 %               and calculates the parameters.
354 %  ac:articulate-chord applies the actions to each NoteEvent in
355 %               the EventChord.
356 #(define (ac:getactions music)
357   (let  loop ((factor ac:normalFactor)
358               (newelements '())
359               (es (ly:music-property music 'elements))
360               (actions '()))
361    (if (null? es)
362     (begin
363      (set! (ly:music-property music 'elements) (reverse newelements))
364      (cond
365       (ac:inTrill (cons 'trill actions))
366       ((and (eq? factor ac:normalFactor) (or ac:inSlur ac:inPhrasingSlur))
367        (append actions (list 'articulation  '(1 . 1)) ))
368       (else (append actions (list 'articulation  factor)))))
369     ; else part
370     (let ((e (car es))
371           (tail (cdr es)))
372      (case (ly:music-property e 'name)
373
374       ((BeamEvent) ; throw away beam events, or they'll be duplicated by turn or trill
375        (loop factor newelements tail actions))
376       ((LineBreakEvent) ; pass through linebreak events.
377        (loop (cons 1 1) (cons e newelements) tail actions))
378       ((FingeringEvent) ; and fingering events too.
379        (loop factor newelements tail actions))
380
381       ((BreathingEvent) ; throw away BreathingEvent ---
382        ; should really shorten previous note a little.
383        (loop (cons 1 1) (cons e newelements) tail actions))
384
385       ((TieEvent)
386        (loop (cons 1 1) (cons e newelements) tail actions))
387
388       ((SkipEvent)
389        (loop (cons 1 1) (cons e newelements) tail actions))
390
391       ((RestEvent)
392        (loop (cons 1 1) (cons e newelements) tail actions))
393
394       ((ArticulationEvent)
395        (let ((articname (ly:music-property e 'articulation-type)))
396         ; TODO: add more here
397         (cond
398          ((string= articname "staccato")
399           (loop ac:staccatoFactor newelements tail actions))
400          ((string= articname "staccatissimo")
401           (loop ac:staccatissimoFactor newelements tail actions))
402          ((string= articname "tenuto")
403           (loop ac:tenutoFactor newelements tail actions))
404          ((string= articname "mordent")
405           (loop (cons 1 1) newelements tail (cons 'mordent actions)))
406          ((string= articname "prall")
407           (loop (cons 1 1) newelements tail (cons 'trill actions)))
408          ((string= articname "trill")
409           (loop (cons 1 1) newelements tail (cons 'trill actions)))
410          ((string= articname "turn")
411           (loop (cons 1 1) newelements tail (cons 'turn actions)))
412          (else (loop factor (cons e newelements) tail actions)))))
413
414       ((TextScriptEvent)
415        (let ((t (ly:music-property e 'text)))
416         (if (not (string? t))
417          (loop factor (cons e newelements) tail actions)
418          (begin
419           (cond
420            ((or
421              (string= t "rall")
422              (string= t "Rall")
423              (string= t "rit.")
424              (string= t "rall."))
425             (loop factor (cons e newelements) tail (cons 'rall actions)))
426            ((or
427              (string= t "poco rall.")
428              (string= t "poco rit."))
429             (loop factor (cons e newelements) tail (cons 'pocoRall actions)))
430            ((or (string= t "a tempo")
431              (string= t "tempo I"))
432           (loop factor (cons e newelements) tail (cons 'aTempo actions)))
433            (else (loop factor (cons e newelements) tail actions)))))))
434
435       ((SlurEvent)
436        (let ((direction (ly:music-property e 'span-direction)))
437         (set! ac:inSlur (eq? direction -1))
438         (loop factor newelements tail actions)))
439
440       ((TrillSpanEvent)
441        (let ((direction (ly:music-property e 'span-direction)))
442         (set! ac:inTrill (eq? direction -1))
443         (if ac:inTrill
444          (loop factor newelements tail (cons 'trill actions))
445          (loop factor (cons e newelements) tail actions))))
446
447       ((PhrasingSlurEvent)
448        (let ((direction (ly:music-property e 'span-direction)))
449         (set! ac:inPhrasingSlur (eq? direction -1))
450         (loop factor newelements tail actions)))
451
452       (else (loop factor (cons e newelements) tail actions)))))))
453
454
455
456 #(define (ac:articulate-chord music)
457   (begin
458    (cond
459
460     ((eq? 'EventChord (ly:music-property music 'name))
461      (let loop ((actions (ac:getactions music)))
462       (if (null? actions)
463         (if (ly:moment> (ly:music-length music) (make-moment 1 4))
464          (ac:to128  music)
465          music)
466
467       (case (car actions)
468
469        ((articulation)
470         (map
471          (lambda (x) (ac:articulate-one-note x (cadr actions)))
472          (ly:music-property music 'elements))
473         (let*
474          ((num (caadr actions))
475           (denom (cdadr actions))
476           (mult (ly:duration-factor ac:currentDuration))
477           (newnum (* (- denom num) (car mult)))
478           (newdenom (* (cdr mult) denom))
479           (len (ly:duration-log ac:currentDuration))
480           (dots (ly:duration-dot-count ac:currentDuration)))
481
482          (if (not (eq? num denom))
483           (make-sequential-music
484            (list (ac:to128 music)
485            (make-music 'EventChord 'elements
486             (list
487              (make-music 'RestEvent 'duration (ly:make-duration len dots newnum newdenom))))))
488           music)))
489
490        ((rall)
491         (set! ac:currentTempo (ly:moment-mul ac:currentTempo ac:rallFactor))
492         (let ((pset (make-music 'PropertySet
493            'value
494            ac:currentTempo
495            'symbol
496            'tempoWholesPerMinute)))
497          (if (null? (cdr actions))
498           (make-sequential-music (list pset music))
499           (make-sequential-music
500            (list pset (loop (cdr actions)))))))
501
502        ((pocoRall)
503         (set! ac:currentTempo (ly:moment-mul ac:currentTempo ac:pocoRallFactor))
504         (let ((pset (make-music 'PropertySet
505            'value
506            ac:currentTempo
507            'symbol
508            'tempoWholesPerMinute)))
509          (if (null? (cdr actions))
510           (make-sequential-music (list pset music))
511           (make-sequential-music
512            (list pset (loop (cdr actions)))))))
513
514        ((aTempo)
515         (set! ac:currentTempo ac:lastTempo)
516         (let ((pset (make-music 'PropertySet
517            'value
518            ac:currentTempo
519            'symbol
520            'tempoWholesPerMinute)))
521          (if (null? (cdr actions))
522           (make-sequential-music (list pset music))
523           (make-sequential-music
524            (list pset (loop (cdr actions)))))))
525
526        ((trill)
527          (ac:trill music))
528
529        ((mordent)
530         (let*
531          ((dur (ly:music-property
532                 (car (ly:music-property music 'elements)) 'duration))
533           (factor (ly:duration-factor dur))
534           (gracenote (ly:music-deep-copy music))
535           (mainnote (ly:music-deep-copy music))
536           (belownote (ly:music-deep-copy music))
537           (mordent (make-sequential-music (list gracenote belownote)))
538 )
539          (begin
540           (music-map (lambda (n)
541            (if (eq? 'NoteEvent (ly:music-property n 'name))
542             (set! (ly:music-property n 'duration)(ly:make-duration 3 0 1 1)))
543                       n)
544            mordent)
545           (map (lambda (y) (ac:down y))
546            (filter
547             (lambda (z) (eq? 'NoteEvent (ly:music-property z 'name)))
548             (ly:music-property belownote 'elements)))
549           (make-sequential-music (list (make-grace-music mordent) mainnote)))))
550        ((turn)
551         (let*
552          ((dur (ly:music-property
553                 (car (ly:music-property music 'elements)) 'duration))
554           (factor (ly:duration-factor dur))
555           (newdur (ly:make-duration (+ (ly:duration-log dur) 2)
556                    (ly:duration-dot-count dur) (car factor)(cdr factor))))
557          (begin
558           (map (lambda (y) (ac:setduration y newdur))
559            (ly:music-property music 'elements))
560           (let* ((above (ly:music-deep-copy music))
561                  (below (ly:music-deep-copy music))
562                  (newmusic (make-sequential-music (list above music below music))))
563            (begin
564             (map (lambda (y) (ac:down y))
565              (filter
566               (lambda (z) (eq? 'NoteEvent (ly:music-property z 'name)))
567               (ly:music-property below 'elements)))
568             (map (lambda (y) (ac:up y))
569              (filter
570               (lambda (z) (eq? 'NoteEvent (ly:music-property z 'name)))
571               (ly:music-property above 'elements)))
572             newmusic)))))
573      ))))
574
575     ((eq? 'KeyChangeEvent (ly:music-property music 'name))
576      (set! ac:current-key music)
577      music
578    )
579
580     ((eq? 'PropertySet (ly:music-property music 'name))
581      (ac:adjust-props (ly:music-property music 'symbol) music)
582      music)
583
584     (else  music))
585  ))
586
587
588
589 % At last ... here's the music function that aplies all the above to a
590 % score.
591 articulate = #(define-music-function (parser location music)
592                (ly:music?)
593                "Adjust times of note to add tenuto, staccato and
594                 normal articulations.
595                 "
596                (music-map ac:articulate-chord music)
597                )
598
599
600 % Override \afterGrace to be in terms of audio, not spacing.
601 % Special handling for a gruppetto after a trill.
602 afterGrace =
603 #(define-music-function
604   (parser location main grace)
605   (ly:music? ly:music?)p
606
607   (let*
608    ((main-length (ly:music-length main))
609     (grace-orig-length (ly:music-length grace))
610     (gracelen (ac:twiddletime main))
611     (grace-factor (ly:moment-div gracelen grace-orig-length))
612     (new-main-length (ly:moment-sub main-length gracelen))
613     (factor (ly:moment-div new-main-length main-length))
614   )
615    (map (lambda (y) (set! (ly:music-property y 'twiddle) gracelen))
616          (filter (lambda (z)
617                   (and
618                    (eq? 'ArticulationEvent (ly:music-property z 'name))
619                    (string= "trill" (ly:music-property z 'articulation-type))))
620           (ly:music-property main 'elements)))
621    (ac:add-articulation "tenuto" grace)
622    (make-sequential-music  (list (ly:music-compress main factor) (ly:music-compress grace grace-factor)))))
623
624 % An appoggiatura takes half the duration of the main note,
625 % or 1/3 if the note is dotted (i.e., half the undotted equivalent time)
626 % Somewhere around the end of the 19th, start of 20th century the rules
627 % changed, but my main interest is early music.
628 appoggiatura =
629 #(define-music-function (parser location grace main)
630   (ly:music? ly:music?)
631   (let* ((maindur (ly:music-length main))
632          (grace-orig-len (ly:music-length grace))
633          (main-orig-len (ly:music-length main))
634          (numerator (ly:moment-main-numerator maindur))
635          (factor (if (eq? (remainder numerator 3) 0)
636                   (ly:make-moment 1 3) (ly:make-moment 1 2))))
637    (ly:music-compress grace
638     (ly:moment-mul factor (ly:moment-div main-orig-len grace-orig-len)))
639    (ly:music-compress main (ly:moment-sub (ly:make-moment 1 1) factor))
640
641     (set! (ly:music-property grace 'elements)
642      (append (ly:music-property grace 'elements)
643       (list (make-music 'SlurEvent 'span-direction -1))))
644     (set! (ly:music-property main 'elements)
645      (append (ly:music-property main 'elements)
646       (list (make-music 'SlurEvent 'span-direction 1))))
647      (make-sequential-music (list grace main))))
648