1// A series of BFormat UGens for SC3. UGens by Josh Parmenter 4-3-2004
2// UGen plug-ins are based on James McCartney's Pan2B and DecodeB2 UGens.
3// added interior localization, z-signal manipulation, and rotate, tilt
4// and tumble transformations.
5// B2Ster equations from
6// http://www.cyber.rdg.ac.uk/P.Sharkey/WWW/icdvrat/WWW96/Papers/keating.htm
7
8BFPanner : MultiOutUGen {
9	*categories {^#["UGens>Multichannel>Ambisonics"]}
10	}
11
12BFDecoder : UGen {
13	*categories {^#["UGens>Multichannel>Ambisonics"]}
14	}
15
16BFEncode1 : BFPanner {
17
18	*ar { arg in, azimuth=0, elevation=0, rho = 1, gain=1, wComp = 0;
19		^this.multiNew('audio', in, azimuth, elevation, rho, gain, wComp)
20	}
21
22	init { arg ... theInputs;
23		inputs = theInputs;
24		channels = [ OutputProxy(\audio,this,0), OutputProxy(\audio,this,1),
25					OutputProxy(\audio,this,2), OutputProxy(\audio,this,3) ];
26		^channels
27	}
28	checkInputs { ^this.checkNInputs(1) }
29}
30
31BFEncodeSter : BFPanner {
32
33	*ar { arg l, r, azimuth=0, width = 0.5pi, elevation=0, rho = 1, gain=1, wComp = 0;
34		^this.multiNew('audio', l, r, azimuth, width, elevation, rho, gain, wComp )
35	}
36
37	init { arg ... theInputs;
38		inputs = theInputs;
39		channels = [ OutputProxy(\audio,this,0), OutputProxy(\audio,this,1),
40					OutputProxy(\audio,this,2), OutputProxy(\audio,this,3) ];
41		^channels
42	}
43	checkInputs { ^this.checkNInputs(2) }
44}
45
46BFEncode2 : BFPanner {
47
48	*ar { arg in, point_x = 1, point_y = 1, elevation=0, gain=1, wComp = 0;
49		^this.multiNew('audio', in, point_x, point_y, elevation, gain, wComp )
50	}
51
52	init { arg ... theInputs;
53		inputs = theInputs;
54		channels = [ OutputProxy(\audio,this,0), OutputProxy(\audio,this,1),
55					OutputProxy(\audio,this,2), OutputProxy(\audio,this,3) ];
56		^channels
57	}
58}
59
60// second order encoder
61FMHEncode0 : BFPanner {
62
63	*ar { arg in, azimuth=0, elevation=0, gain=1;
64		^this.multiNew('audio', in, azimuth, elevation, gain )
65	}
66
67	init { arg ... theInputs;
68		inputs = theInputs;
69		channels = [ OutputProxy(\audio,this,0), OutputProxy(\audio,this,1),
70					OutputProxy(\audio,this,2), OutputProxy(\audio,this,3),
71					OutputProxy(\audio,this,4), OutputProxy(\audio,this,5),
72					OutputProxy(\audio,this,6), OutputProxy(\audio,this,7),
73					OutputProxy(\audio,this,8) ];
74		^channels
75	}
76}
77
78FMHEncode1 : BFPanner {
79
80	*ar { arg in, azimuth=0, elevation=0, rho = 1, gain=1, wComp = 0;
81		^this.multiNew('audio', in, azimuth, elevation, rho, gain, wComp )
82	}
83
84	init { arg ... theInputs;
85		inputs = theInputs;
86		channels = [ OutputProxy(\audio,this,0), OutputProxy(\audio,this,1),
87					OutputProxy(\audio,this,2), OutputProxy(\audio,this,3),
88					OutputProxy(\audio,this,4), OutputProxy(\audio,this,5),
89					OutputProxy(\audio,this,6), OutputProxy(\audio,this,7),
90					OutputProxy(\audio,this,8) ];
91		^channels
92	}
93}
94
95FMHEncode2 : BFPanner {
96
97	*ar { arg in, point_x = 0, point_y = 0, elevation=0, gain=1, wComp = 0;
98		^this.multiNew('audio', in, point_x, point_y, elevation, gain, wComp)
99	}
100
101	init { arg ... theInputs;
102		inputs = theInputs;
103		channels = [ OutputProxy(\audio,this,0), OutputProxy(\audio,this,1),
104					OutputProxy(\audio,this,2), OutputProxy(\audio,this,3),
105					OutputProxy(\audio,this,4), OutputProxy(\audio,this,5),
106					OutputProxy(\audio,this,6), OutputProxy(\audio,this,7),
107					OutputProxy(\audio,this,8) ];
108		^channels
109	}
110}
111
112BFDecode1 : BFDecoder {
113
114	*ar { arg w, x, y, z, azimuth = 0, elevation = 0, wComp = 0, mul = 1, add = 0;
115		^this.multiNew('audio', w, x, y, z, azimuth, elevation, wComp ).madd(mul, add);
116	}
117
118	*ar1 {arg w, x, y, z, azimuth = 0, elevation = 0, maxDist = 10, distance = 10, wComp = 0,
119			mul = 1,  add = 0, scaleflag = 1;
120		var dist, scaler;
121		dist = ((maxDist - distance) / 345);
122		scaler = if((scaleflag == 1), 1/((distance/maxDist)**1.5), 1);
123		^DelayN.ar(this.multiNew('audio', w, x, y, z, azimuth, elevation, wComp ), dist, dist, 			scaler.reciprocal).madd(mul, add);
124	}
125	checkInputs { ^this.checkNInputs(4) }
126}
127
128/* follows Furse / Malham conventions with some tweaking (W is scaled according to x, y, z, r, s, t, u, and v. s, t, u and v are scaled by 2/3.sqrt */
129
130FMHDecode1 : BFDecoder {
131	*ar {arg w, x, y, z, r, s, t, u, v, azimuth = 0, elevation = 0, mul = 1, add = 0;
132		^this.multiNew('audio', w, x, y, z, r, s, t, u, v, azimuth, elevation).madd(mul, add);
133		}
134
135	*ar1 {arg w, x, y, z, r, s, t, u, v, azimuth = 0, elevation = 0, maxDist = 10, distance = 10,
136			mul = 1, add = 0, scaleflag = 1;
137		var dist, scaler;
138		dist = ((maxDist - distance) / 345);
139		scaler = if((scaleflag == 1), 1/((distance/maxDist)**1.5), 1);
140		^DelayN.ar(this.multiNew('audio', w, x, y, z, r, s, t, u, v, azimuth, elevation ),
141			dist, dist, scaler.reciprocal).madd(mul, add);
142	}
143	/*
144	* some common speaker configs, with the appropriate components zeroed out
145	* see http://www.muse.demon.co.uk/ref/speakers.html
146	* for more information
147	*/
148
149	*stereo {arg w, y, mul = 1, add = 0;
150		var zero;
151		zero = K2A.ar(0);
152		^this.ar(w, zero, y, zero, zero, zero, zero, zero, zero, [0.25pi, -0.25pi], 0, mul, add)
153		}
154
155	// stereo pairs
156	*square {arg w, x, y, v, mul = 1, add = 0;
157		var zero;
158		zero = K2A.ar(0);
159		^this.ar(w, x, y, zero, zero, zero, zero, zero, v, [0.25pi, -0.25pi, 0.75pi, -0.75pi],
160			0, mul, add)
161		}
162
163	// quad clockwise
164	*quad {arg w, x, y, v, mul = 1, add = 0;
165		var zero;
166		zero = K2A.ar(0);
167		^this.ar(w, x, y, zero, zero, zero, zero, zero, v, Array.series(4, 0.25pi, -0.5pi),
168			0, mul, add)
169		}
170
171	// point front first
172	*pentagon {arg w, x, y, u, v, mul = 1, add = 0;
173		var zero;
174		zero = K2A.ar(0);
175		^this.ar(w, x, y, zero, zero, zero, zero, u, v, Array.series(5, 0, -0.4pi),
176			0, mul, add)
177		}
178
179	*hexagon {arg w, x, y, u, v, mul = 1, add = 0;
180		var zero;
181		zero = K2A.ar(0);
182		^this.ar(w, x, y, zero, zero, zero, zero, u, v,
183			Array.series(6, 0.16666pi, -0.333333pi),
184			0, mul, add)
185		}
186
187	// front bisects a side
188	*octagon1 {arg w, x, y, u, v, mul = 1, add = 0;
189		var zero;
190		zero = K2A.ar(0);
191		^this.ar(w, x, y, zero, zero, zero, zero, u, v,
192			Array.series(8, 0.125pi, -0.25pi),
193			0, mul, add)
194		}
195
196	// front is a vertex
197	*octagon2 {arg w, x, y, u, v, mul = 1, add = 0;
198		var zero;
199		zero = K2A.ar(0);
200		^this.ar(w, x, y, zero, zero, zero, zero, u, v,
201			Array.series(8, 0, -0.25pi),
202			0, mul, add)
203		}
204
205	*cube {arg w, x, y, z, s, t, v, mul = 1, add = 0;
206		var zero;
207		zero = K2A.ar(0);
208		^this.ar(w, x, y, z, zero, s, t, zero, v,
209			Array.series(8, 0.25pi, -0.5pi),
210			-0.25pi.dup(4) ++ 0.25pi.dup(4), mul, add)
211		}
212
213	// top, then bottom
214	*doubleHex {arg w, x, y, z, s, t, u, v, mul = 1, add = 0;
215		var zero;
216		zero = K2A.ar(0);
217		^this.ar(w, x, y, z, zero, s, t, u, v,
218			Array.series(12, 0.16666pi, -0.33333pi),
219			0.16666pi.dup(6) ++ -0.166666pi.dup(6), mul, add)
220		}
221	// top, pentagonup, pentagondown, bottom
222	*dodecahedron {arg w, x, y, z, r, s, t, u, v, mul = 1, add = 0;
223		var zero;
224		zero = K2A.ar(0);
225		^this.ar(w, x, y, z, r, s, t, u, v,
226			[0] ++ Array.series(10, 0.2, -0.4) ++ [0],
227			[0.5pi] ++ 0.16666pi.dup(5) ++ -0.16666pi.dup(5) ++ [-0.5pi],
228			mul, add)
229		}
230
231	checkInputs { ^this.checkNInputs(9) }
232}
233
234BFManipulate : BFPanner {
235
236	*ar { arg w, x, y, z, rotate = 0, tilt = 0, tumble = 0;
237		^this.multiNew('audio', w, x, y, z, rotate, tilt, tumble);
238	}
239
240	init { arg ... theInputs;
241		inputs = theInputs;
242		channels = [ OutputProxy(\audio,this,0), OutputProxy(\audio,this,1),
243					OutputProxy(\audio,this,2), OutputProxy(\audio,this,3) ];
244		^channels
245	}
246
247 	checkInputs { ^this.checkNInputs(4) }
248
249}
250
251// Rotate tilt and tumble classes, built from Rotate2.  Allows w, x, y and z to be passed in, and
252// returns the new w, x, y, and z
253Rotate : BFPanner {
254	*ar {arg w, x, y, z, rotate;
255		var xout, yout;
256		#xout, yout = Rotate2.ar(x, y, rotate * -0.31830988618379);
257		^[w, xout, yout, z];
258		}
259
260	init { arg ... theInputs;
261		inputs = theInputs;
262		channels = [ OutputProxy(\audio,this,0), OutputProxy(\audio,this,1),
263					OutputProxy(\audio,this,2), OutputProxy(\audio,this,3) ];
264		^channels
265	}
266
267 	checkInputs { ^this.checkNInputs(4) }
268}
269
270Tilt : BFPanner {
271	*ar {arg w, x, y, z, tilt;
272		var xout, zout;
273		#xout, zout = Rotate2.ar(x, z, tilt * -0.31830988618379);
274		^[w, xout, y, zout];
275		}
276
277	init { arg ... theInputs;
278		inputs = theInputs;
279		channels = [ OutputProxy(\audio,this,0), OutputProxy(\audio,this,1),
280					OutputProxy(\audio,this,2), OutputProxy(\audio,this,3) ];
281		^channels
282	}
283
284 	checkInputs { ^this.checkNInputs(4) }
285}
286
287Tumble : BFPanner {
288	*ar {arg w, x, y, z, tilt;
289		var yout, zout;
290		#yout, zout = Rotate2.ar(y, z, tilt * -0.31830988618379);
291		^[w, x, yout, zout];
292		}
293
294	init { arg ... theInputs;
295		inputs = theInputs;
296		channels = [ OutputProxy(\audio,this,0), OutputProxy(\audio,this,1),
297					OutputProxy(\audio,this,2), OutputProxy(\audio,this,3) ];
298		^channels
299	}
300
301 	checkInputs { ^this.checkNInputs(4) }
302}
303
304A2B : BFPanner {
305
306	*ar { arg a, b, c, d;
307		^this.multiNew('audio', a, b, c, d);
308	}
309
310	init { arg ... theInputs;
311		inputs = theInputs;
312		channels = [ OutputProxy(\audio,this,0), OutputProxy(\audio,this,1),
313					OutputProxy(\audio,this,2), OutputProxy(\audio,this,3) ];
314		^channels
315	}
316
317 	checkInputs { ^this.checkNInputs(4) }
318
319}
320
321B2A : BFPanner {
322
323	*ar { arg w, x, y, z;
324		^this.multiNew('audio', w, x, y, z);
325	}
326
327	init { arg ... theInputs;
328		inputs = theInputs;
329		channels = [ OutputProxy(\audio,this,0), OutputProxy(\audio,this,1),
330					OutputProxy(\audio,this,2), OutputProxy(\audio,this,3) ];
331		^channels
332	}
333
334 	checkInputs { ^this.checkNInputs(4) }
335
336}
337
338B2Ster : BFPanner {
339	*ar {arg w, x, y, mul = 1, add = 0;
340		^this.multiNew('audio', w, x, y).madd(mul, add);
341		}
342
343	init {arg ... theInputs;
344		inputs = theInputs;
345		channels = [ OutputProxy(\audio, this, 0), OutputProxy(\audio, this, 1)];		^channels;
346		}
347
348 	checkInputs { ^this.checkNInputs(3) }
349
350	}
351
352// takes w, x, and y from a BF sig, returns a 2 channel UHJ file
353
354B2UHJ : BFPanner {
355	*ar {arg w, x, y;
356		^this.multiNew('audio', w, x, y);
357		}
358
359	init {arg ... theInputs;
360		inputs = theInputs;
361		channels = [ OutputProxy(\audio, this, 0), OutputProxy(\audio, this, 1)];		^channels;
362		}
363
364 	checkInputs { ^this.checkNInputs(3) }
365
366	}
367
368// takes the left signal (ls) and right signal (rs) of a UHJ signal,
369// and returns w, x, and y of a BF signal
370
371UHJ2B : BFPanner {
372	*ar {arg ls, rs;
373		^this.multiNew('audio', ls, rs);
374		}
375
376	init {arg ... theInputs;
377		inputs = theInputs;
378		^channels = [ OutputProxy(\audio, this, 0), OutputProxy(\audio, this, 1),
379			OutputProxy(\audio, this, 2)]
380		}
381
382 	checkInputs { ^this.checkNInputs(2) }
383
384	}
385
386BFFreeVerb {
387	*ar {arg w, x, y, z, mix = 1.0, room = 0.5, damp = 0.5, diffuse = 0.1, mul = 1.0, add = 0.0;
388		var a, b, c, d;
389		#a, b, c, d = B2A.ar(w, x, y, z);
390		#a, b, c, d = FreeVerb.ar([a, b, c, d], mix, room, damp, mul, add);
391		a = a + (b + c * diffuse);
392		b = b + (c + d * diffuse);
393		c = c + (d + a * diffuse);
394		d = d + (a + b * diffuse);
395		^A2B.ar(a, b, c, d);
396		}
397	}
398
399BFGVerb {
400	*ar {arg w, x, y, z, diffuse = 0.0, roomsize = 10, revtime = 3, damping = 0.5,
401			inputbw = 0.5, drylevel = 0.0, earlyreflevel = 0.7, taillevel = 0.5,
402			maxroomsize = 300, mul = 1, add = 0;
403		var a, b, c, d, al, ar, bl, br, cl, cr, dl, dr;
404		#a, b, c, d = B2A.ar(w, x, y, z);
405		#a, b, c, d = DelayN.ar([a, b, c, d], 0.001, Array.fill(4, {Rand.new(0.0, 0.001)}));
406		#ar, al = GVerb.ar(a, roomsize, revtime, damping, inputbw, 45, drylevel,
407			earlyreflevel, taillevel, maxroomsize, mul);
408		#br, bl = GVerb.ar(b, roomsize, revtime, damping, inputbw, 45, drylevel,
409			earlyreflevel, taillevel, maxroomsize, mul);
410		#cr, cl = GVerb.ar(c, roomsize, revtime, damping, inputbw, 45, drylevel,
411			earlyreflevel, taillevel, maxroomsize, mul);
412		#dr, dl = GVerb.ar(d, roomsize, revtime, damping, inputbw, 45, drylevel,
413			earlyreflevel, taillevel, maxroomsize, mul);
414		a = ar + al + (br + cl * diffuse);
415		b = br + bl + (cr + dl * diffuse);
416		c = cr + cl + (dr + al * diffuse);
417		d = dr + dl + (ar + bl * diffuse);
418		^A2B.ar(a, b, c, d) + add;
419		}
420	}
421
422
423
424
425//////////////////////////////////////////////////////////////////////////////////////////////
426// Classes from SuperCollider 2 are in the file AmbisonicsSC2
427//////////////////////////////////////////////////////////////////////////////////////////////
428
429
430
431