Free an array of Envs, based on longest release time

I have an array of SinOscs (given to a Mixer). Each of these SinOscs has it’s own envelope, with it’s own release time. I want to free the enclosing synth (the overall sound) first, when the longest release time is over. I thought i could do something like the following (using a conditional to free the enclosing, when the Env has the longest release time), but this still cuts the sound some where before the longest release time is over. Can any one help with this? Here is my code:

(
{
	var f = rrand(50, 300);
	var n = 12;
	var relTimes = {rrand(1, 5.0)}.dup(n);
	relTimes.sort{arg a, b; a > b};
	Mix.ar(
		Array.fill(
			n,
			{
				arg i;
				i = i + 1;
				i.postln;
				SinOsc.ar(
					f * i,
					mul: EnvGen.kr(
						Env.perc(releaseTime: relTimes.at(i-1)),
						levelScale: 1 / i,
						doneAction: (if (i != n, 0, 2))
					)
				)
			}
		)
	)
}.play
)

PS: I’m also interested in a solution which would work inside of a SynthDef (because of the conditionals evaluations on the server).

You could keep all the doneActions to 0 in the Mix array and use a Env or Line with doneAction 2 which sole purpose is freeing the synth. I also added an atk var since the total duration of each envelope will be atk + relTimes[i]

(
{
	var f = rrand(50, 300);
	var n = 12;
	var relTimes = {rrand(1, 5.0)}.dup(n);
	var atk = 0.01;
	var line = Line.kr(dur: relTimes.maxItem + atk, doneAction: 2);
	relTimes.sort{arg a, b; a > b};
	Mix.ar(
		Array.fill(
			n,
			{
				arg i;
				i = i + 1;
				i.postln;
				SinOsc.ar(
					f * i,
					mul: EnvGen.kr(
						Env.perc(atk, releaseTime: relTimes.at(i-1)),
						levelScale: 1 / i,
						doneAction: (0)
					)
				)
			}
		)
	)
}.play
)
1 Like

Maybe this helps you

(
SynthDef(\test, {
	var n = 12;
	var freq = rrand(50, 300).postln;
	// sort by reverse so the largest value is at position 0
	var releaseTimes = n.collect({rrand(1, 5.0)}).sort.reverse;
	var sig = releaseTimes.collect({|releaseTime, i|
		SinOsc.ar(
			freq: freq * (i+1),
		) * Env.perc(
			releaseTime: releaseTime,
		).ar(
			gate: 1.0,
			// the largest release value (at pos 0) needs to release the synth
			doneAction: if(i==0, {Done.freeSelf}, {Done.none}),
			levelScale: 1 / (i+1),
		);
	});
	sig = Splay.ar(sig) * \amp.kr(0.2);
	Out.ar(\out.kr(0), sig);
}).add;
)

Synth(\test)

Or you could use a pattern for this

(
SynthDef(\testSingle, {
	var sig = SinOsc.ar(
		freq: \freq.kr(200.0),
	);
	var env = Env.perc(
		releaseTime: \releaseTime.kr(1.0),
	).ar(doneAction: Done.freeSelf);
	sig = Pan2.ar(sig*env*\amp.kr(0.2), pos: \pos.kr(0.0));
	Out.ar(\out.kr(0), sig);
}).add;
)


(
Pdef(\chord, Pbind(
	\instrument, \testSingle,
	\num, 12,
	\freq, Pclump(Pkey(\num), Pseq(50 * (1..12))),
	\dur, 5.0,
	\releaseTime, Pclump(Pkey(\num), Pwhite(1.0, 5.0)),
	\amp, Pclump(Pkey(\num), 1/Pseq((1..12))) * 0.1,
)).play;
)
1 Like

This line should be relTimes.sort{arg a, b; a < b}; to sort the envelope times according to the following line:

The following might be a possible way to do this with a SynthDef (but I think there should be a better way than my suggestion):

(
SynthDef(\randSynth, { |out=0|
	var num, freqs, relTimes, envs, sig;
	num = 12;
	freqs = { Rand(31, 63).poll(0).midicps } * (1.. num); 
	relTimes = num.collect { |i| Rand(1, 5.0 * (i + 1) / num) };
	envs = Env.perc(0.01, relTimes).kr / (1.. num);
	FreeSelf.kr(
		envs.collect { |thisEnv|
			Done.kr(thisEnv)
		}.product
	);
	sig = SinOsc.ar(freqs) * envs / num;
	sig = sig.sum ! 2/* * [1, -1]*/;
	Out.ar(out, sig)
}).add
)


Synth(\randSynth)
1 Like

One more thing:

If you want to have lower frequencies longer than higher ones, then my suggestion and code are wrong and should be corrected as follows:

The following line is correct:

but the following line

should be doneAction: if (i == 1, 2, 0).

Of course, my SynthDef should be also changed as follows:

(
SynthDef(\randSynth, { |out=0|
	var num, freqs, relTimes, envs, sig;
	num = 12;
	freqs = { Rand(31, 63).poll(0).midicps } * (1.. num); 
	relTimes = (num..1).collect { |i| Rand(5.4 - (i / num), 4.6) * i / num + 0.5}.poll(0);
	envs = Env.perc(0.01, relTimes).kr / (1.. num);
	FreeSelf.kr(
		envs.collect { |thisEnv|
			Done.kr(thisEnv)
		}.product
	);
	sig = SinOsc.ar(freqs) * envs / num;
	sig = sig.sum ! 2/* * [1, -1]*/;
	Out.ar(out, sig)
}).add
)

Synth(\randSynth)

Env.perc has the useful characteristic that it is always > 0 while active (“true”), and == 0 only when the release is finished (“false”).

So if you logical-or those Boolean values, you’ll get “true” if any envelope is still active, and false when they have all finished.

Logical-or could be done with || (or, since all “true” values are positive and none are negative, because of the envelope’s characteristics, you could use + as well): envelopes.reduce('||') or envelopes.sum.

Then FreeSelf.kr(that_thing <= 0) because this matches false.

(
{
	var f = rrand(50, 300);
	var n = 12;
	var relTimes = {rrand(1, 5.0)}.dup(n);
	var egs = relTimes.collect { |rel, i|
		EnvGen.kr(
			Env.perc(releaseTime: relTimes.at(i)),
			levelScale: 1 / (i + 1),
			doneAction: 2
		)
	};
	FreeSelf.kr(egs.sum <= 0);
	Mix.ar(
		Array.fill(
			n,
			{
				arg i;
				SinOsc.ar(
					f * (i + 1),
					mul: egs[i]
				)
			}
		)
	)
}.play
)

Then there is no need to identify the longest envelope, and it’s much easier to render as a dsp graph. This is very similar to prko’s suggestion, with a different approach to the “done” aspect.

(I’m not at the computer; there could be typos.)

hjh

1 Like

Ah! so simple!
Then my code could be simpler:

(
SynthDef(\randSynth, { |out=0|
	var num, freqs, relTimes, relTimesSorted, envs, sig;
	num = 12;
	freqs = Rand(31, 63).poll(0).midicps * (1.. num); 
	relTimes = Rand(1, 5) / (1.. num);
	// relTimes = Rand(1, 5) / (1.. num) * (Rand(0.9, 1.1)!num);
	// relTimes = Rand(1, 5) / (1.. num).sqrt;
	// relTimes = Rand(1, 5) / (1.. num).exp;
	envs = Env.perc(0.01, relTimes.poll(0)).kr;
	FreeSelf.kr(envs.sum <= 0);
	sig = SinOsc.ar(freqs) * envs / num;
	sig = sig.sum;
	Out.ar(out, sig)
}).add
)

Synth(\randSynth)

It would be nice if { Rand(1, 5.0 } ! 12 could be sorted as asked here: Is it possible to sort the output values of Ugen arrays by output value? - #3 by prko