Envgen.kr(Env.adsr

I am still struggling abit using the envgen classes and it’s subclasses
I amanged to create an adsr , set att,dec, sus,release time ,and set done action To 2 for both envelopes
yet the instrument is not released from the server
If I understand correctly I create an Envgen object at kr , inside I create a subclass called Env.adsr.
Inserted values for that class ( this has NO done action argument ) , when closing the parentheses for this object I am back in the EnvGen object which has a doneAction argument , it’s there where I have said it to value 2 .
The envelope behaves perfect , just not released after doneAction .
Btw , sustain is set to zero

///adsr envelopes 
(
{  var mod, carr,index;
	mod=SinOsc.ar(freq:220,mul:0.4)!2*EnvGen.kr(Env.adsr(attackTime:2,decayTime:0.02,sustainLevel:0,releaseTime:1),gate:1,doneAction:2)*0.5;
	index= mod*5;
	SinOsc.ar(freq:220,phase:index,mul:0.4)!2*EnvGen.kr(Env.adsr(attackTime:0.1,decayTime:2,releaseTime:2,sustainLevel:0),gate:1,doneAction:2)*0.4
}.play
)
/

Your gate is a constant 1.

The envelope will release when the gate input is 0 or negative.

But this is impossible when the gate is a constant 1.

Also it’s strongly recommended to have only one doneAction: 2 in the synth. The modulator envelope should not be able to kill the carrier, so delete doneAction from there.

But… as I look at it again… if sustain is 0 then it seems like you don’t need the sustain phase at all.

A gated envelope means “start playing the note now, and at some unknown time in the future, I’ll say when to go to the release phase.”

But an envelope with sustain = 0 will become silent in a fixed, known amount of time. In this case, you don’t need an Env with a releaseNode. So maybe try Env.perc and don’t use the EnvGen gate input.

hjh

setting gate to zero gives the same result ( when executing code )
Altough I don;t hear anything (obviously) ,the server does not release them

(
{  var mod, carr,index;
	mod=SinOsc.ar(freq:220,mul:0.4)!2*EnvGen.kr(Env.adsr(attackTime:2,decayTime:0.02,sustainLevel:0,releaseTime:1),gate:0,doneAction:2)*0.5;
	index= mod*5;
	SinOsc.ar(freq:220,phase:index,mul:0.4)!2*EnvGen.kr(Env.adsr(attackTime:0.1,decayTime:2,releaseTime:2,sustainLevel:0),gate:0,doneAction:2)*0.4
}.play
)

If you are using Env.adsr, then you need a gate signal.

It is invalid to use a constant gate with Env.adsr.

Also see the edit to my earlier reply – I think you might need a non-gated envelope in this case.

hjh

Still trying to get my head around certain things .
AM I correct in thinking that env.perc and env.adsr are subclasses of the EnvGen .
I could write this bit of code , but I don’t have acces to the doneAction argument in any of these

(
{
	Env.perc(0,0.500,1,-5).ar*SinOsc.ar(220)!2
}.play
)

Instead I should used the EnvGen to get acces to these , like this

//
(
{
	EnvGen.ar(Env.perc(0,0.500,1,-5),doneAction:2)*SinOsc.ar(220)!2
}.play
)
//

Or am I totally missing the point ?

But then again , the last posted example frees the synth from the server , but the intital gate value of the EnvGen is also set to 1 …which is not shown

Both EnvGen gates are set to 1 ,Env.perc releases from server, Env.adsr does not
One can’t possibly expect this to be logical at all

//Env perc frees server 
(
{
EnvGen.ar(Env.perc(0,0.500,1,-5),doneAction:2,gate:1)*SinOsc.ar(220)!2
}.play
)
//adsr does not 
(
{
EnvGen.ar(Env.adsr(0,0.500,0,0),doneAction:2,gate:1)*SinOsc.ar(220)!2
}.play
)
//

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.

vcv-adsr-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);
)

envgen-sus0

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

Incidentally, it’s also possible to write a “node-release” based on the envelope generator’s value, rather than its current stage of evaluation: “release if the eg is now zero and has been nonzero in the past.” That would look like this (also inserting spaces for readability):

(
{
	var mod, carr, index;
	var carr_eg = EnvGen.kr(Env.adsr(attackTime: 0.1, decayTime: 2, releaseTime: 2, sustainLevel: 0), gate: 1);
	var env_has_been_nonzero = RunningMax.kr(carr_eg) > 0;
	var env_is_now_zero = carr_eg <= 0;
	mod = SinOsc.ar(freq: 220, mul: 0.4) * EnvGen.kr(Env.adsr(attackTime: 2, decayTime: 0.02, sustainLevel: 0, releaseTime: 1), gate: 1) * 0.5;
	index = mod * 5;
	FreeSelf.kr(env_is_now_zero * env_has_been_nonzero);
	SinOsc.ar(freq: 220, phase: index, mul: 0.4) ! 2 * carr_eg
}.play
)

hjh

I only get acces to done action ine Env perc when env.perc is INSIDE EnvGen
I can’t write arguments if there is no indication that they exist .


Inside env perc there is only attack,release level ,curve
Are you telling me I can just write doneAction in there and it will accept it ( not in envGEN ) , so why doesn’t it show up in the pop up
Maybe we have a communication error here
It seems we have a communication error

So there are two components to an envelope generator:

  • The envelope definition. This is an instance of the Env class. In the most general sense, it can be created by Env.new(...) but there are also many shortcuts to create common envelope shapes (perc, adsr etc.).

    • The envelope definition’s properties are the levels, times, curves, release node and loop node. These are the properties that control the envelope’s shape. Envelope definition = Env object = envelope shape only.
    • doneAction is about the envelope generator’s behavior. So it is not a property of the envelope definition (and has never been).
  • The envelope generator: EnvGen.ar / EnvGen.kr or you can also call ar/kr on an Env object itself.

    • The envelope generator’s properties are the envelope definition (Env object), gate, doneAction, and level/time scaling/offset factors. These are the properties that control the envelope generator’s behavior.

So the envelope definition (shape) and the envelope generator (behavior) are separate concepts. Their properties are not interchangeable. Shape properties will never belong to EnvGen. Behavior properties will never belong to Env.

The “communication error” is that you’re trying to put all of these properties into one bucket for “envelopes” and then assuming that you can mix and match them however you want.

Now… I did a long post a few days ago explaining about calling methods on objects.

There are many ar methods on many types of object.

When you call ar on an Env instance, the method completion help is available by choosing the right version of ar. So you type Env.adsr( /* your parameters */ ).ar and now ( will pop up a menu where you can choose one particular ar definition.

Env-ar

So then you move down in this list to ar (Env), hit return on that and… there they are.

hjh

1 Like

And, FWIW, I always use EnvGen explicitly. I quite strongly disagree with tutorials using shortcut syntax like Env(something).kr because it introduces exactly this kind of confusion. IMO anEnvelope.kr is leading you off the main road. So I’d say, get really comfortable first with EnvGen.kr(Env(/*shape parameters*/), /* behavior parameters */) and then when you really understand that, maybe you can try the shortcut. (But TBH I don’t feel there’s much to gain. It saves a handful of keystrokes, but makes the code harder to read and understand. So I don’t do it.)

hjh

I found it initially confusing that Env and EnvGen were seperate classes but its made more and more sense as I’ve gotten deeper into SC. Consider that there are other uses for an Env - they can be used by Streams for example. And you might want to select between several preset envelopes in a Synth, or use an envelope in several places…

Maybe some of the confusion comes from language: Envelope Generator might be taken to mean “something that generates an Envelope” rather than “something that generates a signal from an Envelope”. I think the name PlayEnv would solve this (like PlayBuf). While we’re at it PlayBus might not be a bas idea either.

(amazing explanation by James here as always btw)

Thanks for the lengthy post
But I didn’t randomly put doneAction in there
I think I finally got the jist of it , indeed a good exercise to always start with EnvGen

(
{
	var levels,times,curves;
	levels=[0,1,0.2,0.2,0];
	times=[0.001,0.5,1,0.250];
	curves=[5,-5,0,0];
	SinOsc.ar(220)!2
	*EnvGen.ar(Env(levels,times,curves),doneAction:2)
	*0.5
}
.plot(2)
)

Great! And a formula for ADSR is:

SynthDef(\doubleEspresso, { |out, gate = 1, /* other args */ |
    var eg = EnvGen.kr(Env.adsr( /* adsr parameters */ ), gate, doneAction: 2);
    ... other units ...
    // assuming "final signal" is stereo
    // if mono, then: Out.ar(out, (/* final signal */ * eg).dup);
    Out.ar(out, /* final signal */ * eg);
});

This is one of those macros that you’ll write a thousand times over the years, so best to learn it by rote.

hjh

I am astonished by the envelope’s features ,wrongfully assumed that only negative ,linear and positive curves where available , but we can have sine curves etc…
Boy , was I wrong …and not even speaking about the different envelope modes , circular ,steps
Absolutely amazing .

1 Like

I am analyzing some code from the fantastic rc 808 drummachine , this example is the bassdrum
I noticed that sometimes the developers= uses Env( [ ] ).kr instead Of EnvGen , for the pitch envelope ( pitch envelope is fenv on line 9 ) , but the output for that line then is again scaled with EnvGen .
I replaced these with EnvGen and the results are identical , obviously
So I would really like to know if there are any advantages or why did he use the Env in these cases

(
{
arg decay=30, amp=2, gate=0, tone=56;
	var fenv, env, trienv, sig, sub, punch, pfenv;
	env = EnvGen.kr(Env.new([0.11, 1, 0], [0, decay], -225),doneAction:2);
	trienv = EnvGen.kr(Env.new([0.11, 0.6, 0], [0, decay], -230),doneAction:0);
	fenv = Env([tone*7, tone*1.35, tone], [0.05, 0.6], -14).kr;
	pfenv = Env([tone*7, tone*1.35, tone], [0.03, 0.6], -10).kr;
	sig = SinOsc.ar(fenv, pi/2) * env;
	sub = LFTri.ar(fenv, pi/2) * trienv * 0.05;
	punch = SinOsc.ar(pfenv, pi/2) * env * 2;
	punch = HPF.ar(punch, 350);
	sig = (sig + sub + punch) * 2.5;
	sig = Limiter.ar(sig, 0.5) * amp;
	sig = Pan2.ar(sig, 0);
}.play)

they are equivalent to my knowledge, one is just a shorter notation.

In terms of functionality, yes, they’re equivalent.

I think there’s a disadvantage of using EnvGen.kr on one line and Env().kr on the next in that it introduces confusion – “why are they written differently? Is there something I’m missing?” That’s a matter of code style, not behavior, though.

hjh

At the begining I have also found really confusing why is there an Env and EnvGen. And, as you said, @semiquaver, once you get deeper, especially understanding UGens and language-side separation, what is evaluated/executed/run where, it becomes much more logical.

One way to think about these two is that Env is a specification, a shape, a form, a simple numerical representation, not a signal yet, just a table, or a graph, while EnvGen is a UGen, an actual generator, something that outputs a signal that can be then multiplied or used in the dance of different signals, oscilators, etc, on the synthesis server.

I also think as Env comes first, and EnvGen second using the Env as an argument

(
{
	var levels,times,curves,env;

	levels=[0,1,0.2,0.2,0];
	times=[0.001,0.5,1,0.250];
	curves=[5,-5,0,0];
	env=Env(levels,times,curves);

	SinOsc.ar(220)!2
	* EnvGen.ar(env,doneAction:2)
	* 0.5
}
.plot(2)
)