;;; FROM TIME TO TIME: THE REPRESENTATION OF TIMING AND TEMPO ;;; ********************************************************** ;;; ********************************************************** ;;; Timing Functions Framework ;;; which extends the GTF microworld ;;; In Common Lisp ©2007, Bas de Haas ;;; See, Honing, H. (2001). "From time to time: The representation ;;; of timing and tempo." Computer Music Journal, 35 (3), 50-61. ;;; for a detailed desciption. ;;; ********************************************************** ;;; ********************************************************** ;;; time-maps level (macro's have to be loaded first) (defun tm-funcall (time-map score-time performance-time) "returns a new performance-time given a time-map, score-time, performance-time and the optional arguments: score start-time, score end-time, performance-start-time and performance-end-tim. The optional arguments will not be called if the optional arguments are not provided (nil)" (funcall time-map score-time performance-time)) (defmacro anonymous-tm ((score-time performance-time ) &body form) "returns a anonymous time-map which can be a time-shift or a tempo-change. The time-map has optional acces to the score start-time end-time performance start-time and performance end time. A two argument tm will be generated if the optional arguments are not provided (nil)" `#'(lambda (,score-time ,performance-time) ,@form)) (defun identity-tm () "returns a time-map that does not change the tempo, does not add any expressive timing, and just returns the performance-time" (anonymous-tm (score-time performance-time) (declare (ignore score-time)) performance-time)) ;;; ********************************************************** ;;; generalized timing-functions level ;; start-time: rational number denoting the symbolic starting score-time ;; end-time: rational number denoting the symbolic ending score-time (defun anonymous-gtif (start-score-time end-score-time tif &optional (previous-applied-tif (make-tif))) "Return a timing function, a tuple of a time-shift and tempo-change function." (let ((performance-start-time (tif-funcall previous-applied-tif start-score-time start-score-time)) (performance-end-time (tif-funcall previous-applied-tif end-score-time end-score-time))) ;; bind performance-start-time and performance-end-time ;; lexically, the current performance times given ;; the previously applied timing transformations (u). (make-tif ;; Construct a TIF consisting of two time-maps. :tempo-change (anonymous-tm (score-time performance-time) (tm-funcall (get-tif :tempo-change tif) score-time performance-time) ;; Return a tempo-change time-map, ;; a function of score-time and performance-time, ;; with access to score-begin (start-time), score-end (end-time), ;; performance-begin time and performance-end time, ;; and previous applied timing transformations. ) :time-shift (anonymous-tm (score-time performance-time) (tm-funcall (get-tif :time-shift tif) score-time performance-time) ;; with access to score and performances times (as above) ;; Return a time-shift time-map, ) :start-score-time start-score-time :end-score-time end-score-time :performance-start-time performance-start-time :performance-end-time performance-end-time) )) ;;; ********************************************************** ;;; timing-functions level (defun make-tif (&key (time-shift (identity-tm)) (tempo-change (identity-tm)) (start-score-time nil) (end-score-time nil) (performance-start-time nil) (performance-end-time nil)) "returns a Timing-function containing a time-shift and a tempo-change time-map" (list :time-shift time-shift :tempo-change tempo-change :start-score-time start-score-time :end-score-time end-score-time :performance-start-time performance-start-time :performance-end-time performance-end-time)) (defun tif-funcall (tif score-time performance-time) "returns a tempo-changed and time-shifted performance time" (let ((tempo-changed-performance-time (tm-funcall (get-tif :tempo-change tif) score-time performance-time ))) (tm-funcall (get-tif :time-shift tif) score-time tempo-changed-performance-time))) (defun get-tif (keyword tif) "returns the a property of a timing function tif. the keyword sets which property (e.g. :time-shift, :tempo-change etc.)" (getf tif keyword)) ;;; ********************************************************** ;;; some example time-maps (should not be at user level) (defun constant-tempo (tempo-factor) "returns a time-map that changes the tempo with a constant factor" (anonymous-tm (score-time performance-time) (declare (ignore score-time)) (* tempo-factor performance-time))) (defun delay-a-beat (which-beat &key (beats-per-bar 4)(delay 0.2)) "return a time-map that delays a beat in a bar, given a delay and a meter" (anonymous-tm (score-time performance-time) (if (eq (mod (- score-time which-beat) beats-per-bar) 0) (+ performance-time delay) performance-time))) ;;; ********************************************************** ;;; composition & concatenation ;;; TO DO: make concat en compose recursive functions (defun compose-tm (tm-applied-first tm-applied-second) "returns a time-map that consists out of the composition of two time-maps" (anonymous-tm (score-time performance-time) (tm-funcall tm-applied-second score-time (tm-funcall tm-applied-first score-time performance-time)))) (defun concatenate-time-shifts(first-time-shift second-time-shift concat-score-time) "returns a time-map that consists out of a the concatenation of two time-shifts" (anonymous-tm (score-time performance-time) (if (<= score-time concat-score-time) (tm-funcall first-time-shift score-time performance-time ) (tm-funcall second-time-shift score-time performance-time)))) (defun concatenate-tempo-changes (first-tempo-change second-tempo-change concat-score-time concat-perf-time) "returns a time-map that consists out of a the concatenation of two tempo-changes" (anonymous-tm (score-time performance-time) (if (<= score-time concat-score-time) (tm-funcall first-tempo-change score-time performance-time) (let ((first-tempo-change-concat-point (tm-funcall first-tempo-change concat-score-time concat-perf-time)) (second-tempo-change-concat-point (* -1 (tm-funcall second-tempo-change concat-score-time concat-perf-time)))) (+ (tm-funcall second-tempo-change score-time performance-time) first-tempo-change-concat-point second-tempo-change-concat-point))))) (defun compose-tif (tif-applied-first tif-applied-second) "returns a tif composed out of two timing functions" (make-tif :time-shift (compose-tm (get-tif :time-shift tif-applied-first) (get-tif :time-shift tif-applied-second)) :tempo-change (compose-tm (get-tif :tempo-change tif-applied-first) (get-tif :tempo-change tif-applied-second)))) ;; je kan dit vast ook netjes met &rest oplossen, vragen aan HH of LS ;; zou ook het probleem (BUG) op moeten lossen van het niet funcallen ;; van de make-tifs (deze zitten nu in een lijst en worden niet gecalled) (defun compose-tifs (list-of-tifs &optional (composed-tif nil)) "returns a tif composed of two or more timing functions" (if (equal composed-tif nil) (compose-tifs (rest list-of-tifs) (first list-of-tifs)) (if (equal list-of-tifs nil) composed-tif (compose-tifs (rest list-of-tifs) (compose-tif (funcall composed-tif) (funcall (first list-of-tifs))))))) (defun concatenate-tif (tif-applied-first tif-applied-second concat-score-time concat-perf-time) "returns a tif that is the concatenation of two timing-functions" (make-tif :time-shift (concatenate-time-shifts (get-tif :time-shift tif-applied-first) (get-tif :time-shift tif-applied-second) concat-score-time) :tempo-change (concatenate-tempo-changes (get-tif :tempo-change tif-applied-first) (get-tif :tempo-change tif-applied-second) concat-score-time concat-perf-time))) ;;; ********************************************************** ;;; GTF & Timing Functions (GTIF) (defun tifp (tif-or-not) "returns true if tif-or-not is a tif and false if tif-or-not is something else. Tifp does this by checking if tif-or-not is a list with a length of 12 and if contains a time-shift and a tempo-change timemap." (if (listp tif-or-not) (if (getf tif-or-not :time-shift) ; is tif-or-not a property list (if (equal (length tif-or-not) 12) (if (and (functionp (get-tif :time-shift tif-or-not)) (functionp (get-tif :tempo-change tif-or-not))) t nil))))) ;;; ********************************************************** ;;; some helper functions (defun ioi-to-bpm (ioi) "calculates the tempo in beats per minute from the inter onset interval" (* 60000 (expt ioi -1))) (defun bpm-to-ioi (tempo-in-bpm) "calculates the inter onset interval from a tempo in beats per minute" (ioi-to-bpm tempo-in-bpm)) ;ioi-to-bmp is it own inverse function ;;; ********************************************************** ;;; basic gtifs (defun even-eights (score-start score-end tempo-in-bpm) "returns a GTF without exrpessive timing" (anonymous-gtif score-start score-end (make-tif :tempo-change (constant-tempo (bpm-to-ioi tempo-in-bpm))))) ;;; ********************************************************** ;;; plotting & testing (defun generate-metronome (start-time end-time unit &optional (result (list start-time))) "generates a regular metronome that can be used for testing and plotting the timingfunctions framework" (if (equal unit 0) (format t "Choose a number greater than 0 for unit (third argument)") (if (>= (first result) end-time) (reverse result) (generate-metronome start-time end-time unit (cons (+ (first result) unit) result))))) (defun sample-tif (tif &optional (test-score (generate-metronome 0 30 1))) "returns a list of results obtained by funcalling the supplied timing function. The default value of test-score is a list of integers (0 to 30)" (loop for beat in test-score collect (tif-funcall tif beat beat) into result finally (return result))) ;;; ********************************************************** ;;; examples of use #| ;;; *** plotting.lisp must be loaded *** ;; Example 1: plot a tif with a tif with three tempo change and added expressive timing ;; every third beat (the delay is unrelated to the tempo) (plot (sample-tif (make-tif :time-shift (delay-a-beat 3) ; delay the third beat of every measure :tempo-change (concatenate-tempo-changes ; concatenate a tempo change (constant-tempo 3) ; with a concatenated tempo-change (concatenate-tempo-changes (constant-tempo .5) (constant-tempo 3) 22 22) 11 11)))) ;; Example 2: another complex tif, comparable example 1. (plot (sample-tif (concatenate-tif (make-tif :time-shift (concatenate-time-shifts (delay-a-beat 3 :beats-per-bar 4 :delay .5) (delay-a-beat 1 :beats-per-bar 4 :delay .5) 6 ) :tempo-change (constant-tempo 1)) (concatenate-tif (make-tif :time-shift (delay-a-beat 3) :tempo-change (constant-tempo 2)) (make-tif :time-shift (delay-a-beat 1 :beats-per-bar 4 :delay .3) :tempo-change (constant-tempo .5)) 20 20 ) 10 10))) ;; Example 3: a plot of a gtif, which is practically not much more than a tif with ;; access to the start and end score-time. (plot (sample-tif (anonymous-gtif 0 30 (make-tif :time-shift (delay-a-beat 3) :tempo-change (constant-tempo 3))))) ;; Example 4: tif-funcal uses the gtif that maps the score to a constant tempo where every ;; third beat is delayed. tif-funcall calculates the third beat of the third measure (11) (tif-funcall (anonymous-gtif 0 30 (make-tif :time-shift (delay-a-beat 3 :delay .5) :tempo-change (constant-tempo 2))) 11 11) => 22,5 ;; Example 5: shows how to compose multiple (g)tifs with the function ;; compose-tifs (plot (sample-tif (compose-tifs '((make-tif :time-shift (delay-a-beat 1 :beats-per-bar 4 :delay .3) :tempo-change (constant-tempo 2)) (make-tif :time-shift (delay-a-beat 2 :beats-per-bar 4 :delay .3)) (make-tif :time-shift (delay-a-beat 3 :beats-per-bar 4 :delay .3) :tempo-change (constant-tempo .5)))))) (plot (sample-tif (compose-tifs '((make-tif :tempo-change (constant-tempo 3) :time-shift (delay-a-beat 3 :beats-per-bar 4 :delay .4)) (make-tif :time-shift (delay-a-beat 2 :beats-per-bar 4 :delay .6)) (make-tif :time-shift (delay-a-beat 1 :beats-per-bar 4 :delay .4)) )))) (make-tif :time-shift (delay-a-beat 2 :beats-per-bar 4 :delay .3)) ;; tifp checks wether a symbol is a tif: (tifp (make-tif)) =>t (tifp (gensym "some-symbol")) => nil ;; *** for some nice example tif's see Friberg&sundstrom.lisp *** |#