Rule-based Generative Music Scripts

A project log for Fluxamasynth Modules

The Fluxamasynth is a module that makes it easy to add high quality MIDI sound to any Arduino or Raspberry Pi project.

FluxlyFluxly 10/19/2018 at 22:350 Comments

There are many approaches to writing programs to generate music. For example, you can use Markov Chains, or you could hook up a Recurrent Neural Network to the Fluxamasynth Python module to compose new songs based on trained material. I'm less interested in the open-ended approaches to generative music, and more interested in rule sets, programmed choreographies, algorithmic enhancements to composing, and the combination of randomness, rules, and interactive inputs.

The WRND generative radio station project described previously is a script that uses randomness to generate songs governed by a series of rules. In general this can be used to help a composer explore chancespace within a set of constraints. 

For WRND the rule system consists of parts, measures, and notes that are arrays of numbers randomly generated with contraints. Playback is with four MIDI channels with a separate percussion channel. 

First choose the parts:

void chooseParts() {
  parts[0] = random(254)+1;
  parts[1] = random(255);
  parts[1] = parts[1] & (parts[1]^parts[0]);
  parts[2] = random(255);
  parts[2] = parts[2] & (parts[2]^(parts[0]|parts[1]));
  parts[3] = ~(parts[0]|parts[1]|parts[2]);

Each repetition of a song has 8 sections, each of which is an A, B, C, or D part. How parts are repeated is selected by choosing a random number from 1-255. The individual bits of this first number are the "slots" in the 8 sections that will be an A part. For example:

Part A: 88 = 0 1 0 1 1 0 0 0

Another random number is selected for part B, which is ANDed with the exclusive OR of part A so that each "slot" only has one unique part:

Part A: 88  = 0 1 0 1 1 0 0 0
Part B: 176 = 1 0 1 0 0 0 0 0

 The same is done for part C, and part D fills the remaining slots:

Part A: 88  => 0 1 0 1 1 0 0 0
Part B: 176 => 1 0 1 0 0 0 0 0
Part C: 140 => 0 0 0 0 0 1 0 0
Part D:     => 0 0 0 0 0 0 1 1
               B A B A A C D D

So the four parts repeat in the order:


and a song may be 10-100 repeats of this sequence. The measures array tells whether a note is on or off on a particular beat for a particular part:

void chooseMeasures(int p) {

  measures[0+p] = random(254)+1;
  measures[1+p] = random(255);
  measures[1+p] = measures[1+p] & (measures[1+p]^measures[0+p]);
  measures[2+p] = random(255);
  measures[2+p] = measures[2+p] & (measures[2+p]^(measures[0+p]|measures[1+p]));
  measures[3+p] = ~(measures[0+p]|measures[1+p]|measures[2+p]);

So if one beat is an sixteenth note, a sequence of 

1 1 1 1 0 0 0 0

would represent a quarter note followed by a quarter note rest. The way that noteOn/noteOff is chosen means only one note will sound for each "slot". However, when the song is finally run or "performed" there is a dynamic binaryChoice function that determines whether a 1 or 0 means noteOn (and the other noteOff).  

For each part, only one note is played in each of the four channels. The chooseNotes() function assigns a baseNote for the A part and the B, C, and D are all multiples of 3rds and 5ths of the baseNote for that part:

void chooseNotes(int p) {
  int baseNote = random(40)+50;
  noteName[p] = baseNote;
  noteName[p+1]=baseNote + (random(3)-1)*7;
  noteName[p+2]=baseNote + (random(3)-1)*4;
  noteName[p+3]=baseNote + (random(4)-2)*12;

The rhythm and percussion variables generate a beat on the percussion MIDI channel 9.

A generated song sounds something like this:

Play Sample 1

or this:

Play Sample 2