Very simple question about multiplying using arrays

I realise this is a very simple question, but I’m still confused by the problem.

I am not sure why in the following code I only hear two instances of Impulse.ar

(
SynthDef(\pulse, {
	| nadai = 3, amp = 0.2, out = 0 |
	var sig = Impulse.ar([3,4,5]);
	var panned = Pan2.ar(sig, 0);
    Out.ar(out, sig * amp);
}).add;
)

a = Synth(\pulse);

I realise that the version with 3 goes to channel 0, the one with 4 channel 1, and there 5 goes to channel 2, but this is not what I intend. I want to be able to pass an array into the SynthDef and have all play simultaneously and for the Pan2 to place them in the centre of the panning field.

Is it possible to use arrays in this way with SynthDefs, or have I got the wrong end of the stick?

Many thanks for any assistance!

Have a look at this post, it’s exactly the same issue you have. Ask again if you can’t figure looking at it?

Suggestion: Pan2 should post a warning when it is passed an array of inputs!

.

Maybe, but there’s nothing wrong with passing an array to Pan2. You just have to remember to sum the results to avoid passing an array of arrays to the Out. Perhaps then Out should post a warning when it is passed an array of arrays?

1 Like

yes I think that is the way - either warn or automatically flatten…

I think it should error out and do nothing. What does it mean to play, record, or scope a nested array?

1 Like

Hmmm, I don’t know, but I think if I were performing live I would want it to play something, even if it’s not what I intended, and post a warning so that I can correct it.

Problem being it spills into other busses, would you want it to spill into some feedback process?

Of course it depends on the situation. In your case I would probably want an error, but in some cases I would rather play the wrong thing than nothing at all, and I feel like SC typically errs in that direction. We are getting off topic though, so maybe this deserves a separate thread.

offtopic: this might be an edge case or my implementation completely wrong but if you want to have an individual pan position per trigger for an multichannel signal, you have to pass a multichannel signal and a corresponding multichannel pan positions array to the panner and sum them afterwards as far as i know.

(
var timingInformation = { |numChannels, trig, grainFreq|
	var rate = if(trig.rate == \audio, \ar, \kr);
	var arrayOfTrigsAndPhases = numChannels.collect{ |i|
		var localTrig = PulseDivider.perform(rate, trig, numChannels, i);
		var hasTriggered = PulseCount.perform(rate, localTrig) > 0;
		var localPhase = Sweep.perform(rate, localTrig, grainFreq * hasTriggered);
		[localTrig, localPhase];
	};
	var trigsAndPhasesArray = arrayOfTrigsAndPhases.flop;
	(\trigger: trigsAndPhasesArray[0], \phase: trigsAndPhasesArray[1]);
};

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 { |pos| Dser([pos], channelMask) };
	panPositions = panPositions ++ Dser([0], centerMask);
	Demand.perform(rate, trig, 0, Dseq(panPositions, inf)).lag(0.001);
};

var hanningWindow = { |phase|
	(1 - (phase * 2pi).cos) / 2 * (phase < 1);
};

SynthDef(\pulsar, {

	var numChannels = 5;

	var tFreq = \tFreq.kr(5);
	var trig = Impulse.ar(tFreq);
	var grainFreq = \freq.kr(440);

	var timings = timingInformation.(numChannels, trig, grainFreq);
	var chanMask = channelMask.(timings.trigger, numChannels - 1, \channelMask.kr(1), \centerMask.kr(1));

	var grainWindow = hanningWindow.(timings.phase);

	var sig = sin(timings.phase * 2pi);

	sig = sig * grainWindow;

	sig = PanAz.ar(2, sig, chanMask * \panMax.kr(0.8));
	sig = sig.sum;

	sig = sig * \amp.kr(0.25);

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

x = Synth(\pulsar);

x.free;

Did you figure out the issue, this is one such solution:
var sig = Impulse.ar([3,4,5]).sum;

1 Like

Hi Jordan,

Thanks for both your helpful responses - and I’m glad it sparked an additional conversation. I’m reading and re-reading the thread you sent throughout the day. I know what the solution is I just want to make sure I fully understand why, and what the language is actually doing. There are some very good examples in the thread.

Pretty sure I get it.

Put simply: Pan2 cannot receive an array, therefore it needs to be summed into a mono signal before Pan2 can use it?

Obviously more to it than that, and I’m trying to work through those details.

Many thanks to all who contributed to the thread.

1 Like

Of this idea that Pan2 “shouldn’t” get an array: What if I want n detuned oscillators, spread across the stereo field?

// inside a SynthDef
var n = 7;  // or more, or fewer
var detunes = Array.fill(n, { detun ** Rand(-1.0, 1.0) });
var oscs = Saw.ar(freq * detunes);
var panned = Pan2.ar(oscs, Array.series(n, -1.0, (n-1).reciprocal));
var mixed = panned.sum;

It’s ok to mix down before panning, and it’s ok to pan multiple signals separately and then mix down the panned results. What doesn’t work is never mixing them down at all. (And, “Pan2 cannot receive an array” is an incorrect inference, not true at all.)

So I wouldn’t agree with Pan2 issuing a warning in this context. (This is one reason why SC doesn’t warn about many pitfalls – because it’s very hard in advance to predict that nobody will ever find a legitimate use for some coding pattern. Here, I really would rather not be forced to write a collect to pan each oscillator just because somebody put an arbitrary limit on Pan2.)

hjh

Completely agree.
What I was suggesting was that Out.ar shouldn’t accept nested arrays.

1 Like

Definitely a warning if Out receives an array! This would save everyone some time on the learning curve (certainly would have saved me some!)

…but maybe the current playback behavior should continue for backward compatibility?