aboutsummaryrefslogtreecommitdiffstats
path: root/Alc/filters
diff options
context:
space:
mode:
Diffstat (limited to 'Alc/filters')
-rw-r--r--Alc/filters/defs.h112
-rw-r--r--Alc/filters/filter.c129
-rw-r--r--Alc/filters/nfc.c426
-rw-r--r--Alc/filters/nfc.h49
-rw-r--r--Alc/filters/splitter.c109
-rw-r--r--Alc/filters/splitter.h40
6 files changed, 865 insertions, 0 deletions
diff --git a/Alc/filters/defs.h b/Alc/filters/defs.h
new file mode 100644
index 00000000..133a85eb
--- /dev/null
+++ b/Alc/filters/defs.h
@@ -0,0 +1,112 @@
+#ifndef ALC_FILTER_H
+#define ALC_FILTER_H
+
+#include "AL/al.h"
+#include "math_defs.h"
+
+/* Filters implementation is based on the "Cookbook formulae for audio
+ * EQ biquad filter coefficients" by Robert Bristow-Johnson
+ * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
+ */
+/* Implementation note: For the shelf filters, the specified gain is for the
+ * reference frequency, which is the centerpoint of the transition band. This
+ * better matches EFX filter design. To set the gain for the shelf itself, use
+ * the square root of the desired linear gain (or halve the dB gain).
+ */
+
+typedef enum BiquadType {
+ /** EFX-style low-pass filter, specifying a gain and reference frequency. */
+ BiquadType_HighShelf,
+ /** EFX-style high-pass filter, specifying a gain and reference frequency. */
+ BiquadType_LowShelf,
+ /** Peaking filter, specifying a gain and reference frequency. */
+ BiquadType_Peaking,
+
+ /** Low-pass cut-off filter, specifying a cut-off frequency. */
+ BiquadType_LowPass,
+ /** High-pass cut-off filter, specifying a cut-off frequency. */
+ BiquadType_HighPass,
+ /** Band-pass filter, specifying a center frequency. */
+ BiquadType_BandPass,
+} BiquadType;
+
+typedef struct BiquadFilter {
+ ALfloat z1, z2; /* Last two delayed components for direct form II. */
+ ALfloat b0, b1, b2; /* Transfer function coefficients "b" (numerator) */
+ ALfloat a1, a2; /* Transfer function coefficients "a" (denominator; a0 is
+ * pre-applied). */
+} BiquadFilter;
+/* Currently only a C-based filter process method is implemented. */
+#define BiquadFilter_process BiquadFilter_processC
+
+/**
+ * Calculates the rcpQ (i.e. 1/Q) coefficient for shelving filters, using the
+ * reference gain and shelf slope parameter.
+ * \param gain 0 < gain
+ * \param slope 0 < slope <= 1
+ */
+inline ALfloat calc_rcpQ_from_slope(ALfloat gain, ALfloat slope)
+{
+ return sqrtf((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f);
+}
+/**
+ * Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the normalized
+ * reference frequency and bandwidth.
+ * \param f0norm 0 < f0norm < 0.5.
+ * \param bandwidth 0 < bandwidth
+ */
+inline ALfloat calc_rcpQ_from_bandwidth(ALfloat f0norm, ALfloat bandwidth)
+{
+ ALfloat w0 = F_TAU * f0norm;
+ return 2.0f*sinhf(logf(2.0f)/2.0f*bandwidth*w0/sinf(w0));
+}
+
+inline void BiquadFilter_clear(BiquadFilter *filter)
+{
+ filter->z1 = 0.0f;
+ filter->z2 = 0.0f;
+}
+
+/**
+ * Sets up the filter state for the specified filter type and its parameters.
+ *
+ * \param filter The filter object to prepare.
+ * \param type The type of filter for the object to apply.
+ * \param gain The gain for the reference frequency response. Only used by the
+ * Shelf and Peaking filter types.
+ * \param f0norm The normalized reference frequency (ref_freq / sample_rate).
+ * This is the center point for the Shelf, Peaking, and BandPass
+ * filter types, or the cutoff frequency for the LowPass and
+ * HighPass filter types.
+ * \param rcpQ The reciprocal of the Q coefficient for the filter's transition
+ * band. Can be generated from calc_rcpQ_from_slope or
+ * calc_rcpQ_from_bandwidth depending on the available data.
+ */
+void BiquadFilter_setParams(BiquadFilter *filter, BiquadType type, ALfloat gain, ALfloat f0norm, ALfloat rcpQ);
+
+inline void BiquadFilter_copyParams(BiquadFilter *restrict dst, const BiquadFilter *restrict src)
+{
+ dst->b0 = src->b0;
+ dst->b1 = src->b1;
+ dst->b2 = src->b2;
+ dst->a1 = src->a1;
+ dst->a2 = src->a2;
+}
+
+void BiquadFilter_processC(BiquadFilter *filter, ALfloat *restrict dst, const ALfloat *restrict src, ALsizei numsamples);
+
+inline void BiquadFilter_passthru(BiquadFilter *filter, ALsizei numsamples)
+{
+ if(LIKELY(numsamples >= 2))
+ {
+ filter->z1 = 0.0f;
+ filter->z2 = 0.0f;
+ }
+ else if(numsamples == 1)
+ {
+ filter->z1 = filter->z2;
+ filter->z2 = 0.0f;
+ }
+}
+
+#endif /* ALC_FILTER_H */
diff --git a/Alc/filters/filter.c b/Alc/filters/filter.c
new file mode 100644
index 00000000..2b370f89
--- /dev/null
+++ b/Alc/filters/filter.c
@@ -0,0 +1,129 @@
+
+#include "config.h"
+
+#include "AL/alc.h"
+#include "AL/al.h"
+
+#include "alMain.h"
+#include "defs.h"
+
+extern inline void BiquadFilter_clear(BiquadFilter *filter);
+extern inline void BiquadFilter_copyParams(BiquadFilter *restrict dst, const BiquadFilter *restrict src);
+extern inline void BiquadFilter_passthru(BiquadFilter *filter, ALsizei numsamples);
+extern inline ALfloat calc_rcpQ_from_slope(ALfloat gain, ALfloat slope);
+extern inline ALfloat calc_rcpQ_from_bandwidth(ALfloat f0norm, ALfloat bandwidth);
+
+
+void BiquadFilter_setParams(BiquadFilter *filter, BiquadType type, ALfloat gain, ALfloat f0norm, ALfloat rcpQ)
+{
+ ALfloat alpha, sqrtgain_alpha_2;
+ ALfloat w0, sin_w0, cos_w0;
+ ALfloat a[3] = { 1.0f, 0.0f, 0.0f };
+ ALfloat b[3] = { 1.0f, 0.0f, 0.0f };
+
+ // Limit gain to -100dB
+ assert(gain > 0.00001f);
+
+ w0 = F_TAU * f0norm;
+ sin_w0 = sinf(w0);
+ cos_w0 = cosf(w0);
+ alpha = sin_w0/2.0f * rcpQ;
+
+ /* Calculate filter coefficients depending on filter type */
+ switch(type)
+ {
+ case BiquadType_HighShelf:
+ sqrtgain_alpha_2 = 2.0f * sqrtf(gain) * alpha;
+ b[0] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2);
+ b[1] = -2.0f*gain*((gain-1.0f) + (gain+1.0f)*cos_w0 );
+ b[2] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2);
+ a[0] = (gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2;
+ a[1] = 2.0f* ((gain-1.0f) - (gain+1.0f)*cos_w0 );
+ a[2] = (gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2;
+ break;
+ case BiquadType_LowShelf:
+ sqrtgain_alpha_2 = 2.0f * sqrtf(gain) * alpha;
+ b[0] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2);
+ b[1] = 2.0f*gain*((gain-1.0f) - (gain+1.0f)*cos_w0 );
+ b[2] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2);
+ a[0] = (gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2;
+ a[1] = -2.0f* ((gain-1.0f) + (gain+1.0f)*cos_w0 );
+ a[2] = (gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2;
+ break;
+ case BiquadType_Peaking:
+ gain = sqrtf(gain);
+ b[0] = 1.0f + alpha * gain;
+ b[1] = -2.0f * cos_w0;
+ b[2] = 1.0f - alpha * gain;
+ a[0] = 1.0f + alpha / gain;
+ a[1] = -2.0f * cos_w0;
+ a[2] = 1.0f - alpha / gain;
+ break;
+
+ case BiquadType_LowPass:
+ b[0] = (1.0f - cos_w0) / 2.0f;
+ b[1] = 1.0f - cos_w0;
+ b[2] = (1.0f - cos_w0) / 2.0f;
+ a[0] = 1.0f + alpha;
+ a[1] = -2.0f * cos_w0;
+ a[2] = 1.0f - alpha;
+ break;
+ case BiquadType_HighPass:
+ b[0] = (1.0f + cos_w0) / 2.0f;
+ b[1] = -(1.0f + cos_w0);
+ b[2] = (1.0f + cos_w0) / 2.0f;
+ a[0] = 1.0f + alpha;
+ a[1] = -2.0f * cos_w0;
+ a[2] = 1.0f - alpha;
+ break;
+ case BiquadType_BandPass:
+ b[0] = alpha;
+ b[1] = 0;
+ b[2] = -alpha;
+ a[0] = 1.0f + alpha;
+ a[1] = -2.0f * cos_w0;
+ a[2] = 1.0f - alpha;
+ break;
+ }
+
+ filter->a1 = a[1] / a[0];
+ filter->a2 = a[2] / a[0];
+ filter->b0 = b[0] / a[0];
+ filter->b1 = b[1] / a[0];
+ filter->b2 = b[2] / a[0];
+}
+
+
+void BiquadFilter_processC(BiquadFilter *filter, ALfloat *restrict dst, const ALfloat *restrict src, ALsizei numsamples)
+{
+ const ALfloat a1 = filter->a1;
+ const ALfloat a2 = filter->a2;
+ const ALfloat b0 = filter->b0;
+ const ALfloat b1 = filter->b1;
+ const ALfloat b2 = filter->b2;
+ ALfloat z1 = filter->z1;
+ ALfloat z2 = filter->z2;
+ ALsizei i;
+
+ ASSUME(numsamples > 0);
+
+ /* Processing loop is Transposed Direct Form II. This requires less storage
+ * compared to Direct Form I (only two delay components, instead of a four-
+ * sample history; the last two inputs and outputs), and works better for
+ * floating-point which favors summing similarly-sized values while being
+ * less bothered by overflow.
+ *
+ * See: http://www.earlevel.com/main/2003/02/28/biquads/
+ */
+ for(i = 0;i < numsamples;i++)
+ {
+ ALfloat input = src[i];
+ ALfloat output = input*b0 + z1;
+ z1 = input*b1 - output*a1 + z2;
+ z2 = input*b2 - output*a2;
+ dst[i] = output;
+ }
+
+ filter->z1 = z1;
+ filter->z2 = z2;
+}
diff --git a/Alc/filters/nfc.c b/Alc/filters/nfc.c
new file mode 100644
index 00000000..8869d1d0
--- /dev/null
+++ b/Alc/filters/nfc.c
@@ -0,0 +1,426 @@
+
+#include "config.h"
+
+#include "nfc.h"
+#include "alMain.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->base_gain = 1.0f;
+ nfc->gain = 1.0f;
+
+ /* Calculate bass-boost coefficients. */
+ r = 0.5f * w0;
+ b_00 = B[1][0] * r;
+ g_0 = 1.0f + b_00;
+
+ nfc->gain *= g_0;
+ nfc->b1 = 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->base_gain /= g_0;
+ nfc->gain /= g_0;
+ nfc->a1 = 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->gain = nfc->base_gain * g_0;
+ nfc->b1 = 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->base_gain = 1.0f;
+ nfc->gain = 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->gain *= g_1;
+ nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc->b2 = 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->base_gain /= g_1;
+ nfc->gain /= g_1;
+ nfc->a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc->a2 = 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->gain = nfc->base_gain * g_1;
+ nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc->b2 = 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->base_gain = 1.0f;
+ nfc->gain = 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->gain *= g_1;
+ nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc->b2 = 4.0f * b_11 / g_1;
+
+ b_00 = B[3][2] * r;
+ g_0 = 1.0f + b_00;
+
+ nfc->gain *= g_0;
+ nfc->b3 = 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->base_gain /= g_1;
+ nfc->gain /= g_1;
+ nfc->a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc->a2 = 4.0f * b_11 / g_1;
+
+ b_00 = B[3][2] * r;
+ g_0 = 1.0f + b_00;
+
+ nfc->base_gain /= g_0;
+ nfc->gain /= g_0;
+ nfc->a3 = 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->gain = nfc->base_gain * g_1;
+ nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
+ nfc->b2 = 4.0f * b_11 / g_1;
+
+ b_00 = B[3][2] * r;
+ g_0 = 1.0f + b_00;
+
+ nfc->gain *= g_0;
+ nfc->b3 = 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 NfcFilterProcess1(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count)
+{
+ const float gain = nfc->first.gain;
+ const float b1 = nfc->first.b1;
+ const float a1 = nfc->first.a1;
+ float z1 = nfc->first.z[0];
+ int i;
+
+ ASSUME(count > 0);
+
+ for(i = 0;i < count;i++)
+ {
+ float y = src[i]*gain - a1*z1;
+ float out = y + b1*z1;
+ z1 += y;
+
+ dst[i] = out;
+ }
+ nfc->first.z[0] = z1;
+}
+
+void NfcFilterProcess2(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count)
+{
+ const float gain = nfc->second.gain;
+ const float b1 = nfc->second.b1;
+ const float b2 = nfc->second.b2;
+ const float a1 = nfc->second.a1;
+ const float a2 = nfc->second.a2;
+ float z1 = nfc->second.z[0];
+ float z2 = nfc->second.z[1];
+ int i;
+
+ ASSUME(count > 0);
+
+ for(i = 0;i < count;i++)
+ {
+ float y = src[i]*gain - a1*z1 - a2*z2;
+ float out = y + b1*z1 + b2*z2;
+ z2 += z1;
+ z1 += y;
+
+ dst[i] = out;
+ }
+ nfc->second.z[0] = z1;
+ nfc->second.z[1] = z2;
+}
+
+void NfcFilterProcess3(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count)
+{
+ const float gain = nfc->third.gain;
+ const float b1 = nfc->third.b1;
+ const float b2 = nfc->third.b2;
+ const float b3 = nfc->third.b3;
+ const float a1 = nfc->third.a1;
+ const float a2 = nfc->third.a2;
+ const float a3 = nfc->third.a3;
+ float z1 = nfc->third.z[0];
+ float z2 = nfc->third.z[1];
+ float z3 = nfc->third.z[2];
+ int i;
+
+ ASSUME(count > 0);
+
+ for(i = 0;i < count;i++)
+ {
+ float y = src[i]*gain - a1*z1 - a2*z2;
+ float out = y + b1*z1 + b2*z2;
+ z2 += z1;
+ z1 += y;
+
+ y = out - a3*z3;
+ out = y + b3*z3;
+ z3 += y;
+
+ dst[i] = out;
+ }
+ nfc->third.z[0] = z1;
+ nfc->third.z[1] = z2;
+ nfc->third.z[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 NfcFilterProcess(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..12a5a18f
--- /dev/null
+++ b/Alc/filters/nfc.h
@@ -0,0 +1,49 @@
+#ifndef FILTER_NFC_H
+#define FILTER_NFC_H
+
+struct NfcFilter1 {
+ float base_gain, gain;
+ float b1, a1;
+ float z[1];
+};
+struct NfcFilter2 {
+ float base_gain, gain;
+ float b1, b2, a1, a2;
+ float z[2];
+};
+struct NfcFilter3 {
+ float base_gain, gain;
+ float b1, b2, b3, a1, a2, a3;
+ float z[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 NfcFilterProcess1(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count);
+
+/* Near-field control filter for second-order ambisonic channels (4-8). */
+void NfcFilterProcess2(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count);
+
+/* Near-field control filter for third-order ambisonic channels (9-15). */
+void NfcFilterProcess3(NfcFilter *nfc, float *restrict dst, const float *restrict src, const int count);
+
+#endif /* FILTER_NFC_H */
diff --git a/Alc/filters/splitter.c b/Alc/filters/splitter.c
new file mode 100644
index 00000000..e99f4b95
--- /dev/null
+++ b/Alc/filters/splitter.c
@@ -0,0 +1,109 @@
+
+#include "config.h"
+
+#include "splitter.h"
+
+#include "math_defs.h"
+
+
+void bandsplit_init(BandSplitter *splitter, ALfloat f0norm)
+{
+ ALfloat w = f0norm * F_TAU;
+ ALfloat cw = cosf(w);
+ if(cw > FLT_EPSILON)
+ splitter->coeff = (sinf(w) - 1.0f) / cw;
+ else
+ splitter->coeff = cw * -0.5f;
+
+ splitter->lp_z1 = 0.0f;
+ splitter->lp_z2 = 0.0f;
+ splitter->hp_z1 = 0.0f;
+}
+
+void bandsplit_clear(BandSplitter *splitter)
+{
+ splitter->lp_z1 = 0.0f;
+ splitter->lp_z2 = 0.0f;
+ splitter->hp_z1 = 0.0f;
+}
+
+void bandsplit_process(BandSplitter *splitter, ALfloat *restrict hpout, ALfloat *restrict lpout,
+ const ALfloat *input, ALsizei count)
+{
+ ALfloat lp_coeff, hp_coeff, lp_y, hp_y, d;
+ ALfloat lp_z1, lp_z2, hp_z1;
+ ALsizei i;
+
+ ASSUME(count > 0);
+
+ hp_coeff = splitter->coeff;
+ lp_coeff = splitter->coeff*0.5f + 0.5f;
+ lp_z1 = splitter->lp_z1;
+ lp_z2 = splitter->lp_z2;
+ hp_z1 = splitter->hp_z1;
+ for(i = 0;i < count;i++)
+ {
+ ALfloat in = input[i];
+
+ /* Low-pass sample processing. */
+ d = (in - lp_z1) * lp_coeff;
+ lp_y = lp_z1 + d;
+ lp_z1 = lp_y + d;
+
+ d = (lp_y - lp_z2) * lp_coeff;
+ lp_y = lp_z2 + d;
+ lp_z2 = lp_y + d;
+
+ lpout[i] = lp_y;
+
+ /* All-pass sample processing. */
+ hp_y = in*hp_coeff + hp_z1;
+ hp_z1 = in - hp_y*hp_coeff;
+
+ /* High-pass generated from removing low-passed output. */
+ hpout[i] = hp_y - lp_y;
+ }
+ splitter->lp_z1 = lp_z1;
+ splitter->lp_z2 = lp_z2;
+ splitter->hp_z1 = hp_z1;
+}
+
+
+void splitterap_init(SplitterAllpass *splitter, ALfloat f0norm)
+{
+ ALfloat w = f0norm * F_TAU;
+ ALfloat cw = cosf(w);
+ if(cw > FLT_EPSILON)
+ splitter->coeff = (sinf(w) - 1.0f) / cw;
+ else
+ splitter->coeff = cw * -0.5f;
+
+ splitter->z1 = 0.0f;
+}
+
+void splitterap_clear(SplitterAllpass *splitter)
+{
+ splitter->z1 = 0.0f;
+}
+
+void splitterap_process(SplitterAllpass *splitter, ALfloat *restrict samples, ALsizei count)
+{
+ ALfloat coeff, in, out;
+ ALfloat z1;
+ ALsizei i;
+
+ ASSUME(count > 0);
+
+ coeff = splitter->coeff;
+ z1 = splitter->z1;
+ for(i = 0;i < count;i++)
+ {
+ in = samples[i];
+
+ out = in*coeff + z1;
+ z1 = in - out*coeff;
+
+ samples[i] = out;
+ }
+ splitter->z1 = z1;
+}
diff --git a/Alc/filters/splitter.h b/Alc/filters/splitter.h
new file mode 100644
index 00000000..a788bc3e
--- /dev/null
+++ b/Alc/filters/splitter.h
@@ -0,0 +1,40 @@
+#ifndef FILTER_SPLITTER_H
+#define FILTER_SPLITTER_H
+
+#include "alMain.h"
+
+
+/* Band splitter. Splits a signal into two phase-matching frequency bands. */
+typedef struct BandSplitter {
+ ALfloat coeff;
+ ALfloat lp_z1;
+ ALfloat lp_z2;
+ ALfloat hp_z1;
+} BandSplitter;
+
+void bandsplit_init(BandSplitter *splitter, ALfloat f0norm);
+void bandsplit_clear(BandSplitter *splitter);
+void bandsplit_process(BandSplitter *splitter, ALfloat *restrict hpout, ALfloat *restrict lpout,
+ const ALfloat *input, ALsizei count);
+
+/* The all-pass portion of the band splitter. Applies the same phase shift
+ * without splitting the signal.
+ */
+typedef struct SplitterAllpass {
+ ALfloat coeff;
+ ALfloat z1;
+} SplitterAllpass;
+
+void splitterap_init(SplitterAllpass *splitter, ALfloat f0norm);
+void splitterap_clear(SplitterAllpass *splitter);
+void splitterap_process(SplitterAllpass *splitter, ALfloat *restrict samples, ALsizei count);
+
+
+typedef struct FrontStablizer {
+ SplitterAllpass APFilter[MAX_OUTPUT_CHANNELS];
+ BandSplitter LFilter, RFilter;
+ alignas(16) ALfloat LSplit[2][BUFFERSIZE];
+ alignas(16) ALfloat RSplit[2][BUFFERSIZE];
+} FrontStablizer;
+
+#endif /* FILTER_SPLITTER_H */