Showing posts with label 303. Show all posts
Showing posts with label 303. Show all posts

Saturday, February 28, 2009

303 Emulator

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...