% % Copyright (C) 2008, 2009, 2010, 2011 NICTA % Author: Peter Chubb % $Id: articulate.ly,v 1.7 2011-03-24 00:40:00 peterc Exp $ % % % This program is free software; you can redistribute it and/or modify % it under the terms of the GNU General Public License, version 3, % as published by the Free Software Foundation. % % WARNING: this file under GPLv3 only, not GPLv3+ % % This program is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. % See the GNU General Public License for more details. It is % available in the Lilypond source tree, or at % http://www.gnu.org/licenses/gpl-3.0.html % % This script tries to make MIDI output from LilyPond a little more realistic. % It tries to take articulations (slurs, staccato, etc) into account, by % replacing notes with sequential music of suitably time-scaled note plus % skip. % % Trills, turns, mordents and pralls are expanded with rallentendo % and accelerando taken into account. % % As my scheme knowledge is poor (I was teaching myself as I went), there % is much scope for improvement. % See: http://nicta.com.au/people/chubbp/articulate for additional % information about how the articulate function works. %%% Supported items: % Articulations on a single note (staccato, staccatissimo, portato, tenuto). % Slurs and phrasing slurs. % Ornaments (i.e. mordents, trills, turns). % Rallentando, accelerando, ritard and 'a tempo'. % % Please refer to 'MIDI output' (Section 3.5) in the Notation Reference % Manual for a more detailed list of supported items. %%% Technical Details: % * Any note not under a slur or phrasing slur, and not marked with an % explicit articulation, is shortened by ac:normalFactor (default 7/8). % (Shortening a note means replacing the note with a note of a smaller % duration, and a rest to make up for the difference between the durations % of the original and the shortened note.) % * Notes marked with articulations are shortened by factors specific to the % articulation as follows: % staccato not under a slur: ac:staccatoFactor (default 1/2) % under a slur: ac:portatoFactor (default 3/4) % staccatissimo ac:staccatissimoFactor (default 1/4) % portato ac:portatoFactor (default 3/4) % tenuto ac:tenutoFactor (default 1/1 - by default, notes marked % tenuto are not shortened) % * Appoggiaturas are made to take half the value of the note following, % without taking dots into account (so in \appoggiatura c8 d2. the c % will take the time of a crotchet). % * Trills and turns are expanded. The algorithm tries to choose notes % within the time of the current tempo that lead to each twiddle being % around 1/8 seconds; this can be adjusted with the ac:maxTwiddleTime % variable. % * Rall, poco rall and a tempo are observed. It'd be fairly trivial to % make accel. and stringendo and so on work too. % %%%USAGE % In the \score section do: % \articulate << % all the rest of the score % >> % or use the lilywrap script. % % TO DO: % % * Dynamics. % * Fix quantisation for dynamics on single note (replace note % with tied 128th notes?) -- started, needs work. % * Make \fp do the right thing (loud start, then quiet). % % * Inegalite. Notes on-beat steal time from notes off-beat. % Degree of stealing is a parameter: from 1.0 (straight) % to 1.5 (extreme swing). Also fix tenuto to use this. % % * add accel (to match rall), and molto rall. I've never seen % molto accel but some composer somewhere has probably used it. % % * Fermata, and Fermata Lunga % * Add more synonyms for accel and rall: rit ritard stringendo % % * Phrasing. % * Rall at end of piece % * Very slight accel into a phrase, rall out of it. % * Dynamics during a phrase???? unclear how these should be in % the general case % % * Trill algorithm needs work. % % * Cope with more ornaments/articulations. % inverted-turns, etc. % -- accent needs better control of dynamics. % -- Handling of generic ornaments (in lily, `\stopped'; in % most early music: ornament this note (trill, turn % or mordent as the player wishes)) % * Automatic gruppetto at end of trill; better handling of % initial/final grace notes on trill % * Automatic ornaments. % * Spot cadences and ornament % * Look for quaver-dotted note for trills, for example. % * Fill in steps. (Needs lookahead/lookbehind.) % * `afterturn' -- a turn after the start of a note. % * accidentals for trills and turns % CHANGELOG % * Heikki Tauriainen: handle also the \portato articulation (both as an % explicit articulation, and as the articulation to use for slurred % notes marked \staccato). % * David Kastrup: remove redefinitions of \afterGrace and \appoggiatura % and let their actions be performed when \articulate is called by % recognizing and replacing LilyPond's default code for these constructs. % Cf issue 4517 in LilyPond's tracker. % * David Kastrup: basic 2.15.28 compatibility by using event-chord-wrap! % This should really be done by rewriting the code more thoroughly. % * From Iain Nicol: appoggiatura timings were out; add staccatissimo; fix % trillSpanner endpoints. % * Also handle Breathing events (by throwing them away). This isn't ideal; % one should really shorten the note before a little. But I don't know % how to do lookahead in scheme. % * Also ignore explicit line breaks. % * Add Mordents (reported by Patrick Karl) % \version "2.19.22" #(use-modules (srfi srfi-1)) #(use-modules (srfi srfi-11)) #(use-modules (ice-9 debug)) #(use-modules (scm display-lily)) % PARAMETERS % How much to compress notes marked Staccato. CPE Bach says `as short as % may conveniently be played, as if the keys were too hot to touch'. % Most modern sources say 1/2 the notated length of a note. #(define ac:staccatoFactor '(1 . 2)) % How much to compress notes marked staccatissimo. #(define ac:staccatissimoFactor '(1 . 4)) % Shortening factor for notes marked portato (or slurred notes marked % staccato). #(define ac:portatoFactor '(3 . 4)) % And tenuto (if we ever implement time stealing, this should be >1.0) #(define ac:tenutoFactor '(1 . 1)) % How much to articulate normal notes. CPE Bach says 1/2 (and % staccato should be `as short as may conveniently be played') but this % sounds too short for modern music. 7/8 sounds about right. #(define ac:normalFactor '(7 . 8)) % How much to slow down for a rall. or a poco rall. % (or speed up for accel or poco accel) #(define ac:rallFactor (ly:make-moment 60/100)) % 40% slowdown #(define ac:pocoRallFactor (ly:make-moment 90/100)) % 10% slowdown % The absolute time for a twiddle in a trill, in minutes. % Start with 1/4 seconds == 1/240 minutes #(define ac:maxTwiddleTime (ly:make-moment 1/240)) % How long ordinary grace notes should be relative to their notated % duration. 9/40 is LilyPond's built-in behaviour for MIDI output % (though the notation reference says 1/4). #(define ac:defaultGraceFactor 9/40) % What proportion of an ordinary grace note's time should be stolen % from preceding notes (as opposed to stealing from the principal note). % Composers' intentions for this vary. Taking all from the preceding % notes is LilyPond's built-in behaviour for MIDI output. #(define ac:defaultGraceBackwardness 1) % Internal variables, don't touch. % (should probably be part of a context somehow) % Whether to slur, or not #(define ac:inSlur #f) #(define ac:inPhrasingSlur #f) % Whether the current noteevent is in a trill spanner #(define ac:inTrill #f) % assume start in C major. Key change events override this. % Could get from context, but don't know how. #(define ac:current-key (make-music 'KeyChangeEvent 'tonic (ly:make-pitch -1 0 0) 'pitch-alist (list (cons 0 0) (cons 1 0) (cons 2 0) (cons 3 0) (cons 4 0) (cons 5 0) (cons 6 0)))) #(define ac:currentTempo (ly:make-moment 15/1)) % 4 = 60, measured wholes per minute #(define ac:lastTempo ac:currentTempo) % for 'a tempo' or 'tempo I' % The duration of the current note. Start at a crotchet % for no good reason. #(define ac:currentDuration (ly:make-duration 2 0 1/1)) % Amount of musical time (in whole notes) that we need to steal from the % next events seen. #(define ac:stealForward 0) % List of events in the output so far, in reverse order, from which we can % steal time. #(define ac:eventsBackward '()) % Log events for the backward chain. #(define (ac:logEventsBackward music) (music-map (lambda (m) (case (ly:music-property m 'name) ((EventChord) (set! ac:eventsBackward (cons m ac:eventsBackward)) m) ((BarCheck SkipMusic) (let ((wm (make-sequential-music (list m)))) (set! ac:eventsBackward (cons wm ac:eventsBackward)) wm)) (else m))) music)) % Steal time from the backward chain. Adds to ac:stealForward (with a % warning) if it couldn't backward-steal all that was desired. #(define (ac:stealTimeBackward tosteal) (if (<= tosteal 0) #t (if (null? ac:eventsBackward) (begin (ly:warning (_ "articulation failed to steal ~a note backward at beginning of music; stealing forward instead") tosteal) (set! ac:stealForward (+ ac:stealForward tosteal))) (let* ((lastev (car ac:eventsBackward)) (levlen (ly:moment-main (ly:music-length lastev)))) (if (< tosteal levlen) (begin (ly:music-compress lastev (ly:make-moment (/ (- levlen tosteal) levlen))) #t) (begin (if (any (lambda (z) (eq? 'NoteEvent (ly:music-property z 'name))) (ly:music-property lastev 'elements)) (ly:warning (_ "stealing the entirety of a note's time"))) (set! (ly:music-property lastev 'elements) '()) (set! ac:eventsBackward (cdr ac:eventsBackward)) (ac:stealTimeBackward (- tosteal levlen)))))))) % Debugging: display a moment plus some text. % Returns its moment argument so can be used in-line. #(define (display-moment text m) (display text) (display (list (ly:moment-main-numerator m) "/" (ly:moment-main-denominator m))) m ) % Track tempo (and maybe later, other context properties) % as they change. Needs to better cope with saving only Tempo I, % otherwise "a tempo" goes back to the tempo before the last change. #(define (ac:adjust-props sym music) (case sym ((tempoWholesPerMinute) (set! ac:currentTempo (ly:music-property music 'value)) (set! ac:lastTempo ac:currentTempo) ))) % Raise note one step in the current diatonic scale. #(define (ac:up note) (let* ((pitch (ly:music-property note 'pitch)) (notename (ly:pitch-notename pitch)) (new-notename (if (eq? notename 6) 0 (+ 1 notename))) (alterations (ly:music-property ac:current-key 'pitch-alist)) (new-alteration (cdr (assq new-notename alterations))) (new-octave (if (eq? new-notename 0) (+ 1 (ly:pitch-octave pitch)) (ly:pitch-octave pitch))) ) (set! (ly:music-property note 'pitch)(ly:make-pitch new-octave new-notename new-alteration)))) % Lower note one step in the current diatonic scale. #(define (ac:down note) (begin (let* ((pitch (ly:music-property note 'pitch)) (notename (ly:pitch-notename pitch)) (new-notename (if (eq? notename 0) 6 (- notename 1))) (alterations (ly:music-property ac:current-key 'pitch-alist)) (new-alteration (cdr (assq new-notename alterations))) (new-octave (if (eq? new-notename 6) (- (ly:pitch-octave pitch) 1) (ly:pitch-octave pitch))) ) (set! (ly:music-property note 'pitch)(ly:make-pitch new-octave new-notename new-alteration)))) ) % Shorten a note, and save the note's original duration in ac:currentDuration #(define (ac:articulate-one-note m fraction) "Replace m with m*fraction" (if (eq? 'NoteEvent (ly:music-property m 'name)) (let* ((dur (ly:music-property m 'duration)) (l (ly:duration-log dur)) (d (ly:duration-dot-count dur)) (factor (ly:duration-factor dur)) (num (car fraction)) (denom (cdr fraction))) (begin (set! ac:currentDuration dur) (set! (ly:music-property m 'duration) (ly:make-duration l d (* num (car factor)) (* denom (cdr factor)))))) m)) % helper routine to set duration. #(define (ac:setduration music duration) "Set a note's duration." (let ((eventtype (ly:music-property music 'name))) (if (or (eq? eventtype 'NoteEvent) (eq? eventtype 'RestEvent) (eq? eventtype 'SkipEvent)) (set! (ly:music-property music 'duration) duration)))) % Add an articulation event to a note. % Used in afterGrace to mark all notes as tenuto, so they're not shortened #(define (ac:add-articulation type music) (music-map (lambda (m) (if (eq? 'EventChord (ly:music-property m 'name)) (set! (ly:music-property m 'elements) (append (ly:music-property m 'elements) (list (make-music 'ArticulationEvent 'articulation-type type))))) m) music)) % Convert a long note to an equivalent set of short notes, tied together. % This is needed to get smooth dynamics changes. % Need to deal properly with stuff other than the notes (dynamics, markup etc) % Still experimental, so disabled for now. #(define (ac:to128 music) music) #(define (ac:to128_disabled music) (if (or (eq? 'SkipEvent (ly:music-property music 'name)) (eq? 'NoteEvent (ly:music-property music 'name))) (let* ((dur (ly:music-property music 'duration)) (log2 (ly:duration-log dur)) (shiftcount (- 6 log2)) (lastm (ly:music-deep-copy (shift-duration-log music shiftcount 0)))) (set! (ly:music-property music 'elements) (cons (make-music 'TieEvent) (ly:music-property music 'elements))) (make-sequential-music (list (make-repeat "unfold" (1- (expt 2 shiftcount)) (make-sequential-music (list music)) '()) lastm))) music)) % absolute time in minutes of a length of music, as a rational number (moment) #(define (ac:abstime music) (ly:moment-div (ly:music-length music) ac:currentTempo)) % convert absolute time (in minutes) to a moment in the current tempo #(define (ac:abs->mom m) (ly:moment-mul m ac:currentTempo)) % a moment that is ac:maxTwiddletime seconds at the current tempo. #(define (ac:targetTwiddleTime) (ac:abs->mom ac:maxTwiddleTime)) % Nearest twiddletime (in minutes) achievable with power-of-2 divisions of % the original music. (twiddletime is the time for one pair of notes % in a trill) % If the music has a precomputed twiddletime (e.g., from \afterGrace) use that. #(define (ac:twiddletime music) (let* ((tr (filter (lambda (x) (and (eq? 'ArticulationEvent (ly:music-property x 'name)) (string= "trill" (ly:music-property x 'articulation-type)))) (ly:music-property music 'elements))) (pre-t (if (pair? tr) (ly:music-property (car tr) 'twiddle) '())) (hemisemimom (ly:make-moment 1/64)) (t (ac:targetTwiddleTime))) (if (ly:moment? pre-t) pre-t hemisemimom))) % Note: I'm assuming early music practice of starting on the auxiliary note. % Needs to add gruppetto if it's a long trill (TODO) #(define (ac:trill music) " Replace music with time-compressed repeats of the music, maybe accelerating if the length is longer than a crotchet " (let* ((hemisemidur (ly:make-duration 5 0 1/1)) (orig-len (ly:music-length music)) (t (ac:twiddletime music)) (uppernote '()) (note_moment (ly:moment-mul t (ly:make-moment 1/2))) (c1 (ly:moment-div orig-len t)) (c2 (inexact->exact (round (/ (ly:moment-main-numerator c1) (* 2 (ly:moment-main-denominator c1)))))) (count (if (< c2 2) 2 c2))) (set! (ly:music-property music 'elements) (filter (lambda (y) (eq? 'NoteEvent (ly:music-property y 'name))) (ly:music-property music 'elements))) (map (lambda (y) (ac:setduration y hemisemidur)) (ly:music-property music 'elements)) (set! uppernote (ly:music-deep-copy music)) (map (lambda (y) (ac:up y)) (filter (lambda (z) (eq? 'NoteEvent (ly:music-property z 'name))) (ly:music-property uppernote 'elements))) (let* ((trillMusicElements (let loop ((so_far (list uppernote music)) (c count)) (if (> c 1) (loop (append (list (ly:music-deep-copy uppernote) (ly:music-deep-copy music)) so_far) (1- c)) so_far))) (trillMusic (make-sequential-music trillMusicElements)) (newlen (ly:music-length trillMusic)) (factor (ly:moment-div orig-len newlen))) (ly:music-compress trillMusic factor) ; accelerating the music seems to put lily into an infinite loop in ; its layout and midi engines. ; (let* ((realfactor (exp (* (/ 1.0 count) (log 0.75)))) ; (factor (ly:make-moment (inexact->exact (round (* 1024 realfactor))) ; 1024))) ; (ac:accel trillMusic factor)) ))) % Copy music and strip articulations, ties, etc., for generating % mordents etc. #(define (ac:note-copy music) "return a copy of music that is only notes, no articulations, ties, slurs etc" (let ((new-music (ly:music-deep-copy music))) (set! (ly:music-property new-music 'articulations) '()) (set! (ly:music-property new-music 'elements) (filter (lambda (y) (eq? 'NoteEvent (ly:music-property y 'name))) (ly:music-property new-music 'elements))) new-music)) % % Generate a tempoChangeEvent and its associated property setting. % #(define (ac:tempoChange tempo) (make-sequential-music (list (make-music 'TempoChangeEvent 'metronome-count tempo 'tempo-unit (ly:make-duration 0 0 1/1)) (context-spec-music (make-property-set 'tempoWholesPerMinute tempo) 'Score)))) % % Totally unfold repeats, so that the non-obvious sequencing doesn't % confuse us. This is necessary for time stealing to work, because % that relies on the sequence in which we see events matching their % audible sequence. Also unfold multi-measure rests to equivalent % skips, with preceding and following bar checks, so that time stealing % can change the length of the pause without falling foul of the % implicit bar checks. % #(define (ac:unfoldMusic music) (music-map (lambda (m) (case (ly:music-property m 'name) ((UnfoldedRepeatedMusic) (let ((body (ly:music-property m 'element)) (altl (ly:music-property m 'elements)) (rc (ly:music-property m 'repeat-count))) (if (null? altl) (make-sequential-music (list-tabulate rc (lambda (i) (ly:music-deep-copy body)))) (let ((ealtl (if (> (length altl) rc) (take altl rc) altl))) (make-sequential-music (apply append! (append! (list-tabulate (- rc (length ealtl)) (lambda (i) (list (ly:music-deep-copy body) (ly:music-deep-copy (car ealtl))))) (map (lambda (alt) (list (ly:music-deep-copy body) alt)) ealtl)))))))) ((EventChord) (let-values (((trem evl) (partition (lambda (v) (eq? (ly:music-property v 'name) 'TremoloEvent)) (ly:music-property m 'elements)))) (if (null? trem) m (let* ((tremtype (ly:music-property (car trem) 'tremolo-type)) (tremtype-log (1- (integer-length tremtype))) (durev (find (lambda (v) (not (null? (ly:music-property v 'duration)))) evl)) (totaldur (if durev (ly:music-property durev 'duration) (ly:make-duration tremtype-log 0 1))) (tgt-nrep (/ (duration-visual-length totaldur) (duration-log-factor tremtype-log))) (eff-nrep (max (truncate tgt-nrep) 1)) (tremdur (ly:make-duration tremtype-log 0 (* (/ tgt-nrep eff-nrep) (ly:duration-scale totaldur))))) (or (and (= eff-nrep tgt-nrep) (= (ash 1 tremtype-log) tremtype)) (ly:warning (_ "non-integer tremolo ~a:~a") (duration->lily-string (duration-visual totaldur) #:force-duration #t #:time-scale 1) tremtype)) (for-each (lambda (v) (or (null? (ly:music-property v 'duration)) (set! (ly:music-property v 'duration) tremdur))) evl) (set! (ly:music-property m 'elements) evl) (make-sequential-music (list-tabulate eff-nrep (lambda (i) (ly:music-deep-copy m)))))))) ((MultiMeasureRestMusic) (make-sequential-music (list (make-music 'BarCheck) (make-music 'SkipMusic 'duration (ly:music-property m 'duration)) (make-music 'BarCheck)))) (else m))) (unfold-repeats music))) % If there's an articulation, use it. % If in a slur, use (1 . 1) instead (unless the note is marked staccato, % in which case use ac:portatoFactor). % Treat phrasing slurs as slurs, but allow explicit articulation. % % Expect an EventChord. % % trills, turns, ornaments etc. are also treated as Articulations. % Split into two functions: % ac:getactions traverses the elements in the EventChord % and calculates the parameters. % ac:articulate-chord applies the actions to each NoteEvent in % the EventChord. #(define (ac:getactions music) (let ((at-end-of-slur #f)) (let loop ((factor ac:normalFactor) (newelements '()) (es (ly:music-property music 'elements)) (actions '())) (if (null? es) (begin (set! (ly:music-property music 'elements) (reverse newelements)) (if (not (any (lambda (m) (music-is-of-type? m 'rhythmic-event)) newelements)) actions (append (let ((st ac:stealForward)) (if (= st 0) '() (begin (set! ac:stealForward 0) (list 'steal st)))) actions (cond (ac:inTrill '(trill)) ((and (eq? factor ac:normalFactor) (or ac:inSlur ac:inPhrasingSlur)) (list 'articulation '(1 . 1))) ((and (eq? factor ac:staccatoFactor) (or ac:inSlur at-end-of-slur)) (list 'articulation ac:portatoFactor)) (else (list 'articulation factor)))))) ; else part (let ((e (car es)) (tail (cdr es))) (case (ly:music-property e 'name) ((BeamEvent) ; throw away beam events, or they'll be duplicated by turn or trill (loop factor newelements tail actions)) ((LineBreakEvent FingeringEvent MarkEvent BreathingEvent TieEvent SkipEvent RestEvent) ; pass through some events. (loop (cons 1 1) (cons e newelements) tail actions)) ((ArticulationEvent) (let ((articname (ly:music-property e 'articulation-type))) ; TODO: add more here (cond ((string= articname "staccato") (loop ac:staccatoFactor newelements tail actions)) ((string= articname "staccatissimo") (loop ac:staccatissimoFactor newelements tail actions)) ((string= articname "portato") (loop ac:portatoFactor newelements tail actions)) ((string= articname "tenuto") (loop ac:tenutoFactor newelements tail actions)) ((string= articname "mordent") (loop (cons 1 1) newelements tail (cons 'mordent actions))) ((string= articname "prall") (loop (cons 1 1) newelements tail (cons 'prall actions))) ((string= articname "trill") (loop (cons 1 1) newelements tail (cons 'trill actions))) ((string= articname "turn") (loop (cons 1 1) newelements tail (cons 'turn actions))) (else (loop factor (cons e newelements) tail actions))))) ((TextScriptEvent) (let ((t (ly:music-property e 'text))) (if (not (string? t)) (loop factor (cons e newelements) tail actions) (begin (cond ((or (string= t "rall") (string= t "Rall") (string= t "rit.") (string= t "rall.")) (loop factor (cons e newelements) tail (cons 'rall actions))) ((or (string= t "accelerando") (string= t "accel") (string= t "accel.")) (loop factor (cons e newelements) tail (cons 'accel actions))) ((or (string= t "poco accel.")) (loop factor (cons e newelements) tail (cons 'pocoAccel actions))) ((or (string= t "poco rall.") (string= t "poco rit.")) (loop factor (cons e newelements) tail (cons 'pocoRall actions))) ((or (string= t "a tempo") (string= t "tempo I")) (loop factor (cons e newelements) tail (cons 'aTempo actions))) (else (loop factor (cons e newelements) tail actions))))))) ((SlurEvent) (let ((direction (ly:music-property e 'span-direction))) (set! ac:inSlur (eq? direction -1)) (set! at-end-of-slur (eq? direction 1)) (loop factor newelements tail actions))) ((TrillSpanEvent) (let ((direction (ly:music-property e 'span-direction))) (set! ac:inTrill (eq? direction -1)) (if ac:inTrill (loop factor newelements tail (cons 'trill actions)) (loop factor (cons e newelements) tail actions)))) ((PhrasingSlurEvent) (let ((direction (ly:music-property e 'span-direction))) (set! ac:inPhrasingSlur (eq? direction -1)) (loop factor newelements tail actions))) (else (loop factor (cons e newelements) tail actions)))))))) #(define (ac:articulate-chord music) (cond ((eq? 'EventChord (ly:music-property music 'name)) (ac:logEventsBackward (let loop ((actions (ac:getactions music))) (if (null? actions) (if (ly:moment= steallen totallen) (begin (if (any (lambda (z) (eq? 'NoteEvent (ly:music-property z 'name))) (ly:music-property music 'elements)) (ly:warning (_ "stealing the entirety of a note's time"))) (set! ac:stealForward (- steallen totallen)) (make-sequential-music '())) (begin (ly:music-compress music (ly:make-moment (/ (- totallen steallen) totallen))) (loop (cddr actions)))))) ))))) ((eq? 'GraceMusic (ly:music-property music 'name)) (let ((first-ev (call-with-current-continuation (lambda (yield-fev) (music-map (lambda (m) (if (eq? 'EventChord (ly:music-property m 'name)) (yield-fev m) m)) music) #f)))) (if first-ev (let ((fev-pos (find-tail (lambda (m) (eq? m first-ev)) ac:eventsBackward))) (if fev-pos (set! ac:eventsBackward (cdr fev-pos)) (ly:warning (_ "articulation of grace notes has gone awry")))))) (let* ((gmus (ly:music-compress (ly:music-property music 'element) (ly:make-moment ac:defaultGraceFactor))) (glen (ly:moment-main (ly:music-length gmus)))) (ac:stealTimeBackward (* glen ac:defaultGraceBackwardness)) (set! ac:stealForward (+ ac:stealForward (* glen (- 1 ac:defaultGraceBackwardness)))) gmus)) ((memq (ly:music-property music 'name) '(BarCheck SkipMusic)) (let ((totallen (ly:moment-main (ly:music-length music))) (steallen ac:stealForward)) (cond ((= steallen 0) (ac:logEventsBackward music)) ((< steallen totallen) (set! ac:stealForward 0) (ac:logEventsBackward (ly:music-compress music (ly:make-moment (/ (- totallen steallen) totallen))))) (else (set! ac:stealForward (- steallen totallen)) (make-sequential-music '()))))) ((eq? 'KeyChangeEvent (ly:music-property music 'name)) (set! ac:current-key music) music) ((eq? 'PropertySet (ly:music-property music 'name)) (ac:adjust-props (ly:music-property music 'symbol) music) music) (else music))) % At last ... here's the music function that applies all the above to a % score. articulate = #(define-music-function (music) (ly:music?) "Adjust times of note to add tenuto, staccato and normal articulations. " (dynamic-wind (lambda () (set! ac:stealForward 0) (set! ac:eventsBackward '())) (lambda () (music-map ac:articulate-chord (ac:startup-replacements music))) (lambda () (or (= ac:stealForward 0) (begin (ly:warning (_ "articulation failed to steal ~a note at end of music") ac:stealForward) (set! ac:stealForward 0))) (set! ac:eventsBackward '())))) #(define (ac:startup-replacements music) (fold (lambda (f m) (f m)) music (list event-chord-wrap! ac:replace-aftergrace ac:replace-appoggiatura ac:unfoldMusic))) #(define (ac:replace-aftergrace music) (map-some-music (lambda (expr) (with-music-match (expr (music 'SimultaneousMusic elements (?before-grace (music 'SequentialMusic elements ((music 'SkipMusic) (music 'GraceMusic element ?grace)))))) (ac:aftergrace ?before-grace ?grace))) music)) #(define (ac:replace-appoggiatura music) ;; appoggiature are ugly to deal with since they require a main ;; note following them. We only try dealing with this followership ;; in sequential music (map-some-music (lambda (m) (if (eq? 'SequentialMusic (ly:music-property m 'name)) (pair-for-each (lambda (elts) (let ((expr (car elts)) (main (and (pair? (cdr elts)) (cadr elts)))) (and main ;;stolen from define-music-display-methods (with-music-match (expr (music 'GraceMusic element (music 'SequentialMusic elements (?start ?music ?stop)))) ;; we check whether ?start and ?stop look like ;; startAppoggiaturaMusic stopAppoggiaturaMusic (and (with-music-match (?start (music 'SequentialMusic elements ((music 'EventChord elements ((music 'SlurEvent span-direction START)))))) #t) (with-music-match (?stop (music 'SequentialMusic elements ((music 'EventChord elements ((music 'SlurEvent span-direction STOP)))))) #t) (let* ((app (ac:appoggiatura ?music main)) (apps (ly:music-property app 'elements))) (set-car! elts (car apps)) (set-car! (cdr elts) (cadr apps)) #f)))))) (ly:music-property m 'elements))) #f) music)) % Override \afterGrace to be in terms of audio, not spacing. % Special handling for a gruppetto after a trill. #(define (ac:aftergrace main grace) (let* ((main-length (ly:music-length main)) (grace-orig-length (ly:music-length grace)) (gracelen (ac:twiddletime main)) (grace-factor (ly:moment-div gracelen grace-orig-length)) (new-main-length (ly:moment-sub main-length gracelen)) (factor (ly:moment-div new-main-length main-length)) ) (map (lambda (y) (set! (ly:music-property y 'twiddle) gracelen)) (filter (lambda (z) (and (eq? 'ArticulationEvent (ly:music-property z 'name)) (string= "trill" (ly:music-property z 'articulation-type)))) (ly:music-property main 'elements))) (ac:add-articulation "tenuto" grace) (make-sequential-music (list (ly:music-compress main factor) (ly:music-compress grace grace-factor))))) % An appoggiatura takes half the duration of the main note, % or 1/3 if the note is dotted (i.e., half the undotted equivalent time) % Somewhere around the end of the 19th, start of 20th century the rules % changed, but my main interest is early music. #(define (ac:appoggiatura grace main) (let* ((maindur (ly:music-length main)) (grace-orig-len (ly:music-length grace)) (main-orig-len (ly:music-length main)) (numerator (ly:moment-main-numerator maindur)) (factor (if (eq? (remainder numerator 3) 0) (ly:make-moment 1/3) (ly:make-moment 1/2)))) (ly:music-compress grace (ly:moment-mul factor (ly:moment-div main-orig-len grace-orig-len))) (ly:music-compress main (ly:moment-sub (ly:make-moment 1/1) factor)) (set! (ly:music-property grace 'elements) (append (ly:music-property grace 'elements) (list (make-music 'SlurEvent 'span-direction -1)))) (set! (ly:music-property main 'elements) (append (ly:music-property main 'elements) (list (make-music 'SlurEvent 'span-direction 1)))) (make-sequential-music (list grace main))))