diff --git a/source/VBAPUGens/VBAP.cpp b/source/VBAPUGens/VBAP.cpp index 67ecaf503..32c2238d9 100644 --- a/source/VBAPUGens/VBAP.cpp +++ b/source/VBAPUGens/VBAP.cpp @@ -95,17 +95,17 @@ static float atorad = (2.0f * pi) / 360.0f; struct VBAP : Unit { - float x_azi; /* panning direction azimuth */ - float x_ele; /* panning direction elevation */ - float* *x_set_inv_matx; /* inverse matrice for each loudspeaker set */ - float* *x_set_matx; /* matrice for each loudspeaker set */ - int* *x_lsset; /* channel numbers of loudspeakers in each LS set */ - int x_lsset_available; /* have loudspeaker sets been defined with define_loudspeakers */ - int x_lsset_amount; /* amount of loudspeaker sets */ - int x_ls_amount; /* amount of loudspeakers */ - int x_dimension; /* 2 or 3 */ - float x_spread; /* speading amount of virtual source (0-100) */ - float x_spread_base[3]; /* used to create uniform spreading */ + float x_azi; /* panning direction azimuth */ + float x_ele; /* panning direction elevation */ + float* *x_set_inv_matx; /* inverse matrice for each loudspeaker set */ + float* *x_set_matx; /* matrice for each loudspeaker set */ + int* *x_lsset; /* channel numbers of loudspeakers in each LS set */ + int x_lsset_available; /* have loudspeaker sets been defined with define_loudspeakers */ + int x_num_lssets; /* number of loudspeaker sets (pairs or triplets) */ + int x_num_ls; /* number of loudspeakers */ + int x_dimension; /* 2 or 3 */ + float x_spread; /* speading amount of virtual source (0-100) */ + float x_spread_base[3]; /* used to create uniform spreading */ float *final_gs; float *m_chanamp; @@ -260,7 +260,7 @@ static void additive_vbap(float *final_gs, float cartdir[3], VBAP *x) * bug. 2006-08-13 */ // post("x_lsset_amount: %li", x->x_lsset_amount); - for(i=0;ix_lsset_amount;i++){ + for(i=0;ix_num_lssets;i++){ small_g = 10000000.0; neg_g_am = 3; for(j=0;jx_spread > 70) - for(i=0;ix_ls_amount;i++){ + for(i=0;ix_num_ls;i++){ final_gs[i] += (x->x_spread - 70) / 30.0 * (x->x_spread - 70) / 30.0 * 10.0; } - for(i=0,power=0.0;ix_ls_amount;i++){ + for(i=0,power=0.0;ix_num_ls;i++){ power += final_gs[i] * final_gs[i]; } power = sqrt(power); - for(i=0;ix_ls_amount;i++){ + for(i=0;ix_num_ls;i++){ final_gs[i] /= power; } } @@ -389,7 +389,7 @@ static void vbap(float g[3], int ls[3], VBAP *x) { /* calculates gain factors using loudspeaker setup and given direction */ float power; - int i,j,k, gains_modified; + int i,j,k; float small_g; float big_sm_g, gtmp[3]; int winner_set=0; @@ -427,12 +427,12 @@ static void vbap(float g[3], int ls[3], VBAP *x) angle_to_cart(x->x_azi,x->x_ele,cartdir); big_sm_g = -100000.0; /* initial value for largest minimum gain value */ - best_neg_g_am=3; /* how many negative values in this set */ + best_neg_g_am = dim; /* how many negative values in this set */ - for(i=0;ix_lsset_amount;i++){ + for(i=0;ix_num_lssets;i++){ small_g = 10000000.0; - neg_g_am = 3; + neg_g_am = dim; for(j=0;jx_lsset[i][0]; ls[1]= x->x_lsset[i][1]; - if(dim==3){ - g[2]=gtmp[2]; - ls[2]= x->x_lsset[i][2]; - } else { - g[2]=0.0; - ls[2]=0; - } + for(j = 0; j < dim; j++) { + g[j] = gtmp[j]; + ls[j] = x->x_lsset[i][j]; + } + if (dim < 3) { g[2] = 0.0; ls[2] = 0; } // third slot unused for 2D } } @@ -463,34 +459,35 @@ static void vbap(float g[3], int ls[3], VBAP *x) // gain values. This happens when the virtual source is outside of // all loudspeaker sets. */ - gains_modified=0; - for(i=0;ix_set_matx[winner_set][0] * g[0] - + x->x_set_matx[winner_set][1] * g[1] - + x->x_set_matx[winner_set][2] * g[2]; - new_cartdir[1] = x->x_set_matx[winner_set][3] * g[0] - + x->x_set_matx[winner_set][4] * g[1] - + x->x_set_matx[winner_set][5] * g[2]; - if (dim == 3) { - new_cartdir[2] = x->x_set_matx[winner_set][6] * g[0] - + x->x_set_matx[winner_set][7] * g[1] - + x->x_set_matx[winner_set][8] * g[2]; - } else new_cartdir[2] = 0; - cart_to_angle(new_cartdir,new_angle_dir); - x->x_azi = (new_angle_dir[0]); - x->x_ele = (new_angle_dir[1]); + if(gains_clamped) { + float * setmtx = &x->x_set_matx[winner_set][0]; + if (dim == 3) { + new_cartdir[0] = setmtx[0] * g[0] + setmtx[1] * g[1] + setmtx[2] * g[2]; + new_cartdir[1] = setmtx[3] * g[0] + setmtx[4] * g[1] + setmtx[5] * g[2]; + new_cartdir[2] = setmtx[6] * g[0] + setmtx[7] * g[1] + setmtx[8] * g[2]; + } else { + new_cartdir[0] = setmtx[0] * g[0] + setmtx[1] * g[1]; + new_cartdir[1] = setmtx[2] * g[0] + setmtx[3] * g[1]; + new_cartdir[2] = 0.f; } - - power=sqrt(g[0]*g[0] + g[1]*g[1] + g[2]*g[2]); - g[0] /= power; - g[1] /= power; - g[2] /= power; + + cart_to_angle(new_cartdir, new_angle_dir); + x->x_azi = (new_angle_dir[0]); + x->x_ele = (new_angle_dir[1]); + } + + power = sqrt(g[0]*g[0] + g[1]*g[1] + g[2]*g[2]); + g[0] /= power; + g[1] /= power; + g[2] /= power; } @@ -508,7 +505,7 @@ static inline void VBAP_calc_gain_factors(VBAP * unit) // only recalculate gain factors if inputs have changed if((azimuth != unit->x_azi) || (elevation != unit->x_ele) || (spread != unit->x_spread)){ - float g[3]; + float g[3]; // note only 2 slots used in 2D case int ls[3]; int i; @@ -516,20 +513,18 @@ static inline void VBAP_calc_gain_factors(VBAP * unit) unit->x_ele = elevation; unit->x_spread = spread; - if(unit->x_lsset_available ==1){ - vbap(g,ls, unit); - for(i=0;ix_ls_amount;i++) - final_gs[i]=0.0; - for(i=0;ix_dimension;i++){ - final_gs[ls[i]-1]=g[i]; - } - if(unit->x_spread != 0){ - spread_it(unit,final_gs); - } -// for(i=0; i < unit->mNumOutputs; i++){ -// printf("chan %i: %f\n", i, final_gs[i] ); -// } - } + vbap(g,ls, unit); + for(i=0;ix_num_ls;i++) + final_gs[i]=0.0; + for(i=0;ix_dimension;i++){ + final_gs[ls[i]-1]=g[i]; + } + if(unit->x_spread != 0){ + spread_it(unit,final_gs); + } +// for(i=0; i < unit->mNumOutputs; i++){ +// printf("chan %i: %f\n", i, final_gs[i] ); +// } } } @@ -596,22 +591,16 @@ static inline_functions void VBAP_next_simd(VBAP *unit, int inNumSamples) } #endif -// needs to check that numOutputs and x_ls_amount match!! static void VBAP_Ctor(VBAP* unit) { //printf("VBAP-1.0.3.2\n"); - int numOutputs = unit->mNumOutputs, counter = 0, datapointer=0, setpointer=0, i; - - unit->m_chanamp = (float*)RTAlloc(unit->mWorld, numOutputs * sizeof(float)); - - // initialise interpolation levels and outputs - for (int i=0; im_chanamp[i] = 0; - ZOUT0(i) = 0.f; - } + int numOutputs = unit->mNumOutputs; + int datapointer=0, setpointer=0; + int i; + + /* Access loudspeaker data buffer, validate */ - // [dim, numSpeakers, [chanOffsets 0-2, invmx 0-8, [lp1, lp2, lp2].x, sim.y, sim.z] * sets.size].flat - float fbufnum = ZIN0(1); + float fbufnum = ZIN0(1); uint32 ibufnum = (uint32)fbufnum; World *world = unit->mWorld; @@ -628,62 +617,87 @@ static void VBAP_Ctor(VBAP* unit) buf = world->mSndBufs + ibufnum; } - int numvals = buf->samples; - unit->x_dimension = (int)(buf->data[datapointer++]); - unit->x_ls_amount = (int)(buf->data[datapointer++]); - - unit->x_azi = unit->x_ele = unit->x_spread = std::numeric_limits::quiet_NaN(); - - unit->final_gs = (float*)RTAlloc(unit->mWorld, numOutputs * sizeof(float)); - unit->x_lsset_available = 1; - - if(((unit->x_dimension != 2) && (unit->x_dimension != 3)) || (unit->x_ls_amount < 2)) { - printf("vbap: Error in loudspeaker data. Bufnum: %i\n", (int)fbufnum); - unit->x_lsset_available = 0; - // do something else here + // Validate data header: dimension and number of loudspeakers + bool passing = true; + int dim = (int)(buf->data[datapointer++]); // 2D or 3D + int num_ls = (int)(buf->data[datapointer++]); // number of loudspeakers + + if(numOutputs != num_ls) { + printf("VBAP Error: ugen's numOutputs (%d) does not match the number of loudspeakers reported in the loudspeaker buffer (%d). Bufnum: %i\n", numOutputs, num_ls, (int)fbufnum); + passing = false; + } + + if(((dim != 2) && (dim != 3)) || (num_ls < 2)) { + printf("VBAP Error: Error in loudspeaker data. Dimension must be 2 or 3 (%d specified), and there must be at least 2 loudspeakers (%d provided). Bufnum: %i\n", dim, num_ls, (int)fbufnum); + passing = false; } - if(unit->x_dimension == 3) - counter = (numvals - 2) / ((unit->x_dimension * unit->x_dimension*2) + unit->x_dimension); - if(unit->x_dimension == 2) - counter = (numvals - 2) / ((unit->x_dimension * unit->x_dimension) + unit->x_dimension); - unit->x_lsset_amount=counter; - - if(counter<=0){ - printf("vbap: Error in loudspeaker data. Bufnum: %i\n", (int)fbufnum); - unit->x_lsset_available=0; -// return; - } + /* Validate loudspeaker set payload. + buffer data structure: + 2D: [dim, numSpeakers, numSets * [pairChanIndices (1x2), invmtx (2x2), fwdmtx (2x2)]].flat + 3D: [dim, numSpeakers, numSets * [tripChanIndices (1x3), invmtx (3x3), spkcoords (3x3)]].flat + Size of this buffer factors to: 2 + (numSets * (dim + (dim * dim) + (dim * dim))) + This size should match data_length calc in vbap.sc + */ + int dataLength = buf->samples; + int payload = dataLength - 2; // payload size without header + int stride = dim + 2 * dim * dim; // payload stride + int numSets = payload / stride; + + if (numSets <= 0 || (payload % stride) != 0) { + printf("VBAP error: Error in loudspeaker data. Size of the loudspeaker data buffer (%d) doesn't match what's expected for given the number of loudspeaker sets (%d) and the loudspeaker array dimension (%d). Bufnum: %i\n", + dataLength, numSets, dim, (int)fbufnum); + passing = false; + } + + if(!passing) { + unit->x_lsset_available = 0; + SETCALC(*ClearUnitOutputs); + ClearUnitOutputs(unit, 1); + return; + } + + /* Loudspeaker data buffer is now validated, init vars and alloc memory */ + unit->x_lsset_available = 1; + + unit->x_dimension = dim; + unit->x_num_ls = num_ls; + unit->x_num_lssets = numSets; + + // "uninitialized controls" will trigger re-calc in the initial call to _next + // because NaNs are not equal to any floating point number + unit->x_azi = unit->x_ele = unit->x_spread = std::numeric_limits::quiet_NaN(); - unit->x_set_inv_matx = (float**)RTAlloc(unit->mWorld, counter * sizeof(float*)); - unit->x_set_matx = (float**)RTAlloc(unit->mWorld, counter * sizeof(float*)); - unit->x_lsset = (int**)RTAlloc(unit->mWorld, counter * sizeof(int*)); + unit->final_gs = (float*)RTAlloc(unit->mWorld, numOutputs * sizeof(float)); + unit->m_chanamp = (float*)RTAlloc(unit->mWorld, numOutputs * sizeof(float)); + + unit->x_set_inv_matx = (float**)RTAlloc(unit->mWorld, numSets * sizeof(float*)); + unit->x_set_matx = (float**)RTAlloc(unit->mWorld, numSets * sizeof(float*)); + unit->x_lsset = (int**)RTAlloc(unit->mWorld, numSets * sizeof(int*)); - for(i=0; ix_set_inv_matx[i] = (float*)RTAlloc(unit->mWorld, 9 * sizeof(float)); - unit->x_set_matx[i] = (float*)RTAlloc(unit->mWorld, 9 * sizeof(float)); - unit->x_lsset[i] = (int*)RTAlloc(unit->mWorld, 3 * sizeof(int)); + // TODO: why are these a fixed size of 3 x 3? are 2D sets treated as 3D? + for(i=0; ix_set_inv_matx[i] = (float*)RTAlloc(unit->mWorld, (dim * dim) * sizeof(float)); + unit->x_set_matx[i] = (float*)RTAlloc(unit->mWorld, (dim * dim) * sizeof(float)); + unit->x_lsset[i] = (int*)RTAlloc(unit->mWorld, dim * sizeof(int)); } - // probably sets should be created with rtalloc - while(counter-- > 0){ + + /* Read data from loudspeaker data buffer into vars */ + int setCounter = numSets; + while(setCounter-- > 0){ for(i=0; i < unit->x_dimension; i++){ unit->x_lsset[setpointer][i]=(int)buf->data[datapointer++]; } - for(i=0; i < unit->x_dimension*unit->x_dimension; i++){ unit->x_set_inv_matx[setpointer][i]=buf->data[datapointer++]; - /* post("%d",deb++); */ } - if(unit->x_dimension == 3){ + if(unit->x_dimension == 2 || unit->x_dimension == 3){ for(i=0; i < unit->x_dimension*unit->x_dimension; i++){ unit->x_set_matx[setpointer][i]=buf->data[datapointer++]; - } } - setpointer++; } - //printf("vbap: Loudspeaker setup configured!\n"); #ifdef NOVA_SIMD if (!(BUFLENGTH & 15)) @@ -692,52 +706,34 @@ static void VBAP_Ctor(VBAP* unit) #endif SETCALC(VBAP_next); - ZOUT0(0) = ZIN0(0); - unit->x_azi = ZIN0(2); - unit->x_ele = ZIN0(3); - unit->x_spread_base[0] = 0.0; - unit->x_spread_base[1] = 1.0; - unit->x_spread_base[2] = 0.0; - unit->x_spread = ZIN0(4); - - // calculate initial gain factors - float g[3]; - int ls[3]; - float *final_gs = unit->final_gs; - - if(unit->x_lsset_available ==1){ - vbap(g,ls, unit); - for(i=0;ix_ls_amount;i++) - final_gs[i]=0.0; - for(i=0;ix_dimension;i++){ - final_gs[ls[i]-1]=g[i]; - } - if(unit->x_spread != 0){ - spread_it(unit,final_gs); - } - - } else { - // if the ls data was bad, just set every gain to 0 and bail - for(i=0;ix_ls_amount;i++) - final_gs[i]=0.f; - } + // init gains + VBAP_calc_gain_factors(unit); + // init channel amps to avoid fade-in from zero through the first block + for(i=0;ix_num_ls;i++) + unit->m_chanamp[i] = unit->final_gs[i]; + + VBAP_next(unit, 1); // don't call the SIMD _next with one sample! } static void VBAP_Dtor(VBAP* unit) { - int counter = unit->x_lsset_amount; - RTFree(unit->mWorld, unit->final_gs); - for(int i=0; imWorld, unit->x_set_inv_matx[i]); - RTFree(unit->mWorld, unit->x_set_matx[i]); - RTFree(unit->mWorld, unit->x_lsset[i]); + if (unit->x_lsset_available > 0) { // only free if initialization occured + int numsets = unit->x_num_lssets; + RTFree(unit->mWorld, unit->final_gs); + RTFree(unit->mWorld, unit->m_chanamp); + + for(int i=0; imWorld, unit->x_set_inv_matx[i]); + RTFree(unit->mWorld, unit->x_set_matx[i]); + RTFree(unit->mWorld, unit->x_lsset[i]); + } + + RTFree(unit->mWorld, unit->x_set_inv_matx); + RTFree(unit->mWorld, unit->x_set_matx); + RTFree(unit->mWorld, unit->x_lsset); } - - RTFree(unit->mWorld, unit->x_set_inv_matx); - RTFree(unit->mWorld, unit->x_set_matx); - RTFree(unit->mWorld, unit->x_lsset); } ////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/source/VBAPUGens/sc/HelpSource/Classes/VBAP.schelp b/source/VBAPUGens/sc/HelpSource/Classes/VBAP.schelp index 72c5d3db3..fbdedfe02 100644 --- a/source/VBAPUGens/sc/HelpSource/Classes/VBAP.schelp +++ b/source/VBAPUGens/sc/HelpSource/Classes/VBAP.schelp @@ -17,7 +17,7 @@ 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. +A Buffer or its bufnum containing data calculated by an instance of link::Classes/VBAPSpeakerArray::. Its number of channels must correspond to strong::numChans:: above. argument:: azimuth +/- 180 degrees from the median plane (i.e. straight ahead) argument:: elevation @@ -25,11 +25,21 @@ argument:: elevation 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. +discussion:: +warning:: +Gaps of more than 180 degrees are not supported. If you pan a signal into such a gap, the behavior is undefined. +:: + examples:: code:: -Server.default = s = Server.internal; -// 2D -a = VBAPSpeakerArray.new(2, [0, 45, 90, 135, 180, -135, -90, -45]); // 8 channel ring +// Boot the server for the examples below +s.boot; +:: + +subsection:: 2D +code:: +// 8 channel ring +a = VBAPSpeakerArray.new(2, [0, 45, 90, 135, 180, -135, -90, -45]); a.speakers[1].dump; @@ -37,34 +47,64 @@ b = a.loadToBuffer; ( x = { |azi = 0, ele = 0, spr = 0| -VBAP.ar(8, PinkNoise.ar(0.2), b.bufnum, azi, ele, spr); + 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; +{[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; +:: + +subsection:: Plot the loudspeaker gain of a panning source +code:: +( +// 2D: 5 full-range channels of a 5.1 array +~azis = [ -30, 30, 0, -110, 110 ]; +b = Buffer.loadCollection(s, VBAPSpeakerArray.new(2, ~azis).getSetsAndMatrices); +) + +( +{ + VBAP.ar(~azis.size, + DC.ar(1), // signal + b.bufnum, // VBAP data + Line.ar(0, 360, 0.1), // position + 0, 0); +}.plot(0.1); +) + +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 +subsection:: 3D +code:: +// zig zag partial dome +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]]); 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); + 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 +subsection:: GUI example + +code:: +// 5.1 example with CircleRamp ( var speakerList, x=200, y=150, targx=200, targy=150; @@ -88,53 +128,55 @@ 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)}); + 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;}); @@ -149,16 +191,17 @@ 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])}); + 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]); ) :: + diff --git a/source/VBAPUGens/sc/HelpSource/Classes/VBAPSpeakerArray.schelp b/source/VBAPUGens/sc/HelpSource/Classes/VBAPSpeakerArray.schelp index 551fda63d..91e17d43c 100644 --- a/source/VBAPUGens/sc/HelpSource/Classes/VBAPSpeakerArray.schelp +++ b/source/VBAPUGens/sc/HelpSource/Classes/VBAPSpeakerArray.schelp @@ -11,27 +11,32 @@ VBAP was created by Ville Pulkki. For more information on VBAP see http://www.ac This version of VBAP for SC was ported from the 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 classmethods:: + method:: new Create a new VBAPSpeakerArray. argument:: dim Number of dimensions in the array, either 2 (ring or partial ring), or 3 (partial or full dome or sphere). argument:: directions -An link::Classes/Array:: containing speaker locations in degrees. If dim is 2 this will be an array of azimuth angles, if dim is 3 this will be an array of arrays of [azimuth, elevation] angle pairs. +An link::Classes/Array:: containing speaker locations in degrees. If strong::dim:: is code::2::, this will be an array of azimuth angles. If strong::dim:: is code::3:: this will be an array of arrays of code::[azimuth, elevation]:: angle pairs. -Angles are given in degrees, with azimuth +/- 180 degrees from the median plane (i.e. straight ahead), and elevation +/- 90 degrees from the azimuth plane. +Angles are given in degrees, with azimuth +/- 180 degrees from the median plane (i.e. straight ahead), and elevation +/- 90 degrees from the horizontal plane. The order of the speakers corresponds to the order of the outputs when using a VBAP UGen. + +warning:: +Gaps of more than 180 degrees are not supported. If you pan a signal into such a gap, the behavior is undefined. +:: + discussion:: 2D and 3D examples code:: -VBAPSpeakerArray.new(2, [ -30, 30, 0, -110, 110 ]); // 5.1 array +// 2D: a 5.1 loudspeaker array +VBAPSpeakerArray.new(2, [ -30, 30, 0, -110, 110 ]); -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 +// 3D: a zig zag partial dome +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]]); :: -method:: maxNumSpeakers -Set/get the maximum number of speakers in an array. Default is 55. (This limitation will be removed in a later version.) - instancemethods:: method:: dim @@ -44,18 +49,23 @@ method:: speakers Returns an link::Classes/Array:: containing instances of link::Classes/VBAPSpeaker::. method:: getSetsAndMatrices -Sort the speaker array into loudspeaker pairs or triplets and return this data in a form appropriate for loading into a buffer. Once loaded this data can be used by a link::Classes/VBAP:: UGen. -discussion:: +Calculate the loudspeaker panning pairs (2D) or triplets (3D) and their associated forward and inverse matrices which are used by a link::Classes/VBAP:: UGen. + +If you don't need to inspect this data, and just want to use the link::Classes/VBAP:: UGen, you can call link::#-loadToBuffer::. + code:: -a = VBAPSpeakerArray.new(2, [0, 45, 90, 135, 180, -135, -90, -45]); // 8 channel ring -b = Buffer.loadCollection(s, a.getSetsAndMatrices); +// 8 channel ring +a = VBAPSpeakerArray.new(2, [0, 45, 90, 135, 180, -135, -90, -45]); +~lsBufferData = a.getSetsAndMatrices; +b = Buffer.loadCollection(s, ~lsBufferData); :: method:: loadToBuffer -A shortcut for calling getSetsAndMatrices and loading into a Buffer. Returns a link::Classes/Buffer:: object. -discussion:: +Load the loudspeaker panning data into a buffer to be used by the link::Classes/VBAP:: UGen. Calls link::#-getSetsAndMatrices:: internally. + code:: -a = VBAPSpeakerArray.new(2, [0, 45, 90, 135, 180, -135, -90, -45]); // 8 channel ring +// 8 channel ring +a = VBAPSpeakerArray.new(2, [0, 45, 90, 135, 180, -135, -90, -45]); b = a.loadToBuffer; :: diff --git a/source/VBAPUGens/sc/vbap.sc b/source/VBAPUGens/sc/vbap.sc index 8615a90a3..dc7cf0f58 100644 --- a/source/VBAPUGens/sc/vbap.sc +++ b/source/VBAPUGens/sc/vbap.sc @@ -164,7 +164,7 @@ VBAPSpeakerArray { /* remove triangles which had crossing sides with smaller triangles or include loudspeakers*/ - //"triplet_amount before stripping: %\n".postf(sets.size); + //"numTriplets before stripping: %\n".postf(sets.size); sets = sets.reject({|set| i1 = set.chanOffsets[0]; j1 = set.chanOffsets[1]; @@ -172,7 +172,7 @@ VBAPSpeakerArray { (connections[i1][j1] == 0) || (connections[i1][k1] == 0) || (connections[j1][k1] == 0) || this.any_ls_inside_triplet(i1,j1,k1); }); - //"triplet_amount after stripping: %\n".postf(sets.size); + //"numTriplets after stripping: %\n".postf(sets.size); } lines_intersect { |i, j, k, l| @@ -320,21 +320,31 @@ VBAPSpeakerArray { var invdet; var lp1, lp2, lp3; var invmx; - var triplet_amount = 0, pointer,list_length=0; - var result; + var numTriplets, pointer, data_length; + var mat; + var dim; if(sets.isNil, { postln("define-loudspeakers: Not valid 3-D configuration"); ^nil; }); - triplet_amount = sets.size; - //"triplet_amount: %\n".postf(triplet_amount); - list_length = triplet_amount * 21 + 2; - result = FloatArray.newClear(list_length); - - result[0] = dim; - result[1] = numSpeakers; + // Returned FLoatArray data structure: + // "header": + // layout dimentions: 2 or 3 (1) + // number of loudspeakers (1) + // triplet data (x numTriplets): + // channel indices of the triplet (3) + // inverse matrix for gain solving (3 x 3, flattened to 9) + // cartesian coords of each loudspeaker in the triplet (xyz x 3, flattened to 9) + // So buffer size is 1 + 1 + (numTriplets * (3 + 9 + 9)); + dim = 3; // 3D, loudspeaker sets are triplets + numTriplets = sets.size; // number of triplets + data_length = 2 + (numTriplets * (dim + (dim * dim) + (dim * dim))); + mat = FloatArray.newClear(data_length); + + mat[0] = dim; + mat[1] = numSpeakers; pointer=2; sets.do({|set| @@ -364,25 +374,30 @@ VBAPSpeakerArray { invmx[8] = ((lp1.x * lp2.y) - (lp1.y * lp2.x)) * invdet; set.inv_mx = invmx; 3.do({|i| - result[pointer] = set.chanOffsets[i] + 1; + mat[pointer] = set.chanOffsets[i] + 1; pointer = pointer + 1; }); 9.do({|i| - result[pointer] = invmx[i]; + mat[pointer] = invmx[i]; pointer = pointer + 1; }); - result[pointer] = lp1.x; pointer = pointer + 1; - result[pointer] = lp2.x; pointer = pointer + 1; - result[pointer] = lp3.x; pointer = pointer + 1; - result[pointer] = lp1.y; pointer = pointer + 1; - result[pointer] = lp2.y; pointer = pointer + 1; - result[pointer] = lp3.y; pointer = pointer + 1; - result[pointer] = lp1.z; pointer = pointer + 1; - result[pointer] = lp2.z; pointer = pointer + 1; - result[pointer] = lp3.z; pointer = pointer + 1; + + /* directional cosines: angle -> cartesian coords */ + /* mat = [ x1, x2, x3, + y1, y2, y3 + z1, z2, z3 ] */ + mat[pointer] = lp1.x; pointer = pointer + 1; + mat[pointer] = lp2.x; pointer = pointer + 1; + mat[pointer] = lp3.x; pointer = pointer + 1; + mat[pointer] = lp1.y; pointer = pointer + 1; + mat[pointer] = lp2.y; pointer = pointer + 1; + mat[pointer] = lp3.y; pointer = pointer + 1; + mat[pointer] = lp1.z; pointer = pointer + 1; + mat[pointer] = lp2.z; pointer = pointer + 1; + mat[pointer] = lp3.z; pointer = pointer + 1; }); - ^result; + ^mat; } choose_ls_tuplets { @@ -391,10 +406,10 @@ VBAPSpeakerArray { var atorad = (2 * pi / 360); var sorted_lss; var exist; - var amount=0; + var numPairs = 0; // number of loudspeaker pairs var inv_mat; var mat; - var list_length; + var data_length; var result; var pointer; @@ -416,7 +431,7 @@ VBAPSpeakerArray { if(this.calc_2D_inv_tmatrix(speakers[sorted_lss[i]].azi, speakers[sorted_lss[i+1]].azi, inv_mat[i], mat[i]),{ exist[i]=1; - amount = amount + 1; + numPairs = numPairs + 1; }); }); }); @@ -427,13 +442,22 @@ VBAPSpeakerArray { speakers[sorted_lss[0]].azi, inv_mat[numSpeakers-1], mat[numSpeakers-1]), { exist[numSpeakers-1]=1; - amount = amount + 1; + numPairs = numPairs + 1; }); }); /* Output */ - list_length= amount * 10 + 2; - result = Array.newClear(list_length); + // data structure: + // header: + // layout dimention: 2 (1) + // number of loudspeakers (1) + // pair data (x numPairs): + // channel indices of the pair (2) + // inverse matrix for gain solving (2 x 2, flattened to 4) + // forward matrix for inversion (2 x 2, flattened to 4) + // So buffer size is 1 + 1 + (numPairs * (2 + 4 + 4)); + data_length = 2 + (numPairs * (dim + (dim * dim) + (dim * dim))); + result = Array.newClear(data_length); result[0] = dim; result[1] = numSpeakers; @@ -516,15 +540,21 @@ VBAPSpeakerArray { calc_2D_inv_tmatrix { |azi1, azi2, inv_mat, mat| /* calculate inverse 2x2 matrix */ - var x1,x2,x3,x4; + var x1, x2, y1, y2; var det; - var rad2ang = 360.0 / ( 2 * pi ); + azi1 = azi1.degrad; + azi2 = azi2.degrad; + + /* directional cosines: angle -> cartesian coords */ + /* mat = [ x1, x2, + y1, y2 ] */ + mat[0] = x1 = cos(azi1); + mat[1] = x2 = cos(azi2); + mat[2] = y1 = sin(azi1); + mat[3] = y2 = sin(azi2); + + det = (x1 * y2) - ( x2 * y1 ); - mat[0] = x1 = cos(azi1 / rad2ang); - mat[1] = x2 = sin(azi1 / rad2ang); - mat[2] = x3 = cos(azi2 / rad2ang); - mat[3] = x4 = sin(azi2 / rad2ang); - det = (x1 * x4) - ( x3 * x2 ); if(abs(det) <= 0.001, { inv_mat[0] = 0.0; inv_mat[1] = 0.0; @@ -532,10 +562,10 @@ VBAPSpeakerArray { inv_mat[3] = 0.0; ^false; }, { - inv_mat[0] = (x4 / det); - inv_mat[1] = (x3.neg / det); - inv_mat[2] = (x2.neg / det); - inv_mat[3] = (x1 / det); + inv_mat[0] = y2 / det; + inv_mat[1] = x2.neg / det; + inv_mat[2] = y1.neg / det; + inv_mat[3] = x1 / det; ^true; }) }