Spaces:
Running
Running
% | |
% Copyright (C) 2008, 2009, 2010, 2011 NICTA | |
% Author: Peter Chubb <peter.chubb AT nicta.com.au> | |
% $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) | |
% * Thomas Morley: extend unfold-repeats to reflect the possibility to | |
% customize its effect to user-settable repeat-types. Here the most general | |
% setting is hard-coded, resulting in unchanged behaviour. | |
\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 (eqv? notename 6) 0 (+ 1 notename))) | |
(alterations (ly:music-property ac:current-key 'pitch-alist)) | |
(new-alteration (cdr (assq new-notename alterations))) | |
(new-octave (if (eqv? 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 (eqv? notename 0) 6 (- notename 1))) | |
(alterations (ly:music-property ac:current-key 'pitch-alist)) | |
(new-alteration (cdr (assq new-notename alterations))) | |
(new-octave (if (eqv? 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 ac:up | |
(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 (eqv? direction -1)) | |
(set! at-end-of-slur (eqv? direction 1)) | |
(loop factor newelements tail actions))) | |
((TrillSpanEvent) | |
(let ((direction (ly:music-property e 'span-direction))) | |
(set! ac:inTrill (eqv? 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 (eqv? 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<? (ly:make-moment 1/4) (ly:music-length music)) | |
(ac:to128 music) | |
music) | |
(case (car actions) | |
((articulation) | |
(map | |
(lambda (x) (ac:articulate-one-note x (cadr actions))) | |
(ly:music-property music 'elements)) | |
(let* | |
((num (caadr actions)) | |
(denom (cdadr actions)) | |
(mult (ly:duration-factor ac:currentDuration)) | |
(newnum (* (- denom num) (car mult))) | |
(newdenom (* (cdr mult) denom)) | |
(len (ly:duration-log ac:currentDuration)) | |
(dots (ly:duration-dot-count ac:currentDuration))) | |
(if (not (eqv? num denom)) | |
(make-sequential-music | |
(list (ac:to128 music) | |
(make-music 'EventChord 'elements | |
(list | |
(make-music 'RestEvent 'duration (ly:make-duration len dots newnum newdenom)))))) | |
music))) | |
((accel) | |
(set! ac:lastTempo ac:currentTempo) | |
(set! ac:currentTempo (ly:moment-div ac:currentTempo ac:rallFactor)) | |
(let ((pset (ac:tempoChange ac:currentTempo))) | |
(if (null? (cdr actions)) | |
(make-sequential-music (list pset music)) | |
(make-sequential-music | |
(list pset (loop (cdr actions))))))) | |
((pocoAccel) | |
(set! ac:lastTempo ac:currentTempo) | |
(set! ac:currentTempo (ly:moment-div ac:currentTempo ac:pocoRallFactor)) | |
(let ((pset (ac:tempoChange ac:currentTempo))) | |
(if (null? (cdr actions)) | |
(make-sequential-music (list pset music)) | |
(make-sequential-music | |
(list pset (loop (cdr actions))))))) | |
((rall) | |
(set! ac:lastTempo ac:currentTempo) | |
(set! ac:currentTempo (ly:moment-mul ac:currentTempo ac:rallFactor)) | |
(let ((pset (ac:tempoChange ac:currentTempo))) | |
(if (null? (cdr actions)) | |
(make-sequential-music (list pset music)) | |
(make-sequential-music | |
(list pset (loop (cdr actions))))))) | |
((pocoRall) | |
(set! ac:lastTempo ac:currentTempo) | |
(set! ac:currentTempo (ly:moment-mul ac:currentTempo ac:pocoRallFactor)) | |
(let ((pset (ac:tempoChange ac:currentTempo))) | |
(if (null? (cdr actions)) | |
(make-sequential-music (list pset music)) | |
(make-sequential-music | |
(list pset (loop (cdr actions))))))) | |
((aTempo) | |
(set! ac:currentTempo ac:lastTempo) | |
(let ((pset (ac:tempoChange ac:currentTempo))) | |
(if (null? (cdr actions)) | |
(make-sequential-music (list pset music)) | |
(make-sequential-music | |
(list pset (loop (cdr actions))))))) | |
((trill) | |
(ac:trill music)) | |
((prall) | |
; A pralltriller symbol can either mean an inverted mordent | |
; or a half-shake -- a short, two twiddle trill. | |
; We implement as a half-shake. | |
(let* | |
((origlength (ly:music-length music)) | |
(gracedur (ly:make-duration 5 0 1/1)) | |
(gracenote (ac:note-copy music)) | |
(abovenote (ac:note-copy music)) | |
(abovenoteTwo (ac:note-copy music)) | |
(mainnote (ly:music-deep-copy music))) | |
(map (lambda (y) (ac:setduration y gracedur)) | |
(ly:music-property gracenote 'elements)) | |
(map (lambda (y) (ac:setduration y gracedur)) | |
(ly:music-property abovenote 'elements)) | |
(map (lambda (y) (ac:setduration y gracedur)) | |
(ly:music-property abovenoteTwo 'elements)) | |
(map ac:up | |
(filter | |
(lambda (z) (eq? 'NoteEvent (ly:music-property z 'name))) | |
(ly:music-property abovenote 'elements))) | |
(map ac:up | |
(filter | |
(lambda (z) (eq? 'NoteEvent (ly:music-property z 'name))) | |
(ly:music-property abovenoteTwo 'elements))) | |
(let* ((prallMusic (make-sequential-music | |
(list abovenote gracenote abovenoteTwo mainnote))) | |
(newlen (ly:music-length prallMusic)) | |
(factor (ly:moment-div origlength newlen))) | |
(ly:music-compress prallMusic factor)))) | |
((mordent) | |
(let* | |
((origlength (ly:music-length music)) | |
(gracedur (ly:make-duration 5 0 1/1)) | |
(gracenote (ac:note-copy music)) | |
(belownote (ac:note-copy music))) | |
(map (lambda (y) (ac:setduration y gracedur)) | |
(ly:music-property gracenote 'elements)) | |
(map (lambda (y) (ac:setduration y gracedur)) | |
(ly:music-property belownote 'elements)) | |
(map ac:down | |
(filter | |
(lambda (z) (eq? 'NoteEvent (ly:music-property z 'name))) | |
(ly:music-property belownote 'elements))) | |
(let* ((mordentMusic (make-sequential-music (list gracenote belownote music))) | |
(newlen (ly:music-length mordentMusic)) | |
(factor (ly:moment-div origlength newlen))) | |
(ly:music-compress mordentMusic factor)))) | |
((turn) | |
(let* | |
((dur (ly:music-property | |
(car (ly:music-property music 'elements)) 'duration)) | |
(factor (ly:duration-factor dur)) | |
(newdur (ly:make-duration (+ (ly:duration-log dur) 2) | |
(ly:duration-dot-count dur) (car factor)(cdr factor)))) | |
(begin | |
(map (lambda (y) (ac:setduration y newdur)) | |
(ly:music-property music 'elements)) | |
(let* ((above (ly:music-deep-copy music)) | |
(below (ly:music-deep-copy music)) | |
(newmusic (make-sequential-music (list above music below music)))) | |
(begin | |
(map ac:down | |
(filter | |
(lambda (z) (eq? 'NoteEvent (ly:music-property z 'name))) | |
(ly:music-property below 'elements))) | |
(map ac:up | |
(filter | |
(lambda (z) (eq? 'NoteEvent (ly:music-property z 'name))) | |
(ly:music-property above 'elements))) | |
newmusic))))) | |
((steal) | |
(let | |
((totallen (ly:moment-main (ly:music-length music))) | |
(steallen (cadr actions))) | |
(if (>= 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. | |
%% NOTE: 'articulate-unfold' is the function of original version, | |
%% now 'articulate' is the version which 'unfoldMusic' was removed. | |
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 '())))) | |
articulate-unfold = #(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-unfold 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 | |
))) | |
#(define (ac:startup-replacements-unfold 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 (eqv? (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)))) | |