//////////////////////////////////// // // // ALGORITHMIC COMPOSITION // // BASED ON CELLULAR AUTOMATA // // // // Nicolas Besnard, March 2010 // // // //////////////////////////////////// // Implementation of a Wolfram-type cellular automata (CA) // with symmetry conditions: the edges of the "world" are // supposed to be joined together; cells located on the upper // edge and those on the lower edges are neighboors. // // Columns of the CA vector are mapped to notes in a user-defined // table of notes (scale) and then to a frequency. The number // of voices is specified by the user. The mapping of bits to // voices can be randomly generated or specified by the user (in // that case, please be sure not to exceed the vector dimension). // The number of bits giving the notes is specified by the user, // and result in 2^n possible notes in the note table. // // The CA algo gives the next-step vector, given a rule number // specified by the user. A "mutation" of the CA result is possible // with a user-specified probability. This mutation inverts bits // of the new vector, leading to interesting variations. // // The sound is produced by ChucK square oscillators going // through a low pass filter. MIDI output is also enabled, with // specified MIDI port and channel. // // Feedback and comments are welcome! // // // Please see wikipedia for more information on cellular automata: // http://en.wikipedia.org/wiki/Cellular_automaton // // Nicolas Besnard, March 2010 // web: http://electronique-et-musique.blog4ever.com/ // // IMPORTANT NOTICE // This code is provided "as it is" for informational purposes only. // You can use/modify/share this code for non-commercial use only, // as long as you refer to the original code and author. // // DECLARATIONS // User parameters 17 => int vectorSize; // Length of the step vector 3 => int lenBit; // Nb of bits used for the codage of freqs 105 => int CArule; // N# of the cellular automata rule (0..255) 0.1 => float probaMutation; // probability of mutation of the CA (%) 3 => int nbOsc; // Number of oscillators 69 => float baseFreq; // Base freq of the oscillators 200::ms => dur durStep; // Step duration // Definition of the table of notes that will be mapped // to the value of the oscillator bits // (notes given in semitones) [ 0, 3, 5, 7, 8, 12, 15, 17 ] @=> int scale[]; //[ 0, 5, 8, 12] @=> int scale[]; //[ 0, 1, 2, 3, 4, 5, 6, 7 ] @=> int scale[]; //[ 0, 2, 4, 5, 7, 9, 11, 12 ] @=> int scale[]; 1 => int MIDIPort; // Put your MIDI out port number here 1 => int MIDIChannel; // MIDI channel for output messages 0 => int allowRepet; // =1: a note is played by each voice at each step // =0: same notes are not played again // (only affect MIDI output) // Program variables int t0[vectorSize]; // Current step vector int t1[vectorSize]; // Next step vector 1 => t0[t0.cap()/2]; // Initialize with a 1 at mid vector SqrOsc osc[nbOsc]; // Oscillators LPF f => dac; // Low pass filter 600 => f.freq; 3 => f.Q; int startBit[nbOsc]; // Position of the 1st bit in t0 for each osc int endBit[nbOsc]; // Position of the last bit in t0 for each osc float val; int val1; int val2; int val3; int note; int iStep; // MIDI out MidiOut MIDIOut; int iMIDINote[nbOsc]; MidiMsg msgMIDIOut; int bMidiOK; // INITIALISATIONS for (0 => int i; i < nbOsc; i++){ // Connect the oscillators to the filter osc[i] => f; // Set initial frequency and gain baseFreq => osc[i].freq; 0 => osc[i].gain; // Set the position of the frequency code // within the CA vector // startBit[i] must be < vectorSize - lenBit // Fixed mapping.. //2*lenBit * i => startBit[i]; // This line can be customized! //t0.cap()/2 => startBit[i]; // .. or random mapping while(true){ Std.rand2(0, vectorSize - 1) => startBit[i]; if (startBit[i] < vectorSize - lenBit) break; } <<< "Start bit voice", i, ":", startBit[i] >>>; // End bit (do not modify this one) startBit[i] + (lenBit - 1) => endBit[i]; } // Open the MIDI port if(MIDIPort>=0){ if(MIDIOut.open(MIDIPort)) { 1 => bMidiOK; // Print out device that was opened <<< "MIDI device:", MIDIOut.num(), " -> ", MIDIOut.name() >>>; } } // Synchronisation step // (allows launching several shreds in parallel) durStep - (now % durStep) => now; // MAIN LOOP while (true){ // Calculate the current step (freq of each osc) for (0 => int iOsc; iOsc < nbOsc; iOsc++){ 0 => val; // Get the value of the oscillator bits for (0 => int i; i <= (endBit[iOsc]-startBit[iOsc]); i++){ val + t0[endBit[iOsc]-i] * Math.pow(2,i) => val; } // Map the value to a note within the scale // The modulo allows looping within the table // if the value is > to the length of the table scale[(val $ int) % scale.cap()] => note; // Map the note to a frequency // This line can be customized! baseFreq * (iOsc+1) * Math.pow(1.059463, note) => osc[iOsc].freq; // Turn the oscillator on if value becomes >0 // (useful at the beginning of the song) // Comment to disable the oscillator output if (val > 0) 0.3 / nbOsc => osc[iOsc].gain; // Generates MIDI out commands // MIDI on/off sent on channel MIDIChannel if (bMidiOK == 1){ if ((allowRepet == 1) | (Math.round(Std.ftom(2*osc[iOsc].freq())) $ int != iMIDINote[iOsc])){ //MIDI OFF: stop previous note 0x80 + (MIDIChannel - 1) => msgMIDIOut.data1; iMIDINote[iOsc] => msgMIDIOut.data2; MIDIOut.send(msgMIDIOut); // MIDI note based on the frequency Math.round(Std.ftom(2*osc[iOsc].freq())) $ int => iMIDINote[iOsc]; // MIDI ON: start new note 0x90 + (MIDIChannel - 1) => msgMIDIOut.data1; iMIDINote[iOsc] => msgMIDIOut.data2; 127 => msgMIDIOut.data3; MIDIOut.send(msgMIDIOut); } } } // Perform the cellular automata calculation // => create the next step vector for (0 => int i; i < t0.cap(); i++){ if (i == 0){ t0[t0.cap()-1] => val1; } else t0[i-1] => val1; t0[i] => val2; if (i == t0.cap()-1){ t0[0] => val3; } else t0[i+1] => val3; ComputeRule(CArule, val1, val2, val3) => t1[i]; } for (0 => int i; i < t0.cap(); i++){ t1[i] => t0[i]; } // Wait until end of step // Duration of the step depends on CA values.. durStep * (t0[t0.cap() - 1] + 1) => now; // ..or fixed duration //durStep => now; iStep++; } fun int ComputeRule(int rule, int val1, int val2, int val3){ // Computes the new value of a cell based on its previous value (val2) // and values of its neighboors (val1 and val2) int val; // 3-bit value representing the neighborhood int mask; int res; // Calculate the decimal value of the neighborhood 4 * val1 + 2 * val2 + val3 => val; // Bit mask (Math.pow(2, val) $ int) => mask; (mask & rule)/mask => res; // bitwise AND // Mutation of the cellular automata Std.rand2f( 0.0, 100.0 ) => float proba; if (proba < probaMutation){ Std.abs(res - 1) => res; <<< "Mutation!", "" >>>; } return res; }