In the two previous posts, I’ve shown how a house beat in SuperCollider works. At the end of every eight bars, this house beat includes a riser — a buildup sound used to increase tension prior to a transition. This is the most complex sound in the beat, so I’ve saved it for last.

Here’s the beat again:

And here’s the riser:

Next, here’s the code:

// riser
SynthDef("riser",
  { arg amp = 1, startPan = -1, endPan = 1;
    // envs
    var env1 = Env.pairs([[0, 0], [0.4, amp], [3.1, 0.5], [3.5, 0]], \lin); // amp env
    var env2 = Env.pairs([[0, 55], [0.4, 110], [1, 220], [3.1, 1760]], \lin);
    var env3 = Env.pairs([[0, 50], [4, 1]], \exponential);

    // build oscs on the envs
    var squiggly = Saw.ar(freq: SinOsc.ar(env3.kr(doneAction: 2)).range(30, 19900),
      mul: (env1.kr * 0.6)) * amp;
    var riser = Saw.ar(freq: env2.kr, mul: (env1.kr(doneAction: 2) * 0.8)) * amp;

    // first combine these mono channels in a stereo space, then add reverb
    var combined = Pan2.ar(squiggly + riser, pos: Line.kr(startPan, endPan, 4));
    Out.ar(0, FreeVerb.ar(in: combined, mix: 0.5, room: 0.5));
}).add;

As usual, starting off with SynthDef("riser", {...}).add; creates a callback function and registers it under the name “riser.” The callback function takes three keyword arguments — amp, startPan, and endPan — and amp plays its usual role in defining the amplitude, or volume, of the function. Likewise, startPan and endPan play the same roles they play in the synth hat code in the previous post.

In the previous two posts, we’ve seen Env.perc, a tight, quick envelope well-suited to drum synthesis. The Env object has many other methods, each of which generate different types of envelopes, like Env.adsr, Env.dadsr, and Env.asr. This code uses Env.pairs, which is the most flexible option. Env.pairs takes an array of two-element arrays. These pairs each represent the time and level of a point in the envelope. The envelope can have any number of points, and you can also pass in a symbol or string to specify the type of curve you want the envelope to have.

With that in mind, let’s look at the envelope code.

// envs
var env1 = Env.pairs([[0, 0], [0.4, amp], [3.1, 0.5], [3.5, 0]], \lin); // amp env
var env2 = Env.pairs([[0, 55], [0.4, 110], [1, 220], [3.1, 1760]], \lin);
var env3 = Env.pairs([[0, 50], [4, 1]], \exponential);

Two of the envelopes use a linear curve; the third is exponential. The first one lasts three and a half seconds, the second one lasts a little over three seconds, and the third lasts for four seconds. The first envelope starts with a level of zero, goes up, and then goes back down to zero again. The third envelope likewise ramps down from fifty to one. The second one is the exception; it starts at 55 and ramps up to 1760.

As you might guess, env2 is a pitch envelope. A common technique with risers in dance music is to build up from a low frequency to a high one, and that’s what happens here. If you’ve been following along with the previous posts, you won’t be surprised to learn that the top frequency of 1760 represents a high octave of the note A, and the bottom frequency of 55 repesents a low octave of A. Here’s the code which uses this envelope to create a sound running through these frequencies.

var riser = Saw.ar(freq: env2.kr, mul: (env1.kr(doneAction: 2) * 0.8)) * amp;

This is one half of the riser sound. To make it easier to understand, I took out the other half for this recording:

That rise in pitch is what the code is doing right now.

Saw is a SuperCollider unit generator or UGen, an object in SuperCollider which generates sound. In this case, it generates a sawtooth wave, a classic element of subtractive synthesis since the 1960s. By invoking its .ar method, we modulate it at audio rate. So first we pass in the frequency we want with freq: env2.kr, which simply means we’re going to take the control output of the pitch envelope which runs from 55 to 1760, and supply those values to freq over time. Then we pass env1.kr as the value for the Saw UGen’s mul keyword argument. This means we’ll use env1 as an amp envelope. A doneAction of 2 is equal to the value of Done.freeself, and means that SuperCollider will free up the Saw UGen’s memory once it’s done playing this sound. The code multiplies the amp envelope by 0.8 to tone the volume down a bit, and the whole thing gets multiplied by amp, which allows us to make further volume modifications elsewhere in the code, if we need that. If we don’t, multiplying by amp does no harm, since its default value is 1.

Now, as I said, I took out half of the riser sound so that we could understand that part. That part, named riser in the code, does an actual rise in pitch. The next part adds texture. Here’s what that other half sounds like.

And here’s the code.

var squiggly = Saw.ar(freq: SinOsc.ar(env3.kr(doneAction: 2)).range(30, 19900),
  mul: (env1.kr * 0.6)) * amp;

As before, we’re just passing freq and mul to a Saw sawtooth wave UGen, but the results are very different. Or actually, the results with mul are virtually the same; the only differences are that we make the UGen quieter, by damping it down by a factor of 0.6 instead of 0.8, and we don’t dispose of it with doneAction: 2. The reason for that is the code we pass to freq needs the Saw.ar to stick around until env3 is done, and env3 lasts four seconds, while env1 (the amp envelope controlling mul) lasts 3.5 seconds. We could shorten env3 to 3.5 seconds as well if we needed to, but in practice, it doesn’t cause any problems.

Anyway, the mul is clear. What’s going on inside freq is a little more unusual:

Saw.ar(freq: SinOsc.ar(env3.kr(doneAction: 2)).range(30, 19900)

In the same way that calling Saw.ar gets SuperCollider to give us the audio output of a sawtooth wave generator, SinOsc.ar gets us the audio output of a sine wave generator. Passing only one argument to SinOsc.ar means we’re just defining the frequency of the sine wave, and that frequency is a slow envelope that, over 4 seconds, starts at 50 and goes down to 1. However, we then call range(30, 19900). This is actually a method on UGen itself, the superclass, which scales the output of the UGen to fit within the range of frequencies defined. So our sine wave actually starts at 19.9 kHz and scales down to 30 Hz, which is almost the lowest frequency a human being can hear. But wait! Will any ear ever hear this frequency? Because we’re not feeding it into any audio output; we’re simply using it to generate the freq argument for our Saw. What’s going on here?

We’re using our SinOsc as an LFO. At the start of its frequency envelope, it’s oscillating at 19.9 kHz, which is way too fast to really qualify as an LFO — it stands for low-frequency oscillator, after all — and instead basically performs FM synthesis, modulating the sawtooth wave very quickly. As it slows over the course of four seconds, however, it enters LFO territory. So you get a transition in texture over those four seconds. I wish I could say I thought of this myself, but it’s an idea I stole from a Microkorg preset. The Microkorg version didn’t go into FM territory, but the idea was the same.

There are a few more lines of code which add just a little special sauce to this sound.

// first combine these mono channels in a stereo space, then add reverb
var combined = Pan2.ar(squiggly + riser, pos: Line.kr(startPan, endPan, 4));
Out.ar(0, FreeVerb.ar(in: combined, mix: 0.5, room: 0.5));

Pan2.ar turns mono sounds into stereo sounds, and the pos argument (for “position,” i.e., pan position) is the control rate information from a Line drawn, over four seconds, from startPan to endPan. Those values are -1 and 1, respectively, which in this context stand for a signal panned all the way to the left and a signal panned all the way to the right. In other words, we make the sound move across the stereo spectrum. Meanwhile, FreeVerb adds some reverb to the sound, just to give it a bit of presence.

And that’s how we get the end result.