Multichannel audio file rotation inside a multi-speaker ring

Hi guys,
I’m facing something with my installation I’m not able to solve yet.
I have an 8 channel .wav file which I’m playing through a simple synth on 8 speaker audio setup (speakers are placed equidistant on a 8 meters diameter ring).

Now I need to slightly rotate the perspective so that I will no longer have direct correspondence between the individual audio channel and the individual speaker but I would like the audio channels of the audio file to move in a rotative fashion along the ring keeping the correct spacing and balance between the different channels.

With a mono channel I’ve always used the PanAz or the VBAP/VBAPSpeakerArray but now I don’t know the correct object which will do for me.

I’ve given the SplayAz a try (I was thinking of using it changing the rotation or maybe the center parameters) but unfortunately it seen too behave differently respect what I’m expecting.

Is there an object which I can use to keep this process simple? Maybe an plugin or a quark? Do you think I can obtain such a result with a particular use of SplayAz?

1 Like

What about just using 8 PanAz’s?

(
Ndef(\eight, {
	var sig, rotation;
	
	// input
	sig = 8.collect {
		WhiteNoise.ar * Decay.kr(Dust.kr(0.5), 2)
	};
	
	// rotate
	rotation = MouseX.kr(-1, 1);
	sig = sig.sum {
		|chan, i|
		PanAz.ar(8, chan, rotation + (2 * i / 8));
	};
	
}).play.scope;
)
1 Like

Thank you @scztt,
actually I was thinking to a similar solution. The fact is that is seems so strange to me that SC doesn’t have a tool to do such an operation.

However it remains a tricky part: how to extract individual mono signals from a multichannel buffer (using PlayBuf).

For example: this is the original synth (without the rotation stuff).

(
SynthDef(\multichannelRotationWannabe, {
	|
	amp=0.9,
	out=0,
	gate=0,
	loop=1,
	bufnum,
	|
	var sig, env;
	env = EnvGen.ar(Env.asr(0.0, 1, 0.0), gate:gate, doneAction:0);
	sig = PlayBuf.ar(8, bufnum, BufRateScale.ir(bufnum), trigger:gate, loop:loop);
	sig = sig * env * amp;
	Out.ar(out, sig);
}).add;
)

How can I modify it to feed 8 individual PanAz?
Thank you so much…

How about sending the playbuf output to audio busses and then panning each bus. Something like this (the code is just to reflect the idea, not actual code):

~busses = Array.fill(6, {Bus.audio(s)});
Ndef(\playbuf, { 
	Out.ar(
		~busses[0], 
		PlayBuf.ar(8, bufnum, BufRateScale.ir(bufnum), trigger:gate, loop:loop)
	);
});
Ndef(\out, {
	Out.ar(LFSaw.kr(0.1).range(0, 8), In.ar(~busses[0]));
	Out.ar(LFSaw.kr(0.1, 1/8).range(0, 8), In.ar(~busses[1]));
	Out.ar(LFSaw.kr(0.1, 2/8).range(0, 8), In.ar(~busses[2]));
	Out.ar(LFSaw.kr(0.1, 3/8).range(0, 8), In.ar(~busses[3]));
	Out.ar(LFSaw.kr(0.1, 4/8).range(0, 8), In.ar(~busses[4]));
	Out.ar(LFSaw.kr(0.1, 5/8).range(0, 8), In.ar(~busses[5]));
	Out.ar(LFSaw.kr(0.1, 6/8).range(0, 8), In.ar(~busses[6]));
	Out.ar(LFSaw.kr(0.1, 7/8).range(0, 8), In.ar(~busses[7]));
};

I tried somehting similar and it worked.

There is: XFadeRotate (wslib)

1 Like

In your example code, the sig you get from PlayBuf is just an array of 8 channels - identical to the sig in my example. You should be able to apply the same technique: loop over each channel, pan it using PanAz, and then sum all the results.

1 Like

thank you @dkmayer, the problem is that the class is missing documentation :frowning:
I will give it a try.

No, it’s there, but not in the new help system, see the wslib folder (probably in your downloaded-quarks), XFadeRotate.help.rtf

1 Like

In this case, I think there is no dedicated class for it because it’s fairly easy to implement with existing classes: as scztt said, take each individual channel, pan it independently to the angle you want, and sum the 8 PanAz’s.

(FWIW I might disagree slightly with the recommendation to install wslib. There are many useful things in it, but the downside: it’s a very large, monolithic library and there is no way to install only the parts of it that you want, so you also get a lot of cruft. I’ve been tripped up sometimes by taking code into the classroom that I thought was standard but which accidentally relied on something in wslib.)

Not tricky at all – actually this is one of the genius things in SC: multichannel signals are much easier to handle than, say, in dataflow environments where you have to draw a lot of parallel patch cables.

Multichannel signals are represented as arrays.

Pan2.ar(SinOsc.ar, 0)
-> [ an OutputProxy, an OutputProxy ]

PanAz.ar(8, SinOsc.ar, 0);
-> [ an OutputProxy, an OutputProxy, an OutputProxy, an OutputProxy, an OutputProxy, an OutputProxy, an OutputProxy, an OutputProxy ]

You can index into arrays:

p = PanAz.ar(8, SinOsc.ar, 0);
p[0]  // first output channel only

It’s just an array – so you can also .collect over it:

var source = PlayBuf.ar(8, ...);
var rotated = source.collect({ |channel, index|
	// (2/8) -- 2 / numChannels gives you the 'pos' for one channel's width
	PanAz.ar(8, channel, pos: rotateAngle + ((2/8) * index))
}).sum;

And some magic… PlayBuf output (being just an array) is also valid for multichannel expansion:

var source = PlayBuf.ar(8, ...);
var rotated = PanAz.ar(8, source, pos: rotateAngle + ((2/8) * (0..7))).sum;

So I think there’s not really a strong need for a dedicated class.

hjh

1 Like

Thanks to you all @scztt, @loopier, @dkmayer, @jamshark70!
Thank you for your interest in my question and your quick support: I was able to solve using the 8x PanAz solution.

2 Likes