diff options
Diffstat (limited to 'Alc/filters')
-rw-r--r-- | Alc/filters/defs.h | 112 | ||||
-rw-r--r-- | Alc/filters/filter.c | 129 | ||||
-rw-r--r-- | Alc/filters/nfc.c | 426 | ||||
-rw-r--r-- | Alc/filters/nfc.h | 49 | ||||
-rw-r--r-- | Alc/filters/splitter.c | 109 | ||||
-rw-r--r-- | Alc/filters/splitter.h | 40 |
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 */ |