Day Six of 30 Days of Supercollider.
Envelopes control how a single aspect of a sound changes over time. Traditionally, this meant the volume of a sound. When you strike a bell, for example, there’s an initial fast peak of volume, which then slowly fades over a potentially long period, as the bell continues to resonate. If you graphed out that volume level, it might look like this:
Of course different bells will have different curves. A meditation bell sound seems to go on and on, whereas a cowbell fades out pretty quickly.
A flute would have a totally different curve. It will probably reach peak volume more slowly than a bell. Then it will maintain a steady volume for as long as the flutist continues to hold that note and fade out rather quickly, though the flutist could make that fade longer if they wanted to. That might look more like this:
These curves are known as envelopes and they are usually broken down into standard sections:
- Attack. How long it takes for the sound to ramp up to its initial full peak volume.
- Decay. Often that peak volume subsides a bit before it gets to the next phase.
- Sustain. For sounds that can be held for a period of time, this is the volume and length of time they are held at.
- Release. When the sound generation is stopped, how long does it take for the volume to get to zero?
Here are all those parts labeled:
Not all envelopes have all those parts. You can break down envelopes into sustaining and non-sustaining envelopes. Most bells, for example, do not have a sustain section. You strike them, they peak and then they release. The same with most drum sounds. So this kind of envelope is often called a percussive envelope. It just has an attack and a release. Since it starts its release after it reaches the peak, there’s no real decay either.
Sustaining envelopes may or may not have a decay section. So usually you’ll see “adsr” and “asr” envelopes.
In Supercollider there is an envelope class that you can use to construct all these kinds of envelopes and more. For example there is Env.perc
, Env.asr
and Env.adsr
as well as others. To create envelopes, you need a number of volume parameters and a number of time parameters.
Volumes:
- The start volume – where the volume starts – usually 0, but doesn’t have to be.
- The peak volume at the end of the attack.
- The sustain volume – where the volume goes down to after the decay. If there is no decay, this is the same as the peak.
- The release volume – where the volume ends. Again, usually 0, but doesn’t have to be.
Depending on which type of envelope you are using, you may not need all of these.
Times:
- Attack time.
- Decay time.
- Sustain time.
- Release time.
As with volumes, not all envelopes use all of these. Also, most of Supercollider’s envelopes do not have a parameter for sustain time. The end of the sustain period is usually triggered by something else, such as the release of a key, or some other signal. We’ll see examples of this later.
Percussive Envelope
But let’s get started actually creating an envelope and applying it to a sound. I’m going to start with a function that has two vars, sig
for the signal, and env
for the envelope. And I’ll create a pulse (square) wave for the signal.
(
f = {
var sig, env;
sig = Pulse.ar(400);
};
)
f.play
If you evaluate the part in parentheses, it will store that function in the global variable, f
. Then you can evaluate the next line that plays f
. You’ll hear a noise that will just go on forever. Press Cmd/Control + period to stop it. Next we’ll create the envelope. We’ll create a percussive envelope with Env.perc
. This will need to be wrapped in an EnvGen
class, which is an envelope generator. Finally, multiply the two together, the result of which gets returned by the function.
(
f = {
var sig, env;
sig = Pulse.ar(400);
env = EnvGen.ar(Env.perc);
sig = sig * env;
};
)
When you play this, it sounds a bit more bell-like.
But before we do anything more to the envelope itself, open up the Node Tree window. If you’ve played this function a few times, you’ll see a bunch of items hanging around. These are sounds that are still technically playing, but the envelope brought their sound down to zero so you can’t hear them.
To fix this, add a doneAction
to the EnvGen
. Setting this to 2 will cause the sound to be removed when the envelope completes.
(
f = {
var sig, env;
sig = Pulse.ar(400);
env = EnvGen.ar(Env.perc, doneAction: 2);
sig = sig * env;
};
)
Now we can start playing with the parameters to Env.perc
to change the envelope. As mentioned, this kind of envelope just goes quickly to a peak (attack) and then slowly fades out (release). The arguments to the function are:
attackTime = 0.01, releaseTime = 1, mul = 1, curve = -4
Try changing attackTime
and releaseTime
to get an idea how that changes the sound. Another neat trick is to plot the envelope, which you can just to by adding .plot
to the end of the call. Like so:
Env.perc(0.1, 0.3).plot;
Which gives you this:
Notice that the two lines are curved. You might guess that you can change that curve with the curve
parameter. And you’d be right. A curve of 0 creates linear changes to the volume, and straight lines in the plot.
Negative numbers curve one way. The default curve for this envelope is -4, so you have seen how that looks. Positive 4 looks like this:
Higher or lower numbers change the shape of the curve. Try different curves to see and hear the changes they make.
Sustain
A percussive envelope is non-sustaining. It plays and it finishes on its own. Sustaining envelopes will play indefinitely until something ends the sustain. Let’s start with the simplest of these, the ASR envelope, which is created with Env.asr(attackTime = 0.01, sustainLevel = 1, releaseTime = 1, curve = -4)
Note that there is only a sustain level, not a sustain time. Let’s just change the percussive envelope with a default ASR one, and see what happens.
(
f = {
var sig, env;
sig = Pulse.ar(400);
env = EnvGen.ar(Env.asr, doneAction: 2);
sig = sig * env;
};
)
Play that and it pretty much sounds like there is no envelope at all. It goes quickly to full level and just stays there. We need some way of telling the envelope to move on to the next step. This is known as a gate and is an argument to the EnvGen
. The gate is a value that is evaluated as true if it is a positive number, and false if it is zero or negative. If the gate is positive, the note will play to its sustain level and stay there. When the gate goes to zero or below, the sustain ends and the release phase begins.
In order to change the gate at run time, first you’ll need to add a gate
argument to the function. Then you need to save a reference to the object created when you call play
on the function. Then on that object you can call set
so change the gate. Here’s what that all looks like:
(
f = {
arg gate = 1;
var sig, env;
sig = Pulse.ar(400);
env = EnvGen.ar(Env.asr, gate: gate, doneAction: 2);
sig = sig * env;
};
)
a = f.play;
a.set(\gate, 0);
Evaluate the function, then evaluate the play
line to start the sound playing. Finally evaluate the final line to end the sustain, and you’ll hear the release.
Often, these actions would be triggered by a midi controller key press. Pressing the key would trigger the sound to start, and it would play as long as the key was held down. Releasing the key would trigger the code to set the gate to zero, which would then let the note release.
Alternately, you can programatically trigger the gate. One way would be to use some other UGen which just will change from negative to positive sometimes. Here’s an example of this.
(
f = {
var sig, env;
sig = Pulse.ar(400);
env = EnvGen.ar(Env.asr, gate: LFPulse.kr(0.5));
sig = sig * env;
};
)
Here, the gate
argument is set direction to an low frequency pulse UGen running at a frequency of 0.5, meaning it will complete a full cycle every two seconds. So this will go positive for 1 second, playing and sustaining the note. Then it will go negative, ending the sustain and letting the note release. Then back to positive after a second. You can change the width
value of the LFPulse
to change how long the note plays, while still maintaining the rate at which notes occur.
You should also try to get the Env.adsr
envelope working. This works almost exactly the same as the ASR envelope, but has a decay phase and a decayTime
parameter to control the length of that.
Beyond all that…
Earlier I said that envelopes have been traditionally used for volume or amplitude, but they are really just functions that return a stream of values over time, so they can be used to control any parameter of pretty much anything. It’s common to use envelopes on filters for example, to change how the filter is applied to the sound over time.
There are also other envelopes to explore. Or you can just use Env.new
to create a completely custom envelope. You can pass in an array of levels, times and curves for each stage of the envelope, and set which stage is the release. And you can make as many stages as you want and even set up looping envelopes. All very powerful.