Amplification envelope after (low pass) filter produces click

Hi,

I’ve came to curious (at least to me) case, where order of a band pass filter and amp envelope matters in order to not produce a click at steep slope. Example code:

({ // sound is fine despite steep release slope
	var snd, env;
	snd = LFTri.ar(80);
	env = Env.linen(0,0.1,0.01).ar(doneAction:2);
	snd = LPF.ar(snd,30); // filter BEFORE applying envelope
	snd = snd * env;
	snd!2
}.play)

({ // steep release and LPF after env produces click
	var snd, env;
	snd = LFTri.ar(80);
	env = Env.linen(0,0.1,0.01).ar(doneAction:2);
	snd = snd * env;
	snd = LPF.ar(snd,30); // filter AFTER applying envelope
	snd!2
}.play)

Using SinOsc.ar instead of LFTri.ar produces the same effect. Also switching for some other linear low pass filter does not influence this.

I’m probably not getting something about DSP principles here, could someone care to explain why does it come to this?

Hello,

I suppose, what you’re expecting is that a zero value of a source signal stays a zero value after filtering. This, in general, is not the case, and it becomes clear when regarding the difference equation that constitutes the filter. Take the simplest lowpass as an example:

https://www.dsprelated.com/freebooks/filters/Simplest_Lowpass_Filter_I.html

If x[n] equals zero, it’s not necessary that x[n] + x[n-1] is zero too.

Comparing the plots shows the effect (LPF uses other filter coefficients, of course):

(
{ // steep release and LPF after env produces click
	var snd, env;
	snd = LFTri.ar(80);
	env = Env.linen(0,0.1,0.01).ar(doneAction:2);
	snd = snd * env;
	// snd = LPF.ar(snd,30); // filter AFTER applying envelope
	snd!2
}.plot(0.2)
)


(
{ // steep release and LPF after env produces click
	var snd, env;
	snd = LFTri.ar(80);
	env = Env.linen(0,0.1,0.01).ar(doneAction:2);
	snd = snd * env;
	snd = LPF.ar(snd,30); // filter AFTER applying envelope
	snd!2
}.plot(0.2)
)
1 Like

thanks,
thank totally explains it.
I’m not (yet) versed enough in DSP, but your answer makes sense. I will soon learn more about digital filters, I guess. But for now I feel like the lesson I’m taking away from this is that one shouldn’t trust a filter at the end of the chain, in some cases at least.

There’s no problem with that – the problem here is based on two conflicting elements:

  • The envelope terminates the synth;
  • But the filter resonates a bit longer than that.

If you use DetectSilence to stop the synth (based on the filter decay) and delete the doneAction from the envelope, then you can have the filter after the envelope.

This is the basis of modal synthesis using Klank, DynKlank or a bank of Formlet filters, btw. It’s one of my favorite synthesis techniques. It’s completely legit – but you can’t use the normal pattern of EnvGen.ar(..., doneAction: 2) for it.

sig = something * EnvGen.ar(...);  // no doneAction
sig = LPF.ar(sig, ...);  // or other filter
DetectSilence.ar(sig, doneAction: 2);
... and output as usual...

hjh

1 Like