//Otomata SuperCollider prototype
//http://www.batu.in/otomata
//Batuhan Bozkurt 2010
//Boot server first and execute Otomata.new to run.
Otomata
{
var width, <numRows;
var <win, <view;
var cells;
var <cellWidth;
var isPlaying;
var thread;
var routine;
var <>speed;
var <>pitches;
*new
{|argWidth = 500, argNumRows = 9|
^super.new.init(argWidth, argNumRows);
}
init
{|argWidth, argNumRows|
width = argWidth;
numRows = argNumRows;
cellWidth = width / numRows;
isPlaying = false;
speed = 0.2;
cells = List.new;
pitches = [50, 57, 58, 60, 62, 64, 65, 69, 72];
win = Window.new("Otomata", Rect(300, 300, width, width));
win.onClose_({ if(isPlaying, { thread.stop; }); });
view = UserView(win, win.view.bounds).background_(Color.black);
view.mouseDownAction =
{|...args|
if(isPlaying == false,
{
if(args[4] == 0, //left click
{
cells.add(OtomataCell.new(this, (args[1] / cellWidth).floor, (args[2] / cellWidth).floor));
});
});
win.refresh;
};
win.view.keyDownAction =
{|...args|
if(args[1] == $ , //if space pressed
{
if(isPlaying == false,
{
isPlaying = true;
thread = routine.fork;
},
{
thread.stop;
isPlaying = false;
});
});
};
routine =
{
loop
({
cells.do
({|srcCell|
cells.do
({|targetCell|
if(srcCell != targetCell,
{
if((srcCell.curRow == targetCell.curRow) and: { srcCell.curCol == targetCell.curCol },
{
{ srcCell.toNextState(); }.defer;
Synth(\cwMeet);
"two (or more) cells collided at gridX: %, gridY: %.".format(srcCell.curCol, srcCell.curRow).postln;
});
});
});
});
cells.do
({|item|
{ item.advance(); }.defer;
});
speed.wait;
});
};
SynthDef(\Otomatar,
{
arg pan = 0, freq = 100;
var snd;
snd = LPF.ar(WhiteNoise.ar(0.25), freq) * Decay.ar(Impulse.ar(0), 0.1);
DetectSilence.ar(snd, doneAction: 2);
Out.ar(0, Pan2.ar(snd, pan));
}).add;
SynthDef(\cwMeet,
{
var snd;
snd = Impulse.ar(0);
DetectSilence.ar(snd, doneAction: 2);
Out.ar(0, snd.dup);
}).add;
SynthDef(\cwHang,
{
arg pan = 0, note = 50;
var snd;
var trig = Impulse.ar(0);
snd = SinOsc.ar(note.midicps * Rand(0.99, 1.01), 0, 0.5) * Decay.ar(trig, Rand(1.4, 1.8) * 1.5);
snd = snd + (SinOsc.ar(note.midicps * 2 * Rand(0.999, 1.001), 0, 0.25) * Decay.ar(trig, Rand(0.8, 1.2)));
snd = snd + (SinOsc.ar(note.midicps * 3 * Rand(0.999, 1.001), 0, 0.1) * Decay.ar(trig, Rand(0.5, 0.8)));
snd = snd + (LPF.ar(WhiteNoise.ar(0.5), note.midicps * 2.5) * Decay.ar(trig, Rand(0.05, 0.12)));
DetectSilence.ar(snd, doneAction: 2);
Out.ar(0, Pan2.ar(snd, pan));
}).add;
win.front;
}
}
OtomataCell
{
var curState;
var parent;
var <curCol, <curRow;
var view;
*new
{|argParent, argCol, argRow|
^super.new.init(argParent, argCol, argRow);
}
init
{|argParent, argCol, argRow|
curState = 0;
parent = argParent;
curCol = argCol.asInt;
curRow = argRow.asInt;
view =
UserView
(
parent.win,
Rect
(
curCol * parent.cellWidth,
curRow * parent.cellWidth,
parent.cellWidth - 1,
parent.cellWidth - 1
)
).background_(this.stateColor().postln);
view.mouseDownAction_
({|...args|
if(args[4] == 0, //left click
{
this.toNextState();
});
});
}
toNextState
{
curState = ((curState + 1) % 4).asInt;
view.background_(this.stateColor());
}
stateColor
{
curState.switch
(
0, { ^Color.red; },
1, { ^Color.green; },
2, { ^Color.blue; },
3, { ^Color.cyan; }
);
}
advance
{
curState.switch
(
0,
{
if(curRow == 0, { curState = 2; });
curRow = (curRow - 1).fold(0, parent.numRows - 1);
if(curRow == 0, { this.doBound(); });
},
1,
{
if(curCol == (parent.numRows - 1), { curState = 3; });
curCol = (curCol + 1).fold(0, parent.numRows - 1);
if(curCol == (parent.numRows - 1), { this.doBound(); });
},
2,
{
if(curRow == (parent.numRows - 1), { curState = 0; });
curRow = (curRow + 1).fold(0, parent.numRows - 1);
if(curRow == (parent.numRows - 1), { this.doBound(); });
},
3,
{
if(curCol == 0, { curState = 1; });
curCol = (curCol - 1).fold(0, parent.numRows - 1);
if(curCol == 0, { this.doBound(); });
}
);
this.updateView(curCol, curRow);
}
updateView
{
view.bounds =
Rect
(
curCol * parent.cellWidth,
curRow * parent.cellWidth,
parent.cellWidth - 1,
parent.cellWidth - 1
);
view.background = this.stateColor();
}
doBound
{
"triggering sound at gridX: %, gridY: %".format(curCol, curRow).postln;
if((curState == 0) or: { curState == 2; },
{
Synth(\cwHang, [\pan, (curCol / parent.numRows) * 2 - 1, \note, parent.pitches[curCol]]);
},
{
Synth(\cwHang, [\pan, (curCol / parent.numRows) * 2 - 1, \note, parent.pitches[parent.numRows - 1 - curRow]]);
});
}
}