%% http://lsr.di.unimi.it/LSR/Item?id=654 %% version 2013/05/11 %% for lilypond 2.18 or higher %% last changes : - \changePitch : a single s as the last event of newnotes parameter %% will give you the ending rests of the pattern (if there), and two s %% also the beginning ones. If pattern ends with a note, a single %% ending s will have no effects. %% - Is now \language independant (no more #{ c #}) %% - new algorithm for make-notes-list, change-pitch %% and has-notes? (shorter and safer) %% %%%%%%%%%%%%%%%%%%%%%%%%% some utility functions %%%%%%%%%%%%%%%%%%%%% \paper { tagline = ##f } #(define (name-of music) (ly:music-property music 'name)) #(define (has-notes? music) "Return true if there is at least one note in `music, false otherwise." (or (eq? (name-of music) 'NoteEvent) (let ((e (ly:music-property music 'element))) (and (ly:music? e) (has-notes? e))) (let loop ((es (ly:music-property music 'elements))) (and (pair? es) (or (has-notes? (car es)) (loop (cdr es))))))) %% An EventChord is sometimes used as a wrapper in Lilypond, so we have to check %% if a chord is a standard chord with notes. We could have used has-notes? but %% this version is perhaps more efficient. %% Optional events name like 'RestEvent can be included. #(define (note-or-chord? music . otherEvent) "Is music a note or a chord with notes ?" (let ((name (name-of music))) (or (memq name (cons 'NoteEvent otherEvent)) (and (eq? name 'EventChord) ; have this chord at least one note ? (let loop ((es (ly:music-property music 'elements))) (and (pair? es) (or (eq? (name-of (car es)) 'NoteEvent) (loop (cdr es))))))))) #(define (expand-q-chords music); for q chords : see chord-repetition-init.ly (expand-repeat-chords! (list 'rhythmic-event) music)) #(define (clean-music mus) "Try to reduce the number of sequential music" (let ((name (ly:music-property mus 'name))) (cond ((eq? name 'SequentialMusic) (ly:music-set-property! mus 'elements (fold-right (lambda (evt prev-list) (if (eq? (name-of evt) 'SequentialMusic) (append (ly:music-property (clean-music evt) 'elements) prev-list) (cons (clean-music evt) prev-list))) '() (ly:music-property mus 'elements)))) ((eq? name 'SimultaneousMusic) (ly:music-set-property! mus 'elements (map clean-music (ly:music-property mus 'elements)))) ((memq name (list 'RelativeOctaveMusic 'UnrelativableMusic)) (ly:music-set-property! mus 'element (clean-music (ly:music-property mus 'element))))) mus)) %%%%%%%%%%%%%%%%%%%%%%%%%%%%% changePitch %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% #(define cPInsertInPattern (gensym)) #(define cPSamePitch (gensym)) #(define cPSamePitchEnd (gensym)) #(define cPPatternEnd (gensym)) #(define (make-notes-list music) "Make a list with each element will be of one of these types : 1- a note, a chord, a rest 2- an integer, indicating the number of notes to skip in pattern ( The user will indicate that, by a corresponding number of skips (s or \\skip) in `newnotes parameter of \\changePitch ) 3- a list of musics, to be inserted between 2 notes of pattern, and added with the \\insert function, inside `newnotes" (let ((res '()) ; the list to fill (prev #f)) (define (fill-notes-list evt) (let ((tags (ly:music-property evt 'tags)) (name (name-of evt))) (cond ((memq cPInsertInPattern tags) ; a music added by \insert (ly:music-set-property! evt 'tags (delq cPInsertInPattern tags)) ; remove the tag (if (integer? prev)(set! res (cons prev res))) (set! prev (if (pair? prev)(cons evt prev)(list evt)))) ; a list ((memq name (list 'SkipEvent 'SkipMusic)) (if (pair? prev)(set! res (cons prev res))) ; keep the reverse order (set! prev (if (integer? prev) (1+ prev) 1))) ; ((memq name (list 'EventChord 'NoteEvent 'RestEvent)) ((note-or-chord? evt 'RestEvent) ; a note, a chord, or a rest (if (or (pair? prev)(integer? prev))(set! res (cons prev res))) (set! prev evt) (set! res (cons evt res))) (else (let ((elt (ly:music-property evt 'element)) (elts (ly:music-property evt 'elements))) (if (ly:music? elt) (fill-notes-list elt)) (if (pair? elts)(for-each fill-notes-list elts))))))) (fill-notes-list music) (if (or (pair? prev)(integer? prev))(set! res (cons prev res))) (reverse res))) %%%%%%%%%%%% used inside the inner function change-one-note #(define (copy-duration from to) ; from and to as EventChord or NoteEvent (let ((max-dur #f)); in theory, 2 notes in a chord can have a different duration (music-map (lambda (x) ; get main duration from `from (let ((dur (ly:music-property x 'duration))) (if (and (ly:duration? dur) (or (not max-dur) (ly:duration skip-notnote-event? 1))) last-notes-list (cdr last-notes-list)))) (if empty? (loop next-new-notes (cdr pat-list) res) (loop next-new-notes (cdr pat-list) (cons evt res)))))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; end loop ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (let ((relativize (lambda(m) (let* ((clean-newnotes (clean-music newnotes)) (name (name-of clean-newnotes))) (if (memq name (list 'RelativeOctaveMusic 'UnrelativableMusic)) (make-music name 'element m) m))))) (case (length seq-list) ((0) (make-music 'Music 'void #t)) ((1) (relativize (car seq-list))) (else (relativize (clean-music (make-sequential-music seq-list)))))))) changePitch = #(define-music-function (pattern newnotes) (ly:music? ly:music?) "Change each notes in `pattern by the notes (or rests) given in `newnotes. If count of events doesn't match, pattern is duplicated repeatedly or truncate." (let* ((expand-q (lambda (music) (expand-repeat-chords! (cons 'rhythmic-event (ly:parser-lookup '$chord-repeat-events)) music))) (pattern (expand-q pattern)) (newnotes (expand-q newnotes))) (change-pitch pattern newnotes))) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% enhancement functions, working with \changePitch pattern newnotes samePitch = #(define-music-function (music) (ly:music?) "Inside the `pattern parameter of the \\changePitch function, all notes grouped by this function will have the same pitch, according to the current note of `newnotes parameter of \\changePitch." (let((not-first? #f) (last-note #f)) (map-some-music (lambda (x) (cond ((note-or-chord? x) (if not-first? ; set all pitches to the pitch of the first note (ly:music-set-property! x 'to-relative-callback (lambda (x p) ; set pitch to the prev value (ly:prob-set-property! x 'pitch p) p)) (set! not-first? x)) ; do nothing for first note (ly:music-set-property! x 'tags (cons cPSamePitch ; add tag cPSamePitch to x (ly:music-property x 'tags))) (set! last-note x) ; save the note x x) (else #f))) music) (if last-note ; the last saved EventChord (ly:music-set-property! last-note 'tags (cons cPSamePitchEnd ; add cPSamePitchEnd tag, delete cPSamePitch tag (delq cPSamePitch (ly:music-property last-note 'tags))))) music)) %% this function should be no more needed, as copy-arti should avoid pbs %% in relative mode and \samePitch absolute = #(define-music-function (music) (ly:music?) "Make `music unrelativable. To use inside a \\samePitch function in relative mode." (make-music 'UnrelativableMusic 'element music)) insert = #(define-music-function (music) (ly:music?) "Using this function inside the `newnotes parameter of the \\changePitch function, allow you to insert and remplace by `music, all music between one note and his following, in the `pattern parameter of \\changePitch, ." #{ \tag #cPInsertInPattern $music #}) %%%%%%% #(define (n-copy n music) (cond ((> n 1)(make-sequential-music (map (lambda (x)(ly:music-deep-copy music)) (make-list n)))) ((= n 1) music) (else (make-music 'Music 'void #t)))) nCopy = #(define-music-function (n music)(integer? ly:music?) (n-copy n music)) %% same effect as { \repeat unfold n s } but \nSkip works inside the `newnotes %% parameter of \changePitch. nSkip = #(define-music-function (n)(integer?) "Return \\skip \\skip \\skip ... n times." #{ \nCopy #n s #}) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% shortcuts % default values for patI and patII, if the user do not define % them, before using \cPI and \cPII % patI ={ c8. c16 } % not \language independant patI = #(make-music 'SequentialMusic 'elements (list (make-music 'NoteEvent 'duration (ly:make-duration 3 1 1) 'pitch (ly:make-pitch -1 0 0)) (make-music 'NoteEvent 'duration (ly:make-duration 4 0 1) 'pitch (ly:make-pitch -1 0 0)))) % patII = { c4. c8 } patII = #(make-music 'SequentialMusic 'elements (list (make-music 'NoteEvent 'duration (ly:make-duration 2 1 1) 'pitch (ly:make-pitch -1 0 0)) (make-music 'NoteEvent 'duration (ly:make-duration 3 0 1) 'pitch (ly:make-pitch -1 0 0)))) cPI = #(define-music-function (newnotes) (ly:music?) #{ \changePitch \patI $newnotes #}) cPII = #(define-music-function (newnotes) (ly:music?) #{ \changePitch \patII $newnotes #}) #(define cP changePitch) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% The example %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% testMusic = { c d e f g f e d } fillerChord = { 2 } \relative c' { \override Score.RehearsalMark.self-alignment-X = #LEFT \mark "pattern = { c8.-> c16-. }" \changePitch { c8.-> c16-. } { \testMusic } \fillerChord \break \mark "pattern = { c8 c16( c) }" \changePitch { c8 c16( c) } { \testMusic } \fillerChord \fillerChord \break \time 6/8 \mark "pattern = { c4( c8) c8.( c16) c8-. }" \changePitch { c4( c8) c8.( c16) c8-. } { \testMusic } 4. }