SAF
Loading...
Searching...
No Matches
test__hoa_module.c
Go to the documentation of this file.
1/*
2 * Copyright 2020-2021 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
25#include "saf_test.h"
26
28 int i, j, k, nLS, order, nSH;
29 float* ls_dirs_deg, *amp, *en;
30 float** ls_dirs_rad, **decMtx_SAD, **decMtx_MMD, **decMtx_EPAD, **decMtx_AllRAD, **Ysrc, **LSout;
31
32 /* Config */
33 const float acceptedTolerance = 0.00001f;
34 int nTestOrders = 10;
35 int testOrders[10] = {1,2,3,4,5,6,7,8,9,10};
36
37 /* Loop over orders */
38 for(i=0; i<nTestOrders; i++) {
39 order = testOrders[i];
40 nSH = ORDER2NSH(order);
41
42 /* Pull an appropriate t-design for this order */
43 ls_dirs_deg = (float*)__HANDLES_Tdesign_dirs_deg[2 * order-1];
44 nLS = __Tdesign_nPoints_per_degree[2 * order-1];
45 ls_dirs_rad = (float**)malloc2d(nLS, 2, sizeof(float));
46 for(j=0; j<nLS; j++){
47 ls_dirs_rad[j][0] = ls_dirs_deg[j*2] * SAF_PI/180.0f;
48 ls_dirs_rad[j][1] = SAF_PI/2.0f - ls_dirs_deg[j*2+1] * SAF_PI/180.0f; /* elevation->inclination */
49 }
50
51 /* Compute decoders */
52 decMtx_SAD = (float**)malloc2d(nLS, nSH, sizeof(float));
53 decMtx_MMD = (float**)malloc2d(nLS, nSH, sizeof(float));
54 decMtx_EPAD = (float**)malloc2d(nLS, nSH, sizeof(float));
55 decMtx_AllRAD = (float**)malloc2d(nLS, nSH, sizeof(float));
56 getLoudspeakerDecoderMtx(ls_dirs_deg, nLS, LOUDSPEAKER_DECODER_SAD, order, 0, FLATTEN2D(decMtx_SAD));
57 getLoudspeakerDecoderMtx(ls_dirs_deg, nLS, LOUDSPEAKER_DECODER_MMD, order, 0, FLATTEN2D(decMtx_MMD));
58 getLoudspeakerDecoderMtx(ls_dirs_deg, nLS, LOUDSPEAKER_DECODER_EPAD, order, 0, FLATTEN2D(decMtx_EPAD));
59 getLoudspeakerDecoderMtx(ls_dirs_deg, nLS, LOUDSPEAKER_DECODER_ALLRAD, order, 0, FLATTEN2D(decMtx_AllRAD));
60
61 /* SAD/MMD/EPAD should all be equivalent in this special/uniform case */
62 for(j=0; j<nLS; j++)
63 for(k=0; k<nSH; k++)
64 TEST_ASSERT_FLOAT_WITHIN(acceptedTolerance, decMtx_SAD[j][k], decMtx_MMD[j][k]);
65 for(j=0; j<nLS; j++)
66 for(k=0; k<nSH; k++)
67 TEST_ASSERT_FLOAT_WITHIN(acceptedTolerance, decMtx_SAD[j][k], decMtx_EPAD[j][k]);
68
69 /* Compute output for PWs in direction of Loudspeakers: */
70 Ysrc = (float**)malloc2d(nSH, nLS, sizeof(float));
71 getSHreal(order, FLATTEN2D(ls_dirs_rad), nLS, FLATTEN2D(Ysrc));
72 LSout = (float**)malloc2d(nLS, nLS, sizeof(float));
73 cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, nLS, nLS, nSH, 1.0f,
74 FLATTEN2D(decMtx_EPAD), nSH,
75 FLATTEN2D(Ysrc), nLS, 0.0f,
76 FLATTEN2D(LSout), nLS);
77
78 /* Compute amplitude and energy for each source */
79 amp = (float*)calloc1d(nLS, sizeof(float));
80 en = (float*)calloc1d(nLS, sizeof(float));
81 for (int idxSrc=0; idxSrc<nLS; idxSrc++) {
82 for (int idxLS=0; idxLS<nLS; idxLS++) {
83 amp[idxSrc] += LSout[idxLS][idxSrc];
84 en[idxSrc] += LSout[idxLS][idxSrc] * LSout[idxLS][idxSrc];
85 }
86 }
87 /* Check output amplitude and Energy */
88 for (int idxSrc=0; idxSrc<nLS; idxSrc++) {
89 TEST_ASSERT_FLOAT_WITHIN(acceptedTolerance, amp[idxSrc], 1.0);
90 TEST_ASSERT_FLOAT_WITHIN(acceptedTolerance, en[idxSrc], (float)nSH / (float)nLS);
91 }
92
93 /* Clean-up */
94 free(decMtx_SAD);
95 free(decMtx_MMD);
96 free(decMtx_EPAD);
97 free(decMtx_AllRAD);
98 free(ls_dirs_rad);
99 free(amp);
100 free(en);
101 free(Ysrc);
102 free(LSout);
103 }
104}
105
107{
108 double *kr;
109 float *w_n, *gain, *gainDB;
110
111 /* Config */
112 const int order_truncated = 4;
113 const int order_target = 42;
114 const float softThreshold = 12.0f;
115 const int enableMaxRE = 1;
116 const double fs = 48000;
117 const int nBands = 128;
118 kr = malloc1d(nBands * sizeof(double));
119 const double r = 0.085;
120 const double c = 343.;
121 w_n = calloc1d((order_truncated+1), sizeof(float));
122 gain = malloc1d(nBands * sizeof(float));
123
124 /* Prep */
125 double* freqVector = malloc1d(nBands*sizeof(double));
126 for (int k=0; k<nBands; k++)
127 {
128 freqVector[k] = (double)k * fs/(2.0*((double)nBands-1));
129 kr[k] = 2.0*SAF_PId / c * freqVector[k] * r;
130 }
131 if (enableMaxRE) {
132 // maxRE as order weighting
133 float *maxRECoeffs = malloc1d((order_truncated+1) * sizeof(float));
134 beamWeightsMaxEV(order_truncated, maxRECoeffs);
135 for (int idx_n=0; idx_n<order_truncated+1; idx_n++) {
136 w_n[idx_n] = maxRECoeffs[idx_n];
137 w_n[idx_n] /= sqrtf((float)(2*idx_n+1) / (FOURPI));
138 }
139 float w_0 = w_n[0];
140 for (int idx_n=0; idx_n<order_truncated+1; idx_n++)
141 w_n[idx_n] /= w_0;
142 free(maxRECoeffs);
143 }
144 else {
145 // just truncation, no tapering
146 for (int idx_n=0; idx_n<order_truncated+1; idx_n++)
147 w_n[idx_n] = 1.0f;
148 }
149
150 truncationEQ(w_n, order_truncated, order_target, kr, nBands, softThreshold, gain);
151
152 /* Asserting gain offset */
153 TEST_ASSERT_TRUE(gain[0]-1.0 < 2.0e-6);
154
155 /* Asserting that gain within 0 and 12 (+6db soft clip) */
156 gainDB = malloc1d(nBands * sizeof(double));
157 for (int idxBand=0; idxBand<nBands; idxBand++){
158 gainDB[idxBand] = 20.0f*log10f(gain[idxBand]);
159 TEST_ASSERT_TRUE(gainDB[idxBand] > 0-2.0e-6);
160 TEST_ASSERT_TRUE(gainDB[idxBand] < softThreshold + 6.0 + 0-2.0e-6);
161 }
162
163 /* clean-up */
164 free(kr);
165 free(w_n);
166 free(freqVector);
167 free(gain);
168 free(gainDB);
169}
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 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
@ 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_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
#define ORDER2NSH(order)
Converts spherical harmonic order to number of spherical harmonic components i.e: (order+1)^2.
Definition saf_sh.h:51
void beamWeightsMaxEV(int N, float *b_n)
Generates beamweights in the SHD for maximum energy-vector beampatterns.
Definition saf_sh.c:858
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
#define SAF_PI
pi constant (single precision)
const float * __HANDLES_Tdesign_dirs_deg[21]
minimum T-design HANDLES (up to degree 21 only).
#define SAF_PId
pi constant (double precision)
#define FOURPI
4pi (single precision)
const int __Tdesign_nPoints_per_degree[21]
Number of points in each t-design (up to degree 21 only).
void ** malloc2d(size_t dim1, size_t dim2, size_t data_size)
2-D malloc (contiguously allocated, so use free() as usual to deallocate)
Definition md_malloc.c:89
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
#define FLATTEN2D(A)
Use this macro when passing a 2-D dynamic multi-dimensional array to memset, memcpy or any other func...
Definition md_malloc.h:65
Unit test program for the Spatial_Audio_Framework.
void test__truncationEQ(void)
Testing the truncation EQ.
void test__getLoudspeakerDecoderMtx(void)
Testing to assure that (given a uniform loudspeaker layout), the SAD, MMD and EPAD decoders are all e...