Recently I got intrigued with a talk by Adam Neely about polyrhythms. So i decided to create a little program that would allow you to listen how different polyrhythms sound. The idea was to use the RxJS library.
The text assumes some knowledge about reactive programming, preferably RxJS.
How do you play a polyrhythm?
From wikipedia: “a Polyrhythm is the simultaneous use of two or more conflicting rhythms”. Let’s take a 3 vs. 4 polyrhythm as an example: a 3/4 polyrhythm is when you hear 3 evenly spaced pulses in the same amount of time as 4 evenly spaced pulses (and that sequence repeats). If a repeating cycle is 1 second long, one pulse would be heard at: 0 sec, 1/3 sec, 2/3 sec (every 1/3 of a second). The second pulse would be heard at 0 sec, 1/4 sec, 2/4 sec and 3/4 sec (every 1/4 of a second).
A simple way to achieve this programmatically is to divide a cycle in 12 intervals (3*4=12), and have a clock generating numbers at the beginning of each interval (if the cycle is 1 sec, the interval would be 1/12 seconds long). We can then take those numbers as they are generated, and if a number is divisible by 3 or 4, we play a sound.
The table represents one cycle of a 3/4 polyrhythm. Increasing numbers (second row) are generated at regular intervals (first row). The 3rd and 4th row show when a sound is played. IE, at time t=3/12 s, the number 3 is emitted, and since 3 is divisible by 3, a sound is played (represented by X).
You can see and edit the code online here.
The repo is here.
Let’s start from the end: this is what the app should look like, and what it should do.
A summary of the features:
- We can type in our rhythms in the 2 inputs
- We can type in the BPM-beats per minute (BPM is a standard unit in music)
- We have a play/stop button
- When the rhythm is playing, we want to be able to change any of these parameters on the fly
Let’s see the implementation, step by step.
We want to react to changes on the input elements, and to button clicks. Let’s create Observable sequences from these events:
The rhythms sequence is a bit more complex - we start with an array of Observable sequences, one for each input (rhythmChangeEvents$). From there, we use the combineLatest operator, so we get only one sequence that emits every time an input value changes. It emits an array of input values.
We need an Observable emitting boolean values that can serve as a signal to play/stop the sound. We’ll use BehaviorSubject for that:
We create a BehaviorSubject, and emit values from it every time the play button is clicked. On every click, we emit the complement of the last emitted value.
Next, we combine the 3 sequences into 1, that should control the “clock” Observable (that is generating numbers at regular intervals).
Here are the sequences:
- rhythm$ - affects the clock speed, and sound producers
- bpmChanges$ - affects the clock speed (interval)
- play$ - starts/stops the clock
So, the config$ observable emits an array object every time any of the separate Observables emits.
Next, each time config$ emits a new value, we need to react to that value. There are two flows:
- config.play is false - the value signals we should stop, so we map that value to a new Observable sequence that never emits (“NEVER”)
- config.play is true - we map the value to a new Observable sequence (clock). The interval is set based on the BPM and the rhythms (the calculation is done in the function convertToMs).
The switchMap operator is convenient in this case, because it only emits values from the most recently mapped (projected) Observable. In other words, when a new config object is emitted, switchMap stops emitting the last clock sequence, and starts emitting the newly created one.
In the tap operator, we intercept the emitted config value, and change the button text if necessary.
Ok, so now we have a clock that emits numbers in regular intervals. We can use the logic described in the beginning to make some sounds.
In order to play the rhythm, we need access to rhythm$, and to clock$. Again, we use combineLatest to create an Observable that emits the composite object. For each rhythm, we execute a function (a “sound producer”). The function checks if the number (clock) is divisible by the “rhythm” number and plays the sound.
Sound is played using HTML5 audio api. This function just takes a sample available on the web and play it.
The documentation for all the operators used: