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);
	
	
}).add;

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));
}).add;
)



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[0].hypot(dist[1]))
	};
	^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