Lab 11: Part 2B: Recursive Drums
Setup
In the lab11
folder, open recursiveDrums.py
.
At the bottom of the starter file is a test function; you can uncomment things there to run them.
If you want Python to play sounds directly instead of just saving a
.wav
file, you should use the "Manage packages" option in the Thonny
"Tools" menu to install the simpleaudio
package.
For today, we'll just need a few functions from the wavesynth
audio
library: addBeat
, addRest
, rewind
, and setTime
.
The addBeat
function takes 1 argument: the duration of the beat. Durations are
always specified in seconds (whole or fractional).
The addRest
function also takes one parameters specifying the duration.
Both functions add to the "current track," and that track can later be
printed, saved to a file, or played (the testing code does all three).
Within each track, there is a current time, which can be modified by calling
rewind
/fastforward
or setTime
. By default adding a beat or rest changes
the current time to the end of that beat or rest.
Warning: for minimum chaos you should probably use headphones while working on this part of the lab, if you have them, but the 'snare' sounds we synthesize can be pretty loud, so you may want to turn down your volume a bit now and then figure out what's comfortable for you. The audio elements below should be set to 1/2 volume by default, you may also need to turn things up to hear them well.
Task 1. drumBeats
We will start off simple: write a function called drumBeats
which
accepts two parameters: a number of beats, and a duration. It should add
that many beats, each with that duration, to the current track, starting
at the given start time, with each beat directly following the previous
one. To make things interesting, it must use recursion, and may not
use a loop.
Here is what the result sounds like for drumBeats(4, 0.2)
;
you can see the correct printed output below as well.
Show the output for
drumBeats(4, 0.2)
a 0.2s snare beat
and a 0.2s snare beat
and a 0.2s snare beat
and a 0.2s snare beat
And here is what drumBeats(4, 0.2)
followed by
drumBeats(8, 0.1)
sounds like:
Task 2. spacedBeats
Now write a second function, spacedBeats
, which accepts the same
arguments as drumBeats
and which does the same thing, except that it
adds a rest in between each pair of beats. The rests should be twice as
long as the beats.
To make things interesting, see if you can get it to add the rests only between notes, and avoid adding a rest after the last note (of course, you can't hear the difference, but you'll be able to see it in printed output, and if another beat were added to the track, it would matter).
Here is what the result sounds like for
spacedBeats(4, 0.2)
; you can see the correct printed
output below as well.
Show the output for
spacedBeats(4, 0.2)
a 0.2s snare beat
and a 0.4s rest
and a 0.2s snare beat
and a 0.4s rest
and a 0.2s snare beat
and a 0.4s rest
and a 0.2s snare beat
Task 3. decreasingBeats
decreasingBeats
will take the same parameters as drumBeats
and
spacedBeats
, but it will make each beat that it adds shorter than the
previous beat. Specifically, while the first beat that it adds will have
the specified duration, subsequent beats will each be 3/4 of the duration
of the previous beat. Another way to look at that is to say that the
pattern begins with a single beat of the specified duration, and then
continues with a decreasingBeats
sequence which itself begins with a
beat of 3/4 the original duration.
Here is what the result sounds like for
decreasingBeats(8, 0.4)
; you can see the correct printed
output below as well.
Show the output for
decreasingBeats(8, 0.4)
a 0.4s snare beat
and a 0.3s snare beat
and a 0.225s snare beat
and a 0.169s snare beat
and a 0.127s snare beat
and a 0.0949s snare beat
and a 0.0712s snare beat
and a 0.0534s snare beat
Task 4. squeezeBeats
To make things interesting, write a squeezeBeats
function which adds
longer beats at first and in the end, with shorter beats in between. This
function will accept two parameters: duration
and minDuration
. If the
given duration
is less than the given minDuration
, it will do
nothing. However, if the given duration is longer than the minDuration
,
it will add two beats to the current track of the given duration, and in
between those beats, it will add a full squeezeBeats
pattern with 3/4
of the original duration. So for example, if the initial duration
and
minDuration
are 1.6 and 0.8, the beats added will have durations 1.6,
1.2, 0.9, 0.9, 1.2, and 1.6, in that order (0.9 * 0.75 is less than 0.8,
so no further notes are added between the two 0.9 notes).
Note: This one uses a slightly different recursive pattern than the previous tasks, and in particular, it's not really possible to use the base case (the last recursive function call frame) to add the longest beat at the end of the pattern: that beat should actually be added by the very first function call frame (which is also the last to be cleaned up).
Here is what the result sounds like for squeezeBeats(0.2, 0.05)
;
you can see the correct printed output below as well.
Show the output for
squeezeBeats(0.2, 0.05)
a 0.2s snare beat
and a 0.15s snare beat
and a 0.113s snare beat
and a 0.0844s snare beat
and a 0.0633s snare beat
and a 0.0633s snare beat
and a 0.0844s snare beat
and a 0.113s snare beat
and a 0.15s snare beat
and a 0.2s snare beat
Task 5. layeredBeats
What happens if we layer multiple drum beats on top of each other?
layeredBeats
does just that, adding increasing numbers of beats within
the same time segment based on a complexity
value. It takes two
arguments: duration
determines the total duration within which multiple
beats are added, and complexity
determines how many beats (and how many
layers) are used.
When complexity is 1, we just add a single beat that fills the whole duration, and if complexity is less than one, nothing is added. When complexity is two, in addition to the single beat for complexity = 1 (added via a recursive call), we also add two beats, each of which is half of the given duration. Complexity 3 adds a further three beats at 1/3 duration, complexity four adds four beats of 1/4 duration on top of that, and so on.
Note: In each recurive function call frame for layeredBeats
, you'll
want to use your drumBeats
function from above to add multiple beats at
once, and then use rewind
to go back to the beginning of the time
period so that the next layer will overlap with the current one.
Here's a diagram showing how the beats are arranged for complexity 3: three beats cover the whole duration, then two more that also cover the same duration are layered on that, and finally a single beat also covering the same duration is layered on both of those.
-----------
-----|-----
---|---|---
Here is what the result sounds like for
layeredBeats(0.5, 2)
; you can see the correct printed
output below as well.
Show the output for
layeredBeats(0.5, 2)
a 0.25s snare beat
at 0.25s a 0.25s snare beat
at 0s a 0.5s snare beat
...and here is what the result sounds like for
layeredBeats(0.5, 4)
:
Show the output for
layeredBeats(0.5, 4)
a 0.125s snare beat
and a 0.125s snare beat
and a 0.125s snare beat
at 0.375s a 0.125s snare beat
at 0s a 0.167s snare beat
at 0.167s a 0.167s snare beat
at 0.333s a 0.167s snare beat
at 0s a 0.25s snare beat
at 0.25s a 0.25s snare beat
at 0s a 0.5s snare beat
Table of Contents
- Lab 11 Home
- Part 1: Exercises (start with B notebook)
- Part 2A: Recursive Turtles (work on this part OR the next one)
- Part 2B: Recursive Drums (work on this part OR the previous one)
- Reference: Case-by-case recursion strategy
- Reference: Recursive design patterns
- Knowledge Check