Passing an array of symbols to select events

(
var modulators = { |modFreq|
	var sin = SinOsc.ar(modFreq);
	var saw = LFSaw.ar(modFreq);
	var lato = LatoocarfianC.ar(
		freq: modFreq,
		a: LFNoise2.kr(2, 1.5, 1.5),
		b: LFNoise2.kr(2, 1.5, 1.5),
		c: LFNoise2.kr(2, 0.5, 1.5),
		d: LFNoise2.kr(2, 0.5, 1.5)
	);
	var heno = HenonC.ar(
		freq: modFreq,
		a: LFNoise2.kr(1, 0.2, 1.2),
		b: LFNoise2.kr(1, 0.15, 0.15),
	);
	var gendy = Gendy3.ar(
		ampdist: 6,
		durdist: 3,
		adparam: 0.9,
		ddparam: 0.9,
		freq: modFreq,
		ampscale: 1
	);
	(sin: sin, saw: saw, lato: lato, heno: heno, gendy: gendy);
};

var modulatorMatrix = { |modSelect, modFreq, min, max, curve, lag, modOn|
	var numModulators = modSelect.size;
	var matrix = numModulators.collect { |i|
		var mod = modulators.(modFreq[i])[modSelect[i]];
		mod.lincurve(-1, 1, min[i], max[i], curve[i]).lag(lag[i]);
	};
	matrix = matrix.product;
	Select.ar(modOn, [K2A.ar(1), matrix]);
};

SynthDef(\test, {
	
	var modMatrix = modulatorMatrix.(
		modSelect: [\sin, \heno],
		modFreq: [1, 0.5],
		min: [1, 0.1],
		max: [5, 1],
		curve: [4.0, 4.0],
		lag: [0, 0.2],
		modOn: \modMatrixOn.kr(1)
	);
	var freq = \freq.kr(220) * modMatrix;
	var sig = Saw.ar(freq);

	sig = sig !2 * 0.1;

	Out.ar(\out.kr(0), sig);
}).add;
)

Synth(\test);

is it possible to pass an array of symbols to select events like [\sin, \heno] instead of passing an array of indices? or how would you go about selecting a pair of modulators? thanks

AFAICT in your code you already are passing an array of symbols. Rather than looping over modSelect.size, just loop over your list of symbols:

	var matrix = modSelect.collect { |modulatorSymbol, i|
		var mod = modulators.(modFreq[i])[modulatorSymbol];
		mod.lincurve(-1, 1, min[i], max[i], curve[i]).lag(lag[i]);
	};

Also, be aware of the flop method, which restructures a list like [[\sin, \heno], [1, 0.5]] into a list like [[\sin, 1], [\heno, 0.5]]. This is good for unpacking parameters that are expressed as arrays, as in your example code:

modulatorMatrix = {
	|modSelect, modFreq, min, max, curve, lag, modOn|
	
	modulators.(modFreq)[modSelect]
	  .lincurve(-1, 1, min, max, curve)
	  .lag(lag);
};

// and.....
// asPairs makes the event into an array
// flop splits it into multiple
// collect(_.asEvent) turns each back into an event, and 
// performWithEnvir uses the event keys as function arguments
(
modSelect: [\sin, \heno],
modFreq: [1, 0.5],
min: [1, 0.1],
max: [5, 1],
curve: [4.0, 4.0],
lag: [0, 0.2],
modOn: \modMatrixOn.kr(1)
).asPairs.flop.collect(_.asEvent).collect {
	|parameters|
	modulatorMatrix.performWithEnvir(\value, parameters)
}

Also, be careful of this ^^ type of construction.

In your example, modSelect has two elements. So:

  • i == 0: It calls modulators.() and gets 5 different modulators. One of these (sin) goes into the collect result. But… all 5 have already been created and entered into the SynthDef.

  • i == 1: It calls modulators.() again and gets 5 more modulators, of which one is chosen for the collect result. So now 10 modulators have been created and entered into the SynthDef.

After running your code block, I do:

c = SynthDescLib.at(\test).def.children;

c.count(_.isKindOf(HenonC))
-> 2  // but only one is used, one is wasting CPU cycles

c.count(_.isKindOf(Gendy3))
-> 2  // but NONE are used = both are wasting CPU cycles

SynthDef logic is different from language-side function logic. One way to think of it is that language-side functions (also patterns) “pull” values from the trunk of a tree (so, if a particular branch of the tree doesn’t get called, then it does nothing), while synth functions “push” from the top – everything runs, all the time. You probably expected [modSelect[i]] to “pull” only one of the modulators and delete the rest – but according to a “push” model, those modulators got created and they have to stay created (at is not enough to un-create them).

(SynthDef does have a “dead code elimination” pass, where PureUGens [no side effects] that are not connected to any output are removed after the fact. But 1/ Gendy3 for instance affects the state of the random number generator – this is a side effect, so it can’t be a PureUGen and 2/ PureUGen is inconsistenty applied [many filters should be PureUGens but they’re not].)

scztt’s solution doesn’t quite address this either – by passing n frequencies into the function, multichannel expansion will still produce n*5 modulators.

I guess I would restructure modulators to take the symbol as an argument, and produce only one mod. (Alternately, modulators could simply be a dictionary of functions, and then in the loop, you evaluate only one of the functions.)

var modulators = { |which = \sin, modFreq|
	switch(which)
	{ \sin } { SinOsc.ar(modFreq) }
	{ \saw } { LFSaw.ar(modFreq) }
	{ \lato } {
		LatoocarfianC.ar(
			freq: modFreq,
			a: LFNoise2.kr(2, 1.5, 1.5),
			b: LFNoise2.kr(2, 1.5, 1.5),
			c: LFNoise2.kr(2, 0.5, 1.5),
			d: LFNoise2.kr(2, 0.5, 1.5)
		)
	}
	{ \heno } {
		HenonC.ar(
			freq: modFreq,
			a: LFNoise2.kr(1, 0.2, 1.2),
			b: LFNoise2.kr(1, 0.15, 0.15),
		)
	}
	{ \gendy } {
		Gendy3.ar(
			ampdist: 6,
			durdist: 3,
			adparam: 0.9,
			ddparam: 0.9,
			freq: modFreq,
			ampscale: 1
		);
	};
};

var modulatorMatrix = { |modSelect, modFreq, min, max, curve, lag, modOn|
	// here, scztt is right:
	// "something.size.collect" will work but it's an extra step
	// you can just collect the symbol array directly
	var matrix = modSelect.collect { |which, i|
		var mod = modulators.(which, modFreq[i]);
		mod.lincurve(-1, 1, min[i], max[i], curve[i]).lag(lag[i]);
	};
	matrix = matrix.product;
	Select.ar(modOn, [K2A.ar(1), matrix]);
};

And then you get one HenonC and 0 Gendy3’s.

hjh

thank you very much for your suggestions. i thought there would be room for adjustment.
At first i thought of the possibilty to select different modulators via the Synth after SynthDef evaluation.
But maybe its fine how it is for now.

I’m not clear what you mean by adjustment…

If it is an issue with code style when selecting the modulators at SynthDef building time, a couple of adjustments have been suggested already.

If it’s an issue of selecting modulators at runtime, this is possible with numeric arguments. All of them run, but only the selected one(s) affect the result. (There has never been a way to pass a symbol as a synth argument value, so the idea of using a symbol for this is a non-starter. You can, however, have a translation table of symbol → integer index and use this in the synth arg list.)

hjh

1 Like

i meant using switch(which) in terms of effiency and also collecting the symbols directly. I meant not adjusting your code, these already have been the adjustments to my initial example. thanks alot.