Generalize function to n-channels

hey, this is loosely connected to this thread. I would like to generalize the following function for n-channels. Does somebody has an idea how to go about that? thanks.

(
var channelMask = { |trig, numChannels, channelMask, centerMask|
	var rate = if(trig.rate == \audio, \ar, \kr);
	var channelsArray = Select.kr(numChannels, [

		// 2-channels
		[
			Dser([-1], channelMask),
			Dser([1], channelMask),
			Dser([0], centerMask)
		],

		// 3-channels
		[
			Dser([-0.33], channelMask),
			Dser([0.33], channelMask),
			Dser([1], channelMask),
			Dser([0], centerMask)
		],

		// 4-channels
		[
			Dser([-0.25], channelMask),
			Dser([0.25], channelMask),
			Dser([0.75], channelMask),
			Dser([-0.75], channelMask),
			Dser([0], centerMask)
		],

		// 8-channels
		[
			Dser([-0.12], channelMask),
			Dser([0.12], channelMask),
			Dser([0.37], channelMask),
			Dser([0.63], channelMask),
			Dser([0.88], channelMask),
			Dser([-0.88], channelMask),
			Dser([-0.63], channelMask),
			Dser([-0.37], channelMask),
			Dser([0], centerMask)
		]
	]);
	Demand.perform(rate, trig, 0, Dseq(channelsArray, inf)).lag(0.001);
};
)

I’m a little bit curious what is the stumbling block for you…? What I mean is – you constructed these numeric series based on some principle, so clearly you had a rule in mind.

In any case, it looks like an arithmetic series (+), where the step size is 2/n, and the initial offset is -1/n, and the values are wrapped between -1.0 and +1.0.

But, your two-channel example does not follow this rule – for some reason not known to me, for n = 2, both the step size and offset are doubled. If you followed the rule consistently, two channels would be -0.5, +0.5 (plus the special case 0 added at the end of all of your samples). Note, though, these all look like PanAz-style pan positions, rotating in a circle; if so, then -1 and +1 would be the same position, so I think your n = 2 is almost certainly a mistake (which is a problem you can run into if you’re just feeling your way through the numbers, instead of abstracting out the principles on your own).

hjh

initially ive got these from the synthesis part of the nuPG.
Via the github link you find several Ndefs with different panning setups for different channel configurations for TGrains, GrainBuf, PlayBuf so it makes sense that the two channel version should be similiar to Pan2 and the n-channel version > 2 channels should be similiar to PanAz according to the helpfile of TGrains.
However i think its nice to generalize the function for the panning instead of hardcoding it for each SynthDef.
multichannel expansion for panning is quite new to me so i thought i would start from there.
Never worked with Panning other than Pan2 before, also investigating the Ambisonics Toolkit right now.
thanks for decoding the formula i will try to implement it but not sure right now if im able to do so.

my idea would be to pass maxOverlap to channelMask (as the number of channels beeing used for panning) and then you can decide if you would sum them to stereo or have them played via a multiple speaker setup.

OK.

Arithmetic series: Array.series or Pseries. Even if these methods/classes didn’t exist, it’s very easy to generate a series by initializing a variable to the starting value, and for each iteration, add the step size.

Array.series(size, start, step)
Pseries(start, step, length)

You know the size or length: numChannels.

From my last post, you also know the starting value = -1/n, and the step size = 2/n. That’s all the parameters required to generate an arithmetic series.

Soooooo… drum rollArray.series(numChannels, -1 / numChannels, 2 / numChannels).

“and the values are wrapped between -1.0 and +1.0”… Array.series(numChannels, -1 / numChannels, 2 / numChannels).wrap(-1.0, 1.0).

Let’s try it.

(
f = { |numChannels = 2|
	Array.series(numChannels, -1 / numChannels, 2 / numChannels).wrap(-1.0, 1.0)
};
)

f.(3)  // -0.33, 0.33, -1 but in PanAz, -1 pans the same as +1
f.(4)  // -0.25, 0.25, 0.75, -0.75
f.(8)  // OK

So that gives you the numbers inside Dser.

How do you put them inside Dser? “I have an array, and I want to do the same thing to every array element and collect the results” = collect. That is, collect is a general way to transform one array’s contents into something else.

Array.series(numChannels, -1 / numChannels, 2 / numChannels)
.wrap(-1.0, 1.0)
.collect { |pan| Dser([pan], channelMask) }

This will give you an array with “numChannels” Dser objects. Then you also have a center channel, which needs to be appended to the end of this array, using ++.

Array.series(numChannels, -1 / numChannels, 2 / numChannels)
.wrap(-1.0, 1.0)
.collect { |pan| Dser([pan], channelMask) }
++ Dser([0], centerMask)

OK, fair enough.

So in the special case of numChannels == 2, you would multiply by 2 (-0.5 * 2 == -1.0, etc.).

numChannels |==| 2 is 1.0 if true, 0.0 if false.

We need a multiplication factor that is 2.0 if true, 1.0 if false.

Conveniently, we could simply add 1.

(Array.series(numChannels, -1 / numChannels, 2 / numChannels)
.wrap(-1.0, 1.0)
.collect { |pan| Dser([pan], channelMask) }
* ((numChannels |==| 2) + 1)
) ++ Dser([0], centerMask)

hjh

1 Like

thank you very much for the detailed explanation.

if i run:

(
x = { |numChannels|
	(numChannels |==| 2) + 1;
};
x.(2);
)

i get ERROR: Message '+' not understood.

(
var channelMask = { |trig, numChannels, channelMask, centerMask|
	var rate = if(trig.rate == \audio, \ar, \kr);
	var panChannels = Array.series(numChannels, -1 / numChannels, 2 / numChannels).wrap(-1.0, 1.0);
	var panPositions = panChannels.collect { |pan| Dser([pan], channelMask) };
	//panPositions = panPositions * ((numChannels |==| 2) + 1);
	panPositions = panPositions ++ Dser([0], centerMask);
	Demand.perform(rate, trig, 0, Dseq(panPositions, inf)).lag(0.001);
};
channelMask.(trig: Impulse.ar(0), numChannels: 2, channelMask: 1, centerMask: 1);
)

It looked like numChannels would be a SynthDef argument. If so, it will work.

If not, then it would be ((numChannels == 2).asInteger + 1)… did you notice that it’s a Boolean? If you did, then the question would be, how to get a number from a Boolean where 1 is for true and 0 is for false, which is no different from what I had laid out earlier… for which there is a method, as it happens (asInteger).

At this point (again), I’m going to have to say, I’m stretched a little thin and I’m not in a position to finish this for you. I have too many work demands. There is enough information here, I think, for you to take it the rest of the way.

hjh

thanks alot for your help. didnt know about .asInteger. Im able to finish it by myself.