]> git.tdb.fi Git - ext/openal.git/blob - core/filters/nfc.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / core / filters / nfc.cpp
1
2 #include "config.h"
3
4 #include "nfc.h"
5
6 #include <algorithm>
7
8 #include "opthelpers.h"
9
10
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.
17  *
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.
22  *
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).
27  *
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:
34  *
35  * reference_delay = control_distance / speed_of_sound
36  *
37  * This means w0 (for input) or w1 (for output) should be set to:
38  *
39  * wN = 1 / (reference_delay * sample_rate)
40  *
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.
47  */
48
49 namespace {
50
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 }
57 };
58
59 NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept
60 {
61     NfcFilter1 nfc{};
62     float b_00, g_0;
63     float r;
64
65     /* Calculate bass-cut coefficients. */
66     r = 0.5f * w1;
67     b_00 = B[1][0] * r;
68     g_0 = 1.0f + b_00;
69
70     nfc.base_gain = 1.0f / g_0;
71     nfc.a1 = 2.0f * b_00 / g_0;
72
73     /* Calculate bass-boost coefficients. */
74     r = 0.5f * w0;
75     b_00 = B[1][0] * r;
76     g_0 = 1.0f + b_00;
77
78     nfc.gain = nfc.base_gain * g_0;
79     nfc.b1 = 2.0f * b_00 / g_0;
80
81     return nfc;
82 }
83
84 void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept
85 {
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};
89
90     nfc->gain = nfc->base_gain * g_0;
91     nfc->b1 = 2.0f * b_00 / g_0;
92 }
93
94
95 NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept
96 {
97     NfcFilter2 nfc{};
98     float b_10, b_11, g_1;
99     float r;
100
101     /* Calculate bass-cut coefficients. */
102     r = 0.5f * w1;
103     b_10 = B[2][0] * r;
104     b_11 = B[2][1] * r * r;
105     g_1 = 1.0f + b_10 + b_11;
106
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;
110
111     /* Calculate bass-boost coefficients. */
112     r = 0.5f * w0;
113     b_10 = B[2][0] * r;
114     b_11 = B[2][1] * r * r;
115     g_1 = 1.0f + b_10 + b_11;
116
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;
120
121     return nfc;
122 }
123
124 void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept
125 {
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};
130
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;
134 }
135
136
137 NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept
138 {
139     NfcFilter3 nfc{};
140     float b_10, b_11, g_1;
141     float b_00, g_0;
142     float r;
143
144     /* Calculate bass-cut coefficients. */
145     r = 0.5f * w1;
146     b_10 = B[3][0] * r;
147     b_11 = B[3][1] * r * r;
148     b_00 = B[3][2] * r;
149     g_1 = 1.0f + b_10 + b_11;
150     g_0 = 1.0f + b_00;
151
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;
156
157     /* Calculate bass-boost coefficients. */
158     r = 0.5f * w0;
159     b_10 = B[3][0] * r;
160     b_11 = B[3][1] * r * r;
161     b_00 = B[3][2] * r;
162     g_1 = 1.0f + b_10 + b_11;
163     g_0 = 1.0f + b_00;
164
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;
169
170     return nfc;
171 }
172
173 void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept
174 {
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};
181
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;
186 }
187
188
189 NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept
190 {
191     NfcFilter4 nfc{};
192     float b_10, b_11, g_1;
193     float b_00, b_01, g_0;
194     float r;
195
196     /* Calculate bass-cut coefficients. */
197     r = 0.5f * w1;
198     b_10 = B[4][0] * r;
199     b_11 = B[4][1] * r * r;
200     b_00 = B[4][2] * 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;
204
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;
210
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;
219
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;
225
226     return nfc;
227 }
228
229 void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept
230 {
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};
238
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;
244 }
245
246 } // namespace
247
248 void NfcFilter::init(const float w1) noexcept
249 {
250     first = NfcFilterCreate1(0.0f, w1);
251     second = NfcFilterCreate2(0.0f, w1);
252     third = NfcFilterCreate3(0.0f, w1);
253     fourth = NfcFilterCreate4(0.0f, w1);
254 }
255
256 void NfcFilter::adjust(const float w0) noexcept
257 {
258     NfcFilterAdjust1(&first, w0);
259     NfcFilterAdjust2(&second, w0);
260     NfcFilterAdjust3(&third, w0);
261     NfcFilterAdjust4(&fourth, w0);
262 }
263
264
265 void NfcFilter::process1(const al::span<const float> src, float *RESTRICT dst)
266 {
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
272     {
273         const float y{in*gain - a1*z1};
274         const float out{y + b1*z1};
275         z1 += y;
276         return out;
277     };
278     std::transform(src.cbegin(), src.cend(), dst, proc_sample);
279     first.z[0] = z1;
280 }
281
282 void NfcFilter::process2(const al::span<const float> src, float *RESTRICT dst)
283 {
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
292     {
293         const float y{in*gain - a1*z1 - a2*z2};
294         const float out{y + b1*z1 + b2*z2};
295         z2 += z1;
296         z1 += y;
297         return out;
298     };
299     std::transform(src.cbegin(), src.cend(), dst, proc_sample);
300     second.z[0] = z1;
301     second.z[1] = z2;
302 }
303
304 void NfcFilter::process3(const al::span<const float> src, float *RESTRICT dst)
305 {
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
317     {
318         float y{in*gain - a1*z1 - a2*z2};
319         float out{y + b1*z1 + b2*z2};
320         z2 += z1;
321         z1 += y;
322
323         y = out - a3*z3;
324         out = y + b3*z3;
325         z3 += y;
326         return out;
327     };
328     std::transform(src.cbegin(), src.cend(), dst, proc_sample);
329     third.z[0] = z1;
330     third.z[1] = z2;
331     third.z[2] = z3;
332 }
333
334 void NfcFilter::process4(const al::span<const float> src, float *RESTRICT dst)
335 {
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
350     {
351         float y{in*gain - a1*z1 - a2*z2};
352         float out{y + b1*z1 + b2*z2};
353         z2 += z1;
354         z1 += y;
355
356         y = out - a3*z3 - a4*z4;
357         out = y + b3*z3 + b4*z4;
358         z4 += z3;
359         z3 += y;
360         return out;
361     };
362     std::transform(src.cbegin(), src.cend(), dst, proc_sample);
363     fourth.z[0] = z1;
364     fourth.z[1] = z2;
365     fourth.z[2] = z3;
366     fourth.z[3] = z4;
367 }