Modulate an LPF Freq cutoff using an envelope or LFO

im adding a DC.ar(1) with an envelope before distorting the signal. and would like to use Decay.ar as an envelope generator and not sure if the output is between 0 and 1 for the mapping with .linlin? made some plots and used .poll already, any ideas? (i think its working, but would just like to make sure its right).

dcEnv = Decay.ar(Delay2.ar(trig), \tDec.kr(2) * LFNoise1.ar(4).abs);
dcBias = (DC.ar(1) * dcEnv.linlin(0, 1, 0, \dcAmount.kr(1))).poll;
mid = RLPF.ar(mid, fltRange, 0.1);
mid = mid + dcBias;
mid = (mid ** (LFNoise2.ar(2).range(1, 27)).lag(0.01)).fold2;
mid = LeakDC.ar(mid);

EDIT: i think otherwise i could also do just dcBias = DC.ar(\dcAmount.kr(1)) * dcEnv;

tl;dr - it depends on the trigger signal and Decay’s decay time argument

Decay.ar is just a leaky integrator, but with the T60 decay time conversion already done for you:

(
{
	var trig, decay, integrate;
	var decayTime, pole, sampleRate = 44100;
	
	trig = Impulse.ar(0);
	
	decayTime = 0.01;
	pole = exp(log(0.001) / (decayTime * sampleRate)); // convert decay time in seconds to a leak coefficient for Integrator.ar
	
	integrate = Integrator.ar(trig, pole);
	decay = Decay.ar(trig, decayTime);
	
	[integrate, decay, integrate - decay] // signals are exactly equal
}.plot;
)

Integrators simply sum the current sample of a signal with its previous output sample. Leaky integrators add a coefficient to attenuate the previous sample, mostly exactly because non-leaky integrators easily overshoot. This is described by the following difference equation:

y[n] = x[n] + coef * y[n - 1]

where

y[n]    system output at discrete time n
y[n-1]  system output at discrete time n-1 (the previous sample)
x[n]    system input at discrete time n
coef    "leak" coefficient, usually a constant slightly less than 1.0 (e.g. 0.99).

So you can think of integrators as simple feedback lowpass filters. Demonstrating integrator overshoot with a large decay time (coefficient close to 1.0) and a fast trigger signal:

(
{
	var trig, dec;
	
	trig = Impulse.ar(0.01.reciprocal * 2);
	dec = Decay.ar(trig, 0.5);

	[trig, dec]
}.plot(0.3);
)

You can only depend on the output staying within [0, 1] if your signal decays to zero quickly enough (before the next trigger arrives).

thanks a lot for the detailed explanation :slight_smile:

EDIT: this was really helpful. i was wondering why the behaviour for Decay was different from the EnvGen playing the SynthDef via Pmono. But its only for bigger \tDec.kr(4) values because of the overshoot right? Looks the same for small \tDec.kr(0.2) values.

(
{
	var trig = Trig.ar(Impulse.ar(0.2.reciprocal), SampleDur.ir);
	var decay = \tDec.kr(4) * LFNoise1.ar(4).abs;
	var dec = Decay.ar(Delay2.ar(trig), decay);
	var env = EnvGen.ar(Env([0, 1, 0], [0, decay], -8), Delay1.ar(trig));

	[dec, env]
}.plot(1);
)

EDIT: i thought of Decay sounding way “better” applying the DC.ar with big values \tDec.kr(4) to the signal with Impulse.ar(0.2.reciprocal) compared to EnvGen. The reason for that is the overshoot then right? And for some reason absolutely necessary to use the Trig.ar(trig, SampleDur.ir); and not putting the trigger into Decay or EnvGen directly otherwise i had a constant feedback like tone.