SAF
Loading...
Searching...
No Matches
saf_hoa.c
Go to the documentation of this file.
1/*
2 * Copyright 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
33#include "saf_hoa.h"
34#include "saf_hoa_internal.h"
35
36/* ========================================================================== */
37/* Misc. Functions */
38/* ========================================================================== */
39
41(
42 float* insig,
43 int order,
44 int signalLength,
45 HOA_CH_ORDER inConvention,
46 HOA_CH_ORDER outConvention
47)
48{
49 int i, nSH;
50
51 nSH = ORDER2NSH(order);
52
53 /* bypass, if 0th order, or no conversion required */
54 if(order==0 || inConvention == outConvention)
55 return;
56
57 /* Convert FUMA to ACN */
58 if(inConvention==HOA_CH_ORDER_FUMA && outConvention==HOA_CH_ORDER_ACN){
59 cblas_sswap(signalLength, &insig[1*signalLength], 1, &insig[3*signalLength], 1); /* Swap X and Z */
60 cblas_sswap(signalLength, &insig[1*signalLength], 1, &insig[2*signalLength], 1); /* Swap Z and Y */
61 }
62 /* Convert ACN to FUMA */
63 else if(inConvention==HOA_CH_ORDER_ACN && outConvention==HOA_CH_ORDER_FUMA){
64 cblas_sswap(signalLength, &insig[1*signalLength], 1, &insig[2*signalLength], 1); /* Swap Y and Z */
65 cblas_sswap(signalLength, &insig[1*signalLength], 1, &insig[3*signalLength], 1); /* Swap Z and X */
66 }
67 /* Fill any remaining channels with zeros */
68 for(i=4; i<nSH; i++)
69 memset(&insig[i*signalLength], 0, signalLength * sizeof(float));
70}
71
73(
74 float* insig,
75 int order,
76 int signalLength,
77 HOA_NORM inConvention,
78 HOA_NORM outConvention
79)
80{
81 int n, ch;
82
83 if(order==0 || inConvention == outConvention)
84 return; /* Nothing to do */
85
86 if(inConvention==HOA_NORM_N3D){
87 if(outConvention == HOA_NORM_SN3D){
88 for (n = 0; n<order+1; n++)
89 for (ch = ORDER2NSH(n-1); ch < ORDER2NSH(n); ch++)
90 cblas_sscal(signalLength, 1.0f/sqrtf(2.0f*(float)n+1.0f), &insig[ch*signalLength], 1);
91 }
92 else if(outConvention==HOA_NORM_FUMA){
93 cblas_sscal(signalLength, 1.0f/sqrtf(2.0f), insig, 1);
94 for (ch = 1; ch<4 /* 1st order only */; ch++)
95 cblas_sscal(signalLength, 1.0f/sqrtf(3.0f), &insig[ch*signalLength], 1);
96 }
97 }
98 else if(inConvention==HOA_NORM_SN3D){
99 if(outConvention == HOA_NORM_N3D){
100 for (n = 0; n<order+1; n++)
101 for (ch = ORDER2NSH(n-1); ch<ORDER2NSH(n); ch++)
102 cblas_sscal(signalLength, sqrtf(2.0f*(float)n+1.0f), &insig[ch*signalLength], 1);
103 }
104 else if(outConvention==HOA_NORM_FUMA)
105 cblas_sscal(signalLength, 1.0f/sqrtf(2.0f), insig, 1);
106 }
107 else if(inConvention==HOA_NORM_FUMA){
108 if(outConvention == HOA_NORM_N3D){
109 cblas_sscal(signalLength, sqrtf(2.0f), insig, 1);
110 for (ch = 1; ch<4 /* 1st order only */; ch++)
111 cblas_sscal(signalLength, sqrtf(3.0f), &insig[ch*signalLength], 1);
112 }
113 else if(outConvention == HOA_NORM_SN3D)
114 cblas_sscal(signalLength, sqrtf(2.0f), insig, 1);
115 }
116}
117
119(
120 int N,
121 float* dirs_deg,
122 int nDirs,
123 float* Y
124)
125{
126 int i, nSH;
127 float scale;
128 float* dirs_rad;
129
130 if(nDirs<1)
131 return;
132
133 nSH = (N+1)*(N+1);
134 scale = sqrtf(4.0f*SAF_PI);
135
136 /* convert [azi, elev] in degrees, to [azi, inclination] in radians */
137 dirs_rad = malloc1d(nDirs*2*sizeof(float));
138 for(i=0; i<nDirs; i++){
139 dirs_rad[i*2+0] = dirs_deg[i*2+0] * SAF_PI/180.0f;
140 dirs_rad[i*2+1] = SAF_PI/2.0f - (dirs_deg[i*2+1] * SAF_PI/180.0f);
141 }
142
143 /* get real-valued spherical harmonics */
144 getSHreal(N, dirs_rad, nDirs, Y);
145
146 /* remove sqrt(4*pi) term */
147 utility_svsmul(Y, &scale, nSH*nDirs, NULL);
148
149 free(dirs_rad);
150}
151
153(
154 int N,
155 float* dirs_deg,
156 int nDirs,
157 float* Y
158)
159{
160 int n, m, i, dir, index_n;
161 float Nn0, Nnm;
162 float sleg_n[11], sleg_n_1[11], sleg_n_2[11], ssin_el, sfactorials_n[21];
163 float* leg_n, *leg_n_1, *leg_n_2, *sin_el, *factorials_n;
164
165 if(nDirs<1)
166 return;
167
168 if(N<=10 && nDirs == 1){
169 /* Single direction optimisation for up to 7th order */
170 leg_n = sleg_n;
171 leg_n_1 = sleg_n_1;
172 leg_n_2 = sleg_n_2;
173 sin_el = &ssin_el;
174 factorials_n = sfactorials_n;
175 }
176 else{
177 factorials_n = malloc1d((2*N+1)*sizeof(float));
178 leg_n = malloc1d((N+1)*nDirs * sizeof(float));
179 leg_n_1 = malloc1d((N+1)*nDirs * sizeof(float));
180 leg_n_2 = malloc1d((N+1)*nDirs * sizeof(float));
181 sin_el = malloc1d(nDirs * sizeof(float));
182 }
183 index_n = 0;
184
185 /* precompute factorials */
186 for (i = 0; i < 2*N+1; i++)
187 factorials_n[i] = (float)factorial(i);
188
189 /* cos(inclination) = sin(elevation) */
190 for (dir = 0; dir<nDirs; dir++)
191 sin_el[dir] = sinf(dirs_deg[dir*2+1] * SAF_PI/180.0f);
192
193 /* compute SHs with the recursive Legendre function */
194 for (n = 0; n<N+1; n++) {
195 if (n==0) {
196 for (dir = 0; dir<nDirs; dir++)
197 Y[n*nDirs+dir] = 1.0f;
198 index_n = 1;
199 }
200 else {
201 unnorm_legendreP_recur(n, sin_el, nDirs, leg_n_1, leg_n_2, leg_n); /* does NOT include Condon-Shortley phase term */
202
203 Nn0 = sqrtf(2.0f*(float)n+1.0f);
204 for (dir = 0; dir<nDirs; dir++){
205 for (m = 0; m<n+1; m++) {
206 if (m==0)
207 Y[(index_n+n)*nDirs+dir] = Nn0 * leg_n[m*nDirs+dir];
208 else {
209 Nnm = Nn0* sqrtf( 2.0f * factorials_n[n-m]/factorials_n[n+m] );
210 Y[(index_n+n-m)*nDirs+dir] = Nnm * leg_n[m*nDirs+dir] * sinf((float)m * (dirs_deg[dir*2])*SAF_PI/180.0f);
211 Y[(index_n+n+m)*nDirs+dir] = Nnm * leg_n[m*nDirs+dir] * cosf((float)m * (dirs_deg[dir*2])*SAF_PI/180.0f);
212 }
213 }
214 }
215 index_n += 2*n+1;
216 }
217 utility_svvcopy(leg_n_1, (N+1)*nDirs, leg_n_2);
218 utility_svvcopy(leg_n, (N+1)*nDirs, leg_n_1);
219 }
220
221 if(N>10 || nDirs > 1){
222 free(factorials_n);
223 free(leg_n);
224 free(leg_n_1);
225 free(leg_n_2);
226 free(sin_el);
227 }
228}
229
230
231/* ========================================================================== */
232/* Main Functions */
233/* ========================================================================== */
234
236(
237 int order,
238 int diagMtxFlag,
239 float* a_n
240)
241{
242 int n, i, idx, nSH;
243 double x;
244 double* ppm;
245
246 x = cosf(137.9f*(SAF_PI/180.0f)/((float)order+1.51f));
247 nSH = ORDER2NSH(order);
248 if(diagMtxFlag)
249 memset(a_n, 0, nSH*nSH*sizeof(float));
250 else
251 memset(a_n, 0, nSH*sizeof(float));
252 ppm = calloc1d((order+1),sizeof(double));
253 idx = 0;
254 for(n=0; n<=order; n++){
255 unnorm_legendreP(n, &x, 1, ppm);
256 /* store the first Legendre polynomial value for each order along the
257 * diagonal of a_n */
258 for(i = 0; i<2*n+1; i++){
259 if(diagMtxFlag)
260 a_n[(idx+i)*nSH + (idx+i)] = (float)ppm[0];
261 else
262 a_n[(idx+i)] = (float)ppm[0];
263 }
264 idx += 2*n+1;
265 }
266 free(ppm);
267}
268
270(
271 float* w_n,
272 int order_truncated,
273 int order_target,
274 double* kr,
275 int nBands,
276 float softThreshold,
277 float* gain
278)
279{
280 /* For more information refer to:
281 * Hold, C., Gamper, H., Pulkki, V., Raghuvanshi, N., & Tashev, I. J. (2019).
282 * Improving Binaural Ambisonics Decoding by Spherical Harmonics Domain Tapering
283 * and Coloration Compensation. ICASSP, IEEE International Conference on Acoustics,
284 * Speech and Signal Processing - Proceedings.
285 */
286 double_complex* b_n_target = calloc1d(nBands*(order_target+1), sizeof(double_complex));
287 double_complex* b_n_truncated = calloc1d(nBands*(order_truncated+1), sizeof(double_complex));
288 double* p_target = calloc1d(nBands, sizeof(double));
289 double* p_truncated = calloc1d(nBands, sizeof(double));
290
291 sphModalCoeffs(order_target, kr, nBands, ARRAY_CONSTRUCTION_RIGID, 0., b_n_target);
292 sphModalCoeffs(order_truncated, kr, nBands, ARRAY_CONSTRUCTION_RIGID, 0., b_n_truncated);
293
294 /* p_full */
295 for (int idxBand=0; idxBand<nBands; idxBand++)
296 for (int idxOrder=0; idxOrder<=order_target; idxOrder++)
297 p_target[idxBand] += (2.0*idxOrder + 1.0) * pow(cabs(b_n_target[idxBand*(order_target+1) + idxOrder]), 2.0);
298 /* p_truncated from (tapered) modal weighting */
299 for (int idxBand=0; idxBand<nBands; idxBand++)
300 for (int idxOrder=0; idxOrder<=order_truncated; idxOrder++)
301 p_truncated[idxBand] += w_n[idxOrder] * (2.0*idxOrder + 1.0) * pow(cabs(b_n_truncated[idxBand*(order_truncated+1) + idxOrder]), 2.0);
302
303 /* inverse ratio is filter gain */
304 for (int idxBand=0; idxBand < nBands; idxBand++) {
305 p_target[idxBand] = 1.0/(4.0*SAF_PI) * sqrt(p_target[idxBand]);
306 p_truncated[idxBand] = 1.0/(4.0*SAF_PI) * sqrt(p_truncated[idxBand]);
307 gain[idxBand] = (float)(p_target[idxBand] / (p_truncated[idxBand]+2.23e-13));
308 }
309
310 /* soft clip to limit maximum gain */
311 float clipFactor = powf(10.0f, softThreshold/20.0f);
312 for (int idxBand=0; idxBand<nBands; idxBand++){
313 gain[idxBand] = gain[idxBand]/clipFactor; /* norm by threshold */
314 if (gain[idxBand] > 1.0f)
315 gain[idxBand] = 1.0f + tanhf(gain[idxBand] - 1.0f); /* soft clip to 2 */
316 gain[idxBand] = gain[idxBand] * clipFactor; /* de-norm */
317 }
318
319 /* clean-up */
320 free(b_n_target);
321 free(b_n_truncated);
322 free(p_target);
323 free(p_truncated);
324}
325
327(
328 float* ls_dirs_deg,
329 int nLS,
331 int order,
332 int enableMaxReWeighting,
333 float* decMtx
334)
335{
336 int i, j, nSH;
337 float scale;
338 float* Y_ls, *a_n, *decMtx_maxrE;
339
340 nSH = ORDER2NSH(order);
341 scale = 1.0f/SQRT4PI;
342
343 switch(method){
344 default:
347 /* Sampling Ambisonic Decoder (SAD) is simply the loudspeaker
348 * spherical harmonic matrix scaled by the number of loudspeakers.
349 */
350 Y_ls = malloc1d(nSH*nLS*sizeof(float));
351 getRSH(order, ls_dirs_deg, nLS, Y_ls);
352 cblas_sscal(nLS*nSH, scale, Y_ls, 1);
353 for(i=0; i<nLS; i++)
354 for(j=0; j<nSH; j++)
355 decMtx[i*nSH+j] = (4.0f*SAF_PI) * Y_ls[j*nLS + i]/(float)nLS;
356 free(Y_ls);
357 break;
358
360 /* Mode-Matching Decoder (MMD) is simply the psuedo inverse of the
361 * loudspeaker spherical harmonic matrix. */
362 Y_ls = malloc1d(nSH*nLS*sizeof(float));
363 getRSH(order, ls_dirs_deg, nLS, Y_ls);
364 cblas_sscal(nLS*nSH, scale, Y_ls, 1);
365 utility_spinv(NULL, Y_ls, nSH, nLS, decMtx);
366 free(Y_ls);
367 break;
368
370 getEPAD(order, ls_dirs_deg, nLS, decMtx);
371 break;
372
374 getAllRAD(order, ls_dirs_deg, nLS, decMtx);
375 break;
376 }
377
378 /* Apply maxRE weighting */
379 if(enableMaxReWeighting){
380 a_n = malloc1d(nSH*nSH*sizeof(float));
381 getMaxREweights(order, 1, a_n); /* 1: weights returned as diagonal matrix */
382 decMtx_maxrE = malloc1d(nLS * nSH * sizeof(float));
383 cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, nLS, nSH, nSH, 1.0f,
384 decMtx, nSH,
385 a_n, nSH, 0.0f,
386 decMtx_maxrE, nSH);
387 memcpy(decMtx, decMtx_maxrE, nLS*nSH*sizeof(float));
388
389 free(a_n);
390 free(decMtx_maxrE);
391 }
392}
393
395(
396 float_complex* hrtfs,
397 float* hrtf_dirs_deg,
398 int N_dirs,
399 int N_bands,
401 int order,
402 float* freqVector,
403 float* itd_s,
404 float* weights,
405 int enableDiffCovMatching,
406 int enableMaxReWeighting,
407 float_complex* decMtx
408)
409{
410 int i, k, nSH;
411 float *tmp;
412 float_complex* a_n, *decMtx_rE;
413 const float_complex calpha = cmplxf(1.0f, 0.0f), cbeta = cmplxf(0.0f, 0.0f);
414
415 nSH = ORDER2NSH(order);
416
417 switch(method){
418 default:
420 case BINAURAL_DECODER_LS: getBinDecoder_LS(hrtfs, hrtf_dirs_deg, N_dirs, N_bands, order, weights, decMtx); break;
421 case BINAURAL_DECODER_LSDIFFEQ: getBinDecoder_LSDIFFEQ(hrtfs, hrtf_dirs_deg, N_dirs, N_bands, order, weights, decMtx); break;
422 case BINAURAL_DECODER_SPR: getBinDecoder_SPR(hrtfs, hrtf_dirs_deg, N_dirs, N_bands, order, weights, decMtx); break;
423 case BINAURAL_DECODER_TA: getBinDecoder_TA(hrtfs, hrtf_dirs_deg, N_dirs, N_bands, order, freqVector, itd_s, weights, decMtx); break;
424 case BINAURAL_DECODER_MAGLS: getBinDecoder_MAGLS(hrtfs, hrtf_dirs_deg, N_dirs, N_bands, order, freqVector, weights, decMtx); break;
425 }
426
427 /* apply Max RE weighting per bin */
428 if(enableMaxReWeighting){
429 tmp = malloc1d(nSH*nSH*sizeof(float));
430 a_n = malloc1d(nSH*nSH*sizeof(float_complex));
431 decMtx_rE = malloc1d(NUM_EARS*nSH*sizeof(float_complex));
432 getMaxREweights(order, 1, tmp);
433 for(i=0; i<nSH*nSH; i++)
434 a_n[i] = cmplxf(tmp[i], 0.0f);
435 for(k=0; k<N_bands; k++){
436 cblas_cgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, NUM_EARS, nSH, nSH, &calpha,
437 &decMtx[k*NUM_EARS*nSH], nSH,
438 a_n, nSH, &cbeta,
439 decMtx_rE, nSH);
440 memcpy(&decMtx[k*NUM_EARS*nSH], decMtx_rE, NUM_EARS*nSH*sizeof(float_complex));
441 }
442 free(tmp);
443 free(a_n);
444 free(decMtx_rE);
445 }
446
447 /* apply diffuse-field coherence matching per bin */
448 if(enableDiffCovMatching)
449 applyDiffCovMatching(hrtfs, hrtf_dirs_deg, N_dirs, N_bands, order, weights, decMtx);
450}
451
453(
454 float_complex* hrtfs,
455 float* hrtf_dirs_deg,
456 int N_dirs,
457 int fftSize,
458 float fs,
460 int order,
461 float* itd_s,
462 float* weights,
463 int enableDiffCovMatching,
464 int enableMaxReWeighting,
465 float* decFilters
466)
467{
468 int i, j, k, nBins, nSH;
469 float* freqVector;
470 float_complex* decMtx, *decMtx_bins;
471 void* hSafFFT;
472
473 /* frequency-vector */
474 nBins = fftSize/2 + 1;
475 freqVector = malloc1d(nBins*sizeof(float));
476 getUniformFreqVector(fftSize, fs, freqVector);
477
478 /* compute decoding matrix per bin */
479 nSH = ORDER2NSH(order);
480 decMtx = malloc1d(nBins*NUM_EARS*nSH*sizeof(float_complex));
481 getBinauralAmbiDecoderMtx(hrtfs, hrtf_dirs_deg, N_dirs, nBins, method,
482 order, freqVector, itd_s, weights, enableDiffCovMatching,
483 enableMaxReWeighting, decMtx);
484
485 /* ifft, to obtain time-domain filters */
486 decMtx_bins = malloc1d(nBins*sizeof(float_complex));
487 saf_rfft_create(&hSafFFT, fftSize);
488 for(i=0; i<NUM_EARS; i++){
489 for(j=0; j<nSH; j++){
490 for(k=0; k<nBins; k++)
491 decMtx_bins[k] = decMtx[k*NUM_EARS*nSH + i*nSH + j];
492 saf_rfft_backward(hSafFFT, decMtx_bins, &decFilters[i*nSH*fftSize + j*fftSize]);
493 }
494 }
495
496 saf_rfft_destroy(&hSafFFT);
497 free(freqVector);
498 free(decMtx);
499 free(decMtx_bins);
500}
501
503(
504 float_complex* hrtfs,
505 float* hrtf_dirs_deg,
506 int N_dirs,
507 int N_bands,
508 int order,
509 float* weights,
510 float_complex* decMtx
511)
512{
513 int i, nSH, band;
514 float* Y_tmp;
515 float_complex* W, *Y_na, *H_W, *H_ambi, *decMtx_diffMatched;
516 float_complex C_ref[NUM_EARS][NUM_EARS], C_ambi[NUM_EARS][NUM_EARS];
517 float_complex X[NUM_EARS][NUM_EARS], X_ambi[NUM_EARS][NUM_EARS];
518 float_complex XH_Xambi[NUM_EARS][NUM_EARS], U[NUM_EARS][NUM_EARS];
519 float_complex V[NUM_EARS][NUM_EARS], UX[NUM_EARS][NUM_EARS];
520 float_complex VUX[NUM_EARS][NUM_EARS], M[NUM_EARS][NUM_EARS];
521 const float_complex calpha = cmplxf(1.0f, 0.0f), cbeta = cmplxf(0.0f, 0.0f);
522
523 nSH = ORDER2NSH(order);
524
525 /* integration weights */
526 W = calloc1d(N_dirs*N_dirs, sizeof(float_complex));
527 if(weights!=NULL)
528 for(i=0; i<N_dirs; i++)
529 W[i*N_dirs+i] = cmplxf(weights[i], 0.0f);
530 else
531 for(i=0; i<N_dirs; i++)
532 W[i*N_dirs+i] = cmplxf(1.0f/(float)N_dirs, 0.0f);
533
534 /* SH */
535 Y_tmp = malloc1d(nSH*N_dirs*sizeof(float));
536 Y_na = malloc1d(nSH*N_dirs*sizeof(float_complex));
537 getRSH(order, hrtf_dirs_deg, N_dirs, Y_tmp);
538 for(i=0; i<nSH*N_dirs; i++)
539 Y_na[i] = cmplxf(Y_tmp[i], 0.0f);
540 free(Y_tmp);
541
542 /* apply diffuse-field coherence matching per band */
543 H_W = malloc1d(NUM_EARS*N_dirs*sizeof(float_complex));
544 H_ambi = malloc1d(NUM_EARS*N_dirs*sizeof(float_complex));
545 decMtx_diffMatched = malloc1d(NUM_EARS*nSH*sizeof(float_complex));
546 for(band=0; band<N_bands-1 /* skip Nyquist */; band++){
547 /* Diffuse-field responses */
548 cblas_cgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, NUM_EARS, N_dirs, N_dirs, &calpha,
549 &hrtfs[band*NUM_EARS*N_dirs], N_dirs,
550 W, N_dirs, &cbeta,
551 H_W, N_dirs);
552 cblas_cgemm(CblasRowMajor, CblasNoTrans, CblasConjTrans, NUM_EARS, NUM_EARS, N_dirs, &calpha,
553 H_W, N_dirs,
554 &hrtfs[band*NUM_EARS*N_dirs], N_dirs, &cbeta,
555 (float_complex*)C_ref, NUM_EARS);
556 for(i=0; i<NUM_EARS; i++)
557 C_ref[i][i] = cmplxf(crealf(C_ref[i][i]), 0.0f); /* force diagonal to be real */
558 utility_cchol(NULL, (float_complex*)C_ref, NUM_EARS, (float_complex*)X);
559 cblas_cgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, NUM_EARS, N_dirs, nSH, &calpha,
560 &decMtx[band*NUM_EARS*nSH], nSH,
561 Y_na, N_dirs, &cbeta,
562 H_ambi, N_dirs);
563 cblas_cgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, NUM_EARS, N_dirs, N_dirs, &calpha,
564 H_ambi, N_dirs,
565 W, N_dirs, &cbeta,
566 H_W, N_dirs);
567 cblas_cgemm(CblasRowMajor, CblasNoTrans, CblasConjTrans, NUM_EARS, NUM_EARS, N_dirs, &calpha,
568 H_W, N_dirs,
569 H_ambi, N_dirs, &cbeta,
570 (float_complex*)C_ambi, NUM_EARS);
571 for(i=0; i<NUM_EARS; i++)
572 C_ambi[i][i] = cmplxf(crealf(C_ambi[i][i]), 0.0f); /* force diagonal to be real */
573 utility_cchol(NULL, (float_complex*)C_ambi, NUM_EARS, (float_complex*)X_ambi);
574
575 /* SVD */
576 cblas_cgemm(CblasRowMajor, CblasConjTrans, CblasNoTrans, NUM_EARS, NUM_EARS, NUM_EARS, &calpha,
577 (float_complex*)X_ambi, NUM_EARS,
578 (float_complex*)X, NUM_EARS, &cbeta,
579 (float_complex*)XH_Xambi, NUM_EARS);
580 utility_csvd(NULL, (float_complex*)XH_Xambi, NUM_EARS, NUM_EARS, (float_complex*)U, NULL, (float_complex*)V, NULL);
581
582 /* apply matching */
583 cblas_cgemm(CblasRowMajor, CblasConjTrans, CblasNoTrans, NUM_EARS, NUM_EARS, NUM_EARS, &calpha,
584 (float_complex*)U, NUM_EARS,
585 (float_complex*)X, NUM_EARS, &cbeta,
586 (float_complex*)UX, NUM_EARS);
587 cblas_cgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, NUM_EARS, NUM_EARS, NUM_EARS, &calpha,
588 (float_complex*)V, NUM_EARS,
589 (float_complex*)UX, NUM_EARS, &cbeta,
590 (float_complex*)VUX, NUM_EARS);
591 utility_cglslv(NULL, (float_complex*)X_ambi, NUM_EARS, (float_complex*)VUX, NUM_EARS, (float_complex*)M);
592 cblas_cgemm(CblasRowMajor, CblasConjTrans, CblasNoTrans, NUM_EARS, nSH, NUM_EARS, &calpha,
593 (float_complex*)M, NUM_EARS,
594 &decMtx[band*NUM_EARS*nSH], nSH, &cbeta,
595 decMtx_diffMatched, nSH);
596 memcpy(&decMtx[band*NUM_EARS*nSH], decMtx_diffMatched, NUM_EARS*nSH*sizeof(float_complex));
597 }
598
599 free(W);
600 free(Y_na);
601 free(H_W);
602 free(H_ambi);
603 free(decMtx_diffMatched);
604}
void applyDiffCovMatching(float_complex *hrtfs, float *hrtf_dirs_deg, int N_dirs, int N_bands, int order, float *weights, float_complex *decMtx)
Imposes a diffuse-field covariance constraint on a given binaural decoding matrix,...
Definition saf_hoa.c:503
void getBinauralAmbiDecoderMtx(float_complex *hrtfs, float *hrtf_dirs_deg, int N_dirs, int N_bands, BINAURAL_AMBI_DECODER_METHODS method, int order, float *freqVector, float *itd_s, float *weights, int enableDiffCovMatching, int enableMaxReWeighting, float_complex *decMtx)
Computes binaural ambisonic decoding matrices (one per frequency) at a specific order,...
Definition saf_hoa.c:395
void convertHOAChannelConvention(float *insig, int order, int signalLength, HOA_CH_ORDER inConvention, HOA_CH_ORDER outConvention)
Converts an Ambisonic signal from one channel ordering convention to another.
Definition saf_hoa.c:41
HOA_CH_ORDER
Available Ambisonic channel ordering conventions.
Definition saf_hoa.h:183
void getLoudspeakerDecoderMtx(float *ls_dirs_deg, int nLS, LOUDSPEAKER_AMBI_DECODER_METHODS method, int order, int enableMaxReWeighting, float *decMtx)
Computes an ambisonic decoding matrix of a specific order, for a given loudspeaker layout.
Definition saf_hoa.c:327
void getBinauralAmbiDecoderFilters(float_complex *hrtfs, float *hrtf_dirs_deg, int N_dirs, int fftSize, float fs, BINAURAL_AMBI_DECODER_METHODS method, int order, float *itd_s, float *weights, int enableDiffCovMatching, int enableMaxReWeighting, float *decFilters)
Computes binaural ambisonic decoding filters for a given HRTF set.
Definition saf_hoa.c:453
LOUDSPEAKER_AMBI_DECODER_METHODS
Ambisonic decoding options for loudspeaker playback.
Definition saf_hoa.h:61
void getRSH(int N, float *dirs_deg, int nDirs, float *Y)
Computes real-valued spherical harmonics [1] for each given direction on the unit sphere.
Definition saf_hoa.c:119
HOA_NORM
Available Ambisonic normalisation conventions.
Definition saf_hoa.h:203
void convertHOANormConvention(float *insig, int order, int signalLength, HOA_NORM inConvention, HOA_NORM outConvention)
Converts an Ambisonic signal from one normalisation convention to another.
Definition saf_hoa.c:73
BINAURAL_AMBI_DECODER_METHODS
Ambisonic decoding options for binaural/headphone playback.
Definition saf_hoa.h:134
void getRSH_recur(int N, float *dirs_deg, int nDirs, float *Y)
Computes real-valued spherical harmonics [1] for each given direction on the unit sphere.
Definition saf_hoa.c:153
void getMaxREweights(int order, int diagMtxFlag, float *a_n)
Computes the weights required to manipulate a hyper-cardioid beam-pattern, such that it has maximum e...
Definition saf_hoa.c:236
void truncationEQ(float *w_n, int order_truncated, int order_target, double *kr, int nBands, float softThreshold, float *gain)
Filter that equalises the high frequency roll-off due to SH truncation and tapering; as described in ...
Definition saf_hoa.c:270
@ HOA_CH_ORDER_FUMA
Furse-Malham (FuMa) convention, often used by older recordings.
Definition saf_hoa.h:187
@ HOA_CH_ORDER_ACN
Ambisonic Channel numbering (ACN) convention, which is employed by all spherical harmonic related fun...
Definition saf_hoa.h:184
@ LOUDSPEAKER_DECODER_ALLRAD
All-Round Ambisonic Decoder (AllRAD): SAD decoding to a t-design, panned for the target loudspeaker d...
Definition saf_hoa.h:109
@ LOUDSPEAKER_DECODER_SAD
Sampling Ambisonic Decoder (SAD): transpose of the loudspeaker spherical harmonic matrix,...
Definition saf_hoa.h:76
@ LOUDSPEAKER_DECODER_DEFAULT
The default decoder is LOUDSPEAKER_DECODER_SAD.
Definition saf_hoa.h:65
@ LOUDSPEAKER_DECODER_EPAD
Energy-Preserving Ambisonic Decoder (EPAD) [1].
Definition saf_hoa.h:96
@ LOUDSPEAKER_DECODER_MMD
Mode-Matching Decoder (MMD): pseudo-inverse of the loudspeaker spherical harmonic matrix.
Definition saf_hoa.h:89
@ HOA_NORM_FUMA
Furse-Malham (FuMa) convention.
Definition saf_hoa.h:208
@ HOA_NORM_SN3D
Schmidt semi-normalisation (SN3D) convention, as used by the AmbiX standard.
Definition saf_hoa.h:206
@ HOA_NORM_N3D
Orthonormalised (N3D) convention, which is the default convention used by SAF.
Definition saf_hoa.h:204
@ BINAURAL_DECODER_MAGLS
Magnitude least-squares decoder [3].
Definition saf_hoa.h:170
@ BINAURAL_DECODER_LS
Least-squares (LS) decoder.
Definition saf_hoa.h:144
@ BINAURAL_DECODER_SPR
Spatial resampling decoder (on the same lines as the virtual loudspeaker approach) [4].
Definition saf_hoa.h:157
@ BINAURAL_DECODER_DEFAULT
The default decoder is BINAURAL_DECODER_LS.
Definition saf_hoa.h:138
@ BINAURAL_DECODER_TA
Time-alignment decoder [2].
Definition saf_hoa.h:165
@ BINAURAL_DECODER_LSDIFFEQ
Least-squares (LS) decoder with diffuse-field spectral equalisation [1].
Definition saf_hoa.h:152
void unnorm_legendreP(int n, double *x, int lenX, double *y)
Calculates unnormalised Legendre polynomials up to order N, for all values in vector x [1].
Definition saf_sh.c:54
#define ORDER2NSH(order)
Converts spherical harmonic order to number of spherical harmonic components i.e: (order+1)^2.
Definition saf_sh.h:51
void unnorm_legendreP_recur(int n, float *x, int lenX, float *Pnm_minus1, float *Pnm_minus2, float *Pnm)
Calculates unnormalised Legendre polynomials up to order N, for all values in vector x.
Definition saf_sh.c:130
void sphModalCoeffs(int order, double *kr, int nBands, ARRAY_CONSTRUCTION_TYPES arrayType, double dirCoeff, double_complex *b_N)
Calculates the modal coefficients for open/rigid spherical arrays.
Definition saf_sh.c:2370
void getSHreal(int order, float *dirs_rad, int nDirs, float *Y)
Computes real-valued spherical harmonics [1] for each given direction on the unit sphere.
Definition saf_sh.c:191
@ ARRAY_CONSTRUCTION_RIGID
Rigid baffle, omni-directional sensors.
Definition saf_sh.h:68
#define SAF_PI
pi constant (single precision)
void utility_cglslv(void *const hWork, const float_complex *A, const int dim, float_complex *B, int nCol, float_complex *X)
General linear solver: single precision complex, i.e.
#define NUM_EARS
2 (true for most humans)
void utility_svvcopy(const float *a, const int len, float *c)
Single-precision, vector-vector copy, i.e.
void saf_rfft_create(void **const phFFT, int N)
Creates an instance of saf_rfft; real<->half-complex (conjugate-symmetric) FFT.
void utility_spinv(void *const hWork, const float *inM, const int dim1, const int dim2, float *outM)
General matrix pseudo-inverse (the svd way): single precision, i.e.
void utility_csvd(void *const hWork, const float_complex *A, const int dim1, const int dim2, float_complex *U, float_complex *S, float_complex *V, float *sing)
Singular value decomposition: single precision complex, i.e.
void getUniformFreqVector(int fftSize, float fs, float *freqVector)
Calculates the frequencies (in Hz) of uniformly spaced bins, for a given FFT size and sampling rate.
long double factorial(int n)
Factorial, accurate up to n<=25.
void saf_rfft_backward(void *const hFFT, float_complex *inputFD, float *outputTD)
Performs the backward-FFT operation; use for complex (conjugate symmetric) to real transformations.
void saf_rfft_destroy(void **const phFFT)
Destroys an instance of saf_rfft.
void utility_cchol(void *const hWork, const float_complex *A, const int dim, float_complex *X)
Cholesky factorisation of a hermitian positive-definate matrix: single precision complex,...
#define SQRT4PI
sqrt(4pi) (single precision)
void utility_svsmul(float *a, const float *s, const int len, float *c)
Single-precision, multiplies each element in vector 'a' with a scalar 's', i.e.
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 higher-order Ambisonics module (SAF_HOA_MODULE)
void getBinDecoder_TA(float_complex *hrtfs, float *hrtf_dirs_deg, int N_dirs, int N_bands, int order, float *freqVector, float *itd_s, float *weights, float_complex *decMtx)
Computes a binaural ambisonic decoder based on the time-alignment (TA) method described in [1].
void getEPAD(int order, float *ls_dirs_deg, int nLS, float *decMtx)
Computes the Energy preserving Ambisonic decoder (EPAD), as detailed in [1].
void getBinDecoder_LSDIFFEQ(float_complex *hrtfs, float *hrtf_dirs_deg, int N_dirs, int N_bands, int order, float *weights, float_complex *decMtx)
Computes a least-squares (LS) binaural ambisonic decoder with diffuse-field equalisation [1].
void getBinDecoder_SPR(float_complex *hrtfs, float *hrtf_dirs_deg, int N_dirs, int N_bands, int order, float *weights, float_complex *decMtx)
Computes a binaural ambisonic decoder based on spatial resampling (i.e, virtual loudspeaker decoding)...
void getBinDecoder_LS(float_complex *hrtfs, float *hrtf_dirs_deg, int N_dirs, int N_bands, int order, float *weights, float_complex *decMtx)
Computes a standard least-squares (LS) binaural ambisonic decoder.
void getBinDecoder_MAGLS(float_complex *hrtfs, float *hrtf_dirs_deg, int N_dirs, int N_bands, int order, float *freqVector, float *weights, float_complex *decMtx)
Computes a binaural ambisonic decoder based on the magnitude least-squares (MagLS) method,...
void getAllRAD(int order, float *ls_dirs_deg, int nLS, float *decMtx)
Computes the All-round Ambisonics decoder (AllRAD), as detailed in [1], which is essentially a spheri...
Internal header for the higher-order Ambisonics module (SAF_HOA_MODULE)