Created
October 28, 2011 18:27
-
-
Save blinks/1322987 to your computer and use it in GitHub Desktop.
ChucK script to simulate Otomata
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* MIDI Helper Methods | |
*/ | |
class MIDIConnection { | |
MidiOut mout; | |
fun void connect(int port) { | |
if (!mout.open(0)) { me.exit(); } | |
} | |
fun void send(int event, int firstArg, int secondArg) { | |
MidiMsg _msg; | |
event => _msg.data1; | |
firstArg => _msg.data2; | |
secondArg => _msg.data3; | |
mout.send(_msg); | |
} | |
fun void noteOff(int chan, int note) { | |
// chan from 0 to 15 | |
send(0x80 + chan, note, 0); | |
} | |
fun void noteOn(int chan, int note, int velocity) { | |
// chan from 0 to 15 | |
send(0x90 + chan, note, velocity); | |
} | |
fun void noteAftertouch(int chan, int note, int pressure) { | |
// chan from 0 to 15 | |
send(0xa0 + chan, note, pressure); | |
} | |
fun void aftertouch(int chan, int pressure) { | |
// chan from 0 to 15 | |
send(0xd0 + chan, pressure, 0); | |
} | |
fun void pitchWheel(int chan, int lsb, int msb) { | |
// chan from 0 to 15 | |
send(0xe0 + chan, lsb, msb); | |
} | |
fun void program(int chan, int prog) { | |
// chan from 0 to 15 | |
send(0xe0 + chan, prog, 0); | |
} | |
// 0xb?: chan ? control mode change | |
// (See http://www.onicos.com/staff/iz/formats/midi-cntl.html) | |
// System Common: | |
// 0xf2: song position ptr | |
// 0xf3: song select [song #, none] | |
// 0xf6: tune request | |
// 0xf7: end of sysex | |
// Real Time: | |
// 0xf8: timing clock | |
// 0xfa: start | |
// 0xfb: continue | |
// 0xfc: stop | |
// 0xfe: active sensing | |
// 0xff: sys reset | |
} | |
/** Play a single note. */ | |
fun void note(MIDIConnection conn, int index, dur length) { | |
conn.noteOn(0, index, 100); | |
length => now; | |
conn.noteOff(0, index); | |
} | |
/** | |
* A cellular automaton, inspired by Otomata. | |
* | |
* @see http://www.earslap.com/projectslab/otomata | |
*/ | |
class CellularAutomaton { | |
complex STATE[16]; | |
#(-1, 0) => STATE[1]; | |
#(0, 1) => STATE[2]; | |
#(1, 0) => STATE[4]; | |
#(0, -1) => STATE[8]; | |
int _scale[]; | |
int _grid[2][24][24]; | |
int _t; | |
float bpm; | |
dur quarter; | |
dur eighth; | |
fun void init(float bpm, int scale[]) { | |
// Define speed and note lengths. | |
(60.0 / bpm)::second => quarter; | |
0.5::quarter => eighth; | |
// Define the scale to be used. | |
scale @=> _scale; | |
if (_scale.cap() > 24) { <<< "scale too long" >>>; } | |
0 => _t; | |
for (0 => int i; i < _scale.cap(); i++) { | |
for (0 => int j; j < _scale.cap(); j++) { | |
0 => _grid[0][i][j]; | |
0 => _grid[1][i][j]; | |
} | |
} | |
// Initialize randomly. | |
Std.rand2(4, 8) => int weight; | |
for (0 => int k; k < weight; k++) { | |
Std.rand() % _scale.cap() => int i; | |
Std.rand() % _scale.cap() => int j; | |
Std.rand2(1, 15) => _grid[_t][i][j]; | |
<<< _grid[_t][i][j], "=>", i, j >>>; | |
} | |
} | |
fun void step(MIDIConnection conn) { | |
(_t + 1) % 2 => int t1; | |
// Clear the grid at t1. | |
for (0 => int i; i < _scale.cap(); i++) { | |
for (0 => int j; j < _scale.cap(); j++) { | |
0 => _grid[t1][i][j]; | |
} | |
} | |
// Compute the next state of the grid. | |
for (0 => int i; i < _scale.cap(); i++) { | |
for (0 => int j; j < _scale.cap(); j++) { | |
_grid[_t][i][j] => int state; // current state. | |
if (state == 0) { | |
// No alive cells. | |
continue; | |
} else if (state & (state - 1) != 0) { | |
// Not a power of 2: Rotate all cells in this location. | |
(state << 1) % 15 => state; | |
} | |
for (1 => int k; k < 16; k << 1 => k) { | |
if ((state & k) == 0) { continue; } | |
// Dealing purely with the `k` component of this cell. | |
STATE[k] => complex dir; | |
i + (dir.re $ int) => int i1; | |
j + (dir.im $ int) => int j1; | |
if (i1 < 0 || i1 >= _scale.cap()) { | |
// Use j1 to determine the note. | |
spork ~ note(conn, _scale[j1], eighth); | |
(k << 2) % 15 |=> _grid[t1][i][j]; // rotate 180 | |
} else if (j1 < 0 || j1 >= _scale.cap()) { | |
// Use i1 to determine the note. | |
spork ~ note(conn, _scale[i1], eighth); | |
(k << 2) % 15 |=> _grid[t1][i][j]; // rotate 180 | |
} else { | |
k |=> _grid[t1][i1][j1]; | |
} | |
} | |
} | |
} | |
t1 => _t; | |
eighth => now; | |
} | |
fun void loopForever(MIDIConnection conn) { | |
while (true) { step(conn); } | |
} | |
} | |
// Connect to the first MIDI Out. | |
MIDIConnection conn; | |
0 @=> int index; | |
conn.connect(0); | |
// Start up the automaton and loop forever. | |
CellularAutomaton ca; | |
ca.init(140.0, // bpm | |
// Otomata Scale: D A Bb C D E F A C | |
[62, 69, 70, 72, 74, 76, 77, 81, 84]); | |
ca.loopForever(conn); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment