The first several posts on this blog show you how a house beat, its drums, and its riser sound work. Next I’m going to show you how I built a short house track — around two and a half minutes long — entirely in SuperCollider.

First, here’s the track:

This is kind of just a demo, and a real house track would be maybe twice as long, but I think it serves well as a proof of concept. If you’ve been following the previous posts, you recognize these drum sounds, as well as the riser. The previous posts explain those sounds in detail. What’s new are some more melodic sounds — a synth pad and a sound like low bells or perhaps piano strings being scraped — and the fact that this is a track rather than an eight-bar loop.

So there are just three things to explain:

  • pattern sequencing in SuperCollider
  • the pad synth
  • the bells / piano strings synth, which uses Karplus-Strong synthesis

We’ll start at the beginning. As before, there’s a git repo on GitHub, and you’ll need to run a simple installer script if you’re following along in SuperCollider. The git repo has a README which explains how to do that.

Pattern Sequencing in SuperCollider

In previous posts, we saw that SuperCollider provides a mechanism for sequencing called Pbind, for pattern binding. Pbinds allow us to play musical patterns like drumbeats and melodies. The question is how to sequence them, one after the other.

SuperCollider has a mechanism for this called Pspawner. You invoke Pspawner along with a callback which takes a spawner object as an argument. It’s idiomatic for this object to be named spawner, but in this code, I went with the name salmon since I was in a goofy mood and spawning is a thing that salmon do.

Here’s how a typical use of Pspawner might look:

~synthHatPattern = Pbind(
  \instrument, \hat,
  \dur, 1/4,
  \amp, Pseq(~hatAmps, inf),
  \startPan, Pseq(~hatStarts, inf),
  \endPan, Pseq(~hatEnds, inf),
  \pitchVariation, Prand([3520, 2637, 7040], inf),
  \release, Pseq(~hatReleases, inf)
);

~clock = TempoClock(125/60);

Pspawner({ arg salmon;
  salmon.par(~synthHatPattern);
}).play(~clock);

The spawner’s .par method stands for “parallel,” and it sets a pattern playing which can play in parallel (i.e., at the same time) with other patterns. We’ve seen this Pbind code in previous posts. All in all, this code will give you results which are basically identical to calling .play(~clock) at the end of the Pbind. But because we’re using .par, we can play other Pbind objects at the same time.

Also, just like “where there’s smoke, there’s fire,” where there’s parallel, there’s sequential. The spawner has a .seq object which will play a pattern, and then, when that pattern ends, the spawner will also stop playing any patterns which it was playing in parallel. That’s what’s going on in this code, which plays the first few bars of the track:

Pspawner({ arg salmon;
  salmon.par(~crashPattern.value(2));
  salmon.par(~synthHatPattern);
  salmon.par(~stringsPad.value(2));
  salmon.seq(~kickPattern.value(6));

  salmon.seq(~riserPattern.value(1));
}).play(~clock);

This code plays a crash pattern twice, and keeps playing the synth hat pattern and a synth pad until it’s played a kick pattern 6 times, at which point it stops and plays a riser pattern once. That’s how the track begins; more specifically, that’s the first fifteen seconds of it.

But you’ll notice that I’m not just passing patterns to the spawner in most cases here. Instead I’m calling value() on these objects and passing those values to the spawner. That’s because this code is built on the code in the previous posts which played a drum pattern on inifinite repeat. All the drum patterns in that existing code worked on the assumption that they would run forever. Rather than rewrite all that code from scratch, the easiest thing was to write functions which returned Pbind objects. Here’s what one of those functions looks like:

~crashPattern = { arg measures ;
  Pbind(
    \instrument, \crash,
    \dur, 2,
    \amp, Pseq(~crashAmps, measures / 2)
)};

Invoking these functions works as shown: functionName.value(argument). This is some of the weirdest syntax in all of SuperCollider to me personally, because I would have expected functionName(argument) instead. However, prior to this, every time I’d used SuperCollider, it’d been through systems like Overtone and Sonic Pi, which retain SuperCollider’s power but replace its weird programming language with more familiar, popular languages. This is easier in terms of writing code, but it’s also harder in terms of understanding what SuperCollider is actually doing. SuperCollider is very powerful, so it’s totally worth the effort to grapple with the syntax a little. Anyway, long story short: functions which return Pbinds.

These simple mechanisms are almost all you need in order to do pattern sequencing in SuperCollider. The code for this track uses only two other methods for its sequencing. First, there’s salmon.suspendAll;. This simply stops all running patterns. Second, there’s wait(). You pass this method a number of beats, and it’ll wait for that many beats before the song moves forward. And that’s it.

Here’s this code in action:

Pspawner({ arg salmon;
  salmon.par(~crashPattern.value(2));
  salmon.par(~synthHatPattern);
  salmon.par(~stringsPad.value(2));
  salmon.seq(~kickPattern.value(6));

  salmon.seq(~riserPattern.value(1));

  salmon.par(~crashPattern.value(2));
  salmon.par(~pluckQuasiArp);

  salmon.par(~zapPattern.value(3.5)); // because this amps array is twice as long
  salmon.seq(~kickPattern.value(7));
  wait(4);

  salmon.suspendAll;

  salmon.par(~crashPattern.value(2));
  salmon.par(~stringsPad.value(2));
  salmon.seq(~kickPattern.value(7));
  wait(4);

  salmon.par(~crashPattern.value(2));
  salmon.par(~pluckQuasiArp);
  salmon.par(~synthHatPattern);

  salmon.par(~sampleClapPattern.value(7));
  salmon.par(~sampleHatPattern.value(7));
  salmon.par(~kickPattern.value(7));
  salmon.seq(~zapPattern.value(4)); // again, double-length amps array

  salmon.par(~crashPattern.value(2));
  salmon.par(~stringsPad.value(2));
  salmon.par(~snarePattern.value(6));
  salmon.seq(~kickPattern.value(6));

  salmon.seq(~riserPattern.value(1));

  salmon.par(~crashPattern.value(2));
  salmon.par(~sampleClapPattern.value(7));
  salmon.par(~sampleHatPattern.value(7));
  salmon.seq(~kickPattern.value(7));
  wait(4);

  salmon.par(~crashPattern.value(2));
  salmon.par(~stringsPad.value(2));
  salmon.par(~snarePattern.value(7));
  salmon.par(~sampleClapPattern.value(7));
  salmon.par(~sampleHatPattern.value(7));
  salmon.seq(~zapPattern.value(4));  // function should handle this

  salmon.par(~crashPattern.value(2));
  salmon.seq(~kickPattern.value(6));

  salmon.seq(~riserPattern.value(1));

  salmon.par(~crashPattern.value(2));
  salmon.seq(~stringsPad.value(2));
  salmon.suspendAll;

  salmon.seq(~crashPattern.value(2));

}).play(~clock);

It might make for cleaner/DRYer code, and/or a track structure which is easier to understand, if I were to wrap these sections up into helper functions. But to get a track going, this is enough. If you’re curious, try reading this code while the track plays. It’s pretty easy to match up the lines of code with the parts of the track.

All that’s left to explain now is the two new sounds: the pad synth and the Karplus-Strong sound.

The Pad Synth

Here’s what the pad synth sounds like:

Both this and the next patch were created by Bruno Ruviaro, one of the two instructors at the CCRMA SuperCollider workshop that I took. This patch was introduced during class. I found the next patch coincidentally, via a Google search, although it’s not really a coincidence per se that a person teaching the workshop had also written popular patches available online.

Anyway, here’s the code:

// pad synth (from presentation today)
SynthDef("padSynth", { arg midinote, gate = 1;
  var ampEnv, freq, gen, fmod, rqmod, snd;
  ampEnv = EnvGen.ar(
    envelope: Env.adsr(3, 3, 0.8, 5, 0.5),
    gate: gate,
    doneAction: 2);

  freq = { midinote.midicps * LFNoise2.kr(1,0.01,1) }!12;
  gen = LFSaw.ar(freq: freq, mul: ampEnv);
  fmod = LFCub.kr(1/12, mul: ampEnv).range(1, MouseX.kr(2,16));
  rqmod = LFNoise2.kr(1/8, mul: ampEnv).range(0.1,1.0);
  snd = RLPF.ar(gen, freq * fmod, rqmod, ampEnv);
  Out.ar(0, Splay.ar(snd * ampEnv * 0.3));
}).add;

First we define a function, and add that function to SuperCollider’s list of synths under the name “padSynth”. The function takes a midinote and a gate. In my hardware studio, I usually either send MIDI notes or gate signals, in my Eurorack modular. But they’re just data in SuperCollider, and you can use both if you want. The gate is passed to an amplitude envelope here:

ampEnv = EnvGen.ar(
  envelope: Env.adsr(3, 3, 0.8, 5, 0.5),
  gate: gate,
  doneAction: 2);

The gate signal is very important. When the gate closes, it does the doneAction. In this case (and most cases) the doneAction is 2, which is equal to the constant Done.freeSelf, so when the gate closes, the synth gets freed. In other words, SuperCollider garbage-collects it, freeing up memory. So without gate, you can bog down your computer and produce all sorts of unpleasant glitching. (SuperCollider can also produce pleasant glitching, of course.)

Anyway, an amp envelope is a fairly standard feature in a synthesizer, but the rest of this code features some interesting quirks. Although it holds to the same basic model as a classic subtractive synth, the freq in this synth isn’t just determined by converting the MIDI note into a frequency. It also multiplies that frequency by the output of a noise generator, to produce a wavering and not quite perfectly tuned sound, reminiscent of classic 1970s/80s analog polysynths:

freq = { midinote.midicps * LFNoise2.kr(1,0.01,1) }!12;

The !12 means “create twelve copies of this.” This thickens the sound.

The next few lines control the filter.

fmod = LFCub.kr(1/12, mul: ampEnv).range(1, MouseX.kr(2,16));
rqmod = LFNoise2.kr(1/8, mul: ampEnv).range(0.1,1.0);
snd = RLPF.ar(gen, freq * fmod, rqmod, ampEnv);

With rqmod, it uses a noise generator to modulate the filter resonance, and it puts an LFO on the filter frequency with LFCub, which I believe stands for “low-frequency cubic,” because it outputs a waveform similar to a sine wave but made of cubic pieces. The top frequency is determined by the mouse position, which I actually forgot when I was recording the audio samples in this blog post, but if you clone the repo and run the installer, you can experience this effect by moving your mouse while the pad sound plays. The function then feeds these generators into RLPF, which stands for “resonant low-pass filter.” As explained in a previous post, Out and Splay just send the audio to an output bus and pan it across the stereo image, respectively.

So that’s how the pad works. Let’s look at the Karplus-Strong sound.

Karplus-Strong Synthesis

The creators of Karplus-Strong synthesis, Kevin Karplus and Alexander Strong, originally called it “digitar” synthesis, because they felt it produced digital guitar hybrid sounds. It’s easy to understand where they’re coming from. Here’s the Karplus-Strong or digitar sound from the track:

Here’s the code:

// Bruno's Karplus-Strong synth
SynthDef("plucking", {arg amp = 0.1, freq = 440, decay = 5, dampen = 0.1;

var env, snd;
env = Env.linen(0, decay, 0).kr(doneAction: 2);
snd = FreeVerb.ar(Pluck.ar(
        in: WhiteNoise.ar(amp),
        trig: Impulse.kr(0),
        maxdelaytime: 0.1,
        delaytime: freq.reciprocal,
        decaytime: decay,
    coef: dampen,
    mul: env
  ), mix: 0.7, room: 0.8);
    Out.ar(0, [snd, snd] * env);
}).add;

I didn’t write this code, but I can tell you roughly how it works. The Pluck is a Karplus-Strong unit generator; you set its delaytime at the reciprocal of the frequency you want to hear because Karplus-Strong synthesis works by looping a short burst of noise through a delay which is also set to a quite short time. In a situation like that, the pitch of the resulting sound — which is ultimately just white noise echoing a lot — is inversely proportional to the delay time. That’s because the looping of the delay functions like an oscillator, generating a waveform by looping the initial burst of white noise. In a sense, Karplus-Strong is a relatively simple form of wavetable synthesis. Anyway, returning to the code, the WhiteNoise provides the noise, of course, and the Impulse makes it a short burst. The amp envelope generated by Env.linen is not staccato, but that’s applied to the overall sound.

So this is how you construct a complete track in SuperCollider. This is the capstone of the past few blog posts, which are so far the only ones on this blog, so I hope you’ve found this edifying, inspiring, or at least useful. You can make complete tracks entirely within SuperCollider, and hopefully these blog posts have shown you how.