1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
class:: VBAP
summary:: Vector Base Amplitude Panner
categories:: UGens>Multichannel>Panners
related:: Classes/VBAPSpeakerArray, Classes/CircleRamp
description::
An implementation of Vector Base Amplitude Panning. footnote::
This version of VBAP for SC was ported from the ver. 0.99 PD code by Scott Wilson, as part of the BEASTMulch project. Development was partially funded by the Arts and Humanities Research Council: http://www.ahrc.ac.uk
:: This allows for equal power panning of a source over an arbitrary array of equidistant speakers. Normally this would be a ring, a dome, or partial dome.
VBAP was created by Ville Pulkki. For more information on VBAP see http://www.acoustics.hut.fi/research/cat/vbap/
classmethods::
method:: ar, kr
argument:: numChans
The number of output channels.
argument:: in
The signal to be panned.
argument:: bufnum
A Buffer or its bufnum containing data calculated by an instance of VBAPSpeakerArray. Its number of channels must correspond to numChan above.
argument:: azimuth
+/- 180 degrees from the median plane (i.e. straight ahead)
argument:: elevation
+/- 90 degrees from the azimuth plane.
argument:: spread
A value from 0-100. When 0, if the signal is panned exactly to a speaker location the signal is only on that speaker. At values higher than 0, the signal will always be on more than one speaker. This can smooth the panning effect by making localisation blur more constant.
examples::
code::
Server.default = s = Server.internal;
// 2D
a = VBAPSpeakerArray.new(2, [0, 45, 90, 135, 180, -135, -90, -45]); // 8 channel ring
a.speakers[1].dump;
b = a.loadToBuffer;
(
x = { |azi = 0, ele = 0, spr = 0|
VBAP.ar(8, PinkNoise.ar(0.2), b.bufnum, azi, ele, spr);
}.scope;
)
// test them out
{[45, 90, 135, 180, -135, -90, -45, 0].do({|ang| x.set(\azi, ang); 1.wait; }) }.fork;
// try the spread
x.set(\spr, 20);
x.set(\spr, 100); // all speakers
x.free; b.free;
// 3D
a = VBAPSpeakerArray.new(3, [[-22.5, 14.97], [22.5, 14.97], [-67.5, 14.97], [67.5, 14.97], [-112.5, 14.97], [112.5, 14.97], [-157.5, 14.97], [157.5, 14.97], [-45, 0], [45, 0], [-90, 0], [90, 0], [-135, 0], [135, 0], [0, 0], [180, 0]]); // zig zag partial dome
b = Buffer.loadCollection(s, a.getSetsAndMatrices);
(
// pan around the circle up and down
x = { |azi = 0, ele = 0, spr = 0|
var source;
source = PinkNoise.ar(0.2);
VBAP.ar(16, source, b.bufnum, LFSaw.kr(0.5, 0).range(-180, 180) * -1, SinOsc.kr(3, 0).range(0, 14.97), spr);
}.play;
)
//////// 5.1 GUI example with CircleRamp
(
var speakerList, x=200, y=150, targx=200, targy=150;
var atorad = (2pi) / 360, rtoang = 360.0 / (2pi);
var targRotate, actRotate, targPoint, actPoint;
var maxShiftPerFrame = 20, frameInterval = 0.01;
var resched = false, count = 0;
var panBus, widthBus, recButton;
var a, b, c, e;
maxShiftPerFrame = maxShiftPerFrame * atorad;
actPoint = Point(x, y) - Point(200, 200);
panBus = Bus.control;
widthBus = Bus.control.set(60);
w = Window.new("5.1 Panner", Rect(128, 64, 400, 450)).front;
w.view.background_(Color.grey(0.3));
w.view.decorator = FlowLayout(w.view.bounds);
speakerList = [[-30, "L"], [30, "R"], [0, "C"], [-110, "Ls"], [110, "Rs"]];
c = UserView.new(w,Rect(0, 0, 400, 380));
c.canFocus = false;
c.drawFunc = {
Color.grey(0.8).set;
// draw the speaker layout
Pen.translate(200,200);
((actPoint.theta + (0.5pi)).wrap2(pi) * rtoang).round(0.01).asString.drawCenteredIn(Rect.aboutPoint(0@170, 30, 10), Font.new("Arial", 10), Color.grey(0.8));
Pen.strokeOval(Rect.aboutPoint(0@0, 150, 150));
Pen.rotate(pi);
speakerList.do({|spkr|
Pen.use({
Pen.rotate(spkr[0] * atorad);
Pen.moveTo(0@170);
Pen.strokeRect(r = Rect.aboutPoint(0@170, 30, 10));
if(spkr[0].abs < 90, {
Pen.use({
Pen.translate(0, 170);
Pen.rotate(pi);
spkr[1].drawCenteredIn(Rect.aboutPoint(0@0, 30, 10),
GUI.font.new("Arial", 10), Color.grey(0.8));
});
},{
spkr[1].drawCenteredIn(r, GUI.font.new("Arial", 10), Color.grey(0.8));
});
});
});
Pen.moveTo(0@0);
// draw the pan point
Pen.rotate(actPoint.theta + 0.5pi);
targPoint = Point(x, y) - Point(200, 200);
// trunc to avoid loops due to fp math
targRotate = (targPoint.theta - actPoint.theta).trunc(1e-15);
// wrap around
if(targRotate.abs > pi, {targRotate = (2pi - targRotate.abs) * targRotate.sign.neg});
actRotate = targRotate.clip2(maxShiftPerFrame).trunc(1e-15);
actPoint = actPoint.rotate(actRotate);
Pen.rotate(actRotate);
Pen.lineTo(0@150);
Pen.stroke;
Pen.fillOval(Rect.aboutPoint(0@150, 7, 7));
Pen.addWedge(0@0, 140, neg(e.value * 0.5) * atorad + 0.5pi, e.value * atorad);
Pen.stroke;
Color.grey(0.8).alpha_(0.1).set;
Pen.addWedge(0@0, 140, neg(e.value * 0.5) * atorad + 0.5pi, e.value * atorad);
Pen.fill;
if((actRotate.abs > 0), {AppClock.sched(frameInterval, {w.refresh})}, {count = 0;});
if(count%4 == 0, {panBus.set((actPoint.theta + (0.5pi)).wrap2(pi) * rtoang)});
};
c.mouseMoveAction_({|v,inx,iny| x = inx; y = iny; w.refresh;});
c.mouseDownAction_({|v,inx,iny| x = inx; y = iny; w.refresh;});
e = EZSlider.new(w, 380@20, "Stereo Width", [0, 180].asSpec, {arg ez; widthBus.set(ez.value); w.refresh}, labelWidth: 80);
e.labelView.setProperty(\stringColor,Color.grey(0.8));
w.refresh;
// VBAP
a = VBAPSpeakerArray.new(2, speakerList.collect(_.first));
b = a.loadToBuffer;
SynthDef('VBAP 5 chan', { |azi = 0, ele = 0, spr = 0, width = 60, vbapBuf|
var panned, source;
source = SinOsc.ar([440, 660], 0, Decay2.ar(Impulse.ar([1, 0.9]), 0.1, 0.2));
azi = azi.circleRamp;
panned = VBAP.ar(5, source, vbapBuf, [azi - (0.5 * width), azi + (0.5 * width)], ele, spr);
// 'standard' channel order for 5.1
[0, 1, 2, 4, 5].do({arg bus, i; Out.ar(bus, panned[0][i])});
[0, 1, 2, 4, 5].do({arg bus, i; Out.ar(bus, panned[1][i])});
}).play(s, [vbapBuf: b.bufnum, azi: panBus.asMap, width: widthBus.asMap]);
)
::
|