diff options
author | Chris Robinson <[email protected]> | 2018-03-22 07:15:19 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2018-03-22 07:17:48 -0700 |
commit | 8e976a92f7e23504275fb9d1d5d55df215c7d47d (patch) | |
tree | d9b825d607e65c30be79fed3385b5139be2c489e /Alc/filters | |
parent | 7a23330ffe34c0d04f882fdb45f68733ba49659d (diff) |
Move NFC filters to the filter directory
Diffstat (limited to 'Alc/filters')
-rw-r--r-- | Alc/filters/nfc.c | 428 | ||||
-rw-r--r-- | Alc/filters/nfc.h | 49 |
2 files changed, 477 insertions, 0 deletions
diff --git a/Alc/filters/nfc.c b/Alc/filters/nfc.c new file mode 100644 index 00000000..b9006955 --- /dev/null +++ b/Alc/filters/nfc.c @@ -0,0 +1,428 @@ + +#include "config.h" + +#include "nfc.h" + +#include <string.h> + + +/* Near-field control filters are the basis for handling the near-field effect. + * The near-field effect is a bass-boost present in the directional components + * of a recorded signal, created as a result of the wavefront curvature (itself + * a function of sound distance). Proper reproduction dictates this be + * compensated for using a bass-cut given the playback speaker distance, to + * avoid excessive bass in the playback. + * + * For real-time rendered audio, emulating the near-field effect based on the + * sound source's distance, and subsequently compensating for it at output + * based on the speaker distances, can create a more realistic perception of + * sound distance beyond a simple 1/r attenuation. + * + * These filters do just that. Each one applies a low-shelf filter, created as + * the combination of a bass-boost for a given sound source distance (near- + * field emulation) along with a bass-cut for a given control/speaker distance + * (near-field compensation). + * + * Note that it is necessary to apply a cut along with the boost, since the + * boost alone is unstable in higher-order ambisonics as it causes an infinite + * DC gain (even first-order ambisonics requires there to be no DC offset for + * the boost to work). Consequently, ambisonics requires a control parameter to + * be used to avoid an unstable boost-only filter. NFC-HOA defines this control + * as a reference delay, calculated with: + * + * reference_delay = control_distance / speed_of_sound + * + * This means w0 (for input) or w1 (for output) should be set to: + * + * wN = 1 / (reference_delay * sample_rate) + * + * when dealing with NFC-HOA content. For FOA input content, which does not + * specify a reference_delay variable, w0 should be set to 0 to apply only + * near-field compensation for output. It's important that w1 be a finite, + * positive, non-0 value or else the bass-boost will become unstable again. + * Also, w0 should not be too large compared to w1, to avoid excessively loud + * low frequencies. + */ + +static const float B[4][3] = { + { 0.0f }, + { 1.0f }, + { 3.0f, 3.0f }, + { 3.6778f, 6.4595f, 2.3222f }, + /*{ 4.2076f, 11.4877f, 5.7924f, 9.1401f }*/ +}; + +static void NfcFilterCreate1(struct NfcFilter1 *nfc, const float w0, const float w1) +{ + float b_00, g_0; + float r; + + nfc->g = 1.0f; + nfc->coeffs[0] = 1.0f; + + /* Calculate bass-boost coefficients. */ + r = 0.5f * w0; + b_00 = B[1][0] * r; + g_0 = 1.0f + b_00; + + nfc->coeffs[0] *= g_0; + nfc->coeffs[1] = (2.0f * b_00) / g_0; + + /* Calculate bass-cut coefficients. */ + r = 0.5f * w1; + b_00 = B[1][0] * r; + g_0 = 1.0f + b_00; + + nfc->g /= g_0; + nfc->coeffs[0] /= g_0; + nfc->coeffs[1+1] = (2.0f * b_00) / g_0; +} + +static void NfcFilterAdjust1(struct NfcFilter1 *nfc, const float w0) +{ + float b_00, g_0; + float r; + + r = 0.5f * w0; + b_00 = B[1][0] * r; + g_0 = 1.0f + b_00; + + nfc->coeffs[0] = nfc->g * g_0; + nfc->coeffs[1] = (2.0f * b_00) / g_0; +} + + +static void NfcFilterCreate2(struct NfcFilter2 *nfc, const float w0, const float w1) +{ + float b_10, b_11, g_1; + float r; + + nfc->g = 1.0f; + nfc->coeffs[0] = 1.0f; + + /* Calculate bass-boost coefficients. */ + r = 0.5f * w0; + b_10 = B[2][0] * r; + b_11 = B[2][1] * r * r; + g_1 = 1.0f + b_10 + b_11; + + nfc->coeffs[0] *= g_1; + nfc->coeffs[1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1; + nfc->coeffs[2] = (4.0f * b_11) / g_1; + + /* Calculate bass-cut coefficients. */ + r = 0.5f * w1; + b_10 = B[2][0] * r; + b_11 = B[2][1] * r * r; + g_1 = 1.0f + b_10 + b_11; + + nfc->g /= g_1; + nfc->coeffs[0] /= g_1; + nfc->coeffs[2+1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1; + nfc->coeffs[2+2] = (4.0f * b_11) / g_1; +} + +static void NfcFilterAdjust2(struct NfcFilter2 *nfc, const float w0) +{ + float b_10, b_11, g_1; + float r; + + r = 0.5f * w0; + b_10 = B[2][0] * r; + b_11 = B[2][1] * r * r; + g_1 = 1.0f + b_10 + b_11; + + nfc->coeffs[0] = nfc->g * g_1; + nfc->coeffs[1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1; + nfc->coeffs[2] = (4.0f * b_11) / g_1; +} + + +static void NfcFilterCreate3(struct NfcFilter3 *nfc, const float w0, const float w1) +{ + float b_10, b_11, g_1; + float b_00, g_0; + float r; + + nfc->g = 1.0f; + nfc->coeffs[0] = 1.0f; + + /* Calculate bass-boost coefficients. */ + r = 0.5f * w0; + b_10 = B[3][0] * r; + b_11 = B[3][1] * r * r; + g_1 = 1.0f + b_10 + b_11; + + nfc->coeffs[0] *= g_1; + nfc->coeffs[1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1; + nfc->coeffs[2] = (4.0f * b_11) / g_1; + + b_00 = B[3][2] * r; + g_0 = 1.0f + b_00; + + nfc->coeffs[0] *= g_0; + nfc->coeffs[2+1] = (2.0f * b_00) / g_0; + + /* Calculate bass-cut coefficients. */ + r = 0.5f * w1; + b_10 = B[3][0] * r; + b_11 = B[3][1] * r * r; + g_1 = 1.0f + b_10 + b_11; + + nfc->g /= g_1; + nfc->coeffs[0] /= g_1; + nfc->coeffs[3+1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1; + nfc->coeffs[3+2] = (4.0f * b_11) / g_1; + + b_00 = B[3][2] * r; + g_0 = 1.0f + b_00; + + nfc->g /= g_0; + nfc->coeffs[0] /= g_0; + nfc->coeffs[3+2+1] = (2.0f * b_00) / g_0; +} + +static void NfcFilterAdjust3(struct NfcFilter3 *nfc, const float w0) +{ + float b_10, b_11, g_1; + float b_00, g_0; + float r; + + r = 0.5f * w0; + b_10 = B[3][0] * r; + b_11 = B[3][1] * r * r; + g_1 = 1.0f + b_10 + b_11; + + nfc->coeffs[0] = nfc->g * g_1; + nfc->coeffs[1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1; + nfc->coeffs[2] = (4.0f * b_11) / g_1; + + b_00 = B[3][2] * r; + g_0 = 1.0f + b_00; + + nfc->coeffs[0] *= g_0; + nfc->coeffs[2+1] = (2.0f * b_00) / g_0; +} + + +void NfcFilterCreate(NfcFilter *nfc, const float w0, const float w1) +{ + memset(nfc, 0, sizeof(*nfc)); + NfcFilterCreate1(&nfc->first, w0, w1); + NfcFilterCreate2(&nfc->second, w0, w1); + NfcFilterCreate3(&nfc->third, w0, w1); +} + +void NfcFilterAdjust(NfcFilter *nfc, const float w0) +{ + NfcFilterAdjust1(&nfc->first, w0); + NfcFilterAdjust2(&nfc->second, w0); + NfcFilterAdjust3(&nfc->third, w0); +} + + +void NfcFilterUpdate1(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count) +{ + const float b0 = nfc->first.coeffs[0]; + const float a0 = nfc->first.coeffs[1]; + const float a1 = nfc->first.coeffs[2]; + float z1 = nfc->first.history[0]; + int i; + + for(i = 0;i < count;i++) + { + float out = src[i] * b0; + float y; + + y = out - (a1*z1); + out = y + (a0*z1); + z1 += y; + + dst[i] = out; + } + nfc->first.history[0] = z1; +} + +void NfcFilterUpdate2(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count) +{ + const float b0 = nfc->second.coeffs[0]; + const float a00 = nfc->second.coeffs[1]; + const float a01 = nfc->second.coeffs[2]; + const float a10 = nfc->second.coeffs[3]; + const float a11 = nfc->second.coeffs[4]; + float z1 = nfc->second.history[0]; + float z2 = nfc->second.history[1]; + int i; + + for(i = 0;i < count;i++) + { + float out = src[i] * b0; + float y; + + y = out - (a10*z1) - (a11*z2); + out = y + (a00*z1) + (a01*z2); + z2 += z1; + z1 += y; + + dst[i] = out; + } + nfc->second.history[0] = z1; + nfc->second.history[1] = z2; +} + +void NfcFilterUpdate3(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count) +{ + const float b0 = nfc->third.coeffs[0]; + const float a00 = nfc->third.coeffs[1]; + const float a01 = nfc->third.coeffs[2]; + const float a02 = nfc->third.coeffs[3]; + const float a10 = nfc->third.coeffs[4]; + const float a11 = nfc->third.coeffs[5]; + const float a12 = nfc->third.coeffs[6]; + float z1 = nfc->third.history[0]; + float z2 = nfc->third.history[1]; + float z3 = nfc->third.history[2]; + int i; + + for(i = 0;i < count;i++) + { + float out = src[i] * b0; + float y; + + y = out - (a10*z1) - (a11*z2); + out = y + (a00*z1) + (a01*z2); + z2 += z1; + z1 += y; + + y = out - (a12*z3); + out = y + (a02*z3); + z3 += y; + + dst[i] = out; + } + nfc->third.history[0] = z1; + nfc->third.history[1] = z2; + nfc->third.history[2] = z3; +} + +#if 0 /* Original methods the above are derived from. */ +static void NfcFilterCreate(NfcFilter *nfc, const ALsizei order, const float src_dist, const float ctl_dist, const float rate) +{ + static const float B[4][5] = { + { }, + { 1.0f }, + { 3.0f, 3.0f }, + { 3.6778f, 6.4595f, 2.3222f }, + { 4.2076f, 11.4877f, 5.7924f, 9.1401f } + }; + float w0 = SPEEDOFSOUNDMETRESPERSEC / (src_dist * rate); + float w1 = SPEEDOFSOUNDMETRESPERSEC / (ctl_dist * rate); + ALsizei i; + float r; + + nfc->g = 1.0f; + nfc->coeffs[0] = 1.0f; + + /* NOTE: Slight adjustment from the literature to raise the center + * frequency a bit (0.5 -> 1.0). + */ + r = 1.0f * w0; + for(i = 0; i < (order-1);i += 2) + { + float b_10 = B[order][i ] * r; + float b_11 = B[order][i+1] * r * r; + float g_1 = 1.0f + b_10 + b_11; + + nfc->b[i] = b_10; + nfc->b[i + 1] = b_11; + nfc->coeffs[0] *= g_1; + nfc->coeffs[i+1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1; + nfc->coeffs[i+2] = (4.0f * b_11) / g_1; + } + if(i < order) + { + float b_00 = B[order][i] * r; + float g_0 = 1.0f + b_00; + + nfc->b[i] = b_00; + nfc->coeffs[0] *= g_0; + nfc->coeffs[i+1] = (2.0f * b_00) / g_0; + } + + r = 1.0f * w1; + for(i = 0;i < (order-1);i += 2) + { + float b_10 = B[order][i ] * r; + float b_11 = B[order][i+1] * r * r; + float g_1 = 1.0f + b_10 + b_11; + + nfc->g /= g_1; + nfc->coeffs[0] /= g_1; + nfc->coeffs[order+i+1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1; + nfc->coeffs[order+i+2] = (4.0f * b_11) / g_1; + } + if(i < order) + { + float b_00 = B[order][i] * r; + float g_0 = 1.0f + b_00; + + nfc->g /= g_0; + nfc->coeffs[0] /= g_0; + nfc->coeffs[order+i+1] = (2.0f * b_00) / g_0; + } + + for(i = 0; i < MAX_AMBI_ORDER; i++) + nfc->history[i] = 0.0f; +} + +static void NfcFilterAdjust(NfcFilter *nfc, const float distance) +{ + int i; + + nfc->coeffs[0] = nfc->g; + + for(i = 0;i < (nfc->order-1);i += 2) + { + float b_10 = nfc->b[i] / distance; + float b_11 = nfc->b[i+1] / (distance * distance); + float g_1 = 1.0f + b_10 + b_11; + + nfc->coeffs[0] *= g_1; + nfc->coeffs[i+1] = ((2.0f * b_10) + (4.0f * b_11)) / g_1; + nfc->coeffs[i+2] = (4.0f * b_11) / g_1; + } + if(i < nfc->order) + { + float b_00 = nfc->b[i] / distance; + float g_0 = 1.0f + b_00; + + nfc->coeffs[0] *= g_0; + nfc->coeffs[i+1] = (2.0f * b_00) / g_0; + } +} + +static float NfcFilterUpdate(const float in, NfcFilter *nfc) +{ + int i; + float out = in * nfc->coeffs[0]; + + for(i = 0;i < (nfc->order-1);i += 2) + { + float y = out - (nfc->coeffs[nfc->order+i+1] * nfc->history[i]) - + (nfc->coeffs[nfc->order+i+2] * nfc->history[i+1]) + 1.0e-30f; + out = y + (nfc->coeffs[i+1]*nfc->history[i]) + (nfc->coeffs[i+2]*nfc->history[i+1]); + + nfc->history[i+1] += nfc->history[i]; + nfc->history[i] += y; + } + if(i < nfc->order) + { + float y = out - (nfc->coeffs[nfc->order+i+1] * nfc->history[i]) + 1.0e-30f; + + out = y + (nfc->coeffs[i+1] * nfc->history[i]); + nfc->history[i] += y; + } + + return out; +} +#endif diff --git a/Alc/filters/nfc.h b/Alc/filters/nfc.h new file mode 100644 index 00000000..f0987d97 --- /dev/null +++ b/Alc/filters/nfc.h @@ -0,0 +1,49 @@ +#ifndef FILTER_NFC_H +#define FILTER_NFC_H + +struct NfcFilter1 { + float g; + float coeffs[1*2 + 1]; + float history[1]; +}; +struct NfcFilter2 { + float g; + float coeffs[2*2 + 1]; + float history[2]; +}; +struct NfcFilter3 { + float g; + float coeffs[3*2 + 1]; + float history[3]; +}; + +typedef struct NfcFilter { + struct NfcFilter1 first; + struct NfcFilter2 second; + struct NfcFilter3 third; +} NfcFilter; + + +/* NOTE: + * w0 = speed_of_sound / (source_distance * sample_rate); + * w1 = speed_of_sound / (control_distance * sample_rate); + * + * Generally speaking, the control distance should be approximately the average + * speaker distance, or based on the reference delay if outputing NFC-HOA. It + * must not be negative, 0, or infinite. The source distance should not be too + * small relative to the control distance. + */ + +void NfcFilterCreate(NfcFilter *nfc, const float w0, const float w1); +void NfcFilterAdjust(NfcFilter *nfc, const float w0); + +/* Near-field control filter for first-order ambisonic channels (1-3). */ +void NfcFilterUpdate1(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count); + +/* Near-field control filter for second-order ambisonic channels (4-8). */ +void NfcFilterUpdate2(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count); + +/* Near-field control filter for third-order ambisonic channels (9-15). */ +void NfcFilterUpdate3(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count); + +#endif /* FILTER_NFC_H */ |