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




No comments:

Post a Comment