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
65 /* Calculate bass-cut coefficients. */
70 nfc.base_gain = 1.0f / g_0;
71 nfc.a1 = 2.0f * b_00 / g_0;
73 /* Calculate bass-boost coefficients. */
78 nfc.gain = nfc.base_gain * g_0;
79 nfc.b1 = 2.0f * b_00 / g_0;
84 void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept
86 const float r{0.5f * w0};
87 const float b_00{B[1][0] * r};
88 const float g_0{1.0f + b_00};
90 nfc->gain = nfc->base_gain * g_0;
91 nfc->b1 = 2.0f * b_00 / g_0;
95 NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept
98 float b_10, b_11, g_1;
101 /* Calculate bass-cut coefficients. */
104 b_11 = B[2][1] * r * r;
105 g_1 = 1.0f + b_10 + b_11;
107 nfc.base_gain = 1.0f / g_1;
108 nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
109 nfc.a2 = 4.0f * b_11 / g_1;
111 /* Calculate bass-boost coefficients. */
114 b_11 = B[2][1] * r * r;
115 g_1 = 1.0f + b_10 + b_11;
117 nfc.gain = nfc.base_gain * g_1;
118 nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
119 nfc.b2 = 4.0f * b_11 / g_1;
124 void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept
126 const float r{0.5f * w0};
127 const float b_10{B[2][0] * r};
128 const float b_11{B[2][1] * r * r};
129 const float g_1{1.0f + b_10 + b_11};
131 nfc->gain = nfc->base_gain * g_1;
132 nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
133 nfc->b2 = 4.0f * b_11 / g_1;
137 NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept
140 float b_10, b_11, g_1;
144 /* Calculate bass-cut coefficients. */
147 b_11 = B[3][1] * r * r;
149 g_1 = 1.0f + b_10 + b_11;
152 nfc.base_gain = 1.0f / (g_1 * g_0);
153 nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
154 nfc.a2 = 4.0f * b_11 / g_1;
155 nfc.a3 = 2.0f * b_00 / g_0;
157 /* Calculate bass-boost coefficients. */
160 b_11 = B[3][1] * r * r;
162 g_1 = 1.0f + b_10 + b_11;
165 nfc.gain = nfc.base_gain * (g_1 * g_0);
166 nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
167 nfc.b2 = 4.0f * b_11 / g_1;
168 nfc.b3 = 2.0f * b_00 / g_0;
173 void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept
175 const float r{0.5f * w0};
176 const float b_10{B[3][0] * r};
177 const float b_11{B[3][1] * r * r};
178 const float b_00{B[3][2] * r};
179 const float g_1{1.0f + b_10 + b_11};
180 const float g_0{1.0f + b_00};
182 nfc->gain = nfc->base_gain * (g_1 * g_0);
183 nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
184 nfc->b2 = 4.0f * b_11 / g_1;
185 nfc->b3 = 2.0f * b_00 / g_0;
189 NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept
192 float b_10, b_11, g_1;
193 float b_00, b_01, g_0;
196 /* Calculate bass-cut coefficients. */
199 b_11 = B[4][1] * r * r;
201 b_01 = B[4][3] * r * r;
202 g_1 = 1.0f + b_10 + b_11;
203 g_0 = 1.0f + b_00 + b_01;
205 nfc.base_gain = 1.0f / (g_1 * g_0);
206 nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
207 nfc.a2 = 4.0f * b_11 / g_1;
208 nfc.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
209 nfc.a4 = 4.0f * b_01 / g_0;
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 = nfc.base_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;
229 void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept
231 const float r{0.5f * w0};
232 const float b_10{B[4][0] * r};
233 const float b_11{B[4][1] * r * r};
234 const float b_00{B[4][2] * r};
235 const float b_01{B[4][3] * r * r};
236 const float g_1{1.0f + b_10 + b_11};
237 const float g_0{1.0f + b_00 + b_01};
239 nfc->gain = nfc->base_gain * (g_1 * g_0);
240 nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
241 nfc->b2 = 4.0f * b_11 / g_1;
242 nfc->b3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
243 nfc->b4 = 4.0f * b_01 / g_0;
248 void NfcFilter::init(const float w1) noexcept
250 first = NfcFilterCreate1(0.0f, w1);
251 second = NfcFilterCreate2(0.0f, w1);
252 third = NfcFilterCreate3(0.0f, w1);
253 fourth = NfcFilterCreate4(0.0f, w1);
256 void NfcFilter::adjust(const float w0) noexcept
258 NfcFilterAdjust1(&first, w0);
259 NfcFilterAdjust2(&second, w0);
260 NfcFilterAdjust3(&third, w0);
261 NfcFilterAdjust4(&fourth, w0);
265 void NfcFilter::process1(const al::span<const float> src, float *RESTRICT dst)
267 const float gain{first.gain};
268 const float b1{first.b1};
269 const float a1{first.a1};
270 float z1{first.z[0]};
271 auto proc_sample = [gain,b1,a1,&z1](const float in) noexcept -> float
273 const float y{in*gain - a1*z1};
274 const float out{y + b1*z1};
278 std::transform(src.cbegin(), src.cend(), dst, proc_sample);
282 void NfcFilter::process2(const al::span<const float> src, float *RESTRICT dst)
284 const float gain{second.gain};
285 const float b1{second.b1};
286 const float b2{second.b2};
287 const float a1{second.a1};
288 const float a2{second.a2};
289 float z1{second.z[0]};
290 float z2{second.z[1]};
291 auto proc_sample = [gain,b1,b2,a1,a2,&z1,&z2](const float in) noexcept -> float
293 const float y{in*gain - a1*z1 - a2*z2};
294 const float out{y + b1*z1 + b2*z2};
299 std::transform(src.cbegin(), src.cend(), dst, proc_sample);
304 void NfcFilter::process3(const al::span<const float> src, float *RESTRICT dst)
306 const float gain{third.gain};
307 const float b1{third.b1};
308 const float b2{third.b2};
309 const float b3{third.b3};
310 const float a1{third.a1};
311 const float a2{third.a2};
312 const float a3{third.a3};
313 float z1{third.z[0]};
314 float z2{third.z[1]};
315 float z3{third.z[2]};
316 auto proc_sample = [gain,b1,b2,b3,a1,a2,a3,&z1,&z2,&z3](const float in) noexcept -> float
318 float y{in*gain - a1*z1 - a2*z2};
319 float out{y + b1*z1 + b2*z2};
328 std::transform(src.cbegin(), src.cend(), dst, proc_sample);
334 void NfcFilter::process4(const al::span<const float> src, float *RESTRICT dst)
336 const float gain{fourth.gain};
337 const float b1{fourth.b1};
338 const float b2{fourth.b2};
339 const float b3{fourth.b3};
340 const float b4{fourth.b4};
341 const float a1{fourth.a1};
342 const float a2{fourth.a2};
343 const float a3{fourth.a3};
344 const float a4{fourth.a4};
345 float z1{fourth.z[0]};
346 float z2{fourth.z[1]};
347 float z3{fourth.z[2]};
348 float z4{fourth.z[3]};
349 auto proc_sample = [gain,b1,b2,b3,b4,a1,a2,a3,a4,&z1,&z2,&z3,&z4](const float in) noexcept -> float
351 float y{in*gain - a1*z1 - a2*z2};
352 float out{y + b1*z1 + b2*z2};
356 y = out - a3*z3 - a4*z4;
357 out = y + b3*z3 + b4*z4;
362 std::transform(src.cbegin(), src.cend(), dst, proc_sample);