Ok, hopefully you’ve had a peek at the time tutorial and you’re all set for callbacks.  In The Basics we looked at various ways to play a sequence of notes.  We’re now going to expand on that theme.  Let’s define a function that uses callback to temporally recurse through a list of pitch values.

;; plays a sequence of pitches
(define play-seq
(lambda (time plst)
(play-note time piano (car plst) 80 11025)
(if (not (null? (cdr plst)))
(callback (+ time 10000) 'play-seq (+ time 11025) (cdr plst)))))

(play-seq (now) '(60 62 63 65 67 68 71 72))

This should look very similar to the examples in The Basics.  Now let’s change play-seq to enable it to repeat the sequence indefinitely.

;; plays a sequence of pitches indefinitely
(define play-seq
(lambda (time plst)
(play-note time piano (car plst) 80 11025)
(if (null? (cdr plst))
(callback (+ time 10000) 'play-seq (+ time 11025) '(60 62 65))
(callback (+ time 10000) 'play-seq (+ time 11025) (cdr plst)))))

(play-seq (now) '(60 62 65))

Ok, now while play-seq is running change the ‘(60 62 65) in play-seq to ‘(60 62 67) and re-evaluate the play-seq function.  Now try changing it to ‘(60 62 67 69) and re-evaluating.  Because play-seq uses this list to reinitialize plst whenever plst is null any changes we make are reflected when this re-initialization occurs.  A useful little trick.  Stop the play-seq function by re-defining play-seq to be the function that does nothing (define play-seq (lambda args)).

Let’s extend play-seq to include a rhythm list as well.

;; plays a sequence of pitches
(define play-seq
(lambda (time plst rlst)
(play-note time piano (car plst) 80 (car rlst))
(callback (+ time (* .5 (car rlst))) 'play-seq (+ time (car rlst))
(if (null? (cdr plst))
'(60 62 65 69 67)
(cdr plst))
(if (null? (cdr rlst))
'(11025 11025 22050 11025)
(cdr rlst)))))

(play-seq (now) '(60 62 65 69 67) '(11025 11025 22050 11025))

Note that our pitch list and our rhythm list are different lengths.  Unlike for-each (and map) we can iterate through these two lists independently - meaning they can be of different lengths which allows us to play with various phasing techniques.  Have a play, change the lengths/values of both lists inside the play-seq function - remember to re-evaluate play-seq when you are ready for your changes to take effect.  Try calling play-seq again to start a second sequence playing.  Try to create a nice offset - you’ll need to press ctrl-space at just the right time :-)  Note that after the first iteration through the sequence both running instances of play-seq will assume the same lists (because callback sets the same list values when it’s time to reinitialize the lists).  As an exercise for the reader, think about how you could avoid that problem (i.e. keep the lists independent for each instance of play-seq).

Ok, so we can now manually change the list each cycle through, but what if we would like to change the list programmatically.  No problem, just you use a function instead of a literal list - of course this is now no longer an ostanati!

;; plays a random pentatonic sequence of notes
(define play-seq
(lambda (time plst rlst)
(play-note time piano (car plst) 80 (* .65 (car rlst)))
(callback (+ time (* .5 (car rlst))) 'play-seq (+ time (car rlst))
(if (null? (cdr plst))
(make-list-with-proc 4 (lambda (i) (random '(60 62 64 67 69))))
(cdr plst))
(if (null? (cdr rlst))
(make-list 4 11025)
(cdr rlst)))))

(play-seq (now) '(60 62 64 67) '(11025))

One final thing before we move on.  Just a quick performance tip (musical performance of course!).  It is very easy to add some metric interest by oscillating the volume to peak on down beats.  Let’s make a small modification to the previous example to demonstrate this simple little cheat ;)  Also we’ll shorten the durations a little (constant legato gets a touch boring).

;; plays a random pentatonic sequence of notes with a metric pulse
(define play-seq
(lambda (time plst rlst)
(play-note time piano (car plst)
(+ 60 (* 50 (cos (* 0.03125 3.141592 time))))
(* .65 (car rlst)))
(callback (+ time (* .5 (car rlst))) 'play-seq (+ time (car rlst))
(if (null? (cdr plst))
(make-list-with-proc 4 (lambda (i) (random '(60 62 64 67 69))))
(cdr plst))
(if (null? (cdr rlst))
(make-list 4 11025)
(cdr rlst)))))

(play-seq (now) '(60 62 64 67) '(11025))