Pbinds and Patterns
Introduction
According to SuperCollider's Practical Guide to Patterns
Patterns describe calculations without explicitly stating every step. They are a higher-level representation of a computational task. While patterns are not ideally suited for every type of calculation, when they are appropriate they free the user from worrying about every detail of the process. Using patterns, one writes what is supposed to happen, rather than how to accomplish it.
A large part of my live coding performances involve using patterns, specifically Pbinds as Proxies inside of ProxySpace (see section 2.2.1) to create rhythmic elements that are synchronised to ProxySpace's TempoClock. In this case, 'rhythmic elements' can mean percussion, melody, bass, pads, or generally anything that is played 'in tempo'.
SynthDefs, Arguments and Pbinds
The Pbinds I perform with work in conjunction with a set of SynthDefs (these can be found in the SynthDefs.scd file of the Setup folder) which serve various musical purposes, and plays them with specifies arguments at a given duration. This isn't a particularly intuitive concept to explain, but an example can help illustrate how this works. Take the SynthDef I use the most, bplay
:
SynthDef(\bplay,
{arg out = 0, buf = 0, rate = 1, amp = 0.5, pan = 0, pos = 0, rel=15;
var sig,env ;
sig = Pan2.ar(PlayBuf.ar(2,buf,BufRateScale.ir(buf) * rate,1,BufDur.kr(buf)*pos*44100,doneAction:2),pan);
env = EnvGen.ar(Env.linen(0.0,rel,0),doneAction:2);
sig = sig * env;
sig = sig * amp;
Out.ar(out,sig);
}).add;
bplay
is a simple stereo-panned sample player driven by the PlayBuf class, which takes the following arguments:
out
: the bus to be played to (this is needed as an argument for the SynthDef to work correctly inside ProxySpace, and I don't usually touch itbuf
: the buffer to be read by the synth (all of which are loaded into dictionaryd
by defaultrate
: the speed the sample will be played at (with no compensation for pitch)amp
: how loud the sample is, with 1 being the original volume of the samplepan
: where the sound is placed in the stereo field, with0
being centrepos
: the position from which the sample starts playing, normalised from0
to1
, e.g. a value of0.5
will play the sample from the middlerel
: in this case specifies how long the server is allowed to keep the instance open before freeing it. Normally the instance will be freed when the sample is finished playing, but in the case of very long samples or samples played backwards this freeing may not occur, leading to server load building in the background due to dead running processes. This default value of 15
Pbinds also have some arguments that need satisfying:
instrument
: the SynthDef that will be used to deliver this 'instance' in the patterndur
: The duration of each 'instance', if used directly in ProxySpace, adur
value of 1 results in an 'instance' once every clock cycle. Note: the defaultdur
value of a Pbind is 1, and the defaultinstrument
value is SuperCollider's built in Piano synth, but specifying both anyway (especiallyinstrument
) is good practice.
So, if I wanted to have a kick drum playing once each beat in time with the ProxySpace timer, after I had run my setup file I would do the following:
(
~k = Pbind(\instrument,\bplay,\buf,d["k"][0],\dur,1);
~k.play;
)
This Pbind ~k
, spefifies that the instrument it will be using is bplay
, the buffer bplay
reads from will be the first index of the k
entry in the dictionary (which contains kick drums), and that the dur
/duration is 1
, once per cycle. If any arguments that the SynthDef takes are not specified as part of the Pbind, the SynthDef's default values will be used. Pbind arguments have to be given as key-value pairs, anything else will result in a syntax error, eg:
~k = Pbind(\instrument,\bplay,\buf,d["k"][0],\dur,1,\rate);
As part of these key-value pairs, Pbinds can take Pattern classes as inputs. Pwhite
gives random values between a minimum and maximum. If I wanted to specify a random pitch of the kick drum, I could add this to the pattern:
~k = Pbind(\instrument,\bplay,\buf,d["k"][0],\dur,1,\rate,Pwhite(1,1.2));
Nesting pattern classes
Pattern classes can also be nested. Here are a few examples of some more complex percussive patterns. Once you start nesting pattern classes, things can get complicated quite quickly.
//to play with these examples, make sure the Setup File has been run
//footwork kickdrums
(
p.clock.tempo = 2.4;
~k = Pbind(\instrument,\bplay,\buf,d["k"][0],\dur,Pbjorklund2(Pseq([3,3,3,5],inf),8)/4,\amp,1,\rate,Pseq([1,1.2],inf));
~k.play;
)
//skittery hi-hats
(
p.clock.tempo = 1.5;
~h = Pbind(\instrument,\bplay,\buf,d["ch"][0],\dur,Pwrand([0.25,Pseq([0.125],2),0.5,Pseq([0.125/2],4)],[4,1,1,0.5].normalizeSum,inf),\amp,Pwhite(0.2,1));
~h.play;
)
//offset percussion patterns for techno feel behind a basic kick
(
p.clock.tempo = 135/60;
~c = Pbind(\instrument,\bplay,\buf,d["sfx"][6],\dur,Pbjorklund2(Pexprand(2,15).round(1),16,inf,Pwhite(1,5).asStream)/4,\amp,1,\rate,2.2);
~c2 = Pbind(\instrument,\bplay,\buf,d["sfx"][6],\dur,Pbjorklund2(Pexprand(2,15).round(1),16,inf,Pwhite(1,5).asStream)/4,\amp,1,\rate,1.9);
~k = Pbind(\instrument,\bplay,\buf,d["sk"][0],\dur,1,\amp,5);
~c.play;
~c2.play;
~k.play;
)
//snare running forwards and back
(
p.clock.tempo = 150/60;
~sn = Pbind(\instrument,\bplay,\buf,d["s"][4],\dur,Pwhite(1,4)/2,\amp,1,\rate,Prand([1,-1],inf),\pos,Pkey(\rate).linlin(-2,2,0.9,0));
~sn.play;
)
Extra arguments for melody/pitch
Pbinds also have some additional trickery for anything involving pitch.
Let's look at the sinfb
SynthDef (the arguments are listed in the code block for simplicity)
//SinFB Bass
(
SynthDef(\sinfb, {
arg freq = 440, atk = 0.01, sus = 0, rel = 1, fb = 0, amp = 0.3, out = 0, pan=0;
var sig, env;
env = EnvGen.ar(Env.linen(atk,sus,rel),1,1,0,1,2);
sig = SinOscFB.ar(freq,fb,1);
sig = sig*env;
Out.ar(out,Pan2.ar(sig,pan,amp));
}).add;
);
/*
freq: frequency
atk: attack
sus: sustain
rel: release
fb: phase feedback
amp: amplitude
out: output bus
pan: stereo panning
*/
Here, the freq
argument is the pitch of the oscillator. Pitch can be specified manually, like so:
~sinfb = Pbind(\instrument,\sinfb,\dur,0.25,\freq,Pwhite(100,900));
However, if a variable in a SynthDef is given the name freq
, Pbind allows the specification of the following in place of freq
to activate a 'scale mode':
scale
: the scale and tuning used - scales can be listed withScale.directory
and tunings withTuning.directory
(defaultScale.major(\et12)
)degree
: the degree of the scale to be played (default0
)octave
: the octave of the scale to be played (default5
)
Only one of these arguments needs to be specified to be in 'scale mode', for example:
//run up the major scale
~sinfb = Pbind(\instrument,\sinfb,\dur,0.25,\degree,Pseq((0..7),inf));
But using all three gives full control over the parameters of the pitch used inside of a musical scale
//run up and down chromatic scale one degree at a time
~sinfb = Pbind(\instrument,\sinfb,\scale,Scale.chromatic(\et12),\degree,Pseq((0..12).pyramid.mirror,inf),\octave,6,\dur,0.125/2,\amp,0.3,\fb,0.8,\rel,0.1)
By using the 'scale mode' of Pbinds you can easily adopt pitch structures that are organised around any scale and tuning you wish - SuperCollider has a bunch bundled in, but way more can be added with the Scala Scale library through quarks such as TuningLib and TuningTheory, and arbitrary scales can be specified.
Why I don't use Pdefs
Another approach to using patterns is to make metapatterns by placing Pbinds (and Pmonos) inside of a Pdef
, but i've found this to be too verbose to use while performing, and i've personally had some problems getting them to sync for performances that reply on strict metric patterns.
More on Patterns
Patterns form a huge part of my live sets, so I will be referencing them frequently throughout this repo, talking about their use in both rhythmic and melodic arrangement.