Found in the depths of the SC Users list archive, a very nice 303 emulator created by SuperCollidist extraordinaire Lance J. Putnam.
To run this you'll need to evaluate the two sections separately, then send the sequncer routine a play message, like this.
~seqr.play;
This is a fairly large chunk of code so here's an .rtf version.
(
s = Server.internal;
SynthDef("sc303", { arg out=0, freq=440, wave=0, ctf=100, res=0.2,
sus=0, dec=1.0, env=1000, gate=0, vol=0.2;
var filEnv, volEnv, waves;
// can't use adsr with exp curve???
//volEnv = EnvGen.ar(Env.adsr(1, 0, 1, dec, vol, 'exp'), In.kr(bus));
volEnv = EnvGen.ar(Env.new([10e-10, 1, 1, 10e-10], [0.01, sus, dec], 'exp'), gate);
filEnv = EnvGen.ar(Env.new([10e-10, 1, 10e-10], [0.01, dec], 'exp'), gate);
waves = [Saw.ar(freq, volEnv), Pulse.ar(freq, 0.5, volEnv)];
Out.ar(out, RLPF.ar( Select.ar(wave, waves), ctf + (filEnv * env), res).dup * vol);
}).send(s);
)
/*
s.sendMsg("s_new", "sc303", 3000, 0, 0);
s.sendMsg("n_set", 3000, 'gate', 1, 'freq', 220);
s.sendMsg("n_set", 3000, 'gate', 0);
s.sendMsg("n_set", 3000, 'dec', 1);
s.sendMsg("n_free", ~node)
*/
// make GUI!
(
~bpm = 140;
~step = 0;
// set default note ons
~defaultNoteOns = Array.fill(16,1);
// this is the note on array that will be used in the sequencer routine
~noteOns = ~defaultNoteOns;
// set root
~root = 36;
// set default pitches
~defaultPitches = [ 0, 12, 0, 0, -12, 0, 0, 0, -12, 0, 12, 0, 3, -5, 2, 0 ];
// this is the pitch array that will be used in the sequencer routine
~pitches = ~defaultPitches;
~tStepLED = Array.new; // the step indicator LEDs as an array
~sNoteOn = Array.new; // the note-on sliders as an array
~tNoteOn = Array.new; // the note-on value boxes as an array
~sPitch = Array.new; // the pitch sliders as an array
~tPitch = Array.new; // the pitch value boxes as an array
w = SCWindow("SC-303", Rect(128, 64, 510, 320));
w.view.background = Color.white;
w.front;
~layoutLeft = SCCompositeView(w, Rect(0, 0, 400, 320)); // make the left pane of the window
~layoutLeft.decorator = FlowLayout(~layoutLeft.bounds); // auto-arrange the elements
~layoutRight = SCCompositeView(w, Rect(400, 0, 400, 120));
~layoutRight.decorator = FlowLayout(~layoutRight.bounds);
// make transport controls
// Tempo
SCStaticText(~layoutRight, Rect(0,0, 45, 14)).string_("BPM: ");
~tBpm = SCNumberBox(~layoutRight, Rect(0,0, 45, 14))
.string_(~bpm)
.action_({ arg item;
~bpm = item.value;
~sBpm.value_((~bpm-40)/200);
});
~layoutRight.decorator.nextLine;
~sBpm = SCSlider(~layoutRight, Rect(0,0, 100, 20))
.background_(Color.new(0.8,0.8,0.8))
.value_((~bpm-40)/200)
.action_({ arg sldr;
~bpm = ((sldr.value)*200) + 40;
~tBpm.string_(~bpm);
});
~layoutRight.decorator.nextLine;
// Reset
SCButton(~layoutRight, Rect(0,0,45,20))
.states_([["|<"]]) .action_({ arg item; ~tStepLED[~step].background_(Color.new(0.6,0,0)); ~step = 0; ~tStepLED[0].background_(Color.new(1,0,0)); }); // Play/Pause SCButton(~layoutRight, Rect(0,0,45,20)) .states_([[">"],["||"]])
.action_({ arg item;
if( item.value == 0, {
~seqr.stop;
},{
~seqr.reset;
~seqr.play;
});
});
~layoutRight.decorator.nextLine;
// Root note
SCStaticText(~layoutRight, Rect(0,0, 45, 14)).string_("Root: ");
~tRoot = SCStaticText(~layoutRight, Rect(0,0, 45, 14)).string_(~root);
~layoutRight.decorator.nextLine;
SCSlider(~layoutRight, Rect(0,0, 100, 20))
.background_(Color.new(0.8,0.8,0.8))
.value_((~root-24)/36)
.step_(1/48)
.action_({ arg sldr;
~root = ((sldr.value)*36) + 24;
~tRoot.string_(~root);
});
// make synth control labels
SCStaticText(~layoutLeft, Rect(0,0, 45, 14)).string_("Wave");
SCStaticText(~layoutLeft, Rect(0,0, 80, 14)).string_("Cutoff");
SCStaticText(~layoutLeft, Rect(0,0, 80, 14)).string_("Resonance");
SCStaticText(~layoutLeft, Rect(0,0, 80, 14)).string_("Decay");
SCStaticText(~layoutLeft, Rect(0,0, 80, 14)).string_("Envelope");
~layoutLeft.decorator.nextLine;
// make the synth controls
// wave-type
SCButton(~layoutLeft, Rect(0,0,45,20))
.states_([["Saw"],["Square"]])
.action_({ arg item;
s.sendMsg("n_set", ~node, 'wave', item.value);
});
// cut-off frequency
SCSlider(~layoutLeft, Rect(0,0, 80, 20))
.background_(Color.new(0.8,0.8,0.8))
.action_({ arg sldr;
s.sendMsg("n_set", ~node, 'ctf', ((sldr.value)*(10000-100))+100); // modify synth param
});
// resonance amt
SCSlider(~layoutLeft, Rect(0,0, 80, 20))
.background_(Color.new(0.8,0.8,0.8))
.action_({ arg sldr;
s.sendMsg("n_set", ~node, 'res', (1-sldr.value)*(0.97)+0.03); // modify synth param
});
// decay amt
SCSlider(~layoutLeft, Rect(0,0, 80, 20))
.background_(Color.new(0.8,0.8,0.8))
.action_({ arg sldr;
s.sendMsg("n_set", ~node, 'dec', (sldr.value)*2); // modify synth param
});
// envelope amt
SCSlider(~layoutLeft, Rect(0,0, 80, 20))
.background_(Color.new(0.8,0.8,0.8))
.action_({ arg sldr;
s.sendMsg("n_set", ~node, 'env', (sldr.value)*10000); // modify synth param
});
~layoutLeft.decorator.nextLine;
// make step LEDs
16.do({ arg i;
~tStepLED = ~tStepLED.add(
SCStaticText(~layoutLeft, Rect(0,0,20,12))
.background_(Color.new(0.6,0,0))
.stringColor_(Color.white)
.string_(i+1)
);
});
~tStepLED[~step].background_(Color.new(1,0,0));
~layoutLeft.decorator.nextLine;
// make the note-on sliders
16.do({ arg i;
~sNoteOn = ~sNoteOn.add(
SCSlider(~layoutLeft, Rect(0,0,20,60)) // position doesn't matter; width, height do
.background_(Color.new(0,0,0.7)) // color the slider
.step_(0.1) // values are stepped by 0.1
.value_(~noteOns[i]) // assign it its value from the note on array
.action_({ arg sldr; // when the slider is moved...
~noteOns[i] = sldr.value; // ... update note-on array
~tNoteOn[i].string = sldr.value; // ... update its value box
// ...change color of value box
~tNoteOn[i].background = Color.new((1-~noteOns[i]),1,(1-~noteOns[i]));
})
);
});
~layoutLeft.decorator.nextLine;
// make the note-on value boxes
16.do({ arg i;
~tNoteOn = ~tNoteOn.add(
SCStaticText(~layoutLeft, Rect(0,0,20,20))
.background_(Color.new((1-~noteOns[i]),1,(1-~noteOns[i])))
.string_(~sNoteOn[i].value)
.align_(0)
.action_({ arg text;
text.string.postln;
})
);
});
~layoutLeft.decorator.nextLine;
// make the pitch sliders
16.do({ arg i;
~sPitch = ~sPitch.add(
SCSlider(~layoutLeft, Rect(0,0,20,120))
.value_((~pitches[i]+12)/24)
.step_(1/24)
.background_(Color.new(0.8,0.8,0.8))
.action_({ arg item;
~pitches[i] = ((item.value * 24)-12);
~tPitch[i].string = ((item.value * 24)-12);
})
);
});
~layoutLeft.decorator.nextLine;
// make the pitch value boxes
16.do({ arg i;
~tPitch = ~tPitch.add(
SCStaticText(~layoutLeft, Rect(0,0,20,20))
.background_(Color.white)
.string_(~pitches[i].value)
.align_(0)
);
});
/*~tempButt = SCButton(w, Rect(0,0,60,60))
.states_([
["0", Color.red, Color.black]
]);*/
)
// do other stuff
(
~node = s.nextNodeID;
// start the synth
s.sendMsg("s_new", "sc303", ~node, 1, 1);
// make the step sequencer
~seqr = Routine.new({
loop({
// turn on LED
{~tStepLED[~step].background_(Color.new(1,0,0)); /*(~step.asString + "on").postln;*/}.defer;
if(~noteOns[~step].coin, {
// play note!
// this is out of sync
// s.sendMsg("n_set", ~node, 'gate', 1, 'freq', (~pitches[~step]+~root).midicps);
// (7.5/~bpm).wait;
// s.sendMsg("n_set", ~node, 'gate', 0);
// (7.5/~bpm).wait;
// buffer playback on server (adds start/stop delay, but better inter-note timing)
s.sendBundle(s.latency, ["n_set", ~node, 'gate', 1, 'freq', (~pitches[~step]+~root).midicps]);
(7.5/~bpm).wait;
s.sendBundle(s.latency, ["n_set", ~node, 'gate', 0]);
(7.5/~bpm).wait;
},{
// it's a rest
(15/~bpm).wait;
});
// turn off LED
{~tStepLED[(~step-1)%16].background_(Color.new(0.6,0,0)); /*(((~step-1)%16).asString + "off").postln;*/}.defer;
~step = (~step + 1)%16;
});
});
)
~seqr.play;
~seqr.stop;
~seqr.reset;
I'm going to go into this in more detail and possibly add to it or adapt it later...