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




Thursday, February 26, 2009

Auto Gui

Hidden away in the GUI Overview help file this little gem,





Automatic GUI

You can get a quick simple automatic interface for a Synth with SynthDesc : makeWindow.

(
s.waitForBoot({
SynthDef("test", { arg out, freq=330, amp=0.6;
Out.ar(out, SinOsc.ar(freq,0,amp))
}).store;


SynthDescLib.global.at(\test).makeWindow;
});
)


An auto GUI creator.
I haven't really read around it much yet, but it seems to work pretty well.

I decided to mate it with the wonderful Atari2600 Ugen from Fredrik Olofsson.

(
SynthDef(\atari2600, {|out= 0, gate= 1, tone0= 5, tone1= 8, freq0= 10, freq1= 20, rate= 1, amp= 1, pan= 0|
var e, z;
e= EnvGen.kr(Env.asr(0.01, amp, 0.05), gate, doneAction:2);
z= Atari2600.ar(tone0, tone1, freq0, freq1, 15, 15, rate);
Out.ar(out, Pan2.ar(z*e, pan));
}).store
)

SynthDescLib.global.at(\atari2600).makeWindow;






And I got a handy widget for exploring a new Ugen.

Tuesday, February 24, 2009

Gooey

And now a bit more explanation of what I'm doing with my Moog GUI. 

There are a few different ways of creating GUIs and controlling synths in SuperCollider, for now I've gone with a simple approach using Pattern Proxies and the standard Slider class, but I might try something different as I add features. 

Here's some of my GUI code again, 

(
var window = Window.new("Moog",Rect(400, 400, 140, 100)).front;
var cutoffSlider, cutoffRef, resgainSlider;


~cutoffRef = PatternProxy();



cutoffSlider = Slider.new(window,Rect(10, 25, 100, 20));
 cutoffSlider.action_{
 var val;
 val = (10000*(cutoffSlider.value));
 val.postln;
 ~cutoffRef.source_(val);
  };


StaticText.new(window,Rect(10, 5, 100, 20))
 .string_("Cutoff")
 .action_{|v| };


)
This is the bit that creates the window and the filter cutoff slider. 

The first thing I've done is create a window, the parameters passed to it are the title, the initial position it appears in on the screen and the size of the window itself. 

Then I've created a Pattern Proxy, this is the thing that allows me to control my synth with both a GUI and a pattern.  

A Pattern Proxy is an object that can be placed in in a pattern and be changed whilst the pattern is playing. In this case I'm using a GUI to change the value of the Pattern Proxy as the pattern plays. I've set it as a global variable with a ~ so I can use it outside of the function it's created in.

I then create a slider that controls the value of the Pattern Proxy. It's instantiated with the name of the window it is to appear in and values setting it's size and position in the window. 

The action_ method controls the action the slider performs when it's moved. In this case it takes the value of the slider object and multiplies it by 10000. Slider values move between 0 and 1, so to turn it into something usable by the synth I need to change the range. For simple linear values multiplying the output works fine, but for more complex stuff you can use a ControlSpec. 

I then set the modified value to be the source of the Pattern Proxy, the object it takes it's values from. 

After that there's a static text box, created in a similar way to the slider, as a label. 

To actually make use of the Pattern Proxy, I need a pattern, so here it is again. 

(
Pmono("Moog",
 \freq, Pseq([440, 550, 660, 770, 660, 880], inf),
 \dur, 0.2,
 \oscType, Pseq([1,0,1,0,1], inf),
 \oscType2, Pseq([0,0,2,2,1], inf),
 \cutoff, (~cutoffRef),
 \gain, (~resgainRef)

).play


All I've done is drop in the proxies as I would do with any other pattern streams. 

This time I've used a Pmono, which creates only one instance of the synth on ther server and feeds it all the values in turn, instead of creating new ones with each note. This sounds a tad more like a an authentic Moog, plus I need to use a mono pattern with my new version of the synth that features portamento. 

Shazam. 

Monday, February 23, 2009

Portamento

Before I do anymore GUI stuff a quick addition to my Moog synth itself, portamento!

Also known as slide or glissando, it's a nice feature of many analogue synths, here's the wiki page on it if you're not sure what I'm talking about. 

I've been wanting to add this to my synth since I created it but I thought it might be difficult, but after putting my faith in the hand of providence and searching the SuperCollider user mailing list, I found simple that portamento is in fact dead easy.

Here's my revised SynthDef 


 SynthDef("Moog",{

arg oscType =0, oscType2 = 1, pan = 0, level = 0.5, cutoff = 500, gain = 3.3, attack = 0.1, decay = 0.1, sust = 0.7, rel = 0.2, attackf = 0.1, decayf = 0.1, sustf = 0.9, relf = 0.2, gate = 1, freq =440, lagLev = 0.2;


var oscArray = [Saw.ar(Lag.kr(freq, lagLev)), SinOsc.ar(Lag.kr(freq, lagLev)), Pulse.ar(Lag.kr(freq, lagLev))];
var oscArray2 = [Saw.ar(Lag.kr(freq, lagLev)), SinOsc.ar(Lag.kr(freq, lagLev)), Pulse.ar(Lag.kr(freq, lagLev))];
var ampEnv = EnvGen.ar(Env.adsr(attack, decay, sust, rel), gate, doneAction:2);
var filterEnv = EnvGen.ar(Env.adsr(attackf, decayf, sustf, relf), gate, doneAction:2);
var osc1 = Select.ar(oscType, oscArray);
var osc2 = Select.ar(oscType2, oscArray2);
var fade = Pan2.ar(XFade2.ar(osc1, osc2, pan , level * ampEnv, 0));
var filter = MoogFF.ar(fade, cutoff * filterEnv, gain);
Out.ar(0,filter)

}).store
)

All I've done here is encapsulated each of my frequency arguments for the oscillators in a lag Ugen. The slide time, or the time it takes for the pitch of each note to move from it's original value to the new note is set by the lagLev argument, in seconds.

To make this work you'll need to use a Pmono pattern, it won't work if a new synth is created with each note as it would be with a  Pbind pattern. 

And that's Jazz. 



Sunday, February 22, 2009

A simple GUI

Controlling a synth with text isn't always intuitive, so I've decided to start working towards creating a GUI for my little Moogesque synth. 

To begin with I've created a very simple interface with two sliders that allow me to control the filter cutoff frequency and the resonance gain. 

Here's the Moog synthdef again. 


 SynthDef("Moog",{

arg oscType =0, oscType2 = 1, pan = 0, level = 0.5, cutoff = 500, gain = 3.3, attack = 0.1, decay = 0.1, sust = 0.7, rel = 0.2, attackf = 0.1, decayf = 0.1, sustf = 0.9, relf = 0.2, gate = 1, freq =440;


var oscArray = [Saw.ar(freq ), SinOsc.ar(freq), Pulse.ar(freq)];
var oscArray2 = [Saw.ar(freq), SinOsc.ar(freq), Pulse.ar(freq)];
var ampEnv = EnvGen.ar(Env.adsr(attack, decay, sust, rel), gate, doneAction:2);
var filterEnv = EnvGen.ar(Env.adsr(attackf, decayf, sustf, relf), gate, doneAction:2);
var osc1 = Select.ar(oscType, oscArray);
var osc2 = Select.ar(oscType2, oscArray2);
var fade = Pan2.ar(XFade2.ar(osc1, osc2, pan , level * ampEnv, 0));
var filter = MoogFF.ar(fade, cutoff * filterEnv, gain);
Out.ar(0,filter)

}).store
)

I've added a panner to it to center the sound, but other than that it's the same for now. 

This is my GUI code. 

It should be cross platform but I've only tried it on OS X.  

(
var window = Window.new("",Rect(400, 400, 140, 100)).front;
var cutoffSlider, cutoffRef, resgainSlider;

~resgainRef = PatternProxy(3);
~cutoffRef = PatternProxy(440);



cutoffSlider = Slider.new(window,Rect(10, 25, 100, 20));
 cutoffSlider.action_{
 var val;
 val = (10000*(cutoffSlider.value));
 val.postln;
 ~cutoffRef.source_(val);
  };


StaticText.new(window,Rect(10, 5, 100, 20))
 .string_("Cutoff")
 .action_{|v| };


resgainSlider = Slider.new(window,Rect(10, 65, 100, 20));
 resgainSlider.action_{
 var val;
 val = (4*(resgainSlider.value));
 val.postln;
 ~resgainRef.source_(val);
  };


StaticText.new(window,Rect(10, 45, 100, 20))
 .string_("Resonance Gain")
 .action_{|v| };
)

and here's a pattern incorporating the GUI elements I've created. 

(
Pmono("Moog",
 \freq, Pseq([440, 550, 660, 770, 660, 880], inf),
 \dur, 0.2,
 \oscType, Pseq([1,0,1,0,1], inf),
 \oscType2, Pseq([0,0,2,2,1], inf),
 \cutoff, (~cutoffRef),
 \gain, (~resgainRef)

).play


This time I've used a Pmono for a more authentic sound, but more on that later...

Tuesday, February 17, 2009

A Filter and Even More Drum Machine

Pulled from the ever informative SuperCollider Users Mailing List is this fine little example of an interesting low pass filter courtesy of nonprivate,  aka DJ Cylob.

"

this is kind of nice,

here's the normal sort of lpf:

(

var f;
f = FSinOsc.kr(1, 0, 24, 84).midicps;
RLPF.ar(Saw.ar(70,0.2), f, 0.3)
}.play
)

but with this combo of lpf and mid eq, the bass disappears as the resonance increases, which is a bit more like a real filter.

(

var f;
f = FSinOsc.kr(1, 0, 24, 84).midicps;

MidEQ.ar(
 LPF.ar(Saw.ar(70,0.2), f), 
 f, 

 0.5, 18, 0.6


}.play
)
"

Inspired by this I decided to see if I could use it on my drum machine and at the same learn how to send the output of a pattern through an effect. 

Here's the code I came up with, adapted from the Pbind help file which covers this very topic. 

(
SynthDef(\drums, {|out = 0, bassLevel = 0 , snareLevel = 0, hatLevel = 0, tomLevel = 0, pan1 = 0, pan2 = 0, pan3 = 0, pan4 = 0|

 var env1, env2, env3, env4, bass, snare, hat, tom, bassOut, snareOut, hatOut, tomOut, mixer; 
 env1 = EnvGen.kr(Env.perc(0.001, 0.2, 1, -4), 1, doneAction:2);
 env2 = EnvGen.kr(Env.perc(0.001, 0.5, 1, -1), 1, doneAction:2);
 env3 = EnvGen.kr(Env.perc(0.002, 0.3, 1, -2), 1, doneAction:2);
 env4 = EnvGen.kr(Env.perc(0.001, 0.1, 1, -5), 1, doneAction:2); 
 bass = SinOsc.ar(80) + Crackle.ar(1, 0.5);
 bassOut = Pan2.ar(bass*env1, pan1, bassLevel);
 
 snare = SinOsc.ar(120) - WhiteNoise.ar(0.5, 0.5);
 snareOut = Pan2.ar(snare*env4, pan2, snareLevel); 
 
 hat = Klank.ar(`[ [ 6563, 9875 ],
  [ 0.6, 0.5 ],
  [ 0.002, 0.003] ], PinkNoise.ar(1)); 
 hatOut = Pan2.ar(hat*env3, pan2, hatLevel); 
 
 tom = SinOsc.ar(440); 
 tomOut = Pan2.ar(tom*env4, pan4, tomLevel); 
 
 mixer = Mix.new([bassOut, snareOut, hatOut, tomOut]); 
 
 
 Out.ar(out, mixer);
 
 }).store
 
 )
   
 

 (
  SynthDef("lpfandmideq", { arg out;  
 var audio, efx, f;
 f = SinOsc.kr(3, 0, 24, 84).midicps; 
 audio = In.ar(20,2);
 efx= MidEQ.ar(
 LPF.ar(audio, f), 
 f, 

 0.5, 18, 0.6
); 
 
 Out.ar(out, efx);
}).memStore;  
  )
   
  (  
  a = Synth.after(1, "lpfandmideq");  
  )  
   
   
  (
   
   
  

 a = Pseq ([1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]);
 b = Pseq ([0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]);
 c = Pseq ([0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0]);
 d = Pseq ([0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1]); 
 e = Pwhite (0.14, 0.16, inf);
 
 p = Pbind(
  \instrument, \drums,
  \out, 20,
  \dur, e,
  \bassLevel, Pseq ([a], inf), 
  \snareLevel, Pseq ([b], inf),
  \hatLevel, Pseq ([c], inf),  
  \tomLevel, Pseq ([d], inf)
   
  ).play;
   
   
  )  

 To make this work you will need to make sure that you evaluate each section in brackets separately, apart from that it's pretty striaghtforward really.

In setting up the effect synth I needed to make sure that I set the input bus and the number of channels,  this needs two beacuse the drum machine is a two channel synth

audio = In.ar(20,2);

then create an instance of this synth

(  
  a = Synth.after(1, "lpfandmideq");  
  )  

and finally set the output of my pattern to the same bus that I set as the input of my effect. 

\out, 20,

and that's Jazz. 

Surprisingly easy really and it doesn't sound too bad.

 

Wednesday, February 11, 2009

More drum machine

I've been fiddling with my maliciously inept drum machine a bit more and using a couple of the enormous selection of pattern classes I've added some new features. 

A lot of real drum machines both hardware and software have a 'swing' or 'shuffle' which add a bit of syncopation though I suspect most of them do it in a more sophisticated way than what I've done here.   

e = Pwhite (0.14, 0.16);

 \dur, e,

I've used a Pwhite pattern to randomly select the \dur interval. 

The Pwhite pattern randomly generates values from a range provided by the first two parameters and repeats this a number of times set by the third value entered, with inf being the default. Sticking this into my drum sequence means that the interval between each drum sound will be random, givning it a possibly more 'organic' feel. Or maybe just making it sound wonky.

I've seen some software synths that let the user generate random patterns, I've done something similar using a Pwrand.

 f = Pwrand ([0, 1], [75, 25].normalizeSum, inf);

 \bassLevel, Pseq ([f], inf), 

 A Pwrand pattern generates random numbers from a list with a weighted probability. The above line generates a pattern with a 25% chance of each step having a drum sound  and a 75% chance of being silent.

Pwrand takes two arrays as arguments, the first one being the list of numbers to select randomly from, the second one being a list of probabilities, which should add up to one. Passing the second array a .normaliseSum message neatly side steps my poor mental arithmetic and automatically normalises the array elements to sum to one. Being able to weight the results makes the resulting pattern sound more rhythmic, without long silences or rolls that an unweighted probability would lead to.   

Here's the full pattern with these two bits added. 

 (

 a = Pseq ([1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]);
 b = Pseq ([0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]);
 c = Pseq ([0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0]);
 d = Pseq ([0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1]); 
 e = Pwhite (0.14, 0.16);
 f = Pwrand ([0, 1], [75, 25].normalizeSum, inf);
 
 
 p = Pbind(
  \instrument, \drums,
  \dur, e,
  \bassLevel, Pseq ([f], inf), 
  \snareLevel, Pseq ([b], inf),
  \hatLevel, Pseq ([f], inf),  
  \tomLevel, Pseq ([d], inf)
   
  ).play;
   
   
  )

Tuesday, February 10, 2009

Simple Drum Machine

After shamelessly flashing my Moog synth, I think it's time to reveal another SuperCollider creation. A very simple drum machine, made up of two parts. A SynthDef, defining my drums and a pattern template to play them. 

Here's the SynthDef 

(
SynthDef(\drums, {|out = 0, bassLevel = 0 , snareLevel = 0, hatLevel = 0, tomLevel = 0, pan1 = 0, pan2 = 0, pan3 = 0, pan4 = 0|

 var env1, env2, env3, env4, bass, snare, hat, tom, bassOut, snareOut, hatOut, tomOut, mixer; 
 env1 = EnvGen.kr(Env.perc(0.001, 0.2, 1, -4), 1, doneAction:2);
 env2 = EnvGen.kr(Env.perc(0.001, 0.5, 1, -1), 1, doneAction:2);
 env3 = EnvGen.kr(Env.perc(0.002, 0.3, 1, -2), 1, doneAction:2);
 env4 = EnvGen.kr(Env.perc(0.001, 0.1, 1, -5), 1, doneAction:2); 
 

bass = SinOsc.ar(80) + Crackle.ar(1, 0.5);
 bassOut = Pan2.ar(bass*env1, pan1, bassLevel);
 
 snare = SinOsc.ar(120) - WhiteNoise.ar(0.5, 0.5);
 snareOut = Pan2.ar(snare*env4, pan2, snareLevel); 
 
 hat = Klank.ar(`[ [ 6563, 9875 ],
  [ 0.6, 0.5 ],
  [ 0.002, 0.003] ], PinkNoise.ar(1)); 
 hatOut = Pan2.ar(hat*env3, pan2, hatLevel); 
 
 tom = SinOsc.ar(440); 
 tomOut = Pan2.ar(tom*env4, pan4, tomLevel); 
 
 mixer = Mix.new([bassOut, snareOut, hatOut, tomOut]); 
 
 
 Out.ar(out, mixer);
 
 }).store
 
 )

I have four sound sources, four envelopes and four panners.

I have to admit that the drum sounds are a bit crappy and need some more work, they've got a certain Casio feel to them, and not in a good way, but they do the job.

After some experimentation I've discovered that pretty much anything sounds something like a drum if you put it through a percussive envelope, but so far the best results seem to come from some sort of noise source and/or a SinOsc. 

The bass drum is a fairly low frequency sine with a bit of crackle added to it at a low level.

The snare is a sine with some white noise subtracted from it, I don't fully understand this yet, but subtracting a white noise source from the sine sounded better than adding it. 

A wild guess lead me to the klank UGen in creating the hat( I think it might be a felt one)  and the tom is just a simple SinOsc. 

All these sound sources are controlled with percussive envelopes, each with slightly varying attack and release times. 

These are then passed to a two channel panner, which apart from allowing me to control the level of the sound also allows me to change the position of the sound between two channels. 

   Here's the pattern I use to control it. 
 

(
   
   
  

 a = Pseq ([1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]);
 b = Pseq ([0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]);
 c = Pseq ([0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0]);
 d = Pseq ([0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1]); 
 
 
 p = Pbind(
  \instrument, \drums,
  \dur, 0.12,
  \bassLevel, Pseq ([a], inf), 
  \snareLevel, Pseq ([b], inf),
  \hatLevel, Pseq ([c], inf),  
  \tomLevel, Pseq ([d], inf)
   
  ).play;
   
   
  )  

The args passed to my SynthDef include levels for each of the drums. Sequencing these using a pattern seemed like the easiest way of triggering them. Each value of the pattern can be set at either 0 for no drum or >0 to trigger a drum sound. This might not be the most efficient way of doing things but it does work.

I still don't understand exactly how it works, but the /dur argument sets the tempo, I think each event is timed as a fraction of the default clock setting of 1, but I could be wrong. 

You can do all kinds of great things with patterns, above I'm nesting them rather than defining them directly which will allow me to more easily create complex drum sequences. If I want to compose a song I can define more patterns and sequence them with the main pattern. 

(
   
   
  

 a = Pseq ([1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]);
 b = Pseq ([0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]);
 c = Pseq ([0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0]);
 d = Pseq ([0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1]); 
 e = Pseq ([0, 0,  1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1]);
 
 p = Pbind(
  \instrument, \drums,
  \dur, 0.12,
  \bassLevel, Pseq ([a], inf), 
  \snareLevel, Pseq ([b, e], inf),
  \hatLevel, Pseq ([c], inf),  
  \tomLevel, Pseq ([d], inf)
   
  ).play;
   
   
  )  

Like that. 



Monday, February 9, 2009

Moooog

Last post I showed of my wonderfully entertaining Moog style synth. This time I'm going to go into some of it in  more detail. 

Envelopes have troubled me for a while and mental images of stationary have not helped me understand what they do.  After much study I have learned they are in fact structures that control a particular aspect of a sound over time, usually the amplitude or volume, but also filters and effects.

I use two in this synth, one to control the amplitude of the oscillators and one to control the filter.  

(
SynthDef("Moog",{

arg oscType =0, oscType2 = 1, pan = 0, level = 0.5, cutoff = 500, gain = 3.3, attack = 0.1, decay = 0.1, sust = 0.7, rel = 0.2, attackf = 0.1, decayf = 0.1, sustf = 0.9, relf = 0.2, gate = 1, freq =440;


var oscArray = [Saw.ar(freq ), SinOsc.ar(freq), Pulse.ar(freq)];
var oscArray2 = [Saw.ar(freq), SinOsc.ar(freq), Pulse.ar(freq)];
var ampEnv = EnvGen.ar(Env.adsr(attack, decay, sust, rel), gate, doneAction:2);
var filterEnv = EnvGen.ar(Env.adsr(attackf, decayf, sustf, relf), gate, doneAction:2);
var osc1 = Select.ar(oscType, oscArray);
var osc2 = Select.ar(oscType2, oscArray2);
var fade = XFade2.ar(osc1, osc2, pan , level * ampEnv);
var filter = MoogFF.ar(fade, cutoff * filterEnv, gain);
Out.ar(0,filter)

}).store
)

This here is the SynthDef.  I've created two arrays of oscillators which are selected by the arg osc type being passed to the Select ugen. 

The envelopes are created by these two lines, 

var ampEnv = EnvGen.ar(Env.adsr(attack, decay, sust, rel), gate, doneAction:2);
var filterEnv = EnvGen.ar(Env.adsr(attackf, decayf, sustf, relf), gate, doneAction:2);

I create a new envelope generator object and pass it an adsr (attack, decay, sustain release) evelope specification. The variables attack, decay, sustain and release contain the time in seconds.

var fade = XFade2.ar(osc1, osc2, pan , level * ampEnv);
var filter = MoogFF.ar(fade, cutoff * filterEnv, gain);

Here I times the output of the evnvelopes by the level of the cross fade and the cuttoff frequency of the moog filter giving me a output which is changed over time by the envelope. 

Saturday, February 7, 2009

Moog Thing

This is a blog about SuperCollider.

Whilst supercollider isn't my first language, I'm not a hugely experienced programmer, so this is going to be very much from a newbies perspective.

Lets start at the beginning.

This is the first real SuperCollider program that I managed to get working, very closely aided by my mate Dan who is very good at Supercollider indeed.


(
SynthDef("Moog",{

arg oscType =0, oscType2 = 1, pan = 0, level = 0.5, cutoff = 500, gain = 3.3, attack = 0.1, decay = 0.1, sust = 0.7, rel = 0.2, attackf = 0.1, decayf = 0.1, sustf = 0.9, relf = 0.2, gate = 1, freq =440;


var oscArray = [Saw.ar(freq ), SinOsc.ar(freq), Pulse.ar(freq)];
var oscArray2 = [Saw.ar(freq), SinOsc.ar(freq), Pulse.ar(freq)];
var ampEnv = EnvGen.ar(Env.adsr(attack, decay, sust, rel), gate, doneAction:2);
var filterEnv = EnvGen.ar(Env.adsr(attackf, decayf, sustf, relf), gate, doneAction:2);
var osc1 = Select.ar(oscType, oscArray);
var osc2 = Select.ar(oscType2, oscArray2);
var fade = XFade2.ar(osc1, osc2, pan , level * ampEnv);
var filter = MoogFF.ar(fade, cutoff * filterEnv, gain);
Out.ar(0,filter)

}).store
)

it's a synth. A simple Moog inspired thing, with two oscillators, which can be either saw, sin or square/pulse waves and a mixer to mix them. It's also got a filter and two envelopes, one controlling the filter and one controlling the amplitude.

here's a pattern to play it with.

(
Pbind(
\instrument, "Moog",
\freq, Pseq([440, 550, 660, 770, 660, 330, 880], inf),
\dur, Pseq([0.2, 0.4, 0.1, 0.2, 0.2], inf),
\oscType, Pseq([0,0,0,0,0], inf),
\oscType2, Pseq([0,0,2,2,1], inf),
\cutoff, Pseq([100,599,1000,20,800], inf),
\pan, Pseq([1], inf)

).play
)


I'll go into more details of it's workings later...