constfilt
Compile-time IIR digital filter design for C++17
Loading...
Searching...
No Matches
filter.hpp
1#ifndef CONSTFILT_FILTER_HPP
2#define CONSTFILT_FILTER_HPP
3
4#include "vendor/consteig/consteig.hpp"
5
6namespace constfilt
7{
8
9// NB = number of numerator coefficients, NA = number of denominator
10// coefficients. Convention: a[0] = 1 (monic denominator); stored for
11// uniformity. Direct Form II Transposed implementation.
12template <typename T, consteig::Size NB, consteig::Size NA> class Filter
13{
14 static constexpr consteig::Size M = (NA > NB ? NA : NB) - 1u; // STATE_LEN
15 // NB == NA == 1 gives H(z) = b[0]/a[0] which is a pure gain. It is
16 // mathematically valid but breaks the DF2T implementation (zero-sized
17 // state, unsigned underflow in loops).
18 static_assert(NB >= 1u,
19 "Filter requires at least one numerator coefficient");
20 static_assert(NA >= 1u,
21 "Filter requires at least one denominator coefficient");
22 static_assert(M >= 1u, "Filter requires max(NB, NA) - 1 >= 1");
23
24 protected:
25 T _b[NB]{};
26 T _a[NA]{};
27 mutable T _state[M]{}; // DF2T delay line, zero-initialized
28
29 constexpr Filter() = default;
30
31 constexpr Filter(const T (&b)[NB], const T (&a)[NA])
32 {
33 for (consteig::Size i = 0; i < NB; ++i)
34 {
35 _b[i] = b[i];
36 }
37 for (consteig::Size i = 0; i < NA; ++i)
38 {
39 _a[i] = a[i];
40 }
41 }
42
43 private:
44 constexpr T b_coeff(consteig::Size i) const
45 {
46 return i < NB ? _b[i] : T(0);
47 }
48 constexpr T a_coeff(consteig::Size i) const
49 {
50 return i < NA ? _a[i] : T(0);
51 }
52
53 public:
54 // Real-time: process one sample. Declared const so the filter can be
55 // static constexpr (coefficients at compile time) while still supporting
56 // runtime sample-by-sample processing. _state is mutable for this reason.
57 // Not thread-safe on shared instances without external synchronization.
58 T operator()(T x) const
59 {
60 const T y = _b[0] * x + _state[0];
61
62 for (consteig::Size k = 0; k < M - 1u; ++k)
63 {
64 _state[k] =
65 b_coeff(k + 1u) * x - a_coeff(k + 1u) * y + _state[k + 1u];
66 }
67 _state[M - 1u] = b_coeff(M) * x - a_coeff(M) * y;
68
69 return y;
70 }
71
72 // Batch: process an array with a local zero-initialized state.
73 // constexpr-capable (does not touch member state).
74 template <consteig::Size N>
75 constexpr void operator()(const T (&input)[N], T (&output)[N]) const
76 {
77 T local_state[M]{};
78
79 for (consteig::Size n = 0; n < N; ++n)
80 {
81 const T x = input[n];
82 const T y = _b[0] * x + local_state[0];
83
84 for (consteig::Size k = 0; k < M - 1u; ++k)
85 {
86 local_state[k] = b_coeff(k + 1u) * x - a_coeff(k + 1u) * y +
87 local_state[k + 1u];
88 }
89 local_state[M - 1u] = b_coeff(M) * x - a_coeff(M) * y;
90
91 output[n] = y;
92 }
93 }
94
95 // Accessors (for testing)
96 constexpr const T *coeffs_b() const
97 {
98 return _b;
99 }
100 constexpr const T *coeffs_a() const
101 {
102 return _a;
103 }
104
105 // Reset real-time state to zero.
106 void reset() const
107 {
108 for (consteig::Size k = 0; k < M; ++k)
109 {
110 _state[k] = static_cast<T>(0);
111 }
112 }
113};
114
115} // namespace constfilt
116
117#endif // CONSTFILT_FILTER_HPP