a case study
algorithmic composition
the coldplay song generator
[the Analytical Engine] might act upon other things besides number, were objects found whose mutual fundamental relations could be expressed by those of the abstract science of operations, and which should be also susceptible of adaptations to the action of the operating notation and mechanism of the engine . . . supposing, for instance, that the fundamental relations of pitched sounds in the science of harmony and of musical composition were susceptible of such expression and adaptations, the engine might compose elaborate and scientific pieces of music of any degree of complexity or extent.
Ada Lovelace (sketch of the Analytical Engine) 1842
A while back I got into an argument with my younger brother over the band coldplay. I was arguing that the band was very "formulaic", my brother disagreed. To prove him wrong, I decided to create an algorithm. I broke down what I felt were common patterns in coldplay tracks and created an app that could generate an infinite number of new tracks in the style of coldplay called the coldplay song generator. I originally wrote this in a programming environment called Max/MSP and later rewrote it using it's open source alternative, Puredata. Here I'll discuss how we might recreate this algorithm for the browser using the Web Audio API and Tone.js by making references to various concepts and techniques we've covered so far.
We'll start with this TEMPLATE, the UI contains two buttons, one to toggle the playback and another to generate a new random coldplay track. The rest of the code will be our javascript logic. There have been many algorithms written that aim to identify patterns within a body of music and the identified patterns to create new music in that "style" (one of my favorites is EMI, Experiments in Musical Intelligence by David Cope) that's not what we're going to do. We're going to deduce Coldplay's formula ourselves and create a custom algorithm based on the patterns we ourselves observe in Coldplay's style. This will inevitably produce a "less accurate" representation of the band's style, but failing to "copy" perfectly is one way to create something new. With that in mind, consider this case study a way of exploring other algorithmic musical routes you might take, interesting detours that might lead to your own novel algorithmic compositions.
instrument: our sound source
First, we need to choose our sound source. Coldplay often times centers their music around a piano, so let's borrow the sampler instance from the piano sampler example. We don't need the keymap or the attack/release functions (those are for creating a piano/keyboard instrument) we just want the instance of the sampler to use as our sound source.
To test that your sampler is working try the adding the code below to your sketch. Note: it may take a few seconds for your player to load all those samples, you may need to wait a second or two before the pushing the "cold-play" (toggle) button will play the chord below.
If you go back to listen to the original Clocks song, you may notice that there's a bit of reverb on the piano, using what we learned from the Signal Processing notes, we can create a reverb processing node connected to our destination and then update our sampler so that rather than connecting it directly toDestination()
we connect it to our new processing node connect(reverb)
rythm: transport controls
Assuming our piano is working, let's introduce the transport logic and replace the play
function and event listener with something more like the ones from a transport example. Below I've added a toggle
function similar to that example which is now triggered by the click event of the "cold-play" button.
Next we'll add a few new variables to the top of our file to keep track of our prgram's "state". First, is the step
variable to keep track of the number of beats as well as a notes
array to list out some notes we want to play. I've also created a new play
function which excpects a time argument, which will be passed in by the Tone.Loop
wich we've also defined. The Loop will call the play function every '8n'
or 8th note. The play function is very similar to the ones we've seen in our other transport examples we've covered so far.
clock's "dna"
Here is where we'll create an algorithm based on our interpretation of Coldplay's "formula". As I said earlier, if our goal was to create an accurate representation of the band's style we would be better served by gathering as much data as possible (the musical scores of their entire oeuvre) and running some statistical analysis on it, but our goal isn't really to make the perfect coldplay machine, but rather to use Coldplay as a starting point from which we might explore other hand-crafted musical algorithms, to that end we'll start by breaking down the core chord progression and arpeggio from their song Clocks (which in many ways is a solid example of their "style").
A classic Coldplay song has a 4 chord progression. Meaning there are 4 'bars' or 'measures' in a verse or chorus which repeat. Rather than simply keeping track of our beat within a single measure with a counter, we'll add another state variable to keep track of each of the for measures called bar
. The bar will track which of the 4 measures we're in and the step track which beat within that measure we're in. With a typical 4/4 rythm, a measure contains a total of 4 beats or 4 quarter notes, '4n'. Since our transport is triggering the play function on every 8th note (or '8n') our bar will contain a total of 8 steps (each an 8th note long), so I've added a totalSteps
variable, as well as a totalBars
variable.
I've also replaced the notes array from before with a few new arrays which represent the piano part in our reference song, Clocks. The piano plays a chord progression with the left hand and an accompanying arpeggio with the right hand. Rather than noting the specific notes for these chords and arpeggios, we'll abstract this out a bit more in such a way that will allow us to easily make algorithmic variations on it. We could approach this a number of different ways, but what i decided to do was first identify the key of the song, which seems to be 'F minor', which i've stored in two arrays. The first is called rightHandScale
which starts the F minor scale on the 4th octave (or 'F4'), and the second is called leftHandScale
which is also an f minor but starts the scale one octave lower on 'F3'. coldplay often accompanies the chord progression played on the left hand with arpeggios played one octave above on the right hand.
Following each of the scales, I've added arrays containing other arrays (aka: multi-dimensional arrays) which represent the notes played as indexes (aka "degress") from the scale. Take a look at the leftHandChrd
variable. This array contains an array for each of the 4 chords in the progression (one played at the start of each measure or bar). The first chord is represented by the indexes [1, 3, 6]
. This corresponds to the notes ['G3', 'A#3', 'D#4']
which happens to be a 'G diminished' chord, the first chord in the song clocks. These 'left hand' notes will be played all at the same time, because it's a 'chord', where as the notes in the 'right hand' (stored in the rightHandArp
arrays) will be played one after the other, which is what makes them an 'arpeggio'. Notice that the first 3 indexes in the first right hand array match the 3 indexes in the first left hand array, only reversed. You might also notice that the arpeggio simply repeats those same 3 notes again two (and two thirds) more times. If you compare the subsequent right and left hand arrays to each other you'll notice the same is true. By representing the notes for the song clocks this way, as indexes from the song's key (or the scale the song is written in) a pattern is revealed to us, the song's "DNA".
Next, we'll update our play
function so that it works with our new state data.
In the code above we use the modulus operator %
to do some simple 'clock arithmetic' (no pun intended) to get the current bar or b
a number between 0 and 3, as well as the current 8th note or step s
within that bar/measure, a number between 0 and 7. Then on lines 6-11 we have the logic for the left hand, starting with a conditional statement that checks to see if we're on the first step of the bar (because we only want to play the chord once per measure), if so we grab the array of indexes for that bar's chord and then convert that from an array of numbers to an array of notes by using the javascript array's map method to map out the corresponding notes from the scale. Lastly we'll send that array of notes into our sampler's triggerAttackRelease
and hold that chord for the whole measure or '1n'.
the coldplay "formula"
If we play our algorithm now it should sound exactly like the song clocks. Our last step will be to create a function which randomizes our notes in a way that conforms to Coldplay's "formula". Part of their formula is already represented in our state variables, like the fact that we have a 4 chord progression (noted by totalSteps
with the chord shapes stored as indexes corresponding to a musical scale stored in leftHandChrd
and leftHandScale
respectively) as well as the fact that each chord will be accompanied by an arpeggio (stored in the rightHandArp
and rightHandScale
arrays). We noticed before that the arpeggio in the song Clocks follows a particular pattern based on the inversion of the notes in their corresponding chord. If the chord is: a, b, c then the arpeggio would be c, b, a, c, b, a, c, b. so we'll need to create a function that recreates new versions of this pattern., but first, let's reintroduce a familiar helper function.
We first discussed this createScale
function during in the notes for Chords and Scales. This function takes two arguments, a root note (ex: 'F4') and a "mode" array, a list of values representing the number of 'steps' between each note (starting from our root note) needed to create a particular scale (ex: the aeolian mode pattern is [2, 1, 2, 2, 1, 2, 2] which can be used to derive a 'minor' scale). The function then returns an array of note strings in that particular scale, for example: [ 'F4', 'G4', 'G#4', 'A#4', 'C5', 'C#5', 'D#5', 'F5' ].
We can use the createScale
funtion within another function that recreates new versions of the pattern we identified earlier. Our randomColdplay
function will first choose a new tempo and a new key for the song. Coldplay rarely plays slower then 90bpm (beats per minute) and faster than 140bpm, so we'll select a random integer from that range and assign that as our new transport bpm value. Then we'll choose a new scale, Coldplay often sticks to 'minor' keys (hence the melancholy tone of their music) so we'll define the minor scale shape in an array as well as all a keys array of root notes i've noticed coldplay tends to write their music in. We'll use our createScale
function to create two new scales based on our minor pattern and a randomly chosen key and update our state's rightHandScale
and leftHandScale
ensuring that the latter is one octave under the former.
Once we have a new tempo and new scale we'll create our new array of index values for our chords and our arpeggios. We'll start by clearing the previous arrays and then in a four loop (which loops 4 times, one per bar) we'll select new index values for those a, b, c indexes using a little clock arithmetic to ensure they always stay within the length of the corresponding scale arrays before adding (or 'pushing') our new arrays.
Then we'll make sure to attach this function to the click event of our 'randomize' button. If we want to make sure we also start with a random composition, we could also add a call to this function on the window's load event.
Here's the complete piece.
Attribution: Text and code written by Nick Briz. The code editor icons designed by Meko and licensed under Creative Commons Attribution License (CC BY 3.0). All sounds generated using the Web Audio API and/or Tone.js by Yotam Mann and other contributors.