SAF
Loading...
Searching...
No Matches
saf_utility_dvf.c
Go to the documentation of this file.
1/*
2* Copyright 2020-2021 Michael McCrea, 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_utility_dvf.h"
33#include "saf_utility_filters.h"
34
38static const double p11[19] = { 12.97, 13.19, 12.13, 11.19, 9.91, 8.328, 6.493, 4.455, 2.274, 0.018, -2.24, -4.43, -6.49, -8.34, -9.93, -11.3, -12.2, -12.8, -13.0 };
39static const double p21[19] = { -9.69, 234.2, -11.2, -9.03, -7.87, -7.42, -7.31, -7.28, -7.29, -7.48, -8.04, -9.23, -11.6, -17.4, -48.4, 9.149, 1.905, -0.75, -1.32 };
40static const double q11[19] = { -1.14, 18.48, -1.25, -1.02, -0.83, -0.67, -0.5, -0.32, -0.11, -0.13, 0.395, 0.699, 1.084, 1.757, 4.764, -0.64, 0.109, 0.386, 0.45 };
41static const double q21[19] = { 0.219, -8.5, 0.346, 0.336, 0.379, 0.421, 0.423, 0.382, 0.314, 0.24, 0.177, 0.132, 0.113, 0.142, 0.462, -0.14, -0.08, -0.06, -0.05 };
42static const double p12[19] = { -4.39, -4.31, -4.18, -4.01, -3.87, -4.1, -3.87, -5.02, -6.72, -8.69, -11.2, -12.1, -11.1, -11.1, -9.72, -8.42, -7.44, -6.78, -6.58 };
43static const double p22[19] = { 2.123, -2.78, 4.224, 3.039, -0.57, -34.7, 3.271, 0.023, -8.96, -58.4, 11.47, 8.716, 21.8, 1.91, -0.04, -0.66, 0.395, 2.662, 3.387 };
44static const double q12[19] = { -0.55, 0.59, -1.01, -0.56, 0.665, 11.39, -1.57, -0.87, 0.37, 5.446, -1.13, -0.63, -2.01, 0.15, 0.243, 0.147, -0.18, -0.67, -0.84 };
45static const double q22[19] = { -0.06, -0.17, -0.02, -0.32, -1.13, -8.3, 0.637, 0.325, -0.08, -1.19, 0.103, -0.12, 0.098, -0.4, -0.41, -0.34, -0.18, 0.05, 0.131 };
46static const double p13[19] = { 0.457, 0.455, -0.87, 0.465, 0.494, 0.549, 0.663, 0.691, 3.507, -27.4, 6.371, 7.032, 7.092, 7.463, 7.453, 8.101, 8.702, 8.925, 9.317 };
47static const double p23[19] = { -0.67, 0.142, 3404., -0.91, -0.67, -1.21, -1.76, 4.655, 55.09, 10336., 1.735, 40.88, 23.86, 102.8, -6.14, -18.1, -9.05, -9.03, -6.89 };
48static const double p33[19] = { 0.174, -0.11, -1699., 0.437, 0.658, 2.02, 6.815, 0.614, 589.3, 16818., -9.39, -44.1, -23.6, -92.3, -1.81, 10.54, 0.532, 0.285, -2.08 };
49static const double q13[19] = { -1.75, -0.01, 7354., -2.18, -1.2, -1.59, -1.23, -0.89, 29.23, 1945., -0.06, 5.635, 3.308, 13.88, -0.88, -2.23, -0.96, -0.9, -0.57 };
50static const double q23[19] = { 0.699, -0.35, -5350., 1.188, 0.256, 0.816, 1.166, 0.76, 59.51, 1707., -1.12, -6.18, -3.39, -12.7, -0.19, 1.295, -0.02, -0.08, -0.4 };
51static const int numAz_table = sizeof(q23) / sizeof(q23[0]);
52
53/* a_0 = 0.0875f; Reference head size, 8.75 centimeters, used in the generation of the coeff lookup table. */
54/* a_head = 0.09096f; This head size, see note for head_radius in binauraliser_nf. */
55static const float headDim = SAF_PI * (0.0875f / 0.09096f); /* pi * (a_0 / a_head) */
56static const float sosDiv2PiA = 343.0f / (2.0f * SAF_PI * 0.09096f); /* c / (2pi * a_head) */
57
59static float interpolate_lin
60(
61 float a,
62 float b,
63 float ifac
64)
65{
66 return a + (b-a) * ifac;
67}
68
70static float db2mag
71(
72 float dB
73)
74{
75 return powf(10.f, dB / 20.f);
76}
77
78/*
79 * Calculate high-shelf parameters, g0, gInf, fc, from the lookup table coefficients (10 degree steps).
80 * Called twice per update as the returned values are subsequently interpolated to exact azimuth. */
82(
83 int i, /* Index into the coefficient table, dictated by azimuth. */
84 float rhoIn, /* Normalized source distance. */
85 float* g0, /* High shelf gain at DC. */
86 float* gInf, /* High shelf gain at inf. */
87 float* fc /* High shelf cutoff frequency. */
88)
89{
90 float fc_tmp;
91 double rho = (double)rhoIn;
92 double rhoSq = rho * rho;
93
94 /* Eq (8), (13) and (14) */
95 *g0 = (float)((p11[i] * rho + p21[i]) / (rhoSq + q11[i] * rho + q21[i]));
96 *gInf = (float)((p12[i] * rho + p22[i]) / (rhoSq + q12[i] * rho + q22[i]));
97 fc_tmp = (float)((p13[i] * rhoSq + p23[i] * rho + p33[i]) / (rhoSq + q13[i] * rho + q23[i]));
98
99 /* denormalize fc = fc * sos/(2pi*a) */
100 *fc = fc_tmp * sosDiv2PiA;
101}
102
103/*
104 * Interpolate (linearly) the high shelf parameters generated by
105 * calcDVFShelfParams() which is called twice to generate the high shelf
106 * parameters for the nearest thetas in the lookup table. */
108(
109 float theta, /* Ipsilateral azimuth, on the inter-aural axis [0, 180] (deg). */
110 float rho, /* Distance, normalized to head radius, >= 1. */
111 /* output */
112 float* iG0,
113 float* iGInf,
114 float* iFc
115)
116{
117 int theta_idx_lower, theta_idx_upper;
118 float ifac, thetaDiv10;
119 float g0_1, g0_2; /* high shelf gain at DC */
120 float gInf_1, gInf_2; /* high shelf gain at inf */
121 float fc_1, fc_2; /* high shelf cutoff frequency */
122
123 /* Linearly interpolate DC gain, HF gain, center freq at theta.
124 * Table is in 10 degree steps, floor(x/10) gets lower index. */
125 theta = SAF_CLAMP(theta, 0.f, 180.f);
126 rho = SAF_MAX(rho, 1.f);
127 thetaDiv10 = theta / 10.f;
128 theta_idx_lower = (int)thetaDiv10;
129 theta_idx_upper = theta_idx_lower + 1;
130 if(theta_idx_upper >= numAz_table) {
131 theta_idx_upper = numAz_table - 1;
132 theta_idx_lower = theta_idx_upper - 1;
133 }
134
135 calcDVFShelfParams(theta_idx_lower, rho, &g0_1, &gInf_1, &fc_1);
136 calcDVFShelfParams(theta_idx_upper, rho, &g0_2, &gInf_2, &fc_2);
137
138 /* interpolation factor between table steps */
139 ifac = thetaDiv10 - theta_idx_lower;
140 *iG0 = interpolate_lin(g0_1, g0_2, ifac);
141 *iGInf = interpolate_lin(gInf_1, gInf_2, ifac);
142 *iFc = interpolate_lin(fc_1, fc_2, ifac);
143}
144
145/*
146 * Calculate the DVF filter coefficients from shelving filter parameters.
147 */
149(
150 /* Input */
151 float g0, /* High shelf dc gain */
152 float gInf, /* High shelf high gain */
153 float fc, /* High shelf center freq */
154 float fs, /* Sample rate */
155 /* Output */
156 float* b0, /* IIR coeffs */
157 float* b1,
158 float* a1
159)
160{
161 float v0, g0_mag, tanF, v0tanF;
162 float a_c, v, va_c;
163
164 v0 = db2mag(gInf); /* Eq. (12), (10), and (11) */
165 g0_mag = db2mag(g0); // optim TBD: revisit; does g0, gInf need to be in dB?
166 tanF = tanf((headDim / fs) * fc); // optim TBD: this /fs calc can be optimized out with precomputed head dimension
167 v0tanF = v0 * tanF;
168 a_c = (v0tanF - 1.f) / (v0tanF + 1.f);
169
170 v = (v0 - 1.f) * 0.5f; /* Eq (10) */
171 va_c = v * a_c;
172 *b0 = g0_mag * (v - va_c + 1.f); /* = V*(1 - a_c) + 1 */
173 *b1 = g0_mag * (va_c - v + a_c); /* = V*(a_c - 1) + a_c */
174 *a1 = a_c;
175}
176
178(
179 float alpha, /* Lateral angle, on the inter-aural axis [0, 180] (deg) */
180 float rho, /* Distance, normalized to head radius, >= 1 */
181 float fs, /* Sample rate */
182 float* b,
183 float* a
184)
185{
186 float iG0, iGInf, iFc;
187 interpDVFShelfParams(alpha, rho, &iG0, &iGInf, &iFc);
188 dvfShelfCoeffs(iG0, iGInf, iFc, fs, &b[0], &b[1], &a[1]);
189}
190
192(
193 float azimuth, /* azimuth wrt 0˚ forward-facing (deg, [-360, 360]) */
194 float elevation, /* elevation from the horizontal plane (deg, [-180, 180]) */
195 float* alphaLR, /* (&) 2-element array of lateral angle alpha for left and right ear (deg, [0,180]) */
196 float* betaLR /* (&) 2-element array of vertical angle beta for left and right ear (deg, [0,90]) */
197)
198{
199 float az_rad, el_rad, alpha_ipsi, beta_ipsi, alpha_ipsi_deg, beta_ipsi_deg;
200 float sinaz, sinel, cosaz, cosel;
201
202 az_rad = DEG2RAD(azimuth);
203 el_rad = DEG2RAD(elevation);
204 sinaz = sinf(az_rad);
205 sinel = sinf(el_rad);
206 cosaz = cosf(az_rad);
207 cosel = cosf(el_rad);
208 alpha_ipsi = SAF_PI/2.f - acosf(sinaz * cosel);
209 beta_ipsi = asinf(sinel / sqrtf(powf(sinel,2.f) + (powf(cosaz,2.f) * powf(cosel,2.f))));
210
211 /* Convert interaural-polar coordinates back to spherical _ranges_. */
212 if(beta_ipsi > SAF_PI/2.f) {
213 alpha_ipsi = SAF_PI - alpha_ipsi; /* [0,pi] */
214 beta_ipsi = SAF_PI - beta_ipsi; /* [0,pi/2] */
215 }
216
217 /* Convert to ipsilateral offset from the left ear: [-360, 360]->[0, 180] */
218 alpha_ipsi = fabsf(SAF_PI/2.f - alpha_ipsi);
219 if (alpha_ipsi > SAF_PI)
220 alpha_ipsi = 2*SAF_PI - alpha_ipsi;
221
222 alphaLR[0] = alpha_ipsi_deg = RAD2DEG(alpha_ipsi);
223 alphaLR[1] = 180.f - alpha_ipsi_deg;
224
225 if (betaLR != NULL) {
226 betaLR[0] = beta_ipsi_deg = RAD2DEG(beta_ipsi);
227 betaLR[1] = 180.f - beta_ipsi_deg;
228 }
229}
void doaToIpsiInteraural(float azimuth, float elevation, float *alphaLR, float *betaLR)
Convert a frontal azimuth/elevation to a modified Interaural-Polar coordinate.
#define SAF_CLAMP(a, min, max)
Ensures value "a" is clamped between the "min" and "max" values.
#define SAF_PI
pi constant (single precision)
#define DEG2RAD(x)
Converts degrees to radians.
#define SAF_MAX(a, b)
Returns the maximum of the two values.
void interpDVFShelfParams(float theta, float rho, float *iG0, float *iGInf, float *iFc)
Calculate the shelving filter parameters for the Distance Variation Function filter from the source (...
void dvfShelfCoeffs(float g0, float gInf, float fc, float fs, float *b0, float *b1, float *a1)
Calculate the DVF filter coefficients from shelving filter parameters.
void calcDVFCoeffs(float alpha, float rho, float fs, float *b, float *a)
Calculate the Distance Variation Function (DVF) filter coefficients, as described in [1].
void calcDVFShelfParams(int i, float rhoIn, float *g0, float *gInf, float *fc)
Calculate the high shelf gains and cutoff parameters, given a azimuth index i and distance rho.
#define RAD2DEG(x)
Converts radians to degrees
static float interpolate_lin(float a, float b, float ifac)
Linear interpolation between two values.
static const double p11[19]
Table 1: Coefficients for Eqs.
static float db2mag(float dB)
Covert decibels to a magnitude.
Distance variation function filter coefficient data [1].
A collection of IIR/FIR filter and filterbank designs.