aboutsummaryrefslogtreecommitdiffstats
path: root/alc/filters/biquad.h
blob: d7a195a9351178befa67adf744aba1c6d0e2d0fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#ifndef FILTERS_BIQUAD_H
#define FILTERS_BIQUAD_H

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <utility>

#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 and peaking filters, the specified gain
 * is for the centerpoint of the transition band. This better fits EFX filter
 * behavior, which expects the shelf's reference frequency to reach the given
 * gain. To set the gain for the shelf or peak itself, use the square root of
 * the desired linear gain (or halve the dB gain).
 */

enum class BiquadType {
    /** EFX-style low-pass filter, specifying a gain and reference frequency. */
    HighShelf,
    /** EFX-style high-pass filter, specifying a gain and reference frequency. */
    LowShelf,
    /** Peaking filter, specifying a gain and reference frequency. */
    Peaking,

    /** Low-pass cut-off filter, specifying a cut-off frequency. */
    LowPass,
    /** High-pass cut-off filter, specifying a cut-off frequency. */
    HighPass,
    /** Band-pass filter, specifying a center frequency. */
    BandPass,
};

template<typename Real>
class BiquadFilterR {
    /* Last two delayed components for direct form II. */
    Real mZ1{0.0f}, mZ2{0.0f};
    /* Transfer function coefficients "b" (numerator) */
    Real mB0{1.0f}, mB1{0.0f}, mB2{0.0f};
    /* Transfer function coefficients "a" (denominator; a0 is pre-applied). */
    Real mA1{0.0f}, mA2{0.0f};

    void setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ);

public:
    void clear() noexcept { mZ1 = mZ2 = 0.0f; }

    /**
     * Sets the filter state for the specified filter type and its parameters.
     *
     * \param type The type of filter to apply.
     * \param f0norm The normalized reference frequency (ref / 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 gain The gain for the reference frequency response. Only used by
     * the Shelf and Peaking filter types.
     * \param slope Slope steepness of the transition band.
     */
    void setParamsFromSlope(BiquadType type, Real f0norm, Real gain, Real slope)
    {
        gain = std::max<Real>(gain, 0.001f); /* Limit -60dB */
        setParams(type, gain, f0norm, rcpQFromSlope(gain, slope));
    }

    /**
     * Sets the filter state for the specified filter type and its parameters.
     *
     * \param type The type of filter to apply.
     * \param f0norm The normalized reference frequency (ref / 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 gain The gain for the reference frequency response. Only used by
     * the Shelf and Peaking filter types.
     * \param bandwidth Normalized bandwidth of the transition band.
     */
    void setParamsFromBandwidth(BiquadType type, Real f0norm, Real gain, Real bandwidth)
    { setParams(type, gain, f0norm, rcpQFromBandwidth(f0norm, bandwidth)); }

    void copyParamsFrom(const BiquadFilterR &other)
    {
        mB0 = other.mB0;
        mB1 = other.mB1;
        mB2 = other.mB2;
        mA1 = other.mA1;
        mA2 = other.mA2;
    }


    void process(Real *dst, const Real *src, const size_t numsamples);

    /* Rather hacky. It's just here to support "manual" processing. */
    std::pair<Real,Real> getComponents() const noexcept { return {mZ1, mZ2}; }
    void setComponents(Real z1, Real z2) noexcept { mZ1 = z1; mZ2 = z2; }
    Real processOne(const Real in, Real &z1, Real &z2) const noexcept
    {
        Real out{in*mB0 + z1};
        z1 = in*mB1 - out*mA1 + z2;
        z2 = in*mB2 - out*mA2;
        return out;
    }

    /**
     * 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
     */
    static Real rcpQFromSlope(Real gain, Real slope)
    { return std::sqrt((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
     */
    static Real rcpQFromBandwidth(Real f0norm, Real bandwidth)
    {
        const Real w0{al::MathDefs<Real>::Tau() * f0norm};
        return 2.0f*std::sinh(std::log(Real{2.0f})/2.0f*bandwidth*w0/std::sin(w0));
    }
};

using BiquadFilter = BiquadFilterR<float>;

#endif /* FILTERS_BIQUAD_H */