One last envelope question (I swear!) - regarding "multi-purpose" envelopes

Hi everyone -

I’ve been haunting the forum the past couple of weeks with a few questions about envelopes. I thought I understood them much better than I apparently do and I appreciate everyone’s time in helping me get a few of the nuances under control. It encompasses a number of subjects that I’ve been a little weak on, so it’s been a great amount of learning.

In any event, I’ve arrived at what I think is the final bit of code for this project. I’ve tried to boil this down several times and I think this might be the most concise description:

I have a tone with an envelope that is tied to a window opening.
When I open the window, I would primarily like the tone to fade in and sustain itself at a releaseNode, unless otherwise specified. At other times, I will want to supply a alternate envelope that does not sustain.

While the window is open, I would like the option of “triggering” the envelope - clicking a button and having the envelope play from beginning to end - but I would also like the act of closing the window to trigger a “release”, accompanied by a doneAction, and presumably a gateOff.

This is a pretty convoluted process - and I’m not sure there is a way to do it as simply as I’ve laid out below. I can imagine an alternate path with switchable envelopes and/or multiple SynthDefs - and I’ll take that route, if the consensus is that the attached approach is goofy - but I am just trying to bear down on some concepts instead of relying on workarounds. Let me know! Thanks for all help.

(
var win, envel, envView, skew1=1, skew2=1, envTimeBasis=1, gateButton;
win = Window("", Rect(100, 100, 200, 200)).front;
win.onClose_({
	 //this is a 5 second fadeout to test before attempting to graft the drawn envelope. 
	//it seems to produce a "burst" effect", so be wary.
	x.set(Env([0.5, 0.5, 0], [0, 10], [1, -1]));  
	x.set(\gate, 0);
	x.set(\dAction, 2);
});

SynthDef("r", {|gate=1, dAction = 0|

	var env, envSig, envArray;
	var sig = SinOsc.ar(400, 0.1);

		envArray = Env(
			[0, 0.1, 1, 0.1, 0],
			[0.1, 0.1, 0.1, 0.1],
			[1, 1.neg, 1, 1.neg], 1);

		env = \env.kr(envArray);
		envSig = EnvGen.kr(env, gate, doneAction: dAction);
        sig = sig * envSig;
	Out.ar(0, sig);
}).add;

x = Synth("r", [\gate, 1]);  

//in theory, i would like this to function as a "trigger" button - but i'm not sure what the best way of writing that would be, since i want to use both functionalities.
gateButton = Button(win, Rect(10, 150))
.states_([["gate on", Color.rand, Color.rand], ["gate off", Color.rand, Color.rand]])
.action_({|v|
	if (v.value == 0){	
		x.set(\env, envView);
		x.set(\gate, 1);		
	}
	{
		x.set(\env, envView);
		x.set(\gate, 0);}
	    ///gate off seems to play a reverse of the envelope?  
});

//default values
envView = Env([0, 0.5, 1, 0.5, 0], [0.25, 0.25,  0.25,  0.25],  [2, -2, 2, -2]);

envel = EnvelopeView(win, Rect(50, 50, 100, 100))
        .keepHorizontalOrder_(true)
		.drawLines_(true)
		.selectionColor_(Color.red)
		.drawRects_(true)
		.resize_(5)
		.step_(0.005)
		.setEnv(envView)
	    .action_({
		envel.value.postln;
	    envView = Env(envel.value[1], envel.value[0].differentiate.drop(1)*envTimeBasis,[skew1,     skew2.neg, skew2, skew1.neg] );
	    x.set(\env, envView);
	});
)

I don’t think anything here is terribly crazy – should be fairly straightforward?

If the envelope definition has a releaseNode, then it will run forward and hold at the releaseNode as long as gate > 0.

If the releaseNode is empty, then it will run forward to the end.

You can set gate to 0 momentarily and back to 1 soon after this.

Be aware, though, that your idea of “from beginning to end” may be different from EnvGen’s. The first envelope node goes from the EnvGen’s current level to the second levels value, using the first times and curves values. If you’re sustaining at 1, a gate retrigger will go from 1 to the first target level. It will not jump to 0 instantaneously and rise from there. Then you’ll think “retriggering doesn’t work” but it is working as designed (and in the same way that commercial synths’ envelope generators work – challenge: find a VST or hardware synth that doesn’t behave in the way I just described – I don’t think it exists).

You can address this by adding a very short 0 node at the beginning:

Env([0, 0, 1, 0], [0.003, attack, release], ...)

Then the retrigger will first take 3 ms to go to 0, then rise.

This is a very common point of confusion – “it should go to my initial level” – but an envelope segment can only go toward a target value. There is no starting value for an envelope segment. The first levels value is only used once, to initialize the EnvGen; it is never used again after that.

It doesn’t – probably the preceding explanation clarifies this.

hjh

1 Like