Multichannel expansion of the method .delay of Env

Dear users,

Suddenly, the idea of making a short delay to simulate a binaural effect only using Env without Delay classes. The idea seems to have arisen from my laziness in typing.
I found an interesting or odd behaviour of the .delay method when using multichannel as in Ex. 2. The right channel is cut; thus, I hear a click arising by sudden ending at a non-zero amplitude.
I added one more point in Env as in Ex. 3 to resolve it. I am not sure if it is a reasonable solution.

As an alternative, one could suggest Env.dadsr, but Env.dadsr has no delay effect at the release moment.

Could anyone explain why the method .delay of Env does not accept the multichannel expansion; and give opinions if my way to resolve it could generally be acceptable or not?

I really appreciate any help you can provide.

(
// Ex. 1: one-channel delay
{
	var env = Env(
		[0, 1, 0.5, 0], 
		[0.01, 0.025, 0.5]
	).delay(0.5);
	SinOsc.ar(470) * EnvGen.kr(env, doneAction: Done.freeSelf)
}.play
)

(
// Ex. 2: two-channel delay with cut phenomenon at the second channel.
{
	var env = Env(
		[0, 1, 0.7, 0], 
		[0.02, 0.2, 0.1]
	).delay([0, 0.05]);
	SinOsc.ar(470) * EnvGen.kr(env, doneAction: Done.freeSelf)
}.play
)

(
// Ex. 3: two-channel delay without cut phenomenon at the second channel.
{
	var delayL = 0, delayR=0.05;
	var env = Env(
		[0, 1, 0.7, 0, 0], 
		[0.02, 0.2, 0.1, max(delayL, delayR)]
	).delay([delayL, delayR]);
	SinOsc.ar(470) * EnvGen.kr(env, doneAction: Done.freeSelf)
}.play
)

(
// Ex. 4: two-channel delay with Env.dadsr. It does not have the click issue, but there is no delay effect when releasing.
fork{
	x = { |gate=1|
		var env = Env.dadsr([0, 1], 0.02, 0.2, 0.5, 1, 1, -4);
		SinOsc.ar(470) * EnvGen.kr(env, gate, doneAction: Done.freeSelf)
	}.play;
	1.5.wait;
	x.set(\gate, 0)
}
)

Multiple envelope generators should not all use a doneAction. EnvGen doesn’t have any way to know which one will be the longest, so it’s up to the user to control for this.

hjh

2 Likes

Here’s a way to trigger a DoneAction only after the last of several envelopes (or other UGens that set a done flag) is finished playing, illustrated using a slight modification of your second example:

(
{
	var env = Env(
		[0, 1, 0.7, 0], 
		[0.02, 0.2, 0.1]
	).delay([0, 0.2, 0.4, 0.6]).kr; // works with any amount (>= 2) of channels
	
	DetectSilence.kr(1 - Done.kr(env).reduce('*'), 0, 0, 2);
	Splay.ar(SinOsc.ar(470) * env)
}.play
)

The Done trigger can be further delayed using TDelay if needed (waiting for reverbs/delays to fully decay, etc.)

3 Likes

Thanks!

@jamshark70

EnvGen doesn’t have any way to know which one will be the longest, …

I had experienced the same problem earlier, but strangely my thought did not reach there this time in the .delay example.

@bovil43810
Your algorithm is much better than my example 3. Thank you very much!