So far we have been using Impromptu’s default time standard, samples per second, to control rhythm and duration information.  As musicians though, we are more used to working with beats and tempo.  Here’s a simple example working with samples.  Note that throughout this tutorial I’m using gm drums (with the DLS instrument) and passing midi-channel 10 (9) to the play-note function.   At the end of this page you’ll find a list of general midi drum definitions which I’ll be using in this tutorial - for *gm:maracas* etc..

;; samples per second loop
(define drum-loop
(lambda (time dur)
(play-note time drums *gm:maracas* 80 dur 9)
(callback (+ time (* .5 dur)) 'drum-loop (+ time dur) (random '(22050 11025)))))

(drum-loop (now) 11025)

And here’s one way that we could go about transforming this into a more abstract notion of time.

;; beat loop
(define drum-loop
(lambda (time dur)
(let ((d (* dur *samplerate*)))
(play-note time drums *gm:maracas* 80 d 9)
(callback (+ time (* .5 d)) 'drum-loop (+ time d) (random '(0.5 0.25))))))

(drum-loop (now) 0.25)

So what’s the advantage here - this seems like more work for nothing?  Well, we actually gain two big advantages.
(1) Ratio’s are easier to deal with than samples - 0.25 is easier to remember than 11025 (assuming a samplerate of 44100)
(2) this system supports alternate tempos - we can change tempo without having to change any rhythm values.

Let’s play back the same example at 120 bpm - remembering that by default impromptu runs at 60 bpm or 1.0 = *samplerate*.  We’ll also add triplets to our quavers and semi-quavers.

;; beat loop at 120bpm
(define drum-loop
(lambda (time dur)
(let ((d (* dur .5 *samplerate*)))
(play-note time drums *gm:maracas* 80 d 9)
(callback (+ time (* .5 d)) 'drum-loop (+ time d)
(random (list (/ 1 3) 0.5 0.25))))))

(drum-loop (now) 0.25)

Let’s try using an oscillator to drift the playback speed back and forth over time.

;; beat loop with tempo shift
(define drum-loop
(lambda (time dur)
(let ((d (* dur (+ .5 (* .25 (cos (* 16 3.141592 time)))) *samplerate*)))
(play-note time drums *gm:maracas* 80 d 9)
(callback (+ time (* .5 d)) 'drum-loop (+ time d)
(random (list 0.5))))))

(drum-loop (now) 0.5)

Have a listen to the result on my machine - note that all values are now 0.5 so we should get a nice even rhythm with a tempo change over time drums_one.mp3.  Doesn’t sound very even does it!  It turns out that tempo is a lot more subtle than you might expect.  What we actually need is a linear function that can more evenly distribute our beats with respect to tempo changes.  Luckily the impromprtu library time-lib.scm will do this for us.

As it turns out time-lib.scm includes a function called make-metro that will solve a few problems for us.  At it’s simplest, make-metro is a function that accepts a tempo and returns a closure to us.  We can then call that closure with a cumulative time in beats and have an absolute sample number returned to us.  This makes more sense as a practical exercise so let me demonstrate.

;; create a metronome starting at 120 bpm
(define *metro* (make-metro 120))

;; beat loop
(define drum-loop
(lambda (time duration)
(print time duration)
(play-note (*metro* time) drums *gm:maracas* 80 (*metro* 'dur duration) 9)
(callback (*metro* (+ time (* .5 duration))) 'drum-loop (+ time duration)
(random (list 0.5)))))

(drum-loop (*metro* 'get-beat) 0.5)

You should notice a couple of things here.

1) We start our loop by calling (*metro* ‘get-beat).  This asks our *metro* closure to return the next available beat number to us (i.e. fmod 1.0) - *metro* starts ticking over beats as soon as it’s initalized (actually this is not really true - *metro* is a linear function - but it is easier to think about it this way).

2) time is now in beats (not in samples) and is cumulative.  Check your logview for an idea about what time equals each cycle around drum-loop.  Also remember that floating point is subject to rounding error - don’t worry about that overly.

3) (*metro* ‘dur duration) returns a duration in samples relative to the current tempo.

4) The closure returned by (make-metro) is really a kind of object and the symbol names are method names - message names really.  Any arguments after the “message name” are passed with the “message” are dispatched inside the closure to the appropriate “method”.  What we are using here is a form of message passing.  Who said scheme wasn’t an OO language!!

OK now how about those tempo changes.  No problem, we just need to use another message ‘set-tempo which takes a new tempo in bpm (beats per minute).

;; create a metronome starting at 120 bpm
(define *metro* (make-metro 120))

;; beat loop with tempo shift
(define drum-loop
(lambda (time duration)
(*metro* 'set-tempo (+ 120 (* 40 (cos (* .25 3.141592 time)))))
(play-note (*metro* time) drums *gm:maracas* 80 (*metro* 'dur duration) 9)
(callback (*metro* (+ time (* .5 duration))) 'drum-loop (+ time duration)
(random (list 0.5)))))

(drum-loop (*metro* 'get-beat) 0.5)

And here’s what it sounds like drums_two.mp3.  Much better, I’m sure you will agree.  Now the really cool thing about *metro* is that you can now use it to sync as many callback loops as you like.  Let’s add a second drum-loop call.  Notice also that we have added an argument to the ‘get-beat message that asks ‘get-beat to quantize to a 4.0 (i.e. four beats) instead of the default 1.0.  I’m going to play maracas and tambourine with a slight 0.25 offset.

;; create a metronome starting at 120 bpm
(define *metro* (make-metro 120))

;; beat loop with tempo shift
(define drum-loop
(lambda (time duration pitch)
(play-note (*metro* time) drums pitch 80 (*metro* 'dur duration) 9)
(callback (*metro* (+ time (* .5 duration))) 'drum-loop (+ time duration)
duration
pitch)))

;; shift tempo over time using oscillator
(define tempo-shift
(lambda (time)
(*metro* 'set-tempo (+ 120 (* 40 (cos (* .25 3.141592 time)))))
(callback (*metro* (+ time .2)) 'tempo-shift (+ time .25))))

(drum-loop (*metro* 'get-beat 4) 0.5 *gm:maracas*)
(drum-loop (*metro* 'get-beat 4.25) 0.5 *gm:tambourine*)
(tempo-shift (*metro* 'get-beat 1.0))

And it sounds like this drums_three.mp3  Ahhh, like clockwork.  Notice that now we are running two independent drum-loop temporal callbacks we need to put the tempo shift in a separate function - we don’t want the tempo to be set independently by two seperate loops!

We now have almost enough information to build our first drum machine!

APPENDIX

; gm drums
(define *gm:kick* 35)
(define *gm:kick-2* 36)
(define *gm:side-stick* 37)
(define *gm:snare* 38)
(define *gm:hand-clap* 39)
(define *gm:snare-2* 40)
(define *gm:low-floor-tom* 41)
(define *gm:closed-hi-hat* 42)
(define *gm:hi-floor-tom* 43)
(define *gm:pedal-hi-hat* 44)
(define *gm:low-tom* 45)
(define *gm:open-hi-hat* 46)
(define *gm:low-mid-tom* 47)
(define *gm:hi-mid-tom* 48)
(define *gm:crash* 49)
(define *gm:hi-tom* 50)
(define *gm:ride* 51)
(define *gm:chinese* 52)
(define *gm:ride-bell* 53)
(define *gm:tambourine* 54)
(define *gm:splash* 55)
(define *gm:cowbell* 56)
(define *gm:crash-2* 57)
(define *gm:vibraslap* 58)
(define *gm:ride-2* 59)
(define *gm:hi-bongo* 60)
(define *gm:low-bongo* 61)
(define *gm:mute-hi-conga* 62)
(define *gm:hi-conga* 63)
(define *gm:low-conga* 64)
(define *gm:hi-timbale* 65)
(define *gm:low-timbale* 66)
(define *gm:hi-agogo* 67)
(define *gm:low-agogo* 68)
(define *gm:cabasa* 69)
(define *gm:maracas* 70)
(define *gm:short-whistle* 71)
(define *gm:long-whistle* 72)
(define *gm:short-guiro* 73)
(define *gm:long-guiro* 74)
(define *gm:claves* 75)
(define *gm:hi-wood-block* 76)
(define *gm:low-wood-block* 77)
(define *gm:mute-cuica* 78)
(define *gm:open-cuica* 79)
(define *gm:mute-triangle* 80)
(define *gm:open-triangle* 81)
(define *gm:mute-surdo* 86)
(define *gm:open-surdo* 87)