8 #include "opthelpers.h"
11 /* Near-field control filters are the basis for handling the near-field effect.
12 * The near-field effect is a bass-boost present in the directional components
13 * of a recorded signal, created as a result of the wavefront curvature (itself
14 * a function of sound distance). Proper reproduction dictates this be
15 * compensated for using a bass-cut given the playback speaker distance, to
16 * avoid excessive bass in the playback.
18 * For real-time rendered audio, emulating the near-field effect based on the
19 * sound source's distance, and subsequently compensating for it at output
20 * based on the speaker distances, can create a more realistic perception of
21 * sound distance beyond a simple 1/r attenuation.
23 * These filters do just that. Each one applies a low-shelf filter, created as
24 * the combination of a bass-boost for a given sound source distance (near-
25 * field emulation) along with a bass-cut for a given control/speaker distance
26 * (near-field compensation).
28 * Note that it is necessary to apply a cut along with the boost, since the
29 * boost alone is unstable in higher-order ambisonics as it causes an infinite
30 * DC gain (even first-order ambisonics requires there to be no DC offset for
31 * the boost to work). Consequently, ambisonics requires a control parameter to
32 * be used to avoid an unstable boost-only filter. NFC-HOA defines this control
33 * as a reference delay, calculated with:
35 * reference_delay = control_distance / speed_of_sound
37 * This means w0 (for input) or w1 (for output) should be set to:
39 * wN = 1 / (reference_delay * sample_rate)
41 * when dealing with NFC-HOA content. For FOA input content, which does not
42 * specify a reference_delay variable, w0 should be set to 0 to apply only
43 * near-field compensation for output. It's important that w1 be a finite,
44 * positive, non-0 value or else the bass-boost will become unstable again.
45 * Also, w0 should not be too large compared to w1, to avoid excessively loud
51 constexpr float B
[5][4] = {
55 { 3.6778f
, 6.4595f
, 2.3222f
},
56 { 4.2076f
, 11.4877f
, 5.7924f
, 9.1401f
}
59 NfcFilter1
NfcFilterCreate1(const float w0
, const float w1
) noexcept
68 /* Calculate bass-boost coefficients. */
74 nfc
.b1
= 2.0f
* b_00
/ g_0
;
76 /* Calculate bass-cut coefficients. */
83 nfc
.a1
= 2.0f
* b_00
/ g_0
;
88 void NfcFilterAdjust1(NfcFilter1
*nfc
, const float w0
) noexcept
90 const float r
{0.5f
* w0
};
91 const float b_00
{B
[1][0] * r
};
92 const float g_0
{1.0f
+ b_00
};
94 nfc
->gain
= nfc
->base_gain
* g_0
;
95 nfc
->b1
= 2.0f
* b_00
/ g_0
;
99 NfcFilter2
NfcFilterCreate2(const float w0
, const float w1
) noexcept
102 float b_10
, b_11
, g_1
;
105 nfc
.base_gain
= 1.0f
;
108 /* Calculate bass-boost coefficients. */
111 b_11
= B
[2][1] * r
* r
;
112 g_1
= 1.0f
+ b_10
+ b_11
;
115 nfc
.b1
= (2.0f
*b_10
+ 4.0f
*b_11
) / g_1
;
116 nfc
.b2
= 4.0f
* b_11
/ g_1
;
118 /* Calculate bass-cut coefficients. */
121 b_11
= B
[2][1] * r
* r
;
122 g_1
= 1.0f
+ b_10
+ b_11
;
124 nfc
.base_gain
/= g_1
;
126 nfc
.a1
= (2.0f
*b_10
+ 4.0f
*b_11
) / g_1
;
127 nfc
.a2
= 4.0f
* b_11
/ g_1
;
132 void NfcFilterAdjust2(NfcFilter2
*nfc
, const float w0
) noexcept
134 const float r
{0.5f
* w0
};
135 const float b_10
{B
[2][0] * r
};
136 const float b_11
{B
[2][1] * r
* r
};
137 const float g_1
{1.0f
+ b_10
+ b_11
};
139 nfc
->gain
= nfc
->base_gain
* g_1
;
140 nfc
->b1
= (2.0f
*b_10
+ 4.0f
*b_11
) / g_1
;
141 nfc
->b2
= 4.0f
* b_11
/ g_1
;
145 NfcFilter3
NfcFilterCreate3(const float w0
, const float w1
) noexcept
148 float b_10
, b_11
, g_1
;
152 nfc
.base_gain
= 1.0f
;
155 /* Calculate bass-boost coefficients. */
158 b_11
= B
[3][1] * r
* r
;
160 g_1
= 1.0f
+ b_10
+ b_11
;
163 nfc
.gain
*= g_1
* g_0
;
164 nfc
.b1
= (2.0f
*b_10
+ 4.0f
*b_11
) / g_1
;
165 nfc
.b2
= 4.0f
* b_11
/ g_1
;
166 nfc
.b3
= 2.0f
* b_00
/ g_0
;
168 /* Calculate bass-cut coefficients. */
171 b_11
= B
[3][1] * r
* r
;
173 g_1
= 1.0f
+ b_10
+ b_11
;
176 nfc
.base_gain
/= g_1
* g_0
;
177 nfc
.gain
/= g_1
* g_0
;
178 nfc
.a1
= (2.0f
*b_10
+ 4.0f
*b_11
) / g_1
;
179 nfc
.a2
= 4.0f
* b_11
/ g_1
;
180 nfc
.a3
= 2.0f
* b_00
/ g_0
;
185 void NfcFilterAdjust3(NfcFilter3
*nfc
, const float w0
) noexcept
187 const float r
{0.5f
* w0
};
188 const float b_10
{B
[3][0] * r
};
189 const float b_11
{B
[3][1] * r
* r
};
190 const float b_00
{B
[3][2] * r
};
191 const float g_1
{1.0f
+ b_10
+ b_11
};
192 const float g_0
{1.0f
+ b_00
};
194 nfc
->gain
= nfc
->base_gain
* g_1
* g_0
;
195 nfc
->b1
= (2.0f
*b_10
+ 4.0f
*b_11
) / g_1
;
196 nfc
->b2
= 4.0f
* b_11
/ g_1
;
197 nfc
->b3
= 2.0f
* b_00
/ g_0
;
201 NfcFilter4
NfcFilterCreate4(const float w0
, const float w1
) noexcept
204 float b_10
, b_11
, g_1
;
205 float b_00
, b_01
, g_0
;
208 nfc
.base_gain
= 1.0f
;
211 /* Calculate bass-boost coefficients. */
214 b_11
= B
[4][1] * r
* r
;
216 b_01
= B
[4][3] * r
* r
;
217 g_1
= 1.0f
+ b_10
+ b_11
;
218 g_0
= 1.0f
+ b_00
+ b_01
;
220 nfc
.gain
*= g_1
* g_0
;
221 nfc
.b1
= (2.0f
*b_10
+ 4.0f
*b_11
) / g_1
;
222 nfc
.b2
= 4.0f
* b_11
/ g_1
;
223 nfc
.b3
= (2.0f
*b_00
+ 4.0f
*b_01
) / g_0
;
224 nfc
.b4
= 4.0f
* b_01
/ g_0
;
226 /* Calculate bass-cut coefficients. */
229 b_11
= B
[4][1] * r
* r
;
231 b_01
= B
[4][3] * r
* r
;
232 g_1
= 1.0f
+ b_10
+ b_11
;
233 g_0
= 1.0f
+ b_00
+ b_01
;
235 nfc
.base_gain
/= g_1
* g_0
;
236 nfc
.gain
/= g_1
* g_0
;
237 nfc
.a1
= (2.0f
*b_10
+ 4.0f
*b_11
) / g_1
;
238 nfc
.a2
= 4.0f
* b_11
/ g_1
;
239 nfc
.a3
= (2.0f
*b_00
+ 4.0f
*b_01
) / g_0
;
240 nfc
.a4
= 4.0f
* b_01
/ g_0
;
245 void NfcFilterAdjust4(NfcFilter4
*nfc
, const float w0
) noexcept
247 const float r
{0.5f
* w0
};
248 const float b_10
{B
[4][0] * r
};
249 const float b_11
{B
[4][1] * r
* r
};
250 const float b_00
{B
[4][2] * r
};
251 const float b_01
{B
[4][3] * r
* r
};
252 const float g_1
{1.0f
+ b_10
+ b_11
};
253 const float g_0
{1.0f
+ b_00
+ b_01
};
255 nfc
->gain
= nfc
->base_gain
* g_1
* g_0
;
256 nfc
->b1
= (2.0f
*b_10
+ 4.0f
*b_11
) / g_1
;
257 nfc
->b2
= 4.0f
* b_11
/ g_1
;
258 nfc
->b3
= (2.0f
*b_00
+ 4.0f
*b_01
) / g_0
;
259 nfc
->b4
= 4.0f
* b_01
/ g_0
;
264 void NfcFilter::init(const float w1
) noexcept
266 first
= NfcFilterCreate1(0.0f
, w1
);
267 second
= NfcFilterCreate2(0.0f
, w1
);
268 third
= NfcFilterCreate3(0.0f
, w1
);
269 fourth
= NfcFilterCreate4(0.0f
, w1
);
272 void NfcFilter::adjust(const float w0
) noexcept
274 NfcFilterAdjust1(&first
, w0
);
275 NfcFilterAdjust2(&second
, w0
);
276 NfcFilterAdjust3(&third
, w0
);
277 NfcFilterAdjust4(&fourth
, w0
);
281 void NfcFilter::process1(const al::span
<const float> src
, float *RESTRICT dst
)
283 const float gain
{first
.gain
};
284 const float b1
{first
.b1
};
285 const float a1
{first
.a1
};
286 float z1
{first
.z
[0]};
287 auto proc_sample
= [gain
,b1
,a1
,&z1
](const float in
) noexcept
-> float
289 const float y
{in
*gain
- a1
*z1
};
290 const float out
{y
+ b1
*z1
};
294 std::transform(src
.cbegin(), src
.cend(), dst
, proc_sample
);
298 void NfcFilter::process2(const al::span
<const float> src
, float *RESTRICT dst
)
300 const float gain
{second
.gain
};
301 const float b1
{second
.b1
};
302 const float b2
{second
.b2
};
303 const float a1
{second
.a1
};
304 const float a2
{second
.a2
};
305 float z1
{second
.z
[0]};
306 float z2
{second
.z
[1]};
307 auto proc_sample
= [gain
,b1
,b2
,a1
,a2
,&z1
,&z2
](const float in
) noexcept
-> float
309 const float y
{in
*gain
- a1
*z1
- a2
*z2
};
310 const float out
{y
+ b1
*z1
+ b2
*z2
};
315 std::transform(src
.cbegin(), src
.cend(), dst
, proc_sample
);
320 void NfcFilter::process3(const al::span
<const float> src
, float *RESTRICT dst
)
322 const float gain
{third
.gain
};
323 const float b1
{third
.b1
};
324 const float b2
{third
.b2
};
325 const float b3
{third
.b3
};
326 const float a1
{third
.a1
};
327 const float a2
{third
.a2
};
328 const float a3
{third
.a3
};
329 float z1
{third
.z
[0]};
330 float z2
{third
.z
[1]};
331 float z3
{third
.z
[2]};
332 auto proc_sample
= [gain
,b1
,b2
,b3
,a1
,a2
,a3
,&z1
,&z2
,&z3
](const float in
) noexcept
-> float
334 float y
{in
*gain
- a1
*z1
- a2
*z2
};
335 float out
{y
+ b1
*z1
+ b2
*z2
};
344 std::transform(src
.cbegin(), src
.cend(), dst
, proc_sample
);
350 void NfcFilter::process4(const al::span
<const float> src
, float *RESTRICT dst
)
352 const float gain
{fourth
.gain
};
353 const float b1
{fourth
.b1
};
354 const float b2
{fourth
.b2
};
355 const float b3
{fourth
.b3
};
356 const float b4
{fourth
.b4
};
357 const float a1
{fourth
.a1
};
358 const float a2
{fourth
.a2
};
359 const float a3
{fourth
.a3
};
360 const float a4
{fourth
.a4
};
361 float z1
{fourth
.z
[0]};
362 float z2
{fourth
.z
[1]};
363 float z3
{fourth
.z
[2]};
364 float z4
{fourth
.z
[3]};
365 auto proc_sample
= [gain
,b1
,b2
,b3
,b4
,a1
,a2
,a3
,a4
,&z1
,&z2
,&z3
,&z4
](const float in
) noexcept
-> float
367 float y
{in
*gain
- a1
*z1
- a2
*z2
};
368 float out
{y
+ b1
*z1
+ b2
*z2
};
372 y
= out
- a3
*z3
- a4
*z4
;
373 out
= y
+ b3
*z3
+ b4
*z4
;
378 std::transform(src
.cbegin(), src
.cend(), dst
, proc_sample
);