Argument for multichannel expansion in a SynthDef

hey, im aware of this thread: Problems with variable literal array and NamedControl - #3 by jamshark70

whats a good way to use an argument / variable for multichannel expansion in a SynthDef.
for example: { something } ! variable;

when i for example know the range could be between 1 and 20.
I thought of having an Array with 20 items \myArray.kr(#[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]);
and then should i use Demand Ugens for picking the right amount of numbers, or whats the right way to go about that?

thanks.

This one definitely belongs in the top 10 commonly-asked SC questions.

… and the key part of the answer is that SynthDefs cannot create or destroy elements dynamically. So a SynthDef has to be more like Max/MSP or Pd, in that all of the elements must exist upfront. (In Pd, you would [clone] an abstraction a given number of times, and [switch~] individual instances on and off as needed.)

Instead of creating and destroying them, you can dynamically suppress some of the elements’ output.

Exactly how you do that depends on the context.

Usually, the context for multichannel expansion is a parallel signal chain, in which a structure (e.g. oscillator → filter) is duplicated and mixed down, for a richer sound. In this case, “suppressing” an output happens in the mixdown stage – multiplying by 0. Here, there’s one “gate” per channel, which is 1 if the channel should sound and 0 if not.

(
var maxHarmonics = 30;

a = { |n = 1, freq = 220|
	var harmonics = NamedControl.kr(\harmonics,
		Array.fill(maxHarmonics, { |i| i + 1 })
	);
	var gates = n >= (1 .. maxHarmonics);
	
	((SinOsc.ar(freq * harmonics) * Lag.kr(gates, 0.1))
	.sum * 0.1
	).dup
}.play;
)

(
c = Bus.control(s, 1);
b = { MouseX.kr(0, 30) }.play(outbus: c);
a.map(\n, c);
)

The thread that you referenced was about demand-rate sequencing. This is a completely different context, so you should not expect a sequencing solution to apply to parallel-chain mixdown. (Just because “there’s a thread about dynamic arrays and it uses Demand units” does not mean that Demand is applicable to every case involving dynamic sizing.)

hjh

yes, ive seen the difference to my question of multichannel expansion with dynamic arrays.
I just wanted to point out that i was trying to come up with a solutuon on my own.

thanks for the clarification :slight_smile:
i will try to implement your example.

Thanks for posting this. I’d been scratching my head how to dynamically change number of harmonics in an additive synthesizer using an argument!

But I’m still scratching my head about one thing here as i clearly don’t get something that’s going on here (still learning here!).

Why does the following (simplified version of your code) of work when n is specified as argument:

(
a = { |n = 5, freq = 220|
	var harmonics, gates;
	harmonics = [1,2,3,4,5];
	gates = n >= harmonics;
	((SinOsc.ar(freq * harmonics) * gates).sum * 0.1).dup
}.play;
)

yet if i replace n directly with a number then it fails:

(
a = { |n = 5, freq = 220|
	var harmonics, gates;
	harmonics = [1,2,3,4,5];
	gates = 5 >= harmonics;
	((SinOsc.ar(freq * harmonics) * gates).sum * 0.1).dup
}.play;
)

The subtle difference here is that in the first case, after compilation, the n results in a Control ugen as it was defined as a Function argument, thus the comparison also results in a ugen resp. an array of comparison ugens. In the second case, both parts of the comparison are non-ugens and the result (polymorphism !) is an Array of Booleans, a pure language thing.
These differences are very hard to spot and easy to overlook. You can check the miSCellaneous_lib quark’s tutorial “Patterns and array args” which treats related examples, also without Patterns.

Thank you so much!
I’ve enjoyed reading through the examples in the miSCellaneous_lib tutorials. What a great resource!

There is a nuance here that I’m confused about though. If the second (failing) version results in an array of booleans (language), what exactly is the first one (successful version) resulting in?

Specifically, what will gates be when

gates = n >= harmonics;

is evaluated?

Is gates an array of ugens or an array of integers (is there a difference)? So the following is a success but I’m still unsure what * gates IS specifically.

a = {var harmonics, gates;
	harmonics = (1..5);
	gates = Control.ir(4) >= harmonics;
	((SinOsc.ar(220 * harmonics) * gates).sum * 0.1).dup
}.play;

If you have a UGen, and you perform a Boolean operation on it, the result must be a UGen. In the server, the UGen’s signal will be 1 when the condition is true, and 0 when it’s false.

This matches C language behavior (false = 0, true = 1).

It also matches the behavior of analog modular comparators, where true = high voltage, false = 0V.

hjh

You can also check with poll:

a  = { var harmonics, gates;
	harmonics = (1..5);
	gates = NamedControl.kr(\n, 4) >= harmonics;
	gates.poll;
	((SinOsc.ar(220 * harmonics) * gates).sum * 0.1).dup
}.play;

a.set(\n, 2)

I think this where I’ve been a little unclear (perhaps more clear now, thanks!). Fundamentally i am confused about the usage and understanding of the term Ugen in these contexts.

When you say “the result must be a UGen” (and so gate is an array of UGens as confirmed by polling as per @dkmayer’s suggestion… thanks!) you are not describing any specific unit generator (it is not a type of UGen like SinOsc, which i guess was my question as to “what gates IS”). It is rather a control value (which according to the poll is indeed a ugen). But (as per the Help definition) UGens represent calculations with signals_and are used to generate or process both audio and control signals. But now we are using the term UGen in both its signal genration context AND as the control value outcome of a process.

I hope my confusion makes sense… i’ve no C programming experience and definitive explanations on these areas are hard to find (and are not really covered the more introductory materials I’ve been studying).

Control values are simply slow signals (different sample rate). It would be better to refer to them as control signals, rather than values.

Yes, but are control signals also Ugens?

UGens are presented in much of the literature as the things that generate signals. But then in the above example the control signals appear to be also UGens. This is where I am confused. In @jamshark70’s modular analogy an oscillator generates voltage but the voltage is the result of the oscillator and not a subset of the type oscillator.

What went maybe a bit under in the discussion so far is the existence of the class BinaryOpUGen.
The polymorphism characteristic of SC lang operators (like *, / etc.) I mentioned means that in one case the result might be a number and in other cases – like here, as one component is already a UGen – it is a BinaryOpUGen (still a SC lang object), which then, after compilation is the thing in the synthdef structure that performs the operation per kr or ar sample, here the >= operation with result 0 or 1.

1 Like