Rhythmic Automata

In an earlier tutorial Cellular Automata were mapped to pitch. In this program they are mapped to rhythm. In my view this is a more musically sucessful mapping and the evolving nature of the automata is more clearly evident becuase a one-dimensional matrix is used (i.e, an array or list of cells).

Each cell in the array represents one semi-quaver and, in the score, notes are sounded on 'live' cells while rests are used where the cells are 'dead.'

The rules that are applied to update the cells each iteration are the key to the musical style that emerges. These rules are, like traditional CA, based on the number of neighboring cells. Musically this relates to rhythmic density; the more neighbours a semi-quaver has the more densly populated is that area of the bar. So, by varying the rules you can provide sparseness (density) tendencies. As well, rules influence the liklihood of cell change and thus the stability of the rhytmic patterns (how quickly they evolve).

This is what the result sounds like.

Here is the source file.

RhythmicAutomata.java

Let's have a closer look.

import jm.music.data.*;
import jm.JMC;
import jm.util.*;


We import the jMusic constants, the data types, and the Util package that contains the Write class.

public class RhythmicAutomata implements JMC{    
/* declare the empty bar arrays */

int barLength = 16;
int[] bar = new int[barLength];
int[] tempBar = new int[barLength];

/* Declare the jMusic score and parts */

Part part1 = new Part("Hats", 0, 9);
Part part2 = new Part("Rim Shot", 0, 9);
Part part3 = new Part("Claps", 0, 9);
Score score = new Score("AlgoRhythm", 84);


The class is decared and so are a number of class variables. Notice that an array of int's called 'bar' is declared. This holds the once dimensional cell matrix. The 'tempBar' array is used when we apply the rules to evolve the array one genertion.

 	public static void main(String[] args) {
new RhythmicAutomata();
}


The main() method creates an instance of this class.

Create one part of music bar by bar

	public RhythmicAutomata() {        
/* fill array */

initialise();
makeMusic(part1, 42);
/* calc next array */
for(int i=0; i<23; i++) {
nextBar();
makeMusic(part1, 42);
printBar();
}


The constructor makes a couple of method calls to fill the array (see details below), then sets up a loop that generates 23 bars of music, printing the state of the array to the terminal each time through.

Pack parts into the score and export

      		score.addPart(part1);

/* save as a MIDI file */

Write.midi(score, "RhythmicAutomata.mid");
}

This should be familiar to jMusic coders. Add the part with 23 phrases of one bar each into a score then translate it into a standard MIDI file.

Set up an inital array of cells

	private void initialise() {
System.out.println();
System.out.println("====== Initialising automata array ======");
for(int i=0; i<barLength; i++) {
bar[i] = (int)(Math.random() * 2);
}
printBar();
}

The constructor called many methods. The initialise() method is the first of them.

It beings our displaying of the data using println statements.

The for-loop sets up the values of the cells choosing randomly betwenn 1 and 0.

Display the music at the command line

	private void printBar() {
for (int i=0; i<barLength; i++) {
System.out.print(bar[i] + " ");
}
System.out.println();
}


This method prints the contens of the array to the terminal as a string of 1s or 0s.

Notice that the System.out.print() method does not add a carrage return, after each cell value is added a System.out.prinln() method adds the 'return' at the end of the line.

Apply the CA rules for one generation

  	private void nextBar() {
int score = 0;

/* calculate the number of neighbours */

for(int i=0; i<barLength; i++) {
for(int j=1; j<3; j++) {
if (i-j > 0) score += bar[i - j];
if (i+j < barLength) score += bar[i +j];
}

/* apply the rules of evolution */

if (score == 0) {
if (bar[i] == 1) tempBar[i] = 0;
else tempBar[i] = 1;
}
if (score == 1) tempBar[i] = bar[i];
if (score == 2) tempBar[i] = bar[i];
if (score == 3) {
if (bar[i] == 1) tempBar[i] = 0;
else tempBar[i] = 1;
}
if (score == 4) tempBar[i] = 0;

/* reset score value for next index */

score = 0;
}

/* shift new values in */

bar = tempBar;
}


This is the guts of the program. In this method the rules are applied. First the number of live (1) cells three palces either side of the current cell are counted. (The variable score here is unfortunate - it is not a jMusic score, but the numbers of 1's that have been encouted).

Depending upon the number of active neighbours this cell either lives or dies. If it has 1 or 2 neighbours it is unchanged, if it has 3 neighors and is alive it changes its state, and if it has four neighbours it's getting too crowded and it dies.

The 'tempBar' array is used to store the values of the next iteration during this process, once completed its contents are transferred to the 'bar' variable and become the current state.

Create one bar of music

	private void makeMusic(Part p, int pitch) {
Phrase phr = new Phrase();
for (int i=0; i<barLength; i++) {
if (bar[i] == 0) phr.addNote(new Note (REST, SQ));
else phr.addNote(new Note(pitch,
SQ, (int)(Math.random() * 60 + 60)));
}
p.addPhrase(phr);
}
}


The makeMusic() method turns the 1s and 0s into notes and rests. Each phrase thus created is added to the part. Tis method is called once for each bar of music that is required.

Things to do to modify this class

// Try changing sounds

// Try changing tempo

// Try changing bar length

// Try adding another part

// Try capturing bars then playing in reverse order - going from cohesive -> random

// Try changing the rules

// Try making it take into account 3 neighbours each side rather than 2

// Try starting with composed patterns rather than random patterns




© 2002 Andrew R Brown