Problems with variable literal array and NamedControl

As I read (https://supercollider.github.io/tutorials/user-faq) that literal array arguments are fixed in size at the point of SynthDef compilation, I’m trying out NameControls in my synth, so I can send different sized arrays in the event to feed into the Dseq ugen. Something is not going right as the default array declared in the SynthDef is limiting the size of the array passed to it via the event, and not replaced by it as I’d expect. Basically this example below is no different than just passing an arg. What am I doing wrong here?

thanks

(
SynthDef(\trill, { |rate=0.25, octave=0 |
	var notesAr = \notesAr.ir(#[0,0,0,0,0]).poll(1);
	var notes = Dseq(notesAr, inf);
	var seq = Duty.kr(rate, level: notes);
	var osc = LFTri.ar((seq + octave).midicps, mul: 0.1)!2;
	var env = EnvGen.kr(Env.linen(2, 4, 2), doneAction: 2);
	Out.ar(0, osc * env);
}).store;
)

(
~pat = Pdef(\p1, Pbind(
	\instrument, \trill,
	\notesAr, [[90, 86, 83, 79].mirror1],
	\dur, Pwhite(0.1, 3, 1)
).trace);
)
~pat.play;
~pat.stop;
1 Like

The important thing to note is that sizes of arrays in a SynthDef are always fixed. You can never extend, but you can define a max size and go up to it. This holds for multichannel signals, drate ugens etc.

In your example the SynthDef needs args for duration and size so that the divided duration can be calculated (or divided size could be calculated in the pattern alternatively). E.g.

(
SynthDef(\trill, { |out = 0, size = 5, dur = 1, amp = 0.1|
	var notesAr = \notesAr.ir(0!10);
	var notes, seq, osc, env;

	notes = Dser(notesAr, size);
	seq = Duty.ar(dur / size, level: Dser(notesAr, size));
	seq = Demand.ar(Impulse.ar(size / dur), 0, notes);
	osc = SinOsc.ar(seq.midicps.lag(0.005))!2;
	env = EnvGen.ar(Env([0, amp, amp, 0], [0.01, dur - 0.03, 0.01]), doneAction: 2);
	Out.ar(out, osc * env);
}).add;
)

(
~pat = Pbind(
	\dur, Pwhite(0.5, 1),
	\instrument, \trill,
	\notesAr, [[90, 86, 83, 79].mirror1],
	\size, Pkey(\notesAr).collect { |x| x[0].size },
	\tempo, 1,
	\amp, 0.1, 
	\out, 0
).trace.play;
)

WIth sequening the sequences for the Dseq/Dser mind the bracketing, e.g.

(
~pat = Pbind(
	\dur, Pwhite(0.5, 1),
	\instrument, \trill,
	\notesAr, Pseq([[[90, 86, 83, 79]], [[91, 89, 85, 82, 80, 75]]], inf).collect { |x| [x[0].mirror1] },
	\size, Pkey(\notesAr).collect { |x| x[0].size },
	\out, 0
).trace.play;
)

The miSCellaneous_lib quark contains the tutorial “Patterns and array args” with a number of such examples.

I missed Daniel’s answer before writing this… posting anyway.

So, on the one hand, A: “As I read, that literal array arguments are fixed in size at the point of SynthDef compilation”

And on the other hand, B: “the default array declared in the SynthDef is limiting the size of the array passed to it via the event, and not replaced by it as I’d expect.”

If array arguments are fixed in size, then how would one expect them to be replaced in synth messaging?

The answer is, of course, that they are not replaced, because they cannot be replaced. The SynthDef structure must be absolutely, 100% fixed and unchanging at the time of compiling it. You can write a new SynthDef with the same name and replace the old one, but you cannot do any form of Synth.new or aSynth.set or pattern synth messaging and expect the structure to change.

How is the array size related to structure? Because all of the array elements have to be connected to inputs of other units. If you have more or fewer array elements, then you need more or fewer connections. That’s a change in structure, so it’s illegal.

There is no difference between:

SynthDef(\name, { |arrayArg = #[0, 0, 0, 0, 0]|
	...
}).add;

and

SynthDef(\name, {
	var arrayArg = NamedControl.kr(\arrayArg, #[0, 0, 0, 0, 0]);
	...
}).add;

None. No difference. (Well, there is one difference – the function-arg syntax will fold the array into a long list of channels of one Control UGen, whereas NamedControl always creates a separate Control unit for itself. But that’s irrelevant to array sizes.)

Your best workaround, I think, is to pass another argument for the number of elements that you’re actually using.

(
SynthDef(\trill, { |rate = 0.25, octave = 0, arraySize = 1|
	// Changing to .kr
	// .ir doesn't change, so why poll it?
	// and why is it named notesAr? It's not .ar
	var notesAr = \notesAr.kr(#[0, 0, 0, 0, 0]);
	// Dseq acts like Pn; Dser acts like Pser
	var notes = Dseq([Dser(notesAr, arraySize)], inf);
	var seq = Duty.kr(rate, level: notes);
	// one octave == 12 midinotes
	var osc = LFTri.ar((seq + (octave * 12)).midicps, mul: 0.1)!2;
	var env = EnvGen.kr(Env.linen(2, 4, 2), doneAction: 2);
	Out.ar(0, osc * env);
}).add;  // 'store' litters your HD with .scsyndef files you might not need
)

a = Synth(\trill, [rate: 0.05, octave: 5, arraySize: 2, notesAr: [0, 2]]);
a.set(\arraySize, 4, \notesAr, [0, 4, 2, 5]);

hjh

Thanks chaps, that’s a big help.

This was the key for me; repeating the sequence indefinately