# Simple spatialisation: function, SynthDef, pseudo-UGen, or UGen?

Hi,

I’m trying to write a helper function (in my mind it’s a modified version of Pan/Out UGen) and I thought more experienced coders could be more quick with advice than me spending a lot of time experimenting (at least this time)…

So my problem is that I want to be able to send a sound to certain “point in space” that contains 8 speakers (4 on one wall and 4 on the other). by specifing X,Y of the ‘source sound’ I would calculate distance of that point in space from all 8 speakers and from this calculate amplification for each speaker.

I started to write a SynthDef for that, but I think I cannot really just plug another sound as an argument into that, as if I would send it to another UGen. My idea was to simply write something like `OctoPanOut.ar(0, sound, x, y)` at the end of a SynthDef.

Thinking this further I was thinking to simply write a function that would calculate values to send to a Out.ar ugen, but then I’m not sure if it would be better to write a pseudo-UGen. (i have a feeling writing an UGen would not be necessary for such problem).

How would you approach such problem?

here’s a code that i started to write that uses distance calculation - I’m sure it can be optimised:

``````SynthDef(\OctoPanOut, {
arg in, in_x, in_y;

var s1_x=0, s1_y=0, s1_amp,
s2_x=0, s2_y=360, s2_amp,
s3_x=0, s3_y=720, s3_amp,
s4_x=0, s4_y=1080, s4_amp,
s5_x=650, s5_y=1260, s5_amp,
s6_x=650, s6_y=900, s6_amp,
s7_x=650, s7_y=540, s7_amp,
s8_x=650, s8_y=180, s8_amp ;

s1_amp = 1 / (1 + ( (s1_x - in_x).squared + (s1_y - in_y).squared ).sqrt);
s2_amp = 1 / (1 + ( (s2_x - in_x).squared + (s2_y - in_y).squared ).sqrt);
s3_amp = 1 / (1 + ( (s3_x - in_x).squared + (s3_y - in_y).squared ).sqrt);
s4_amp = 1 / (1 + ( (s4_x - in_x).squared + (s4_y - in_y).squared ).sqrt);
s5_amp = 1 / (1 + ( (s5_x - in_x).squared + (s5_y - in_y).squared ).sqrt);
s6_amp = 1 / (1 + ( (s6_x - in_x).squared + (s6_y - in_y).squared ).sqrt);
s7_amp = 1 / (1 + ( (s7_x - in_x).squared + (s7_y - in_y).squared ).sqrt);
s8_amp = 1 / (1 + ( (s8_x - in_x).squared + (s8_y - in_y).squared ).sqrt);

Out.ar(0, in * s1_amp);
Out.ar(1, in * s2_amp);
Out.ar(2, in * s3_amp);
Out.ar(3, in * s4_amp);
Out.ar(4, in * s5_amp);
Out.ar(5, in * s6_amp);
Out.ar(6, in * s7_amp);
Out.ar(7, in * s8_amp);

``````

I went further on experimenting, and tried with a function. And it seems just to kinda work - I have to account for exponentiality (narrowness of focus) with some factor, but as proof of concept this seems to work - would anyone find any problem with such approach - could there be improvements?

``````(

~octoPanOut = {
arg in, in_x, in_y;

var s1_x=0, s1_y=0, s1_amp,
s2_x=0, s2_y=360, s2_amp,
s3_x=0, s3_y=720, s3_amp,
s4_x=0, s4_y=1080, s4_amp,
s5_x=650, s5_y=1260, s5_amp,
s6_x=650, s6_y=900, s6_amp,
s7_x=650, s7_y=540, s7_amp,
s8_x=650, s8_y=180, s8_amp ;

s1_amp = 1 / (1 + ( (s1_x - in_x).squared + (s1_y - in_y).squared ).sqrt);
s2_amp = 1 / (1 + ( (s2_x - in_x).squared + (s2_y - in_y).squared ).sqrt);
s3_amp = 1 / (1 + ( (s3_x - in_x).squared + (s3_y - in_y).squared ).sqrt);
s4_amp = 1 / (1 + ( (s4_x - in_x).squared + (s4_y - in_y).squared ).sqrt);
s5_amp = 1 / (1 + ( (s5_x - in_x).squared + (s5_y - in_y).squared ).sqrt);
s6_amp = 1 / (1 + ( (s6_x - in_x).squared + (s6_y - in_y).squared ).sqrt);
s7_amp = 1 / (1 + ( (s7_x - in_x).squared + (s7_y - in_y).squared ).sqrt);
s8_amp = 1 / (1 + ( (s8_x - in_x).squared + (s8_y - in_y).squared ).sqrt);

[in * s1_amp, in * s2_amp, in * s3_amp, in * s4_amp, in * s5_amp, in * s6_amp, in * s7_amp, in * s8_amp]

};

SynthDef(\testOcto, {
arg x= 0, y;
var son = PinkNoise.ar() * 0.5;
y = SinOsc.kr(0.5).range(0,2000);
Out.ar(0,~octoPanOut.value(son, x, y));
)

x = Synth(\testOcto);
``````

What about taking VBAP and converting x/y to an angle ?

interesting proposition. i didn’t know about VBAP. unfortunately speakers are not equidistant.

there is an operator hypot:
` 3 hypot: 4 //5`
that can save you some typing!

also nicer to do by iterating over arrays maybe! check out .do and .collect methods

I would definitely make it a Pseudo-UGen, but just because it would be the fastest way for me. Something like:

``````OctoPan : MultiOutUGen {

classvar <>speakers = #[
[0,0],[0,360],[0,720],[0,1080],[650,1260],[650,900],[650,540],[650,180]
];

*ar { arg in,x=0,y=0,speakers;
^this.multiNew('audio', in,x,y);
}

*new1 { arg rate,in,x,y;
var amps = this.speakers.collect{|coords|
var dist = coords - [x,y];
1/(1+dist.hypot(dist))
};
^in*amps;
}

}
``````

ADD: Actually the main advantage of a Pseudo UGen over a function or a prototype, is that the Pseudo UGen does multichannel expansion correctly with minimal effort (just by implementing the `multiNew` and `new1` functions)

2 Likes