Change array size from Pbind

I read that arrays have a fixed size, so trying to change the size of an array from Pbind does not work this way:

(SynthDef.new(\imps,{ arg freq = 100,gate=1;
	var snd, env, pulse;
	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(\size.kr(1),freq,2.1),0,0.3)*env;

	snd = Mix.ar(snd);
	Out.ar(0,snd);
}).add);

Pbind(\instrument,\imps,
	\size, 2,
	\dur, Pseq([1, Rest(2), 1],inf);
).play(c);

I have already tried a lot of things but can not come up with a solution. Can you recommend a workaround?

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

1 Like

Thank you so much! This is very helpful, and provides exactly the insights I need to understand Supercollider better. For someone who is new to this, such explanations are priceless.