Add missing linear resampler to the option setting list
[openal-soft.git] / alc / filters / nfc.cpp
blob9a28517c732e797a455f323ab063d993115cd45f
2 #include "config.h"
4 #include "nfc.h"
6 #include <algorithm>
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
46 * low frequencies.
49 namespace {
51 constexpr float B[5][4] = {
52 { 0.0f },
53 { 1.0f },
54 { 3.0f, 3.0f },
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
61 NfcFilter1 nfc{};
62 float b_00, g_0;
63 float r;
65 nfc.base_gain = 1.0f;
66 nfc.gain = 1.0f;
68 /* Calculate bass-boost coefficients. */
69 r = 0.5f * w0;
70 b_00 = B[1][0] * r;
71 g_0 = 1.0f + b_00;
73 nfc.gain *= g_0;
74 nfc.b1 = 2.0f * b_00 / g_0;
76 /* Calculate bass-cut coefficients. */
77 r = 0.5f * w1;
78 b_00 = B[1][0] * r;
79 g_0 = 1.0f + b_00;
81 nfc.base_gain /= g_0;
82 nfc.gain /= g_0;
83 nfc.a1 = 2.0f * b_00 / g_0;
85 return nfc;
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
101 NfcFilter2 nfc{};
102 float b_10, b_11, g_1;
103 float r;
105 nfc.base_gain = 1.0f;
106 nfc.gain = 1.0f;
108 /* Calculate bass-boost coefficients. */
109 r = 0.5f * w0;
110 b_10 = B[2][0] * r;
111 b_11 = B[2][1] * r * r;
112 g_1 = 1.0f + b_10 + b_11;
114 nfc.gain *= g_1;
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. */
119 r = 0.5f * w1;
120 b_10 = B[2][0] * r;
121 b_11 = B[2][1] * r * r;
122 g_1 = 1.0f + b_10 + b_11;
124 nfc.base_gain /= g_1;
125 nfc.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;
129 return nfc;
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
147 NfcFilter3 nfc{};
148 float b_10, b_11, g_1;
149 float b_00, g_0;
150 float r;
152 nfc.base_gain = 1.0f;
153 nfc.gain = 1.0f;
155 /* Calculate bass-boost coefficients. */
156 r = 0.5f * w0;
157 b_10 = B[3][0] * r;
158 b_11 = B[3][1] * r * r;
159 b_00 = B[3][2] * r;
160 g_1 = 1.0f + b_10 + b_11;
161 g_0 = 1.0f + b_00;
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. */
169 r = 0.5f * w1;
170 b_10 = B[3][0] * r;
171 b_11 = B[3][1] * r * r;
172 b_00 = B[3][2] * r;
173 g_1 = 1.0f + b_10 + b_11;
174 g_0 = 1.0f + b_00;
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;
182 return nfc;
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
203 NfcFilter4 nfc{};
204 float b_10, b_11, g_1;
205 float b_00, b_01, g_0;
206 float r;
208 nfc.base_gain = 1.0f;
209 nfc.gain = 1.0f;
211 /* Calculate bass-boost coefficients. */
212 r = 0.5f * w0;
213 b_10 = B[4][0] * r;
214 b_11 = B[4][1] * r * r;
215 b_00 = B[4][2] * 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. */
227 r = 0.5f * w1;
228 b_10 = B[4][0] * r;
229 b_11 = B[4][1] * r * r;
230 b_00 = B[4][2] * 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;
242 return nfc;
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;
262 } // namespace
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};
291 z1 += y;
292 return out;
294 std::transform(src.cbegin(), src.cend(), dst, proc_sample);
295 first.z[0] = z1;
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};
311 z2 += z1;
312 z1 += y;
313 return out;
315 std::transform(src.cbegin(), src.cend(), dst, proc_sample);
316 second.z[0] = z1;
317 second.z[1] = z2;
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};
336 z2 += z1;
337 z1 += y;
339 y = out - a3*z3;
340 out = y + b3*z3;
341 z3 += y;
342 return out;
344 std::transform(src.cbegin(), src.cend(), dst, proc_sample);
345 third.z[0] = z1;
346 third.z[1] = z2;
347 third.z[2] = z3;
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};
369 z2 += z1;
370 z1 += y;
372 y = out - a3*z3 - a4*z4;
373 out = y + b3*z3 + b4*z4;
374 z4 += z3;
375 z3 += y;
376 return out;
378 std::transform(src.cbegin(), src.cend(), dst, proc_sample);
379 fourth.z[0] = z1;
380 fourth.z[1] = z2;
381 fourth.z[2] = z3;
382 fourth.z[3] = z4;