Array of Signals in a SynthDef

(Initial sound taken from the cover of Thor Magnusson’s book, Scoring Sound)

I have seen this somewhere but I can’t seem to find it. I’m looking for the proper way to have arrays of signals inside a synthdef, and pass the initial array.fill a size argument, then use the index as an argument in that function.

I’m sure there’s something obvious, I just can’t figure out how to search for this issue, everything I search for is about using arrays as arguments in SynthDefs

// Get ERROR: Message '*' not understood, or ERROR: binary operator '*' failed., if I change things around like this:


(
SynthDef(\drone, {
	arg freq = 30, out = 0, size = 24;
	var freqosc, mulosc, sig, sig2, index;
sig = SinOsc.ar(freq * index + freqosc, 0, mulosc);
freqosc = LFNoise2.ar(0.1).range(-2,2);
mulosc = LFNoise2.ar(0.5) * (Line.ar(0,0.1,99.rand)/(index*0.2));
	sig2 = {Array.fill(size, {arg index; sig.(freq * index + freqosc, 0, mulosc)})};
	
	Out.ar(out, sig2);
}).add;
)
		
// when I play this it works: 

{Array.fill(24, {|index| SinOsc.ar(30 * index + LFNoise2.ar(0.1).range(-2,2),	 0, LFNoise2.ar(0.5) * (Line.ar(0,0.1,99.rand)/(index*0.2)))})}.play
		
		```

Any help appreciated

unfortunately SynthDef structure must be fixed at compilation so you can’t have number of elements in array of signals as an argument sadly.

one solution is to fix the number at 64 for example, and simply pad the Array with silence - DC.ar()

As @semiquaver pointed out, you can do that only indirectly (it’s probably one of the top FAQs). Two examples for “pseudo-dynamic” size:

I see. Thank you both. I actually learned alot about the architecture from this. Kinda disappointing though.

Could you not make a bunch of Arrays then Select() them (or whatever that equivalent workaround is that doesn’t take as much CPU)?

@dkmayer Although, I see a lot of talking about the way to do it with NamedControl being the best way, but I don’t see an example of doing this? I’m going to research that more.

To cite myself from the second link I have given:

In this example:

(
~qp1 = [0,1,2,3];
~qp2 = [0,1,2,3,4,5];

SynthDef(\testArray_1, {
	arg size = 7;
	var points = NamedControl.kr(\points, (1..7) * 111);
	// var points = \points.kr((1..7) * 111);
	// indicator is a multichannel *UGen*
	var indicator = { |i| i < size } ! points.size;
	(indicator * points).poll; 
}).add;
)

Sure, Select and passing an index is always possible, but this has nothing to do with the fixed array size limitation.

Ah, I see, because it doesn’t care if it’s not a literal. Ok thanks for all your help. I feel like 80% of the errors and problems I have are just due to not fully understanding the difference in client-side / server-side interaction.

Literally just yesterday: MIDI cc values as array index for Array.fill :laughing:

I suppose we need to implement a way to fake a variable array size and then document it. This is an extremely popular question.

Fixed structure. Think a bit what it takes to build a SynthDef.

  1. Assemble UGen objects, with relationships expressed through each UGen’s inputs array.
  2. Optimize: Math operator substitution. Remove dead units (pure-functional UGens that never reach an output unit may be deleted).
  3. Sort into optimal order.
  4. Encode into the binary format that the server understands.

If you had an array of 10 signals and you want it to change dynamically to an array of 12 signals, then: at step 1 there are now more UGens. Therefore all subsequent steps need to be reevaluated. There is no way to skip steps here. Even changing an array size by 1 means you are all the way back to the beginning.

For large SynthDefs, this can take even a couple hundred milliseconds. So what if you sent your s_new message with a latency-timestamp for 200ms from now, but the server needs 250ms to rebuild the SynthDef? That’s right… “late.” And there is no way to guarantee this won’t happen.

Even worse: you start with size = 3 and later set x.set(\size, 12000) – now the server has at most one hardware buffer of time to do potentially >100 ms of work. It’s impossible.

Well, there is one way to guarantee that you never get into that situation: to disallow it. Build SynthDefs in the language as fixed structures, transmit them to the server as fixed structures, and the server has no power to alter the structure. That’s how SC does it.

Array sizes, array indexing, and conditionals are all things to watch out for.

hjh

Thought: A more informative error might be a good idea!

Ha! I’ve also been working through a similar thing today!

I’m making a simple autopanner thing, I wanted to be able to have a range of waveforms available for the LFO, and I wanted to be able to select the waveform without using Select, because that means I’d have many unused audio rate UGens running. I was hoping to be able to use switch or case but I think the barrier is the same as the array.

My current workaround is using SynthDef.wrap. I’ve got it set up very similarly to the code in the example (thanks to whoever wrote that!). Only thing I don’t like is that instead of being able to ~pan.set(\lfo, \sin), or whatever, I have to free the current running Synth, and call a new one ~pan[1].release; ~pan[0] = Synth(pan_sin) but it’s not a big deal, I think I just need to get over it :slight_smile:

I’m also considering breaking apart the SynthDef, and not thinking about a SynthDef as a complete object, and use several SynthDefs that will act as building blocks to make up the thing, in this case the panner.

That’ll open up things like using the same LFO for several different units…

Anyway, @poison0ak, couldn’t you maybe remove the sig2 variable entirely, and then on the client side call 24 instantiations of the Synth?

(
SynthDef(\drone, {
	arg freq = 30, out = 0, index;
	var freqosc, mulosc, sig;
	
	freqosc = LFNoise2.ar(0.1).range(-2,2);
	
	mulosc = LFNoise2.ar(0.5) * (Line.ar(0,0.1,99.rand)/(index*0.2));
	
	sig = SinOsc.ar(freq * index + freqosc, 0, mulosc);
	
	Out.ar(out, sig ! 2);
}).add;
)

(
24.do({ |i|
	a[i] = Synth(\drone, [\freq, 30, \index, (i + 1)]);
});
)

(
24.do({ |i|
	a[i].free;
});
)