From e3798e98759f267ac17f52f70e29f02194ad0bb5 Mon Sep 17 00:00:00 2001 From: choiharam Date: Thu, 4 Jun 2026 02:08:17 +0200 Subject: [PATCH 1/5] VBAP: fix 2D loudspeaker-set parse stride (heap underflow, crash on free) VBAPSpeakerArray writes 10 floats per 2D set (2 speaker indices + 4 inverse + 4 forward matrix), but VBAP_Ctor parsed 2D at stride 6, miscounting the sets and reading matrix floats as speaker indices. That yields ls[i]==0, so final_gs[ls[i]-1] writes final_gs[-1] -- a heap underflow that corrupts the realtime pool and crashes scsynth when the VBAP unit is freed (VBAP_Dtor -> RTFree). Parse 2D like 3D: stride (dim*dim*2)+dim, and read the forward matrix for 2D as well. Confirmed with AddressSanitizer (overflow gone, valid in-bounds final_gs writes) and a churn test (no crash). Also fixes x_set_matx being uninitialized in the 2D gains_modified branch. --- source/VBAPUGens/VBAP.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/VBAPUGens/VBAP.cpp b/source/VBAPUGens/VBAP.cpp index 67ecaf503..c56763d62 100644 --- a/source/VBAPUGens/VBAP.cpp +++ b/source/VBAPUGens/VBAP.cpp @@ -646,7 +646,7 @@ static void VBAP_Ctor(VBAP* unit) 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); + counter = (numvals - 2) / ((unit->x_dimension * unit->x_dimension*2) + unit->x_dimension); unit->x_lsset_amount=counter; if(counter<=0){ @@ -674,7 +674,7 @@ static void VBAP_Ctor(VBAP* unit) 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++]; From e1d8ee146f6fb035c598e3a627c5c97b3232db5f Mon Sep 17 00:00:00 2001 From: Michael McCrea Date: Sat, 20 Jun 2026 22:39:10 -0400 Subject: [PATCH 2/5] rename vars and add comments to clarify speaker data structure --- source/VBAPUGens/VBAP.cpp | 54 ++++++++++++++++++------------------- source/VBAPUGens/sc/vbap.sc | 45 ++++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 40 deletions(-) diff --git a/source/VBAPUGens/VBAP.cpp b/source/VBAPUGens/VBAP.cpp index c56763d62..c9f6cb73a 100644 --- a/source/VBAPUGens/VBAP.cpp +++ b/source/VBAPUGens/VBAP.cpp @@ -101,8 +101,8 @@ struct VBAP : Unit 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_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 */ @@ -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; } } @@ -430,7 +430,7 @@ static void vbap(float g[3], int ls[3], VBAP *x) best_neg_g_am=3; /* 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; for(j=0;jx_lsset_available ==1){ vbap(g,ls, unit); - for(i=0;ix_ls_amount;i++) + 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]; @@ -600,7 +600,7 @@ static inline_functions void VBAP_next_simd(VBAP *unit, int inNumSamples) static void VBAP_Ctor(VBAP* unit) { //printf("VBAP-1.0.3.2\n"); - int numOutputs = unit->mNumOutputs, counter = 0, datapointer=0, setpointer=0, i; + int numOutputs = unit->mNumOutputs, numSets=0, datapointer=0, setpointer=0, i; unit->m_chanamp = (float*)RTAlloc(unit->mWorld, numOutputs * sizeof(float)); @@ -628,44 +628,44 @@ 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++]); + int dataLength = buf->samples; // loudspeaker data buffer length + int dim = unit->x_dimension = (int)(buf->data[datapointer++]); // 2D or 3D + unit->x_num_ls = (int)(buf->data[datapointer++]); // number of loudspeakers 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)) { + if(((dim != 2) && (dim != 3)) || (unit->x_num_ls < 2)) { printf("vbap: Error in loudspeaker data. Bufnum: %i\n", (int)fbufnum); unit->x_lsset_available = 0; // do something else here } - 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*2) + unit->x_dimension); - unit->x_lsset_amount=counter; + // vbap.sc: data_length = 2 + (numSets * (dim + (dim * dim) + (dim * dim))); + numSets = (dataLength - 2) / (dim + (dim * dim) + (dim * dim)); + unit->x_num_lssets = numSets; - if(counter<=0){ + if(numSets <= 0){ printf("vbap: Error in loudspeaker data. Bufnum: %i\n", (int)fbufnum); unit->x_lsset_available=0; // return; } - 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->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)); } // probably sets should be created with rtalloc - while(counter-- > 0){ + int setCounter = numSets; + while(setCounter-- > 0){ for(i=0; i < unit->x_dimension; i++){ unit->x_lsset[setpointer][i]=(int)buf->data[datapointer++]; } @@ -707,7 +707,7 @@ static void VBAP_Ctor(VBAP* unit) if(unit->x_lsset_available ==1){ vbap(g,ls, unit); - for(i=0;ix_ls_amount;i++) + 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]; @@ -718,7 +718,7 @@ static void VBAP_Ctor(VBAP* unit) } else { // if the ls data was bad, just set every gain to 0 and bail - for(i=0;ix_ls_amount;i++) + for(i=0;ix_num_ls;i++) final_gs[i]=0.f; } } @@ -726,7 +726,7 @@ static void VBAP_Ctor(VBAP* unit) static void VBAP_Dtor(VBAP* unit) { - int counter = unit->x_lsset_amount; + int counter = unit->x_num_lssets; RTFree(unit->mWorld, unit->final_gs); for(int i=0; i Date: Sat, 20 Jun 2026 23:37:43 -0400 Subject: [PATCH 3/5] vbap ctor: fix mem leak, initializations, add checks, clean exit on fail - Replace unnecessary initializations with VBAP_calc_gain_factors. - Fix memory leak - free m_chanamp - Add check that numOutputs == num_ls - Exit cleanly on data buffer corruption - Reorg for proper read/write order and legibility - Catch loudspeaker data buffer stride error and add comments + formatting, cleanup --- source/VBAPUGens/VBAP.cpp | 207 +++++++++++++++++++------------------- 1 file changed, 103 insertions(+), 104 deletions(-) diff --git a/source/VBAPUGens/VBAP.cpp b/source/VBAPUGens/VBAP.cpp index c9f6cb73a..913c53d60 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_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 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; @@ -516,20 +516,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_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] ); -// } - } + 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 +594,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, numSets=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; - // [dim, numSpeakers, [chanOffsets 0-2, invmx 0-8, [lp1, lp2, lp2].x, sim.y, sim.z] * sets.size].flat - float fbufnum = ZIN0(1); + /* Access loudspeaker data buffer, validate */ + + float fbufnum = ZIN0(1); uint32 ibufnum = (uint32)fbufnum; World *world = unit->mWorld; @@ -628,31 +620,60 @@ static void VBAP_Ctor(VBAP* unit) buf = world->mSndBufs + ibufnum; } - int dataLength = buf->samples; // loudspeaker data buffer length - int dim = unit->x_dimension = (int)(buf->data[datapointer++]); // 2D or 3D - unit->x_num_ls = (int)(buf->data[datapointer++]); // number of loudspeakers - - 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(((dim != 2) && (dim != 3)) || (unit->x_num_ls < 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; } - // vbap.sc: data_length = 2 + (numSets * (dim + (dim * dim) + (dim * dim))); - numSets = (dataLength - 2) / (dim + (dim * dim) + (dim * dim)); - unit->x_num_lssets = numSets; - - if(numSets <= 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->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*)); @@ -663,27 +684,23 @@ static void VBAP_Ctor(VBAP* unit) unit->x_set_matx[i] = (float*)RTAlloc(unit->mWorld, 9 * sizeof(float)); unit->x_lsset[i] = (int*)RTAlloc(unit->mWorld, 3 * sizeof(int)); } - // probably sets should be created with rtalloc + + /* 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 == 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 +709,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_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); - } - - } else { - // if the ls data was bad, just set every gain to 0 and bail - for(i=0;ix_num_ls;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_num_lssets; - 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 counter = 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); } ////////////////////////////////////////////////////////////////////////////////////////////////// From d26587c04a10e1d17f3c162782fa53e73acb1761 Mon Sep 17 00:00:00 2001 From: Michael McCrea Date: Mon, 22 Jun 2026 13:34:44 -0400 Subject: [PATCH 4/5] vbap.cpp: fix internal matrix handling in 2D vs. 3D case - reorder matrix in 2D case for uniformity with 3D - give setmtx appropriate dimension and indexing + fix variable initializations and rewrite gain setters for clarity + rename some vars for clarity --- source/VBAPUGens/VBAP.cpp | 81 ++++++++++++++++++------------------- source/VBAPUGens/sc/vbap.sc | 65 ++++++++++++++++------------- 2 files changed, 77 insertions(+), 69 deletions(-) diff --git a/source/VBAPUGens/VBAP.cpp b/source/VBAPUGens/VBAP.cpp index 913c53d60..32c2238d9 100644 --- a/source/VBAPUGens/VBAP.cpp +++ b/source/VBAPUGens/VBAP.cpp @@ -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_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; @@ -680,9 +677,9 @@ static void VBAP_Ctor(VBAP* unit) // 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, 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)); + unit->x_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)); } /* Read data from loudspeaker data buffer into vars */ @@ -723,11 +720,11 @@ static void VBAP_Dtor(VBAP* unit) { if (unit->x_lsset_available > 0) { // only free if initialization occured - int counter = unit->x_num_lssets; + 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]); diff --git a/source/VBAPUGens/sc/vbap.sc b/source/VBAPUGens/sc/vbap.sc index 1f4570eba..dc7cf0f58 100644 --- a/source/VBAPUGens/sc/vbap.sc +++ b/source/VBAPUGens/sc/vbap.sc @@ -321,7 +321,7 @@ VBAPSpeakerArray { var lp1, lp2, lp3; var invmx; var numTriplets, pointer, data_length; - var result; + var mat; var dim; if(sets.isNil, { @@ -341,10 +341,10 @@ VBAPSpeakerArray { dim = 3; // 3D, loudspeaker sets are triplets numTriplets = sets.size; // number of triplets data_length = 2 + (numTriplets * (dim + (dim * dim) + (dim * dim))); - result = FloatArray.newClear(data_length); + mat = FloatArray.newClear(data_length); - result[0] = dim; - result[1] = numSpeakers; + mat[0] = dim; + mat[1] = numSpeakers; pointer=2; sets.do({|set| @@ -374,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 { @@ -535,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; @@ -551,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; }) } From 6ebae7c8deb2e5ae2ed61ebb5c57ed712c56725d Mon Sep 17 00:00:00 2001 From: Michael McCrea Date: Thu, 25 Jun 2026 15:44:20 -0400 Subject: [PATCH 5/5] help: VBAP: add warning about speaker gaps, clarifications + don't set s to internal server (outdated)) + formatting + VBAPSpeakerArray: remove non-existent maxNumSpeakers method --- .../sc/HelpSource/Classes/VBAP.schelp | 175 +++++++++++------- .../Classes/VBAPSpeakerArray.schelp | 38 ++-- 2 files changed, 133 insertions(+), 80 deletions(-) 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; ::