Electronic Music Tools with JavaScript: Week 4

Week 4 - Algorithmic Music

Week four consists of generating melodies algorithmically. This is done by creating sequences of notes by generating random frequencies and rhythms.

A Random Frequency Generator

The first step is to randomly generate a frequency and play it.

var freq = Math.random() * 1000;
playSound(freq, now, now + 0.5);

function playSound(freq, startTime, stopTime) {
  var osc = con.createOscillator();
  osc.frequency.value = freq;
  osc.connect(con.destination);
  osc.start(startTime);
  osc.stop(stopTime);
}

A More Melodic Frequency Generator

Rather than selecting completely random frequencies it helps to limit the possible frequencies which can be randomly selected. For the example below I've chosen the frequencies from a C major scale.

var frequencyChoices = [261.63, 277.18, 293.66, 311.13,
329.23, 369.99, 392.00, 415.30, 440.0, 466.16, 493.88,
523.25];

var freq = frequencyChoices[Math.floor(Math.random() * frequencyChoices.length)];
playSound(freq, now, now + 0.5);

function playSound(freq, startTime, stopTime) {
  var osc = con.createOscillator();
  osc.frequency.value = freq;
  osc.connect(con.destination);
  osc.start(startTime);
  osc.stop(stopTime);
}

Adding Some Rhythm

Playing with a constant rhythm can sound a bit boring so it would be nice to mix up the rhythm a bit and generate a more interesting melody.

The approach the course showed to generate different rhythms was creating an array of probabilities a note is played on each beat and then determining whether or not to play a note on a given beat using this.

As an experiment I tried an approach where I randomly select a rhythm pattern to play on each beat. To do so I create a set of simple rhythm functions and randomly select one of them to play for each beat.

var playFuncs = [playWholeNote, playHalfNotes, playQuarterNotes];

function playWholeNote(freq, startTime) {
  playSound(freq, startTime, startTime + interval);
}

function playHalfNotes(freq, startTime) {
  playSound(freq, startTime, startTime + 0.5 * interval);
  playSound(freq, startTime + 0.5 * interval, startTime + interval);
}

function playQuarterNotes(freq, startTime) {
  playSound(freq, startTime, startTime + 0.25 * interval);
  playSound(freq, startTime + 0.25 * interval, startTime + 0.5 * interval);
  playSound(freq, startTime + 0.5 * interval, startTime + 0.75 * interval);
  playSound(freq, startTime + 0.75 * interval, startTime + interval);
}

Then we can randomly select which rhythm to use for each beat.

var freq = frequencyChoices[Math.floor(Math.random() * frequencyChoices.length)];
var playFunc = playFuncs[Math.floor(Math.random() * playFuncs.length)];
        
playFunc(freq, now);

Melodic Variation

Now many repeats of the same note can sound a bit boring so we can alter the functions to take arrays of notes to play.

function playWholeNote(frequencies, startTime) {
  playSound(frequencies[0], startTime, startTime + interval);
}

function playHalfNotes(frequencies, startTime) {
  playSound(frequencies[0], startTime, startTime + 0.5 * interval);
  playSound(frequencies[1], startTime + 0.5 * interval, startTime + interval);
}

function playQuarterNotes(frequencies, startTime) {
  playSound(frequencies[0], startTime, startTime + 0.25 * interval);
  playSound(frequencies[1], startTime + 0.25 * interval, startTime + 0.5 * interval);
  playSound(frequencies[2], startTime + 0.5 * interval, startTime + 0.75 * interval);
  playSound(frequencies[3], startTime + 0.75 * interval, startTime + interval);
}

We then generate an array of random frequencies and pass it to the random rhythm function.

function getFrequency() {
  return frequencyChoices[Math.floor(Math.random() * frequencyChoices.length)];
}

var frequencies = [getFrequency(), getFrequency(), getFrequency(), getFrequency()];
var playFunc = playFuncs[Math.floor(Math.random() * playFuncs.length)];
        
playFunc(frequencies, now);

For the example below I have also doubled the speed relative to the previous examples.

Algorithmic Music Generation Constraints

Gradually building a music generation system like this got me thinking about the effect of adding constraints to such a system. In a way, the more constraints we add we can increase the musicality of the generated melody but at the same time we are forcing the generator to make music which conforms more closely to non-algorithmic music. It would be interesting to explore the boundaries of applying more or less rules and the effect it had on the end music. Although it is interesting to apply rules and try and get the computer to ‘compose’ music like a human would it is probably equaly interesting to see if the lack of constraints could lead to interesting music which sounds DIFFERENT to human music. And thats not me just trying to defend how awful my basic music generator sounds in the traditional sense, I promise!

How I Found the Course

I've thoroughly enjoyed the course and found it a great introduction to tinkering with the Web Audio API.

Tags: JavaScript, Web, Web Audio