It very often happens in programming that the way you formulate a problem can either help you see potential solutions, or block you from seeing them.
Here, the problem is being formulated in terms of changing the array size: how to alter the structure according to a parameter.
But, looking at it from the perspective of the desired end result: You want to hear size
channels – that is, it doesn’t matter how many channels exist – what matters is how many are audible.
So you can have channels in the SynthDef that are processing, but whose volume is 0.
Looping over the channels to get a new result (collect
) also gives you access to an index number. Taking your example of size = 2:
- Index 0: On
- Index 1: On
- Index 2: Off
- Index 3: Off
- Index 4: Off
- … etc
… and the difference between indices 0 and 1 (to pass through) and indices 2, 3, 4… (to suppress) is that 0 and 1 are less than the given size: i < size
. Used directly as a volume control, that would be a harsh rectangular envelope; applying a slew limiter will provide a smoother attack and release for the entrance or exit of individual channels.
(SynthDef.new(\imps, { arg freq = 100, gate = 1, size = 2;
var maxN = 13; // ~= log base 2.1 of 22050
var snd, env, pulse;
var channelVolumes;
var mainEg = EnvGen.kr(Env.asr(0.01, 1, 0.01), gate, doneAction: 2);
pulse = Impulse.ar(8.5, LFDNoise0.ar(1).range(0.2, 0.6));
env = EnvGen.ar(Env.perc(0.01, 0.8, 1, -9), gate: pulse);
snd = SinOsc.ar(Array.geom(maxN, freq, 2.1));
channelVolumes = snd.collect { |channel, i|
Slew.kr(i < size, 100, 100)
};
snd = (snd * channelVolumes).sum;
snd = snd * (mainEg * env * 0.3);
Out.ar(0, snd);
}).add);
p = Pbind(\instrument,\imps,
\size, Pwhite(1, 13, inf),
\dur, Pseq([1, Rest(2), 1],inf);
).play(c);
p.stop;
I also added a main envelope – in your example, synths do not release, so they will accumulate in the server – and I re-factored the use of the pulse envelope and 0.3 amplitude (it’s more efficient to apply them once to the mixed-down signal, rather than to each channel individually).
Another way is to produce multiple synthdefs:
(
~makeSynthDef = { |maxN|
SynthDef.new(("imps" ++ maxN).asSymbol, { arg freq = 100, gate = 1;
var snd, env, pulse;
var mainEg = EnvGen.kr(Env.asr(0.01, 1, 0.01), gate, doneAction: 2);
pulse = Impulse.ar(8.5, LFDNoise0.ar(1).range(0.2, 0.6));
env = EnvGen.ar(Env.perc(0.01, 0.8, 1, -9), gate: pulse);
snd = SinOsc.ar(Array.geom(maxN, freq, 2.1));
snd = snd.sum;
snd = snd * (mainEg * env * 0.3);
Out.ar(0, snd);
}).add
};
(1..13).do(~makeSynthDef);
)
p = Pbind(
\instrument, Pfunc { ("imps" ++ rrand(1, 13)).asSymbol },
\dur, Pseq([1, Rest(2), 1],inf);
).play(c);
p.stop;
(Edit to reduce maxN: 2.1 ^ 30 would be a ridiculously high frequency, guaranteed to alias.)
hjh