This is absolutely 100% logical.
First, let me re-create your original envelope situation using VCV Rack. I’ve got an ADSR envelope, being driven by a manual CV set to a positive voltage, and I’ve set the sustain level to 0.

Looking at the red indicator lights for the currently active envelope stage, which one is active?
It’s sustain.
The envelope’s level is 0 at this point, so you might have thought “the envelope has released.”
It hasn’t released. It is sustaining. The fact that it’s sustaining at level 0 doesn’t change the fact that it is currently still at the sustain stage.
I’m posting the screenshot from VCV Rack to demonstrate that this is not unique to SuperCollider. This is a general property of all ADSR envelopes. If the gate does not fall to 0, then you never get to the release stage at all. SC’s Env.adsr
is standard in this regard.
Rack has no concept of a doneAction – so, in Rack (as in any modular synth, or in Max and I’d guess Reaktor as well), there’s no audible difference between “sustaining at 0” and “released.” But in SuperCollider, it does make a difference.
If you want the doneAction to fire, then the EnvGen must get all the way to the end of the envelope definition.
Envelope definitions divide into two categories: sustained, and timed.
-
Sustained envelopes have a non-nil releaseNode
. The release node is the index into the levels
array where the envelope will sustain: Env.adsr(a, d, s, r)
translates to Env([0, 1, s, 0], [a, d, r], curve: -4, releaseNode: 2)
. 2
is the index into [0, 1, s, 0]
where s
lives.
- With a sustained envelope, as long as the gate is positive, the envelope will move forward up to the releaseNode index, and then pause there until gate drops to 0 or below.
- So, if you’re using a sustained envelope, the only way to get to the end of the envelope is to provide a gate signal that is positive at some point, and becomes <= 0 later.
- When you say
gate: 1
, you’re guaranteeing that the gate will never become <= 0. So then it’s impossible to reach the end of the envelope, and impossible to fire doneAction
.
-
Timed envelopes have no releaseNode
(it’s nil). A timed envelope simply runs according to its own timing – it doesn’t pause/hold at any level – so a timed envelope can reach its end without any change to gate
.
Env.perc is timed. There’s no releaseNode. So it can finish with gate: 1
.
Env.adsr is sustaining. It has a releaseNode. So it cannot finish with gate: 1
.
This is why I said it’s invalid to use a constant gate with Env.adsr: If you use a constant gate with Env.adsr, it will never – can never – release.
Here’s a plot showing the relationship between a gate signal, an Env.adsr with a nonzero sustain, and an Env.adsr with a zero sustain. I’m using a slight levelBias
so that you can see the moment when doneAction:2
takes effect.
(
{
var trig = Impulse.ar(0);
var gate = Trig1.ar(trig, 0.05);
var envelopeDefinition = Env.adsr(0.01, 0.03, 0.5, 0.04);
var envelopeDefinition2 = Env.adsr(0.01, 0.03, 0, 0.04);
var envgen = EnvGen.ar(envelopeDefinition, gate, levelBias: 0.1, doneAction: 2);
var envgen2 = EnvGen.ar(envelopeDefinition2, gate, levelBias: 0.1, doneAction: 2);
[gate, envgen, envgen2]
}.plot(duration: 0.1);
)

I could write this bit of code , but I don’t have acces to the doneAction argument in any of these
You do have access to it:
Env.adsr.ar(doneAction: 2, gate: myGateSignal)
hjh