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

Partner A

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

Partner B

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

Partner A

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

Partner B

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

Partner A

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