Event Player#

Let us have a look at a first, very simple composition. We finally make use of Pbind to construct a discreate musical event simulation.

(
Pbind(
    \instrument, \default,
    \freq, Pseq([440, 220, 330], 4),
    \dur, 0.4,
    \sustain, 0.1 
).play;
)

As already mentioned, Pbind is a unique Pattern that generates a Stream that spits out (musical) Events. By using the play method on the Pbind pattern, we play all the events the (event) stream gives us. In the above example we play a sequence of three notes 4 times on the \default instrument each seperated by 0.4 beats sustaining 0.1 beats.

dur determines the waiting time between two successive events. Thereby, we do not play all events instantly. Instead we create a rhythm.

Legato and Duration

If the sound sustains longer than dur we get overlapping sounds, i.e., legato.

Legato is nice to control the amount of overlap relative to the duration of the event. For example:

(
p = Pbind(
    \instrument, \default,
    \freq, Pseq([440, 220, 330], 4),
    \dur, 0.25,
    \sustain, 0.5
).play;
)

The same can be achieved by using the legato parameter:

(
p = Pbind(
    \instrument, \default,
    \freq, Pseq([440, 220, 330], 3),
    \dur, 0.25,
    \legato, 1.0 // two times dur overlap
).play;
)

We can call stop on the Stream (not the Pattern!) to stop it (or we can hit CMD + . / Ctrl + . as always).

Now you might ask: what do the events actually look like? As already mentioned, each event is filled with default arguments if they are not defined. For each defined argument, in our case, instrument, freq, dur, and legato, the method next is called on the value referenced by the corresponding name. As we learned, if this value is a number, the number itself is returned. If it is a pattern, it was already transformed into a stream when play was called and will return what the recursive evaluation of next gives us.

Argument Dependence#

Since we combine multiple Streams, we may want to influence one value stream by the other. For example, we might want that the amp depends on the frequency such that we can reduce the amplitude for higher pitches. There are multiple ways to do so. One is by using one of the most powerful Pattern, that is Pfunc. Pfunc expects a function as an argument and this function is called whenever the respective Stream generates its next value. The argument of the next call is passed to the function.

(
var pattern = Pfunc({arg val; val*val;});
var square = pattern.asStream();
square.next(5); // 25
)

Pbind passes the whole event to this function. Therefore, we can look inside the event, and use the information to compute our value. In the following code snippet, we print the amp so you can see the effect.

(
p = Pbind(
    \instrument, \default,
    \freq, Pseq([440, 220, 330], inf),
    \dur, 0.25,
    \legato, 0.2,
    \amp, Pfunc({|e| min(1.0, e[\freq].linexp(100, 500, 1.0, 0.2)).postln;})
).play;
)

Pfunc can do a lot of other things and there is a pattern that is specifically designed for our case. It is called Pkey. Furthermore, we can use utility function trace to post the numbers a pattern spits out. The following code creates exactly the same sound.

(
p = Pbind(
    \instrument, \default,
    \freq, Pseq([440, 220, 330], inf),
    \dur, 0.25,
    \legato, 0.2,
    \amp, Pkey(\freq).linexp(100, 500, 1.0, 0.2).trace
).play;
)

The third way to do this is to use a global variable. However, this seems to be a really dirty method which I do not recommend. I think, using Pkey is the cleanest way to do things.

Cascading Pbinds#

We can, of course, use multiple Pbinds.

(
var intro, middle, outro;
intro = Pbind(
    \instrument, \default,
    \freq, Pseq([440, 220, 330], 3),
    \dur, 0.25,
    \sustain, 0.3,
    \amp, Pkey(\freq).linexp(100, 500, 1.0, 0.2)
);

middle = Pbind(
    \instrument, \default,
    \freq, Pseq([233, 321, 344], 3),
    \dur, 0.25,
    \sustain, 0.3,
    \amp, Pkey(\freq).linexp(100, 500, 1.0, 0.2)
);

outro = Pbind(
    \instrument, \default,
    \freq, Pseq([440, 320, 430], 3),
    \dur, 0.25,
    \sustain, 0.3,
    \amp, Pkey(\freq).linexp(100, 500, 1.0, 0.2)
);

p = Pseq(list: [intro, middle, outro], repeats: 2);
q = p.play;
)

What a masterpiece ;). We can generate the same piece using only one Pbind:

(
p = Pbind(
    \instrument, \default,
	\freq, Pseq([
		Pseq([440, 220, 330], 3), 
		Pseq([233, 321, 344], 3),
		Pseq([440, 320, 430], 3)
	], repeats: 2),
    \dur, 0.25,
    \sustain, 0.3,
    \amp, Pkey(\freq).linexp(100, 500, 1.0, 0.2)
);
q = p.play;
)

We can also play multiple Pbinds in parallel. We can imagine that each Pbind represents one musician in our assemble. Ppar is a pattern that allows us to play multiple Pbinds in parallel. In this example I use a fixed dur and Rest to adjust the actual duration. You can use any symbol to create a Rest (i.e. to do nothing).

(
SynthDef(\snare,{arg hcutoff = 9000, lcutoff = 5000, amp = 1.4;
    var env, hat, bass, sig, cutoff = 5000;
    env = Env.perc(0.01, 0.15).kr(doneAction: Done.freeSelf);
    hat = {PinkNoise.ar()}!2;
    hat = HPF.ar(hat, XLine.ar(lcutoff/4, lcutoff, 0.2));
    hat = LPF.ar(hat, hcutoff);
    bass = LFTri.ar(XLine.kr(150, 10, 0.21))*0.2;
    sig = (hat + bass) * env * amp;
    Out.ar(0, sig);
}).add;
)

(
var melody = Pbind(
    \instrument, \default,
    \scale, Scale.minor,
    \octave, 5,
    \degree, Pseq([
        3, 4, 3, \r,
        1, \r, 6, \r,
    ], inf),
    \dur, 1/4,
    \sustain, 0.2
);

var rythm = Pbind(
    \instrument, \snare,
    \dur, 1/8,
    \amp, Pseq([
        0.9, 1.2, \r, \r, 
        0.8, \r, 1.3, \r,
    ], inf)*0.2
);

Ppar([rythm, melody], inf).play;
)

Another way to sequence Pbinds and Pattern is to use Pspawner. It allows you to play patterns in parallel or in sequence, via a callback function.

(
var melody = Pbind(
    \instrument, \default,
    \scale, Scale.minor,
    \octave, 5,
    \degree, Pseq([
        3, 4, 3, \r,
        1, \r, 6, \r,
    ]),
    \dur, 1/4,
    \sustain, 0.2
);
var rythm = Pbind(
    \instrument, \snare,
    \dur, 1/8,
    \amp, Pseq([
        0.9, 1.2, \r, \r, 
        0.8, \r, 1.3, \r,
    ])*0.2
);

Pspawner({ arg sp;
    3.do {
        sp.par(melody);
        sp.seq(rythm);
        sp.seq(rythm);
    };
    sp.seq(rythm);
    sp.seq(melody);
}).play;
)

Dynamic Changes#

Ok, so we can define a pattern of events, i.e., a Pbind and play it. But would it not be nice to change the pattern while playing it? SuperCollider supports live programming via its powerful Just In Time programming library (JITLib). I will discuss live programming in detail in section Live Coding, but here, I want to mention the Pbindef class.

Pbindef keeps a reference to a Pbind in which single keys can be replaced. It plays on when the old stream ends and a new stream is set and schedules the changes to the beat. Basically, this means that we can:

  1. change our pattern

  2. re-evaluate the code

and the change will appear soon after without ever stopping the pattern. The only difference to Pbind is that a Pbindef requires a unique name. Use the following Pbindef to change the frequencies and re-evaluate the code. Listen to what happens!

(
Pbindef(\melody,
    \instrument, \default,
    \freq, Pseq([440, 220, 330], inf),
    \dur, 0.4,
    \sustain, 0.1 
).play;
)

There is, however, a pitfall. If you are using Pbindef and you change your style of defining pitch, you might run into problems. Once you define \freq, there is no way to use any other argument that determines the frequency since it will always be overwritten by \freq. For example, try \midinote in the above example. You will notice that it does not work. If you change the name of the Pbindef it will work as long as you do not define the \freq argument! Another solution is to set \freq to nil.

Overwriting Arguments

Once you use an argument within a Pbindef you can only unuse it by overwriting it or by setting it to nil!

Naming Conventions#

As mentioned in section Value Conversions, behind the scenes SuperCollider’s event player helps us transforming different values into other values. For example, we can play \midinote instead of \freq. The player will convert the pitch to the correct frequency.

However, we can only take advantage of this support if we name the arguments of the function defined in the SynthDef appropriately.

Naming Conventions

Always use the appropriate names, such as amp and freq for your SynthDef arguments!