SAF
Loading...
Searching...
No Matches
saf_vbap.c
Go to the documentation of this file.
1/*
2 * Copyright 2017-2018 Leo McCormack
3 *
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10 * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
13 * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14 * PERFORMANCE OF THIS SOFTWARE.
15 */
16
32#include "saf_vbap.h"
33#include "saf_vbap_internal.h"
34
38#define ENABLE_VBAP_DEBUGGING_CODE 0
39#if ENABLE_VBAP_DEBUGGING_CODE
41# define SAVE_PATH "../faces.txt"
43# define SAVE_PATH2 "../vbapGains_compressed.txt"
45# define SAVE_PATH3 "../vbapGains_table.txt"
46#endif
47
48/* ========================================================================== */
49/* Misc. Functions */
50/* ========================================================================== */
51
53(
54 float* src_dirs_deg,
55 int S,
56 float* ls_dirs_deg,
57 int L,
58 int omitLargeTriangles,
59 int enableDummies,
60 float spread,
61 float** gtable /* &: S x L */,
62 int* N_gtable /* & S */,
63 int* nTriangles
64)
65{
66 int N_points, numOutVertices, numOutFaces;
67 int* out_faces;
68 float *out_vertices, *layoutInvMtx;
69 int i, L_d;
70 int needDummy[2] = {1, 1};
71 float* ls_dirs_d_deg;
72
73 /* find loudspeaker triangles */
74 out_vertices = NULL;
75 out_faces = NULL;
76 if(enableDummies){
77 /* scan the loudspeaker directions to see if dummies need to be added */
78 for(i=0; i<L; i++){
79 if(ls_dirs_deg[i*2+1] <= -ADD_DUMMY_LIMIT)
80 needDummy[0] = 0;
81 if(ls_dirs_deg[i*2+1] >= ADD_DUMMY_LIMIT)
82 needDummy[1] = 0;
83 }
84 if(needDummy[0] || needDummy[1]){
85 /* add dummies to the extreme top/bottom as required */
86 L_d = L+needDummy[0]+needDummy[1];
87 ls_dirs_d_deg = malloc1d(L_d*2*sizeof(float));
88 memcpy(ls_dirs_d_deg, ls_dirs_deg, L*2*sizeof(float));
89 if (needDummy[0]){
90 ls_dirs_d_deg[i*2+0] = 0.0f;
91 ls_dirs_d_deg[i*2+1] = -90.0f;
92 i++;
93 }
94 if (needDummy[1]){
95 ls_dirs_d_deg[i*2+0] = 0.0f;
96 ls_dirs_d_deg[i*2+1] = 90.0f;
97 }
98
99 /* triangulate while including the dummy loudspeaker directions */
100 findLsTriplets(ls_dirs_d_deg, L_d, omitLargeTriangles, &out_vertices, &numOutVertices, &out_faces, &numOutFaces);
101 free(ls_dirs_d_deg);
102 }
103 else /* triangulate as normal */
104 findLsTriplets(ls_dirs_deg, L, omitLargeTriangles, &out_vertices, &numOutVertices, &out_faces, &numOutFaces);
105 }
106 else
107 findLsTriplets(ls_dirs_deg, L, omitLargeTriangles, &out_vertices, &numOutVertices, &out_faces, &numOutFaces);
108#if ENABLE_VBAP_DEBUGGING_CODE
109 /* save faces and vertices for verification in matlab: */
110 FILE* objfile = fopen(SAVE_PATH, "wt");
111 fprintf(objfile, "faces = [\n");
112 for (i = 0; i < numOutFaces; i++) {
113 fprintf(objfile, " %u, %u, %u;\n",
114 out_faces[3*i+0],
115 out_faces[3*i+1],
116 out_faces[3*i+2]);
117 }
118 fprintf(objfile, "];\n\n\n");
119 fprintf(objfile, "vert = [\n");
120 for (i = 0; i < numOutVertices; i++) {
121 fprintf(objfile, " %f, %f, %f;\n",
122 out_vertices[3*i+0],
123 out_vertices[3*i+1],
124 out_vertices[3*i+2]);
125 }
126 fprintf(objfile, "];\n\n\n");
127 fclose(objfile);
128#endif
129
130 /* Invert matrix */
131 layoutInvMtx = NULL;
132 invertLsMtx3D(out_vertices, out_faces, numOutFaces, &layoutInvMtx);
133
134 /* Calculate VBAP gains for each source position */
135 N_points = S;
136 vbap3D(src_dirs_deg, N_points, numOutVertices, out_faces, numOutFaces, spread, layoutInvMtx, gtable);
137 if(enableDummies){
138 if(needDummy[0] || needDummy[1]){
139 /* remove the gains for the dummy loudspeakers, they have served their purpose and can now be laid to rest */
140 for(i=0; i<N_points; i++)
141 memmove(&(*gtable)[i*L], &(*gtable)[i*numOutVertices], L*sizeof(float));
142 (*gtable) = realloc((*gtable), N_points*L*sizeof(float));
143 }
144 }
145
146 /* output */
147 (*N_gtable) = N_points;
148 (*nTriangles) = numOutFaces;
149#if ENABLE_VBAP_DEBUGGING_CODE
150 /* save gain table for verification in matlab: */
151 FILE* objfile2 = fopen(SAVE_PATH3, "wt");
152 fprintf(objfile2, "vbap_gtable = [\n");
153 for (i = 0; i < N_points; i++) {
154 for (j = 0; j < L; j++) {
155 fprintf(objfile2, " %f", (*gtable)[3*L+j] );
156 if(j<L-1)
157 fprintf(objfile2,",");
158 }
159 fprintf(objfile2, ";\n");
160 }
161 fprintf(objfile2, "];\n\n\n");
162 fclose(objfile2);
163#endif
164
165 /* clean up */
166 free(out_vertices);
167 free(out_faces);
168 free(layoutInvMtx);
169}
170
172(
173 float* ls_dirs_deg,
174 int L,
175 int az_res_deg,
176 int el_res_deg,
177 int omitLargeTriangles,
178 int enableDummies,
179 float spread,
180 float** gtable /* N_srcs x N_lspkrs */,
181 int* N_gtable,
182 int* nTriangles
183)
184{
185 int i, j, N_azi, N_ele, N_points, numOutVertices, numOutFaces;
186 int* out_faces;
187 float fi;
188 float* azi, *ele, *src_dirs, *out_vertices, *layoutInvMtx;
189 int L_d;
190 int needDummy[2] = {1, 1};
191 float* ls_dirs_d_deg;
192
193 /* compute source directions for the grid */
194 N_azi = (int)((360.0f/(float)az_res_deg) + 1.5f);
195 N_ele = (int)((180.0f/(float)el_res_deg) + 1.5f);
196 azi = malloc1d(N_azi * sizeof(float));
197 ele = malloc1d(N_ele * sizeof(float));
198 for(fi = -180.0f, i = 0; i<N_azi; fi+=(float)az_res_deg, i++)
199 azi[i] = fi;
200 for(fi = -90.0f, i = 0; i<N_ele; fi+=(float)el_res_deg, i++)
201 ele[i] = fi;
202 src_dirs = malloc1d((N_azi*N_ele)*2*sizeof(float));
203 for(i = 0; i<N_ele; i++){
204 for(j=0; j<N_azi; j++){
205 src_dirs[(i*N_azi + j)*2] = azi[j];
206 src_dirs[(i*N_azi + j)*2+1] = ele[i];
207 }
208 }
209
210 /* find loudspeaker triangles */
211 out_vertices = NULL;
212 out_faces = NULL;
213 if(enableDummies){
214 /* scan the loudspeaker directions to see if dummies need to be added */
215 for(i=0; i<L; i++){
216 if(ls_dirs_deg[i*2+1] <= -ADD_DUMMY_LIMIT)
217 needDummy[0] = 0;
218 if(ls_dirs_deg[i*2+1] >= ADD_DUMMY_LIMIT)
219 needDummy[1] = 0;
220 }
221 if(needDummy[0] || needDummy[1]){
222 /* add dummies to the extreme top/bottom as required */
223 L_d = L+needDummy[0]+needDummy[1];
224 ls_dirs_d_deg = malloc1d(L_d*2*sizeof(float));
225 memcpy(ls_dirs_d_deg, ls_dirs_deg, L*2*sizeof(float));
226 if (needDummy[0]){
227 ls_dirs_d_deg[i*2+0] = 0.0f;
228 ls_dirs_d_deg[i*2+1] = -90.0f;
229 i++;
230 }
231 if (needDummy[1]){
232 ls_dirs_d_deg[i*2+0] = 0.0f;
233 ls_dirs_d_deg[i*2+1] = 90.0f;
234 }
235
236 /* triangulate while including the dummy loudspeakers */
237 findLsTriplets(ls_dirs_d_deg, L_d, omitLargeTriangles, &out_vertices, &numOutVertices, &out_faces, &numOutFaces);
238 free(ls_dirs_d_deg);
239 }
240 else /* triangulate as normal */
241 findLsTriplets(ls_dirs_deg, L, omitLargeTriangles, &out_vertices, &numOutVertices, &out_faces, &numOutFaces);
242 }
243 else /* triangulate as normal */
244 findLsTriplets(ls_dirs_deg, L, omitLargeTriangles, &out_vertices, &numOutVertices, &out_faces, &numOutFaces);
245#if ENABLE_VBAP_DEBUGGING_CODE
246 /* save faces and vertices for verification in matlab: */
247 FILE* objfile = fopen(SAVE_PATH, "wt");
248 fprintf(objfile, "faces = [\n");
249 for (i = 0; i < numOutFaces; i++) {
250 fprintf(objfile, " %u, %u, %u;\n",
251 out_faces[3*i+0],
252 out_faces[3*i+1],
253 out_faces[3*i+2]);
254 }
255 fprintf(objfile, "];\n\n\n");
256 fprintf(objfile, "vert = [\n");
257 for (i = 0; i < numOutVertices; i++) {
258 fprintf(objfile, " %f, %f, %f;\n",
259 out_vertices[3*i+0],
260 out_vertices[3*i+1],
261 out_vertices[3*i+2]);
262 }
263 fprintf(objfile, "];\n\n\n");
264 fclose(objfile);
265#endif
266
267 /* Invert matrix */
268 layoutInvMtx = NULL;
269 invertLsMtx3D(out_vertices, out_faces, numOutFaces, &layoutInvMtx);
270
271 /* Calculate VBAP gains for each source position */
272 N_points = N_azi*N_ele;
273 vbap3D(src_dirs, N_points, numOutVertices, out_faces, numOutFaces, spread, layoutInvMtx, gtable);
274
275 /* remove the gains for the dummy loudspeakers, they have served their purpose and can now be laid to rest */
276 if(enableDummies){
277 if(needDummy[0] || needDummy[1]){
278 for(i=0; i<N_points; i++)
279 memmove(&(*gtable)[i*L], &(*gtable)[i*numOutVertices], L*sizeof(float));
280 (*gtable) = realloc((*gtable), N_points*L*sizeof(float));
281 }
282 }
283
284 /* output */
285 (*N_gtable) = N_points;
286 (*nTriangles) = numOutFaces;
287#if ENABLE_VBAP_DEBUGGING_CODE
288 /* save gain table for verification in matlab: */
289 FILE* objfile2 = fopen(SAVE_PATH3, "wt");
290 fprintf(objfile2, "vbap_gtable = [\n");
291 for (i = 0; i < N_points; i++) {
292 for (j = 0; j < L; j++) {
293 fprintf(objfile2, " %f", (*gtable)[i*L+j] );
294 if(j<L-1)
295 fprintf(objfile2,",");
296 }
297 fprintf(objfile2, ";\n");
298 }
299 fprintf(objfile2, "];\n\n\n");
300 fclose(objfile2);
301#endif
302
303 /* clean up */
304 free(out_vertices);
305 free(out_faces);
306 free(layoutInvMtx);
307 free(src_dirs);
308 free(azi);
309 free(ele);
310}
311
313(
314 float* vbap_gtable,
315 int nTable,
316 int nDirs,
317 float* vbap_gtableComp, /* nTable x 3 */
318 int* vbap_gtableIdx /* nTable x 3 */
319)
320{
321 int i, j, nt;
322 int idx_nt[3];
323 float gains_nt[3];
324 float gains_sum;
325
326 memset(vbap_gtableComp, 0, nTable*3*sizeof(float));
327 memset(vbap_gtableIdx, 0, nTable*3*sizeof(int));
328
329 /* compress table by keeping only the non-zero gains and their indices, and also convert to AMPLITUDE NORMALISED */
330 for(nt=0; nt<nTable; nt++){
331 gains_sum = 0.0f;
332 for(i=0, j=0; i<nDirs; i++){
333 if(vbap_gtable[nt*nDirs+i]>0.0000001f){
334 gains_nt[j] = vbap_gtable[nt*nDirs+i];
335 gains_sum += gains_nt[j];
336 idx_nt[j] = i;
337 j++;
338 }
339 }
340 //saf_assert(j<4);
341 for(i=0; i<j; i++){
342 vbap_gtableComp[nt*3+i] = SAF_MAX(gains_nt[i]/gains_sum, 0.0f);
343 vbap_gtableIdx[nt*3+i] = idx_nt[i];
344 }
345 }
346#if ENABLE_VBAP_DEBUGGING_CODE
347 /* save gain table for verification in matlab: */
348 FILE* objfile = fopen(SAVE_PATH2, "wt");
349 fprintf(objfile, "vbap_gtableComp = [\n");
350 for (i = 0; i < nTable; i++) {
351 fprintf(objfile, " %f, %f, %f;\n",
352 vbap_gtableComp[3*i+0],
353 vbap_gtableComp[3*i+1],
354 vbap_gtableComp[3*i+2]);
355 }
356 fprintf(objfile, "];\n\n\n");
357 fprintf(objfile, "vbap_gtableIdx = [\n");
358 for (i = 0; i < nTable; i++) {
359 fprintf(objfile, " %u, %u, %u;\n",
360 vbap_gtableIdx[3*i+0],
361 vbap_gtableIdx[3*i+1],
362 vbap_gtableIdx[3*i+2]);
363 }
364 fprintf(objfile, "];\n\n\n");
365 fclose(objfile);
366#endif
367}
368
370(
371 float* vbap_gtable,
372 int nTable,
373 int nDirs
374)
375{
376 int i, j;
377 float* gains_sum;
378
379 gains_sum = calloc1d(nTable,sizeof(float));
380 for(i=0; i<nTable; i++)
381 for(j=0; j<nDirs; j++)
382 gains_sum[i] += vbap_gtable[i*nDirs+j];
383 for(i=0; i<nTable; i++)
384 for(j=0; j<nDirs; j++)
385 vbap_gtable[i*nDirs+j] /= gains_sum[i];
386
387 free(gains_sum);
388}
389
391(
392 float* src_dirs_deg,
393 int S,
394 float* ls_dirs_deg,
395 int L,
396 float** gtable /* S x L */,
397 int* N_gtable,
398 int* nPairs
399)
400{
401 int i, N_points, numOutPairs;
402 int* out_pairs;
403 float *layoutInvMtx, *ls_vertices;
404
405 out_pairs = NULL;
406 findLsPairs(ls_dirs_deg, L, &out_pairs, &numOutPairs);
407 ls_vertices = malloc1d(L*2*sizeof(float));
408 for(i=0; i<L; i++){
409 ls_vertices[i*2+0] = cosf(ls_dirs_deg[i*2]*SAF_PI/180.0f);
410 ls_vertices[i*2+1] = sinf(ls_dirs_deg[i*2]*SAF_PI/180.0f);
411 }
412
413 /* Invert matrix */
414 layoutInvMtx = NULL;
415 invertLsMtx2D(ls_vertices, out_pairs, numOutPairs, &layoutInvMtx);
416
417 /* Calculate VBAP gains for each source position */
418 N_points = S;
419 vbap2D(src_dirs_deg, N_points, L, out_pairs, numOutPairs, layoutInvMtx, gtable);
420 (*nPairs) = numOutPairs;
421 (*N_gtable) = N_points;
422
423 free(ls_vertices);
424 free(out_pairs);
425 free(layoutInvMtx);
426}
427
429(
430 float* ls_dirs_deg,
431 int L,
432 int az_res_deg,
433 float** gtable /* N_gtable x L */,
434 int* N_gtable,
435 int* nPairs
436)
437{
438 int i, N_azi, N_points, numOutPairs;
439 int* out_pairs;
440 float fi;
441 float *src_dirs, *layoutInvMtx, *ls_vertices;
442
443 /* compute directions for the grid */
444 N_azi = (int)((360.0f/(float)az_res_deg) + 1.5f);
445 src_dirs = malloc1d(N_azi * sizeof(float));
446 for(fi = -180.0f, i = 0; i<N_azi; fi+=(float)az_res_deg, i++)
447 src_dirs[i] = fi;
448 out_pairs = NULL;
449 findLsPairs(ls_dirs_deg, L, &out_pairs, &numOutPairs);
450 ls_vertices = malloc1d(L*2*sizeof(float));
451 for(i=0; i<L; i++){
452 ls_vertices[i*2+0] = cosf(ls_dirs_deg[i*2]*SAF_PI/180.0f);
453 ls_vertices[i*2+1] = sinf(ls_dirs_deg[i*2]*SAF_PI/180.0f);
454 }
455
456 /* Invert matrix */
457 layoutInvMtx = NULL;
458 invertLsMtx2D(ls_vertices, out_pairs, numOutPairs, &layoutInvMtx);
459
460 /* Calculate VBAP gains for each source position */
461 N_points = N_azi;
462 vbap2D(src_dirs, N_points, L, out_pairs, numOutPairs, layoutInvMtx, gtable);
463 (*nPairs) = numOutPairs;
464 (*N_gtable) = N_points;
465
466 free(ls_vertices);
467 free(src_dirs);
468 free(out_pairs);
469 free(layoutInvMtx);
470}
471
472/* Laitinen, M., Vilkamo, J., Jussila, K., Politis, A., Pulkki, V. (2014). Gain
473 * normalisation in amplitude panning as a function of frequency and room
474 * reverberance. 55th International Conference of the AES. Helsinki, Finland. */
476(
477 float DTT,
478 float* freq,
479 int nFreq,
480 float* pValues
481)
482{
483 int i;
484 float a1, a2, p0;
485
486 a1 = 0.00045f;
487 a2 = 0.000085f;
488 for(i=0; i<nFreq; i++){
489 p0 = 1.5f - 0.5f * cosf(4.7f*tanhf(a1*freq[i])) * SAF_MAX(0.0f, 1.0f-a2*freq[i]);
490 pValues[i] = (p0-2.0f)*sqrtf(DTT)+2.0f;
491 }
492}
493
494
495/* ========================================================================== */
496/* Main Functions */
497/* ========================================================================== */
498
500(
501 float* ls_dirs_deg,
502 int L,
503 int omitLargeTriangles,
504 float** out_vertices,
505 int* numOutVertices,
506 int** out_faces,
507 int* numOutFaces
508)
509{
510 int i, j, numValidFaces, nFaces;
511 int* validFacesID, *valid_faces, *valid_faces2, *faces;
512 float dotcc, aperture_lim;
513 float vecs[3][3], cvec[3], centroid[3], a[3], b[3], abc[3];
514 CH_FLOAT rcoselev;
515 ch_vertex* vertices;
516
517 /* Build the convex hull for the points on the sphere - in this special case the
518 result equals the Delaunay triangulation of the points */
519 vertices = malloc1d(L*sizeof(ch_vertex));
520 (*numOutVertices) = L;
521 (*out_vertices) = (float*)malloc1d(L*3*sizeof(float));
522 for ( i = 0; i < L; i++) {
523 (*out_vertices)[i*3+2] = (float)((CH_FLOAT)sin((double)ls_dirs_deg[i*2+1]* SAF_PId/180.0));
524 rcoselev = (CH_FLOAT)cos((double)ls_dirs_deg[i*2+1]*SAF_PId/180.0);
525 (*out_vertices)[i*3+0] = (float)(rcoselev * (CH_FLOAT)cos((double)ls_dirs_deg[i*2+0]*SAF_PId/180.0));
526 (*out_vertices)[i*3+1] = (float)(rcoselev * (CH_FLOAT)sin((double)ls_dirs_deg[i*2+0]*SAF_PId/180.0));
527 vertices[i].x = (*out_vertices)[i*3+0];
528 vertices[i].y = (*out_vertices)[i*3+1];
529 vertices[i].z = (*out_vertices)[i*3+2];
530 }
531 faces = NULL;
532 convhull_3d_build(vertices, L, &faces, NULL, NULL, &nFaces);
533#ifndef NDEBUG
534 if(faces==NULL)
535 saf_print_error("Failed to compute the Convex Hull of the specified vertices.");
536#endif
537
538#if 0
539 int k, minIntVal, minIdx, nFaces;
540 int tmp3[3], circface[3];
541
542 /* circularily shift the indices to start from lowest value */
543 for(i=0; i<nFaces; i++){
544 minIntVal = L;
545 minIdx = 0;
546 for(j=0; j<3; j++){
547 if (faces[i*3+j] < minIntVal){
548 minIntVal = faces[i*3+j];
549 minIdx=j;
550 }
551 }
552 for(j=minIdx, k=0; j<minIdx+3; j++, k++)
553 circface[k] = faces[i*3+(j % 3)];
554 for(j=0; j<3; j++)
555 faces[i*3+j] = circface[j];
556 }
557
558 /* sort indices in accending order for the first dimension */
559 for (j=0; j<nFaces - 1; j++) {
560 for (i=0; i<nFaces - 1; i++) {
561 if (faces[(i+1)*3+0] < faces[i*3+0]) {
562 for(k=0; k<3; k++){
563 tmp3[k] = faces[i*3+k];
564 faces[i*3+k] = faces[(i+1)*3+k];
565 faces[(i+1)*3+k] = tmp3[k];
566 }
567 }
568 }
569 }
570
571 /* sort indices in accending order for the second dimension */
572 for (j=0; j<nFaces - 1; j++) {
573 for (i=0; i<nFaces - 1; i++) {
574 if ( (faces[(i+1)*3+1] < faces[i*3+1]) && (faces[(i+1)*3+0] == faces[i*3+0]) ) {
575 for(k=0; k<3; k++){
576 tmp3[k] = faces[i*3+k];
577 faces[i*3+k] = faces[(i+1)*3+k];
578 faces[(i+1)*3+k] = tmp3[k];
579 }
580 }
581 }
582 }
583#endif
584
585 /* Omit triplets if their normals and the centroid to the triplets have an angle larger than pi/2 */
586 numValidFaces = 0;
587 validFacesID = malloc1d(nFaces*sizeof(int));
588 for(i=0; i<nFaces; i++){
589 for(j=0; j<3; j++){
590 vecs[0][j] = (*out_vertices)[faces[i*3+0]*3+j];
591 vecs[1][j] = (*out_vertices)[faces[i*3+1]*3+j];
592 vecs[2][j] = (*out_vertices)[faces[i*3+2]*3+j];
593 }
594 for(j=0; j<3; j++){
595 a[j] = vecs[1][j]-vecs[0][j];
596 b[j] = vecs[2][j]-vecs[1][j];
597 }
598 ccross(a, b, cvec);
599 for(j=0; j<3; j++)
600 centroid[j] = (vecs[0][j] + vecs[1][j] + vecs[2][j])/3.0f;
601 dotcc = cvec[0] * centroid[0] + cvec[1] * centroid[1] + cvec[2] * centroid[2];
602 if(acosf(SAF_MAX(SAF_MIN(dotcc,0.99999999f),-0.99999999f)/* avoids complex numbers */)<(SAF_PI/2.0f)){
603 validFacesID[i] = 1;
604 numValidFaces++;
605 }
606 else{
607 validFacesID[i] = 0;
608 }
609 }
610 valid_faces = malloc1d(numValidFaces*3*sizeof(int));
611 for(i=0, j=0; i<nFaces; i++){
612 if(validFacesID[i]==1){
613 valid_faces[j*3+0] = faces[i*3+0];
614 valid_faces[j*3+1] = faces[i*3+1];
615 valid_faces[j*3+2] = faces[i*3+2];
616 j++;
617 }
618 }
619 free(validFacesID);
620
621 /* Omit Trianges that have an aperture larger than APERTURE_LIMIT_DEG */
622 valid_faces2 = NULL;
623 if(omitLargeTriangles) {
624 aperture_lim = APERTURE_LIMIT_DEG * SAF_PI/180.0f;
625 nFaces = numValidFaces;
626 numValidFaces = 0;
627 validFacesID = malloc1d(nFaces*sizeof(int));
628 for(i=0; i<nFaces; i++){
629 for(j=0; j<3; j++){
630 vecs[0][j] = (*out_vertices)[valid_faces[i*3+0]*3+j];
631 vecs[1][j] = (*out_vertices)[valid_faces[i*3+1]*3+j];
632 vecs[2][j] = (*out_vertices)[valid_faces[i*3+2]*3+j];
633 }
634 dotcc = vecs[0][0] * vecs[1][0] + vecs[0][1] * vecs[1][1] + vecs[0][2] * vecs[1][2];
635 abc[0] = acosf(dotcc);
636 dotcc = vecs[1][0] * vecs[2][0] + vecs[1][1] * vecs[2][1] + vecs[1][2] * vecs[2][2];
637 abc[1] = acosf(dotcc);
638 dotcc = vecs[2][0] * vecs[0][0] + vecs[2][1] * vecs[0][1] + vecs[2][2] * vecs[0][2];
639 abc[2] = acosf(dotcc);
640 if(abc[0]<aperture_lim && abc[1]<aperture_lim && abc[2]<aperture_lim){
641 validFacesID[i] = 1;
642 numValidFaces++;
643 }
644 else{
645 validFacesID[i] = 0;
646 }
647 }
648 valid_faces2 = malloc1d(numValidFaces*3*sizeof(int));
649 for(i=0, j=0; i<nFaces; i++){
650 if(validFacesID[i]==1){
651 valid_faces2[j*3+0] = valid_faces[i*3+0];
652 valid_faces2[j*3+1] = valid_faces[i*3+1];
653 valid_faces2[j*3+2] = valid_faces[i*3+2];
654 j++;
655 }
656 }
657 free(validFacesID);
658 }
659
660 /* output valid faces */
661 (*numOutFaces) = numValidFaces;
662 (*out_faces) = (int*)malloc1d(numValidFaces*3*sizeof(int));
663 if(omitLargeTriangles)
664 memcpy((*out_faces), valid_faces2, numValidFaces*3*sizeof(int));
665 else
666 memcpy((*out_faces), valid_faces, numValidFaces*3*sizeof(int));
667
668 /* clean-up */
669 free(faces);
670 free(vertices);
671 free(valid_faces);
672 if(omitLargeTriangles)
673 free(valid_faces2);
674}
675
677(
678 float* U_spkr /* L x 3 */,
679 int* ls_groups /* N_group x 3 */,
680 int N_group,
681 float** layoutInvMtx/* N_group x 9 */
682)
683{
684 int i, j, n;
685 float tempGroup[9];
686 float tempInv[9];
687 void* hSinv;
688
689 /* pre-calculate inversions of the loudspeaker groups and store into matrix */
690 (*layoutInvMtx) = malloc1d(N_group * 9 * sizeof(float));
691 utility_sinv_create(&hSinv, 3);
692 for(n=0; n<N_group; n++){
693 /* get the unit vectors for the current group */
694 for(i=0; i<3; i++)
695 for(j=0; j<3; j++)
696 tempGroup[j*3+i] = U_spkr[ls_groups[n*3+i]*3 + j]; /* ^T */
697
698 /* get inverse of current group */
699 utility_sinv(hSinv, tempGroup, tempInv, 3);
700
701 /* store the vectorised inverse as a row in the output */
702 cblas_scopy(9, tempInv, 1, (*layoutInvMtx) + n*9, 1);
703 }
704 utility_sinv_destroy(&hSinv);
705}
706
708(
709 float src_azi_rad,
710 float src_elev_rad,
711 float spread,
712 int num_src,
713 int num_rings_3d,
714 float* U_spread
715)
716{
717 int i, j, ns, nr;
718 float theta, sin_theta, cos_theta, scale, spread_rad, ring_rad, U_spread_norm;
719 float u[3], u_x_u[3][3], u_x[3][3], R_theta[3][3], uu2[3], spreadbase_ns[3];
720 float* spreadbase;
721
722 /* rotation matrix using the axis of rotation-angle definition (around source direction) */
723 u[0] = cosf(src_elev_rad) * cosf(src_azi_rad);
724 u[1] = cosf(src_elev_rad) * sinf(src_azi_rad);
725 u[2] = sinf(src_elev_rad);
726 u_x_u[0][0] = powf(u[0],2.0f); u_x_u[0][1] = u[0]*u[1]; u_x_u[0][2] = u[0]*u[2];
727 u_x_u[1][0] = u[0]*u[1]; u_x_u[1][1] = powf(u[1],2.0f); u_x_u[1][2] = u[1]*u[2];
728 u_x_u[2][0] = u[0]*u[2]; u_x_u[2][1] = u[1]*u[2]; u_x_u[2][2] = powf(u[2],2.0f);
729 u_x[0][0] = 0.0f; u_x[0][1] = -u[2]; u_x[0][2] = u[1];
730 u_x[1][0] = u[2]; u_x[1][1] = 0.0f; u_x[1][2] = -u[0];
731 u_x[2][0] = -u[1]; u_x[2][1] = u[0]; u_x[2][2] = 0.0f;
732 theta = 2.0f*SAF_PI/(float)num_src;
733 sin_theta = sinf(theta);
734 cos_theta = cosf(theta);
735 for(i=0; i<3; i++)
736 for(j=0; j<3; j++)
737 R_theta[i][j] = sin_theta*u_x[i][j] + (1.0f-cos_theta)*u_x_u[i][j] + (i==j ? cos_theta : 0.0f);
738
739 /* create a ring of sources on the plane that is purpendicular to the source directions */
740 spreadbase = calloc1d(num_src*3, sizeof(float));
741 if ((src_elev_rad > SAF_PI/2.0f-0.01f ) || (src_elev_rad<-(SAF_PI/2.0f-0.01f)))
742 spreadbase[0] = 1.0f;
743 else{
744 const float u2[3] = {0.0f, 0.0f, 1.0f};
745 ccross(u, (float*)u2, uu2);
746 scale = 0.0f;
747 for(i=0; i<3; i++)
748 scale += powf(uu2[i],2.0f);
749 scale = sqrtf(scale);
750 for(i=0; i<3; i++)
751 spreadbase[i] = uu2[i]/scale;
752 }
753
754 /* get ring of directions by rotating the first vector around the source */
755 for (ns = 1; ns<num_src; ns++){
756 for(i=0; i<3; i++)
757 spreadbase_ns[i] = spreadbase[(ns-1)*3+i];
758 cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, 3, 1, 3, 1.0f,
759 (const float*)R_theta, 3,
760 spreadbase_ns, 1, 0.0f,
761 &spreadbase[ns*3], 1);
762 }
763
764 /* squeeze the perpendicular ring to the desired spread */
765 spread_rad = (spread/2.0f)*SAF_PI/180.0f;
766 ring_rad = spread_rad/(float)num_rings_3d;
767 memset(U_spread, 0, num_rings_3d*num_src*3*sizeof(float));
768 for(nr=0; nr<num_rings_3d; nr++)
769 for (ns = 0; ns<num_src; ns++)
770 for(i=0; i<3; i++)
771 U_spread[(nr*num_src + ns)*3 + i] = u[i] + spreadbase[ns*3+i]*tanf(ring_rad*(float)(nr+1));
772
773 /* normalise vectors to unity (based on first vector) */
774 U_spread_norm = sqrtf(powf(U_spread[0],2.0f) + powf(U_spread[1],2.0f) + powf(U_spread[2],2.0f));
775 for(i=0; i<num_rings_3d*num_src*3; i++)
776 U_spread[i] /= U_spread_norm;
777
778 /* append the original source direction at the end */
779 for(i=0; i<3; i++)
780 U_spread[(num_rings_3d*num_src)*3 + i] = u[i];
781
782 free(spreadbase);
783}
784
785
787(
788 float* src_dirs,
789 int src_num,
790 int ls_num,
791 int* ls_groups,
792 int nFaces,
793 float spread,
794 float* layoutInvMtx,
795 float** GainMtx
796)
797{
798 int i, j, ns, nspr;
799 float azi_rad, elev_rad, min_val, g_tmp_rms, gains_rms;
800 float u[3], g_tmp[3], ls_invMtx_s[3];
801 float* gains;
802
803 (*GainMtx) = malloc1d(src_num*ls_num*sizeof(float));
804 gains = malloc1d(ls_num*sizeof(float));
805
806 /* MDAP (with spread) */
807 if (spread > 0.1f) {
808 const int nSpreadSrcs = 8;
809 const int nRings = 1;
810 float* U_spread;
811 U_spread = malloc1d((nRings*nSpreadSrcs+1)*3*sizeof(float));
812 for(ns=0; ns<src_num; ns++){
813 azi_rad = src_dirs[ns*2+0]*SAF_PI/180.0f;
814 elev_rad = src_dirs[ns*2+1]*SAF_PI/180.0f;
815 getSpreadSrcDirs3D(azi_rad, elev_rad, spread, nSpreadSrcs, nRings, U_spread);
816 memset(gains, 0, ls_num*sizeof(float));
817 for(nspr=0; nspr<(nRings*nSpreadSrcs+1); nspr++){
818 u[0] = U_spread[nspr*3+0];
819 u[1] = U_spread[nspr*3+1];
820 u[2] = U_spread[nspr*3+2];
821 for(i=0; i<nFaces; i++){
822 for(j=0; j<3; j++)
823 ls_invMtx_s[j] = layoutInvMtx[i*9+j];
824 utility_svvdot(ls_invMtx_s, u, 3, &g_tmp[0]);
825 for(j=0; j<3; j++)
826 ls_invMtx_s[j] = layoutInvMtx[i*9+j+3];
827 utility_svvdot(ls_invMtx_s, u, 3, &g_tmp[1]);
828 for(j=0; j<3; j++)
829 ls_invMtx_s[j] = layoutInvMtx[i*9+j+6];
830 utility_svvdot(ls_invMtx_s, u, 3, &g_tmp[2]);
831 min_val = 2.23e13f;
832 g_tmp_rms = 0.0;
833 for(j=0; j<3; j++){
834 min_val = SAF_MIN(min_val, g_tmp[j]);
835 g_tmp_rms += powf(g_tmp[j], 2.0f);
836 }
837 g_tmp_rms = sqrtf(g_tmp_rms);
838 if(min_val>-0.001){
839 for(j=0; j<3; j++)
840 gains[ls_groups[i*3+j]] += g_tmp[j]/g_tmp_rms;
841 }
842 }
843 }
844 gains_rms = 0.0;
845 for(i=0; i<ls_num; i++)
846 gains_rms += powf(gains[i], 2.0f);
847 gains_rms = sqrtf(gains_rms);
848 for(i=0; i<ls_num; i++)
849 (*GainMtx)[ns*ls_num+i] = SAF_MAX(gains[i]/gains_rms, 0.0f);
850 }
851
852 free(U_spread);
853 }
854 /* VBAP (no spread) */
855 else{
856 for(ns=0; ns<src_num; ns++){
857 azi_rad = src_dirs[ns*2+0]*SAF_PI/180.0f;
858 elev_rad = src_dirs[ns*2+1]*SAF_PI/180.0f;
859 u[0] = cosf(azi_rad)*cosf(elev_rad);
860 u[1] = sinf(azi_rad)*cosf(elev_rad);
861 u[2] = sinf(elev_rad);
862 memset(gains, 0, ls_num*sizeof(float));
863 for(i=0; i<nFaces; i++){
864 for(j=0; j<3; j++)
865 ls_invMtx_s[j] = layoutInvMtx[i*9+j];
866 utility_svvdot(ls_invMtx_s, u, 3, &g_tmp[0]);
867 for(j=0; j<3; j++)
868 ls_invMtx_s[j] = layoutInvMtx[i*9+j+3];
869 utility_svvdot(ls_invMtx_s, u, 3, &g_tmp[1]);
870 for(j=0; j<3; j++)
871 ls_invMtx_s[j] = layoutInvMtx[i*9+j+6];
872 utility_svvdot(ls_invMtx_s, u, 3, &g_tmp[2]);
873 min_val = 2.23e13f;
874 g_tmp_rms = 0.0;
875 for(j=0; j<3; j++){
876 min_val = SAF_MIN(min_val, g_tmp[j]);
877 g_tmp_rms += powf(g_tmp[j], 2.0f);
878 }
879 g_tmp_rms = sqrtf(g_tmp_rms);
880 if(min_val>-0.001){
881 for(j=0; j<3; j++)
882 gains[ls_groups[i*3+j]] = g_tmp[j]/g_tmp_rms;
883 break;
884 }
885 }
886 gains_rms = 0.0;
887 for(i=0; i<ls_num; i++)
888 gains_rms += powf(gains[i], 2.0f);
889 gains_rms = sqrtf(gains_rms);
890 for(i=0; i<ls_num; i++)
891 (*GainMtx)[ns*ls_num+i] = SAF_MAX(gains[i]/gains_rms, 0.0f);
892 }
893 }
894
895 free(gains);
896}
897
899(
900 float* ls_dirs_deg,
901 int L,
902 int** out_pairs,
903 int* numOutPairs
904)
905{
906 int n;
907 float* ls_dirs_deg_tmp;
908 int* idx_sorted;
909
910 ls_dirs_deg_tmp = malloc1d(L*sizeof(float));
911 idx_sorted = malloc1d(L*sizeof(int));
912 for(n=0; n<L; n++)
913 ls_dirs_deg_tmp[n] = ls_dirs_deg[n*2];
914
915 /* find the loudspeaker pairs by sorting the angles */
916 sortf(ls_dirs_deg_tmp, NULL, idx_sorted, L, 0);
917 idx_sorted = realloc(idx_sorted, (L+1)*sizeof(int));
918 idx_sorted[L] = idx_sorted[0];
919 (*out_pairs) = malloc1d(L*2*sizeof(int));
920 for(n=0; n<L; n++){
921 (*out_pairs)[n*2] = idx_sorted[n];
922 (*out_pairs)[n*2+1] = idx_sorted[n+1];
923 }
924 (*numOutPairs) = L;
925
926 free(ls_dirs_deg_tmp);
927 free(idx_sorted);
928}
929
931(
932 float* U_spkr /* L x 2 */,
933 int* ls_pairs /* N_group x 2 */,
934 int N_pairs,
935 float** layoutInvMtx/* N_group x 4 */
936)
937{
938 int i, j, n;
939 float tempGroup[4];
940 float tempInv[4];
941 void* hSinv;
942
943 /* pre-calculate inversions of the loudspeaker groups and store into matrix */
944 (*layoutInvMtx) = malloc1d(N_pairs * 4 * sizeof(float));
945 utility_sinv_create(&hSinv, 2);
946 for(n=0; n<N_pairs; n++){
947 /* get the unit vectors for the current group */
948 for(i=0; i<2; i++)
949 for(j=0; j<2; j++)
950 tempGroup[j*2+i] = U_spkr[ls_pairs[n*2+i]*2 + j]; /* ^T */
951
952 /* get inverse of current group */
953 utility_sinv(hSinv, tempGroup, tempInv, 2);
954
955 /* store the vectorised inverse as a row in the output */
956 cblas_scopy(4, tempInv, 1, (*layoutInvMtx) + n*4, 1);
957 }
958
959 utility_sinv_destroy(&hSinv);
960}
961
963(
964 float* src_dirs,
965 int src_num,
966 int ls_num,
967 int* ls_pairs,
968 int N_pairs,
969 float* layoutInvMtx,
970 float** GainMtx
971)
972{
973 int i, j, ns;
974 float azi_rad, min_val, g_tmp_rms, gains_rms;
975 float u[2], g_tmp[2], ls_invMtx_s[2];
976 float* gains;
977
978 (*GainMtx) = malloc1d(src_num*ls_num*sizeof(float));
979 gains = malloc1d(ls_num*sizeof(float));
980 for(ns=0; ns<src_num; ns++){
981 azi_rad = src_dirs[ns]*SAF_PI/180.0f;
982 u[0] = cosf(azi_rad);
983 u[1] = sinf(azi_rad);
984 memset(gains, 0, ls_num*sizeof(float));
985 for(i=0; i<N_pairs; i++){
986 for(j=0; j<2; j++)
987 ls_invMtx_s[j] = layoutInvMtx[i*4+j];
988 cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasTrans, 1, 1, 2, 1.0,
989 ls_invMtx_s, 2,
990 u, 2, 0.0,
991 &g_tmp[0], 1);
992 for(j=0; j<2; j++)
993 ls_invMtx_s[j] = layoutInvMtx[i*4+j+2];
994 cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasTrans, 1, 1, 2, 1.0,
995 ls_invMtx_s, 2,
996 u, 2, 0.0,
997 &g_tmp[1], 1);
998 min_val = 2.23e13f;
999 g_tmp_rms = 0.0;
1000 for(j=0; j<2; j++){
1001 min_val = SAF_MIN(min_val, g_tmp[j]);
1002 g_tmp_rms += powf(g_tmp[j], 2.0f);
1003 }
1004 g_tmp_rms = sqrtf(g_tmp_rms);
1005 if(min_val>-0.001){
1006 for(j=0; j<2; j++)
1007 gains[ls_pairs[i*2+j]] = g_tmp[j]/g_tmp_rms;
1008 }
1009 }
1010 gains_rms = 0.0;
1011 for(i=0; i<ls_num; i++)
1012 gains_rms += powf(gains[i], 2.0f);
1013 gains_rms = sqrtf(gains_rms);
1014 for(i=0; i<ls_num; i++)
1015 (*GainMtx)[ns*ls_num+i] = SAF_MAX(gains[i]/gains_rms, 0.0f);
1016 }
1017
1018 free(gains);
1019}
void convhull_3d_build(ch_vertex *const in_vertices, const int nVert, int **out_faces, CH_FLOAT **out_cf, CH_FLOAT **out_df, int *nOut_faces)
Builds the 3-D convexhull using the quickhull algorithm [1].
#define saf_print_error(message)
Macro to print a error message along with the filename and line number.
#define SAF_PI
pi constant (single precision)
void sortf(float *in_vec, float *out_vec, int *new_idices, int len, int descendFLAG)
Sort a vector of floating-point values into ascending/decending order (optionally returning the new i...
#define SAF_MAX(a, b)
Returns the maximum of the two values.
#define SAF_PId
pi constant (double precision)
void utility_sinv_create(void **const phWork, int maxN)
(Optional) Pre-allocate the working struct used by utility_sinv()
void utility_sinv(void *const hWork, float *A, float *B, const int N)
Matrix inversion: single precision, i.e.
#define SAF_MIN(a, b)
Returns the minimum of the two values.
void utility_svvdot(const float *a, const float *b, const int len, float *c)
Single-precision, vector-vector dot product, i.e.
void utility_sinv_destroy(void **const phWork)
De-allocate the working struct used by utility_sinv()
void findLsTriplets(float *ls_dirs_deg, int L, int omitLargeTriangles, float **out_vertices, int *numOutVertices, int **out_faces, int *numOutFaces)
Computes the 3D convex-hull of a spherical grid of loudspeaker directions.
Definition saf_vbap.c:500
void vbap2D(float *src_dirs, int src_num, int ls_num, int *ls_pairs, int N_pairs, float *layoutInvMtx, float **GainMtx)
Calculates 2D VBAP gains for pre-calculated loudspeaker pairs and predefined source positions.
Definition saf_vbap.c:963
void invertLsMtx3D(float *U_spkr, int *ls_groups, int N_group, float **layoutInvMtx)
Inverts a 3x3 loudspeaker matrix.
Definition saf_vbap.c:677
void getSpreadSrcDirs3D(float src_azi_rad, float src_elev_rad, float spread, int num_src, int num_rings_3d, float *U_spread)
Computes a set of points which surround the source direction given a specific degree of spread.
Definition saf_vbap.c:708
void findLsPairs(float *ls_dirs_deg, int L, int **out_pairs, int *numOutPairs)
Calculates loudspeaker pairs for a circular grid of loudspeaker directions.
Definition saf_vbap.c:899
void generateVBAPgainTable3D(float *ls_dirs_deg, int L, int az_res_deg, int el_res_deg, int omitLargeTriangles, int enableDummies, float spread, float **gtable, int *N_gtable, int *nTriangles)
Generates a 3-D VBAP gain table based on specified loudspeaker directions, with optional spreading [2...
Definition saf_vbap.c:172
void generateVBAPgainTable3D_srcs(float *src_dirs_deg, int S, float *ls_dirs_deg, int L, int omitLargeTriangles, int enableDummies, float spread, float **gtable, int *N_gtable, int *nTriangles)
Generates a 3-D VBAP [1] gain table based on specified source and loudspeaker directions,...
Definition saf_vbap.c:53
void VBAPgainTable2InterpTable(float *vbap_gtable, int nTable, int nDirs)
Renormalises a VBAP gain table (in-place) so it may be utilised for interpolation of data (for exampl...
Definition saf_vbap.c:370
void generateVBAPgainTable2D_srcs(float *src_dirs_deg, int S, float *ls_dirs_deg, int L, float **gtable, int *N_gtable, int *nPairs)
Generates a 2-D VBAP gain table based on specified source and loudspeaker directions.
Definition saf_vbap.c:391
void generateVBAPgainTable2D(float *ls_dirs_deg, int L, int az_res_deg, float **gtable, int *N_gtable, int *nPairs)
Generates a 2-D VBAP gain table based on specified loudspeaker directions.
Definition saf_vbap.c:429
void vbap3D(float *src_dirs, int src_num, int ls_num, int *ls_groups, int nFaces, float spread, float *layoutInvMtx, float **GainMtx)
Calculates 3D VBAP gains given pre-computed loudspeaker triangles for each source direction.
Definition saf_vbap.c:787
void invertLsMtx2D(float *U_spkr, int *ls_pairs, int N_pairs, float **layoutInvMtx)
Inverts a 2x2 loudspeaker matrix.
Definition saf_vbap.c:931
void compressVBAPgainTable3D(float *vbap_gtable, int nTable, int nDirs, float *vbap_gtableComp, int *vbap_gtableIdx)
Compresses a VBAP gain table to use less memory and CPU (by removing the elements which are just zero...
Definition saf_vbap.c:313
void getPvalues(float DTT, float *freq, int nFreq, float *pValues)
Calculates the frequency dependent pValues, which can be applied to ENERGY normalised VBAP gains,...
Definition saf_vbap.c:476
void * malloc1d(size_t dim1_data_size)
1-D malloc (same as malloc, but with error checking)
Definition md_malloc.c:59
void * calloc1d(size_t dim1, size_t data_size)
1-D calloc (same as calloc, but with error checking)
Definition md_malloc.c:69
Main header for the VBAP/MDAP module (SAF_VBAP_MODULE)
void ccross(float a[3], float b[3], float c[3])
Cross product between two 3-element floating point vectors.
Internal header for the VBAP/MDAP module (SAF_VBAP_MODULE)
#define APERTURE_LIMIT_DEG
if omitLargeTriangles==1, triangles with an aperture larger than this are discarded
#define ADD_DUMMY_LIMIT
In degrees, if no ls_dirs have elevation +/- this value, dummies are placed at +/- 90 elevation.
vertex structure, used by convhull_3d
Definition convhull_3d.h:62