Skip to content

Commit 2908d4b

Browse files
committed
Convert filtering method to frequency domain
- Add evalIIRTransferFunction and associated unit tests - Processing function to only update coeffs on changed distatance/direction. - BInauraliser: remove unused var src_dirs. - add `src_dirs_cur` pointer to switch between non/rotated directions. - `doaToIpsiInteraural` returns lateral angle `alpha` to properly parameterize the DVF with changing elevation. - Add binauraliser_nf to README.md
1 parent 154b02b commit 2908d4b

File tree

13 files changed

+750
-262
lines changed

13 files changed

+750
-262
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ Several **examples** have also been included in the repository, which may serve
156156
* **array2sh** - converts microphone array signals into spherical harmonic signals (aka Ambisonic signals), based on theoretical descriptions of the array configuration and construction.
157157
* **beamformer** - a beamformer/virtual microphone generator for Ambisonic signals, with several different beam pattern options.
158158
* **binauraliser** - convolves input audio with interpolated HRTFs, which can be optionally loaded from a SOFA file.
159+
* **binauraliser_nf** - binauraliser, with the addition of proximity filtering for near field sound sources.
159160
* **decorrelator** - a basic multi-channel signal decorrelator.
160161
* **dirass** - a sound-field visualiser based on re-assigning the energy of beamformers. This re-assignment is based on the DoA estimates extracted from spatially-localised active-intensity vectors, which are biased towards each beamformer direction.
161162
* **matrixconv** - a basic matrix convolver with an optional partitioned convolution mode.

examples/include/binauraliser_nf.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,17 @@ void binauraliserNF_process(void* const hBin,
132132
int nOutputs,
133133
int nSamples);
134134

135+
/**
136+
* Alternate version of binauraliserNF_process() that performs frequency-domain
137+
* DVF filtering. Not used but kept for posterity.
138+
*/
139+
void binauraliserNF_processFD(void* const hBin,
140+
const float *const * inputs,
141+
float** const outputs,
142+
int nInputs,
143+
int nOutputs,
144+
int nSamples);
145+
135146

136147
/* ========================================================================== */
137148
/* Set Functions */
@@ -170,12 +181,14 @@ void binauraliserNF_setInputConfigPreset(void* const hBin,
170181
float binauraliserNF_getSourceDist_m(void* const hBin, int index);
171182

172183
/**
173-
* Returns the distance considered to be the far field (beyond which no near field filtering is applied), in METERS
184+
* Returns the distance considered to be the far field (beyond which no near
185+
* field filtering is applied), in METERS
174186
*/
175187
float binauraliserNF_getFarfieldThresh_m(void* const hBin);
176188

177189
/**
178-
* Returns the scaling factor to give the far field threshold headroom (useful for UI range limits)
190+
* Returns the scaling factor to give the far field threshold headroom (useful
191+
* for UI range limits)
179192
*/
180193
float binauraliserNF_getFarfieldHeadroom(void* const hBin);
181194

examples/src/binauraliser/binauraliser.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,14 +200,13 @@ void binauraliser_process
200200
{
201201
binauraliser_data *pData = (binauraliser_data*)(hBin);
202202
int ch, ear, i, band, nSources;
203-
float src_dirs[MAX_NUM_INPUTS][2], Rxyz[3][3], hypotxy;
203+
float Rxyz[3][3], hypotxy;
204204
int enableRotation;
205205

206206
/* copy user parameters to local variables */
207207
nSources = pData->nSources;
208208
enableRotation = pData->enableRotation;
209-
memcpy(src_dirs, pData->src_dirs_deg, MAX_NUM_INPUTS*2*sizeof(float));
210-
209+
211210
/* apply binaural panner */
212211
if ((nSamples == BINAURALISER_FRAME_SIZE) && (pData->hrtf_fb!=NULL) && (pData->codecStatus==CODEC_STATUS_INITIALISED) ){
213212
pData->procStatus = PROC_STATUS_ONGOING;

examples/src/binauraliser/binauraliser_internal.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ extern "C" {
6767

6868
/**
6969
* Main structure for binauraliser. Contains variables for audio buffers,
70-
* afSTFT, HRTFs, internal variables, flags, user parameters
70+
* afSTFT, HRTFs, internal variables, flags, user parameters.
71+
* Note: if this is modified, identically modify _binauraliserNF struct.
7172
*/
7273
typedef struct _binauraliser
7374
{

examples/src/binauraliser_nf/binauraliser_nf.c

Lines changed: 118 additions & 96 deletions
Large diffs are not rendered by default.

examples/src/binauraliser_nf/binauraliser_nf_internal.h

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,13 @@ extern "C" {
6464
* Main structure for binauraliserNF. Contains all variables from binauraliser
6565
* (audio buffers, afSTFT, HRTFs, internal variables, flags, user parameters) plus
6666
* those specific to the near field variant.
67+
* FREQUENCY DOMAIN implementation.
6768
*/
6869
typedef struct _binauraliserNF {
69-
//struct _binauraliser; /**< "inherit" member vars of binauraliser struct */
70+
//struct _binauraliser; /**< "inherit" member vars of binauraliser struct - requires -fms-extensions compile flag */
7071

7172
/* The following variables MUST match those of the _binauraliser struct */
72-
73+
7374
/* audio buffers */
7475
float** inputFrameTD; /**< time-domain input frame; #MAX_NUM_INPUTS x #BINAURALISER_FRAME_SIZE */
7576
float** outframeTD; /**< time-domain output frame; #NUM_EARS x #BINAURALISER_FRAME_SIZE */
@@ -78,7 +79,7 @@ typedef struct _binauraliserNF {
7879
int fs; /**< Host sampling rate, in Hz */
7980
float freqVector[HYBRID_BANDS]; /**< Frequency vector (filterbank centre frequencies) */
8081
void* hSTFT; /**< afSTFT handle */
81-
82+
8283
/* sofa file info */
8384
char* sofa_filepath; /**< absolute/relevative file path for a sofa file */
8485
float* hrirs; /**< time domain HRIRs; FLAT: N_hrir_dirs x #NUM_EARS x hrir_len */
@@ -89,19 +90,19 @@ typedef struct _binauraliserNF {
8990
int hrir_loaded_fs; /**< sampling rate of the loaded HRIRs */
9091
int hrir_runtime_fs; /**< sampling rate of the HRIRs being used for processing (after any resampling) */
9192
float* weights; /**< Integration weights for the HRIR measurement grid */
92-
93+
9394
/* vbap gain table */
9495
int hrtf_vbapTableRes[2]; /**< [0] azimuth, and [1] elevation grid resolution, in degrees */
9596
int N_hrtf_vbap_gtable; /**< Number of interpolation weights/directions */
9697
int* hrtf_vbap_gtableIdx; /**< N_hrtf_vbap_gtable x 3 */
9798
float* hrtf_vbap_gtableComp; /**< N_hrtf_vbap_gtable x 3 */
98-
99+
99100
/* hrir filterbank coefficients */
100101
float* itds_s; /**< interaural-time differences for each HRIR (in seconds); nBands x 1 */
101102
float_complex* hrtf_fb; /**< hrtf filterbank coefficients; nBands x nCH x N_hrirs */
102103
float* hrtf_fb_mag; /**< magnitudes of the hrtf filterbank coefficients; nBands x nCH x N_hrirs */
103104
float_complex hrtf_interp[MAX_NUM_INPUTS][HYBRID_BANDS][NUM_EARS]; /**< Interpolated HRTFs */
104-
105+
105106
/* flags/status */
106107
CODEC_STATUS codecStatus; /**< see #CODEC_STATUS */
107108
float progressBar0_1; /**< Current (re)initialisation progress, between [0..1] */
@@ -110,7 +111,7 @@ typedef struct _binauraliserNF {
110111
int recalc_hrtf_interpFLAG[MAX_NUM_INPUTS]; /**< 1: re-calculate/interpolate the HRTF, 0: do not */
111112
int reInitHRTFsAndGainTables; /**< 1: reinitialise the HRTFs and interpolation tables, 0: do not */
112113
int recalc_M_rotFLAG; /**< 1: re-calculate the rotation matrix, 0: do not */
113-
114+
114115
/* misc. */
115116
float src_dirs_rot_deg[MAX_NUM_INPUTS][2]; /**< Intermediate rotated source directions, in degrees */
116117
float src_dirs_rot_xyz[MAX_NUM_INPUTS][3]; /**< Intermediate rotated source directions, as unit-length Cartesian coordinates */
@@ -133,23 +134,29 @@ typedef struct _binauraliserNF {
133134
int bFlipRoll; /**< flag to flip the sign of the roll rotation angle */
134135
int useRollPitchYawFlag; /**< rotation order flag, 1: r-p-y, 0: y-p-r */
135136
float src_gains[MAX_NUM_INPUTS]; /**< Gains applied per source */
136-
137+
137138
/* End copied _binauraliser struct members. The following are unique to the _binauraliserNF struct */
138-
139-
/* audio buffers */
140-
float** binsrcsTD; /**< near field DVF-filtered sources frame; (#MAX_NUM_INPUTS * #NUM_EARS) x #BINAURALISER_FRAME_SIZE */
141-
float_complex*** binauralTF; /**< time-frequency domain output frame; #HYBRID_BANDS x (#MAX_NUM_INPUTS * #NUM_EARS) x #TIME_SLOTS
142-
* Note: (#MAX_NUM_INPUTS * #NUM_EARS) dimensions are combined because afSTFT requires type float_complex*** */
139+
140+
float b_dvf[MAX_NUM_INPUTS][NUM_EARS][2]; /**< shelf IIR numerator coefficients for each input, left and right. */
141+
float a_dvf[MAX_NUM_INPUTS][NUM_EARS][2]; /**< shelf IIR denominator coefficients for each input, left and right. */
142+
float dvfmags[MAX_NUM_INPUTS][NUM_EARS][HYBRID_BANDS]; /**< DVF filter frequency band magnitudes. */
143+
float dvfphases[MAX_NUM_INPUTS][NUM_EARS][HYBRID_BANDS]; /**< DVF filter frequency band phases. */
144+
143145
/* misc. */
144-
float src_dists_m[MAX_NUM_INPUTS]; /**< source distance, meters */
145-
float farfield_thresh_m; /**< distance considered to be far field (no near field filtering), meters */
146-
float farfield_headroom; /**< scale factor applied to farfield_thresh_m when resetting to the far field, and for UI range, meters */
147-
float nearfield_limit_m; /**< minimum distance allowed for near-field filtering, from head _center_, meters, def. 0.15 */
148-
float head_radius; /**< head radius, used calculate normalized source distance meters, def. 0.09096 */
149-
float head_radius_recip; /**< reciprocal of head radius */
146+
float src_dists_m[MAX_NUM_INPUTS]; /**< Source distance, meters. */
147+
float farfield_thresh_m; /**< Distance considered to be far field (no near field filtering), meters. */
148+
float farfield_headroom; /**< Scale factor applied to farfield_thresh_m when resetting to the far field, and for UI range, meters. */
149+
float nearfield_limit_m; /**< Minimum distance allowed for near-field filtering, from head _center_, meters, def. 0.15. */
150+
float head_radius; /**< Head radius, used calculate normalized source distance meters, def. 0.09096. */
151+
float head_radius_recip; /**< Reciprocal of head radius. */
152+
float (*src_dirs_cur)[2]; /**< Pointer to assign to the current HRTF directions being operated on (non/rotated directions switch). */
153+
154+
/* flags/status */
155+
int recalc_dvfCoeffFLAG[MAX_NUM_INPUTS]; /**< 1: re-calculate the DVF coefficients on change in distance, 0: do not. */
150156

151157
} binauraliserNF_data;
152158

159+
153160
/* ========================================================================== */
154161
/* Internal Functions */
155162
/* ========================================================================== */

framework/modules/saf_utilities/saf_utility_dvf.c

Lines changed: 77 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ static const double p23[19] = { -0.67, 0.142, 3404., -0.91, -0.67, -1.21, -1.76,
4848
static 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 };
4949
static 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 };
5050
static 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 };
51-
5251
static const int numAz_table = sizeof(q23);
53-
//static const float a_0 = 0.0875f; /**< Reference head size, 8.75 centimeters, used in the generation of the coeff lookup table. */
54-
//static const float a_head = 0.09096f; /**< This head size, See note for head_radius in binauraliser_nf. */
55-
static const float headDim = SAF_PI * (0.0875f/*a_0*/ / 0.09096f /*a_head*/);
56-
static const float sosDiv2PiA = 343.0f / (2.0f * SAF_PI * 0.09096f /*a_head*/);
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. */
55+
static const float headDim = SAF_PI * (0.0875f / 0.09096f); /* pi * (a_0 / a_head) */
56+
static const float sosDiv2PiA = 343.0f / (2.0f * SAF_PI * 0.09096f); /* c / (2pi * a_head) */
5757

5858
/** Linear interpolation between two values */
5959
static float interpolate_lin
@@ -78,18 +78,18 @@ static float db2mag
7878
/*
7979
* Calculate high-shelf parameters, g0, gInf, fc, from the lookup table coefficients (10 degree steps).
8080
* Called twice per update as the returned values are subsequently interpolated to exact azimuth. */
81-
void calcHighShelfParams
81+
void calcDVFShelfParams
8282
(
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 */
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. */
8888
)
8989
{
9090
float fc_tmp;
9191
double rho = (double)rhoIn;
92-
double rhoSq = rho*rho;
92+
double rhoSq = rho * rho;
9393

9494
/* Eq (8), (13) and (14) */
9595
*g0 = (float)((p11[i] * rho + p21[i]) / (rhoSq + q11[i] * rho + q21[i]));
@@ -101,69 +101,65 @@ void calcHighShelfParams
101101
}
102102

103103
/*
104-
* Interpolate (linear) the high shelf parameters generated by calcHighShelfParams()
105-
* which is called twice to generate the high shelf parameters for the nearest thetas
106-
* in the lookup table. */
107-
void interpHighShelfParams
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. */
107+
void interpDVFShelfParams
108108
(
109-
float theta, /* ipsilateral azimuth, on the inter-aural axis [0, 180] (deg) */
110-
float rho, /* distance, normalized to head radius, >= 1 */
109+
float theta, /* Ipsilateral azimuth, on the inter-aural axis [0, 180] (deg). */
110+
float rho, /* Distance, normalized to head radius, >= 1. */
111111
/* output */
112112
float* iG0,
113113
float* iGInf,
114114
float* iFc
115115
)
116116
{
117117
int theta_idx_lower, theta_idx_upper;
118-
float ifac;
119-
float thetaDiv10;
118+
float ifac, thetaDiv10;
120119
float g0_1, g0_2; /* high shelf gain at DC */
121120
float gInf_1, gInf_2; /* high shelf gain at inf */
122121
float fc_1, fc_2; /* high shelf cutoff frequency */
123122

124-
// TBD: add range checking? - clip theta and rho to valid range
123+
// TBD: could add range checking, clipping theta and rho to valid range
125124

126-
/* linearly interpolate DC gain, HF gain, center freq at theta */
125+
/* Linearly interpolate DC gain, HF gain, center freq at theta.
126+
* Table is in 10 degree steps, floor(x/10) gets lower index. */
127127
thetaDiv10 = theta / 10.f;
128-
theta_idx_lower = (int)thetaDiv10; /* Table is in 10 degree steps, floor(x/10) gets lower index */
128+
theta_idx_lower = (int)thetaDiv10;
129129
theta_idx_upper = theta_idx_lower + 1;
130-
if(theta_idx_upper == numAz_table) { // alternatively, instead check theta_idx_upper => numAz_table, could clip the value > 180 here
130+
if(theta_idx_upper == numAz_table) { // could alternatively check theta_idx_upper => numAz_table, clip the value > 180 here
131131
theta_idx_upper = theta_idx_lower;
132132
theta_idx_lower = theta_idx_lower - 1;
133133
}
134134

135-
calcHighShelfParams(theta_idx_lower, rho, &g0_1, &gInf_1, &fc_1);
136-
calcHighShelfParams(theta_idx_upper, rho, &g0_2, &gInf_2, &fc_2);
135+
calcDVFShelfParams(theta_idx_lower, rho, &g0_1, &gInf_1, &fc_1);
136+
calcDVFShelfParams(theta_idx_upper, rho, &g0_2, &gInf_2, &fc_2);
137137

138-
ifac = thetaDiv10 - theta_idx_lower; /* interpolation factor between table steps */
138+
/* interpolation factor between table steps */
139+
ifac = thetaDiv10 - theta_idx_lower;
139140
*iG0 = interpolate_lin(g0_1, g0_2, ifac);
140141
*iGInf = interpolate_lin(gInf_1, gInf_2, ifac);
141142
*iFc = interpolate_lin(fc_1, fc_2, ifac);
142143
}
143144

144145
/*
145-
* Generate IIR coefficients from the shelf parameters generated by calcHighShelfParams() and
146-
* interpHighShelfParams(). */
147-
void calcIIRCoeffs
146+
* Calculate the DVF filter coefficients from shelving filter parameters.
147+
*/
148+
void dvfShelfCoeffs
148149
(
149150
/* Input */
150-
float g0, /* high shelf dc gain */
151-
float gInf, /* high shelf high gain */
152-
float fc, /* high shelf center freq */
153-
float fs, /* sample rate */
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 */
154155
/* Output */
155156
float* b0, /* IIR coeffs */
156157
float* b1,
157158
float* a1
158159
)
159160
{
160-
float v0;
161-
float g0_mag;
162-
float tanF;
163-
float v0tanF;
164-
float a_c;
165-
float v;
166-
float va_c;
161+
float v0, g0_mag, tanF, v0tanF;
162+
float a_c, v, va_c;
167163

168164
v0 = db2mag(gInf); /* Eq. (12), (10), and (11) */
169165
g0_mag = db2mag(g0); // optim TBD: revisit; does g0, gInf need to be in dB?
@@ -178,40 +174,56 @@ void calcIIRCoeffs
178174
*a1 = a_c;
179175
}
180176

181-
void applyDVF
177+
void calcDVFCoeffs
182178
(
183-
float theta, /* ipsilateral azimuth, on the inter-aural axis [0, 180] (deg) */
184-
float rho, /* distance, normalized to head radius, >= 1 */
185-
float* in_signal,
186-
int nSamples, /* Number of samples to process */
187-
float fs, /* Sample rate */
188-
float* wz, /* (&) Filter coefficients to be passed to the next block */
189-
float* out_signal
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
190184
)
191185
{
192-
float b[2] = {0.f, 0.f};
193-
float a[2] = {1.f, 0.f};
194186
float iG0, iGInf, iFc;
195-
196-
interpHighShelfParams(theta, rho, &iG0, &iGInf, &iFc);
197-
calcIIRCoeffs(iG0, iGInf, iFc, fs, &b[0], &b[1], &a[1]);
198-
applyIIR(in_signal, nSamples, 2, &b[0], &a[0], wz, out_signal);
187+
interpDVFShelfParams(alpha, rho, &iG0, &iGInf, &iFc);
188+
dvfShelfCoeffs(iG0, iGInf, iFc, fs, &b[0], &b[1], &a[1]);
199189
}
200190

201-
void convertFrontalDoAToIpsilateral
191+
void doaToIpsiInteraural
202192
(
203-
float thetaFront, /* DOA wrt 0˚ forward-facing [deg, (-180, 180)] */
204-
float* ipsiDoaLR /* pointer to 2-element array of left and right ear DoAs wrt interaural axis */
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]) */
205197
)
206198
{
207-
float thetaL;
199+
float az_rad, el_rad, alpha_ipsi, beta_ipsi, alpha_ipsi_deg, beta_ipsi_deg;
200+
float sinaz, sinel, cosaz, cosel;
208201

209-
thetaFront = SAF_CLAMP(thetaFront, -180.f, 180.f);
210-
thetaL = fabsf(90.f - thetaFront);
211-
if (thetaL > 180.f) {
212-
thetaL = 360.f - thetaL;
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] */
213215
}
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;
214221

215-
ipsiDoaLR[0] = thetaL;
216-
ipsiDoaLR[1] = 180.f - thetaL; // thetaR
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+
}
217229
}

0 commit comments

Comments
 (0)