Maximum number of synth controls

I’m using functions to create SynthDefs. Some end up quite large & I got the following post:
ERROR: A synthDef cannot have more than 255 control names.
Is there a way around this on SC3.8.0?
Thanks.

It turns out that the problem with this is not the SynthDef structure (where the number of parameters is a 32-bit integer, allowing [in theory] a couple billion controls, though in practice I guess you would run out of memory before that point).

The problem is in the function used to generate the argument list for events.

(
d = SynthDef(\d, {
	var x = Array.fill(300, { |i|
		NamedControl.kr(("x" ++ i).asSymbol, i)
	});
}).add;
)

ERROR: A synthDef cannot have more than 255 control names.

(
d = SynthDef(\d, {
	var x = Array.fill(300, { |i|
		NamedControl.kr(("x" ++ i).asSymbol, i)
	});
}).send(s);
)

(no error)

So you can have more than 255 control names – but if you want to use that def with patterns, then you’ll have to create the msgFunc yourself.

Alternately, you could clump some of the controls into arrays. If you have controls x0, x1, x2, x3, x4, then these are five separate control names. If you have NamedControl.kr(\x, [0, 1, 2, 3, 4]) or (equivalent) \x.kr([0, 1, 2, 3, 4]), this is one control name with five values, and it will count only once against the limit.

hjh

PS To demo the real problem:

// let's try to make a function with 300 argument names
(
var str = "{ |";

300.do { |i|
	if(i > 0) { str = str ++ ", " };
	str = str ++ "x" ++ i;
};

str = str ++ "| x0 }";

str.postln;

f = str.interpret;
)

{ |x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30, x31, x32, x33, x34, x35, x36, x37, x38, x39, x40, x41, x42, x43, x44, x45, x46, x47, x48, x49, x50, x51, x52, x53, x54, x55, x56, x57, x58, x59, x60, x61, x62, x63, x64, x65, x66, x67, x68, x69, x70, x71, x72, x73, x74, x75, x76, x77, x78, x79, x80, x81, x82, x83, x84, x85, x86, x87, x88, x89, x90, x91, x92, x93, x94, x95, x96, x97, x98, x99, x100, x101, x102, x103, x104, x105, x106, x107, x108, x109, x110, x111, x112, x113, x114, x115, x116, x117, x118, x119, x120, x121, x122, x123, x124, x125, x126, x127, x128, x129, x130, x131, x132, x133, x134, x135, x136, x137, x138, x139, x140, x141, x142, x143, x144, x145, x146, x147, x148, x149, x150, x151, x152, x153, x154, x155, x156, x157, x158, x159, x160, x161, x162, x163, x164, x165, x166, x167, x168, x169, x170, x171, x172, x173, x174, x175, x176, x177, x178, x179, x180, x181, x182, x183, x184, x185, x186, x187, x188, x189, x190, x191, x192, x193, x194, x195, x196, x197, x198, x199, x200, x201, x202, x203, x204, x205, x206, x207, x208, x209, x210, x211, x212, x213, x214, x215, x216, x217, x218, x219, x220, x221, x222, x223, x224, x225, x226, x227, x228, x229, x230, x231, x232, x233, x234, x235, x236, x237, x238, x239, x240, x241, x242, x243, x244, x245, x246, x247, x248, x249, x250, x251, x252, x253, x254, x255, x256, x257, x258, x259, x260, x261, x262, x263, x264, x265, x266, x267, x268, x269, x270, x271, x272, x273, x274, x275, x276, x277, x278, x279, x280, x281, x282, x283, x284, x285, x286, x287, x288, x289, x290, x291, x292, x293, x294, x295, x296, x297, x298, x299| x0 }
ERROR: Too many arguments in function definition (> 255)
1 Like

Also something to consider - the limit is 255 control names. If you are iterating many serial parameters, e.g. /freq1, /freq2, /freq3, /freq4, consider collapsing these into a single array parameter:

{
freqs = \freqs.kr([100, 200, 300, 400]); // array of size=4
sig = SinOsc.ar(freqs[0]) + LFSaw.ar(freqs[1]) + ...
}
1 Like

That’s great, I’m nearly there…

This works (I’m trying to get it into a similar format to my project):

SynthDef(\test, { | out, controls=#[110,110,0] |
var freq, ffreq, res;
#freq, ffreq, res = controls;
Out.ar( out, Pan2.ar( RLPF.ar(Saw.ar(freq),ffreq,1-res), 0, 0.3) );
}).add;

~b1 = Bus.control(s,1); ~b1.set(110);
~b2 = Bus.control(s,1); ~b2.set(110);
~b3 = Bus.control(s,1); ~b3.set(0);

n = NodeProxy.audio(s, 2);
n.play;
n.source = \test;
n.set(\controls, [ ~b1.asMap, ~b2.asMap, ~b3.asMap ]);

~b2.set(440);
~b3.set(0.5);
~b1.set(220);

But when using a pattern I can’t get the buses to map to the controls:

SynthDef(\toast, { | out, controls=#[440,0.1,10], pan = 0, gate = 1 |
var freq, amp, nharms, audio, env;
#freq, amp, nharms = controls;
audio = Blip.ar(freq, nharms, amp);
env = EnvGen.kr(Env.asr(0.01,1,0.5), gate, doneAction: 2);
OffsetOut.ar(out, Pan2.ar(audio, pan, env) );
}).add;

~b1 = Bus.control(s,1); ~b1.set(440);
~b2 = Bus.control(s,1); ~b2.set(0.1);
~b3 = Bus.control(s,1); ~b3.set(10);

n = NodeProxy.audio(s, 2);
m = NodeProxy.audio(s, 2);
n.play;
m.source = Pbind(\instrument, \toast, \legato, 0.25, \delta, 1, \gate, 1);
n.source = m;
n.set(\controls, [ ~b1.asMap, ~b2.asMap, ~b3.asMap ]);

~b2.set(0.5);
~b3.set(3);
~b1.set(220);

?

Getting closer…

SynthDef(\toast, { | out, controls=#[440,0.1,10], pan = 0, gate = 1 |
var freq, amp, nharms, audio, env;
#freq, amp, nharms = controls;
audio = Blip.ar(freq, nharms, amp);
//env = Linen.kr(gate, doneAction: 2);
env = EnvGen.kr(Env.asr(0.01,1,0.5), gate, doneAction: 2);
OffsetOut.ar(out, Pan2.ar(audio, pan, env) );
}).add;

~b1 = Bus.control(s,1); ~b1.set(440);
~b2 = Bus.control(s,1); ~b2.set(0.1);
~b3 = Bus.control(s,1); ~b3.set(10);

n = NodeProxy.audio(s, 2);
n.put(0,\toast,now:false);
n.put(1,Pbind(\legato, 0.25, \delta, 1, \gate, 1),now:false );
n.play;
n.set(\controls, [~b1.asMap, ~b2.asMap, ~b3.asMap] );

~b2.set(0.5);
~b3.set(3);
~b1.set(220);

Please wrap your code in:

 ``` three backticks ```

So it’s formatted correctly when you post!

You’ve hit a bit of a subtle gotcha when using Pbind to send to arrayed parameters. In a Pbind, the first layer of array depth of any parameter gets expanded to multiple synth instances. So:

Pbind(
    \freqs, [100, 200, 300]
)

… results in three Synths, one for each freq. In order to prevent this and make sure your array is expanded into an array parameter, you simple have to wrap it in another array:

Pbind(
   \freqs, [[100, 200, 300]] // put the array in an array
)

Once you remember the “first array level gets expanded to multiple synths”, this makes sense - and even lets you use array args WITH expansion, if you were to do e.g.:

Pbind(
   \freqs, [[100, 200, 300], [400, 500, 600]] // two synths, each with array args
)

Oh, and just to mention it explicitly: your n.set is just passing the value on to the Pbind, which is why it needs to be wrapped. Somewhat unintuitively, if your NodeProxy was just a single Synth rather than a pattern, I think you could get away with sending your current array.

2 Likes

One final tip, from someone who uses arrayed controls pretty aggressively :slight_smile:

kr-rate controls - that is, controls you specify using \freqs.kr([100, 200, 300]) (all controls specified in the function argument style that you’re using are by-default kr rate) - do consume a bit of CPU power, because the Synth is processing them as if they were constantly changing, even if they are not. This can add up if you have 100 controls per synth and many synths at a time.

There’s some performance benefit to using ir rate controls (instantaneous: that is, read once when the synth is spawned and then never again). You can do these using \freqs.ir([100, 200, 300]) or prefixing your function arg with an i_, e.g. i_freqs=#[100, 200, 300].

Really don’t worry about this unless you’re hitting like >50% CPU for whatever it is you’re building… but I thought I’d mention it because it’s obvious in retrospect, but took me AGES to figure out.

2 Likes

I can get the synthDef loading ok, but only the first bus in the array gets mapped. Is this the correct syntax?
nodeProxy.set(\x, [bus0.asMap, bus1.asMap, bus2.asMap...]);
or is the only way to map an array of buses to one control directly via the pattern?
Thanks again…

Same applies to mapping the buses it seems
nodeProxy.set(\x, [[bus0.asMap, bus1.asMap, bus2.asMap...]]);
Sorted.
Many thanks,
L.