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;
   
   
  )