Arrays as arguments to SynthDef?

Can I pass an Array() into a SynthDef instance as an argument? SC doesn’t seem to like it when I try:

(
SynthDef(\sino, { arg l = [120, 800, 120];
	var sin, e, eg;

	e = Env.new(l, [1, 1]);
	eg = EnvGen.kr(e, 1, 1, 0, 1, 2);
	sin = SinOsc.ar(eg, 0, 1, 0);

	Out.ar(0, Pan2.ar(sin, 0, 0.1));

}).add
)

b = Synth(\sino);
b.set(\l, [120, 800, 120]);

b.free;

It’s not supported to use a regular array in this way. This is documented somewhere but I forget where.

The SynthDef argument default may be a literal array #[... numbers...]. The # is not optional in this context.

Or, define the input as a NamedControl.

hjh

3 Likes

https://doc.sccode.org/Classes/SynthDef.html#Literal%20Array%20Arguments

2 Likes

@scztt also wrote this supernice post about passing envs to synths and patterns:

1 Like

BTW: miSCellaneous_lib quark also contains a tutorial “Event Patterns and array args”

1 Like

wondering if there’s a straightforward way to make large literal arrays? #(1…100) doesn’t work…

String hackery is possible:

g = { |a| ("#" ++ a).asString }

(
(
"{ 
	arg freq = " ++ g.([400, 500]) ++ "; 
	SinOsc.ar(freq, 0, 0.1) 
}.play"
).interpret
)

Similarily you could also use the preProcessor, but I suppose these workarounds don’t pay as it’s easier to use NamedControl.

is this dynamic or relegated to the size defined by arg ?
I’ll do some testing. Also, if you can comment on the purported size limit mentioned by @semiquaver below I would be appreciative! I will also look into NamedControl.

Thank you James!

For the large array case, just use NamedControl.

The only benefit to a literal array here is convenience of writing. Naturally this benefit obtains only for small arrays.

If the array is large, or if its size/contents need to vary according to the result of some expression, then the benefit of a literal array disappears and there’s no longer any point in trying to force the use case into that model. At that point: save yourself the trouble and use NamedControl.

hjh

1 Like

Or would NamedControl help for arrays ?

In my case I’m stuck with the original argument size.

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

SynthDef(\testArray,{
	arg points=#[111,222,333,444,555,666,777];
	points=points.multichannelExpandRef(1);
	points.size.poll; // always 7
	points[2].poll; 
	points[5].poll; // returns the default value if not provided in the arg
}).add;
)

z=Synth(\testArray,args: [\points,~qp1.asRef]);

We’re all and always ! :slight_smile:
There’s no way to circumvent this, when wanting dynamic length array input you have to decide for a max size first in your synthdef. Then you’re free to pass smaller arrays and handle them appropriately. This appropriate handling can be done in different ways

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

SynthDef(\testArray_1, {
	arg size = 7, points = #[111, 222, 333, 444, 555, 666, 777];
	// indicator is a multichannel *UGen*
	var indicator = { |i| i < size } ! points.size;
	(indicator * points).poll; 
}).add;
)

// compare

z = Synth(\testArray_1, args: [\points, ~qp1]);

z = Synth(\testArray_1, args: [\points, ~qp1, \size, ~qp1.size]);


// alternatively - then 'size' and indicator in the synthdef isn't needed

z = Synth(\testArray_1, args: [\points, ~qp1.copy.extend(7, 0)]);

Of course it’s possible to fill/extend with numbers different from 0.

NamedControl doesn’t change anything about the fixed array size limitation, it only makes it easier to write large array args.

And as my goal was to have a multidimensional arrays… :-/
Anything to look at the level of the buffers ?

I tried this, but I fail to retrieve the buffer content (b.get is asynchronous, and I wished I could make it synchronous):

b = Buffer.alloc(s,~qp1.size)
b.setn(0, ~qp1);

(
SynthDef(\testBuf,{
	var trig=Impulse.kr(0.5);
	var idx=TIRand.kr(0,b.numFrames-1,trig);
	var pos=0;
	b.get(idx,{|p| pos=p});
	Poll.kr(trig,b.numFrames,"\n----\nSize");
	Poll.kr(trig,idx,"\nIndex");
	Poll.kr(trig,pos,"\nCue point");
}).add;
)

z=Synth(\testBuf);

You’d have to translate your concept of multidimensional structuring – which we don’t know yet – into a one-dimensional array. This doesn’t seem impossible to me.

As you noticed, this is a no-go, BufRd should do.

I’m (trying to) build a multiple PlayBuf SynthDef, talking an arbitrary number of buffers as input and for each fo them a list of cue points. The synth would choose at random a buffer, read it from/to at-random-choosen start and end cue points. Upon reaching the end cue point, choose another buffer, and so on, until release.

This could be achieved with patterns, but I don’t like to mix in my patterns the the musicals considerations and the “non-musical” ones (i.e. the technical stuff such as the management of the buffer looping, cue points, …) .

I’ll have a try to flat those arrays and to use BufRd for reading them.
Or to build on-the-fly the SynhtDef around a general function

This sounds all doable. I think it needs splitting the task: choosing the data structure in which Buffers and cue points are stored and writing the SynthDef for switching (also see James’ general remarks in this parallel thread Parsing array into interpretable string for genetic algorithm, it perfectly applies here too).

I would approach it like this: audio Buffers can be stored in an Array. Cue points (I assume you have several start and end points per Buffer) can also be stored in Buffers. These cue point buffers (one cue point buffer per audio buffer) would also be stored in an Array. Cue point numbers per Buffer could be stored in a separate buffer or in the cue point Buffers themselves.
By doing so there is no need to use array arguments at all, you can define the first Buffer index and the number of Buffers (analogously for cue point Buffers) as SynthDef args. Choice is done with TIRand:

// Array of Buffers
~buffers = [ ... ] 

~bufOffset = ~buffers[0].bufNum

~numBuffers = ~buffers.size

// Array of Buffers
~cuePoints = [ ... ] 

~cuePointOffset = ~cuePoints[0].bufNum

// numbers of sections to choose from per Buffer, could also be stored in cuePoint Buffers
~cuePointPairNums = Buffer(...)



SynthDef(..., { |bufOffset, numBuffers, cuePointOffset, ( cuePointPairNums ) ...| 
 	...
})

The next task would be writing the SynthDef for switching. If you search the forum for PlayBufCF several threads with related topics are popping up. If you don’t want to use PlayBufCF see James’ SynthDef as a template: How to get rid of popping sounds when jumping around Playbuf Basically you’d just have to toggle between two PlayBufs (or BufRds) with crossfade (e.g. with SelectX)

Great ! In the big lines, this is what I had in mind above. So it confirms it. It is working pretty fine.
Only one thing is when putting the data in the buffers. The Buffer.alloc seems to take some time to complete. And so a buf.setn(...) just after the Buffer.alloc doesn’t work. The buffer stays with its default value. I tried with the completionMessage, but couldn’t make it work.

I went to that ugly/awful workaround: a waiting loop… :grimacing:
Any better way to manage the time needed by the Buffer.alloc to complete ?

(
~makeBufs = {
	arg bufArray, cueArrays;
	var bufBuf, cueBuf;
	var bnums=bufArray.collect({|b| b.bufnum;});
	var csizes=cueArrays.collect({|c| c.size;});
	var flat=cueArrays.flat;

	bufBuf=Buffer.alloc(s,bufArray.size*2);
	1000000.do({|i| var x=i+1;}); // wait a little bit
	// bufBuf.updateInfo; // does not work
	bufBuf.setn(0,bnums); // too soon ?
	bufBuf.setn(bufArray.size,csizes);

	cueBuf=Buffer.alloc(s,flat.size);
	1000000.do({|i| var x=i+1;}); // wait a little bit
	cueBuf.setn(0,flat);

	[bufBuf,cueBuf];
};
)

http://doc.sccode.org/Classes/Server.html#-sync

Which is used in many places in the help:

./Guides/Bundled-Messages.schelp:51: s.sync; // wait until synthdef is loaded
./Guides/standalones.schelp:253: // s.sync; // e.g. when loading buffers
./Tutorials/A-Practical-Guide/PG_Cookbook05_Using_Samples.schelp:192: s.sync;
./Tutorials/A-Practical-Guide/PG_Cookbook05_Using_Samples.schelp:273: s.sync;
./Classes/PartConv.schelp:62: s.sync;
./Classes/PartConv.schelp:71: s.sync;
./Classes/Signal.schelp:72: s.sync;
./Classes/Wavetable.schelp:72: s.sync;
./Classes/Server.schelp:478: s.sync; // wait until load is done and then send the rest of the bundle
./Classes/Server.schelp:492: s.sync;
./Classes/Server.schelp:751: s.sync ( c );
./Classes/Synth.schelp:239: s.sync;

EDIT: Also, you can allocate multiple buffers and do one s.sync for all of them.

hjh

2 Likes

But not in the Buffer help :innocent:
Thanks. I worth also noting that the s.sync requires to be executed in a Routine, which he is not explained.