A Riser in SuperCollider
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:
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.
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.
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.
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:
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.
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.