Accelerating \fadeTime for EnvGate

Hello everyone!

I have a quick question regarding the speeding up of the release of an EnvGate while it’s running. Consider this example:

(
SynthDef(\testEnv, {
	var env = EnvGate(0);
	Out.kr(\out.ir(0), env)
}).add
)

(
~bus = Bus.control(s);
~env = Synth(\testEnv, [\out, ~bus, \fadeTime, 2]);
~bus.scope
)

//Start the release over 10 seconds
~env.set(\gate, 0, \fadeTime, 10);

//Speeding the release time up while it's runnning? (not working)
~env.set(\fadeTime, 0.5);

Is it possible to change the \fadeTime parameter while the EnvGate is in release mode?

Alternatively, is there a way to do this in a single SynthDef? (I have found a way to do it with 2 connected SynthDefs, but I’d like to keep this as simple as possible).

I am looking for a solution that would allow me to change the release time dynamically as many times as I want, as long as the Synth is still running.

Thanks!

I believe that, once an envelope segment starts, there’s no way to modify the parameters of the same segment, without retriggering. But in the release segment, the gate is 0, so that wouldn’t work as a trigger.

I can’t quite think of a good workaround now. Maybe someone else can.

hjh

I’m not exactly sure what you’re going for here, but it sounds like a single synth, with a re-triggerable envelope with variable release times? When you set the release time to 4, do you expect that the release would have 4 seconds remaining, or that the total release time would be re-adjusted to be a 4 second segment (e.g. if you were at 2 seconds, there would be 2 second less)?

You can always do the last segment of the envelope with something like Sweep? You’d need to make the release of your envelope drop to 0 immediately, and the use a 0 \gate or \trig value to start a Sweep that ramps from 1 down to 0 over there right amount of time. Then you could add the two together (Env and Sweep) and should be able to get the right value. The sweep could look something like:

// this should trigger when gate goes from positive to 0
sweep = Sweep.kr(gate.neg + 0.000001, \releaseTime.kr(1).reciprocal)
env = sweep + env;

It’ll be a little tricky to get this right, but it should be do-able with only 4 or 5 UGens at most? It’s be easy to wrap this up as an extension of Env, so you can do e.g. Env.variableRelease(...) which takes the same args as Env but chops off the last one and adds an appropriate Sweep.

It may also be worth checking to see if IEnvGen works correctly when you vary the envelope levels while it’s running.

I took your advice to use Sweep and came up with this solution:

(
SynthDef(\testEnv, { | t_release = 0 |
	var fadeTime, gate;
	var selectRelease;
	var riseEnv, riseEndPoint, fallEnv, env;
	
	//1 / fadeTime
	fadeTime = \fadeTime.kr(1).reciprocal;
	
	//rise envelope
	riseEnv = Sweep.kr(1, fadeTime).clip(0, 1);
	
	//Sample the end point when triggering release
	riseEndPoint = Latch.kr(riseEnv, t_release);
	
	//Fall envelope
	fallEnv = 1 - (Sweep.kr(t_release, fadeTime).clip(0, 1));
	
	//Scale by end point (to toggle release while rise is still going)
	fallEnv = fallEnv * riseEndPoint;
	
	//select fallEnv on trigger
	selectRelease = ToggleFF.kr(t_release);
	
	//Final envelope (use gate to either select rise or fall)
	env = Select.kr(selectRelease, [riseEnv, fallEnv]);
	
	//Release node when fallEnv is done
	DetectSilence.kr(fallEnv, doneAction:2);
	
	Out.kr(\out.ir(0), env);
}).add
)

(
~bus = Bus.control(s);
~env = Synth(\testEnv, [\out, ~bus, \fadeTime, 5]);
~bus.scope
)

//trigger release
~env.set(\t_release, 1)

//Now it can be accelerated / decelareted
~env.set(\fadeTime, 1);

It’s quite verbose, but it does its intended job

1 Like

No, nothing in the env that IEnvGen loads is modulatable thereafter. Everything is treated as ir. So it’s even less flexible than the regular EnvGen in that regard. You can of course time-warp via the index in IEnvGen and externally modulate/map the levels it returns so something else. But basically none of the envelope classes in SC are any good for “change this right now” unless the segments are short and the evenlopes are replayed/retriggered or cycled using the loop node. Otherwise it’s more or less doing a “custom envelope” using Select among various Sweeps, pretty much like in vitreo’s solution. It’s a also a bit tricky to chain the triggers from on Sweep to the next. If you need a cycling envelope of this kind LocalIn and LocalOut can be used to pass the final segment trigger back to the first. A semi-custom envelope that I wrote that’s simple enough to demonstrate the bag of tricks is this two-segment one, which is just ramp and a hold. It seems like no big deal, until you consider what could or should happen in the current segment when the parameters change.

(Ndef(\ei, { arg t_go = 0, a1 = 0.2, a2 = 0.95, t2 = 8, t3 = 3;
	var slope = (a2 - a1) / t2;
	var swar = Sweep.kr(t_go + LocalIn.kr(1, 0), slope).clip(a1, a2);
	var idse = (swar >= a2); // index for select
	var tcon = Sweep.kr(idse); // time elapsed at "constant" amp hold
	LocalOut.kr(idse * HPZ1.kr(tcon >= t3) > 0); // no trigs if in ramp-up seg
    // added a bit of tremolo to hold segment so it's visible that it's not stuck
	Select.kr(idse, [swar, SinOsc.kr(10, 0, 0.05, a2)]); 
}))

Basically, the intent here is that when t2 is changed up or down, the slope in the ramp changes right now and implicitly the time left in the ramp, but it’s never a sudden jump. Conceptually the current time is considered as a fraction of t2 and that fraction is seamlessly used with the new t2. And likewise when the amplitude a2 is changed up that causes the ramp to go faster, although it may abruptly cut down the current amplitude if exceeds the valued a2 is changed to. Whereas in the hold segment, changing t3 downwards does do a cut-out of the segment, causing the whole envelope to re-cycle.

This code actually shows some semantic problems when changing the parameters of an envelope segment as it runs: e.g. what should happen when the end time is lowered below the time that has already elapsed? Just cut out or change the slope so that the end level (amplitude) is reached faster? It really depends on what you want for that particular knob, so that’s why probably there’s no such implementation in the SC library.

It’s also a bit tricky to make sure the external trigger t_go does the right thing regardless in which segment the envelope is at. Basically some internal triggers need to be disabled when an external one happened already. Code like this gets hairy fast. It’s really more like making a circuit than (sequential) code. In my original code there was also a t1 that did something else, but I’ve removed it here for simplicity. Also the sudden downward changes of a2 are here as such for demonstration purposes. I actually use a LagUD with a larger down than up constant to smooth those too a little bit.

There is already at least one case where EnvGen can jump to another segment before reaching the end of the current segment: in a gated envelope, if the gate closes before reaching the sustain level.

(
var time = 0.01;
{
	var env = Env.adsr(time * 0.1, time * 0.4, 0.5, time * 0.2, curve: \lin);
	// gate is contrived to close during the decay segment
	var gate = Trig1.ar(Impulse.ar(0), time * 0.25);
	EnvGen.ar(env, gate)
}.plot(duration: time);
)

It’s really the same case as “the end time is lowered below the time that has already elapsed” – it should go to the next segment immediately, but it hasn’t reached the target level.

So both could be handled in the same way: Advance immediately to the next segment, beginning from the EnvGen’s current output level (so, no discontinuity).

Envelope segments have always been defined as a target level, time and curve and are assumed to start from the previous output level (with one initial level, to “prime the pump”). But because the extra initial level is folded into the levels array, it’s easy to misunderstand an envelope segment as a starting level, target, time and curve. The latter conception introduces problems that aren’t actually necessary.

hjh