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
)
(
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)
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.