constfilt
Compile-time IIR digital filter design for C++17
Loading...
Searching...
No Matches
analog_filter.hpp
1#ifndef CONSTFILT_ANALOG_FILTER_HPP
2#define CONSTFILT_ANALOG_FILTER_HPP
3
4#include "discretize.hpp"
5#include "filter.hpp"
6#include "stability.hpp"
7
8namespace constfilt
9{
10
11struct LowPass
12{
13};
14
15struct HighPass
16{
17};
18
19// Discretize an analog (continuous-time, s-domain) transfer function into a
20// digital Filter.
21//
22// Coefficients are in descending power order:
23// coeff[0]*s^N + coeff[1]*s^{N-1} + ... + coeff[N]
24//
25// Template parameters:
26// T - numeric type (float, double, ...)
27// N - filter order (degree of denominator)
28// Method - TustinNW (default), TustinPW, ZOH, or MatchedZ
29// CheckStab - reserved for future use; currently has no effect (see below).
30//
31// Constructors:
32// AnalogFilter(b_c, a_c, sample_rate_hz)
33// b_c - s-domain numerator array [N+1], descending order
34// a_c - s-domain denominator array [N+1], descending order
35// sample_rate_hz - sample rate in Hz
36//
37// AnalogFilter(b_c, a_c, sample_rate_hz, method_tag)
38// method_tag - method instance; for TustinPW supply
39// constfilt::prewarp(warp_hz)
40//
41// AnalogFilter(continuous_tf, sample_rate_hz)
42// continuous_tf - s-domain TransferFunction (e.g. from a subclass)
43// sample_rate_hz - sample rate in Hz
44//
45// AnalogFilter(continuous_tf, sample_rate_hz, method_tag)
46// method_tag - method instance; for TustinPW supply
47// constfilt::prewarp(warp_hz)
48//
49// AnalogFilter(continuous_tf, factored_tf, sample_rate_hz, method_tag)
50// factored_tf - the poles, zeros, and gain of continuous_tf in
51// factored form. Required alongside continuous_tf.
52// For ZOH, the poles are used to build the Vandermonde
53// eigenvector matrix analytically. For MatchedZ, the
54// poles and zeros are mapped directly to the z-domain.
55template <typename T, consteig::Size N, typename Method = TustinNW,
56 bool CheckStab = true>
57class AnalogFilter : public Filter<T, N + 1u, N + 1u>
58{
59 static_assert(N >= 1u, "Filter order must be at least 1");
60
61 using BoundMethod = typename bind_method<T, Method>::type;
62
63 public:
64 constexpr AnalogFilter(const T (&b_c)[N + 1u], const T (&a_c)[N + 1u],
65 T sample_rate_hz)
66 : AnalogFilter(
67 checked_discretize(b_c, a_c, sample_rate_hz, BoundMethod{}))
68 {
69 static_assert(!is_tustinpw_tag<Method>::value,
70 "TustinPW requires a warp frequency; use the "
71 "method-tag constructor with constfilt::prewarp(wc_hz)");
72 }
73
74 constexpr AnalogFilter(const T (&b_c)[N + 1u], const T (&a_c)[N + 1u],
75 T sample_rate_hz, BoundMethod method_tag)
76 : AnalogFilter(checked_discretize(b_c, a_c, sample_rate_hz, method_tag))
77 {
78 }
79
80 constexpr AnalogFilter(TransferFunction<T, N + 1u, N + 1u> continuous_tf,
81 T sample_rate_hz)
82 : AnalogFilter(checked_discretize(continuous_tf.b, continuous_tf.a,
83 sample_rate_hz, BoundMethod{}))
84 {
85 static_assert(!is_tustinpw_tag<Method>::value,
86 "TustinPW requires a warp frequency; use the "
87 "method-tag constructor with constfilt::prewarp(wc_hz)");
88 }
89
90 constexpr AnalogFilter(TransferFunction<T, N + 1u, N + 1u> continuous_tf,
91 T sample_rate_hz, BoundMethod method_tag)
92 : AnalogFilter(checked_discretize(continuous_tf.b, continuous_tf.a,
93 sample_rate_hz, method_tag))
94 {
95 }
96
97 constexpr AnalogFilter(TransferFunction<T, N + 1u, N + 1u> continuous_tf,
98 const FactoredTF<T, N> &factored_tf,
99 T sample_rate_hz, BoundMethod method_tag)
100 : AnalogFilter(checked_discretize_factored(continuous_tf.b,
101 continuous_tf.a, factored_tf,
102 sample_rate_hz, method_tag))
103 {
104 }
105
106 private:
107 constexpr explicit AnalogFilter(
108 TransferFunction<T, N + 1u, N + 1u> digital_tf)
109 : Filter<T, N + 1u, N + 1u>(digital_tf.b, digital_tf.a)
110 {
111 }
112
113 static constexpr TransferFunction<T, N + 1u, N + 1u> checked_discretize(
114 const T (&b_c)[N + 1u], const T (&a_c)[N + 1u], T sample_rate_hz,
115 BoundMethod method_tag)
116 {
117 // Stability check is currently disabled. See:
118 // https://github.com/MitchellThompkins/constfilt/issues/14
119 //
120 // The original implementation used:
121 // throw "constfilt: unstable analog filter";
122 // which serves dual purpose in C++17: a runtime exception and, when
123 // evaluated in a constexpr context, a compile-time error. This is
124 // incompatible with -fno-exceptions and freestanding (no-stdlib)
125 // builds.
126 //
127 // Candidate replacements, each with trade-offs:
128 // __builtin_trap() - preserves compile-time error via the
129 // non-constexpr call rule, but is
130 // GCC/Clang specific.
131 // [[noreturn]] customization point (declared, not defined) -
132 // portable,
133 // user supplies the handler; linker error
134 // if forgotten; compile-time error still
135 // works via non-constexpr call rule.
136 // Remove entirely - current state; no check at runtime or
137 // compile time; caller's responsibility.
138 //
139 // if (CheckStab &&
140 // check_stability(tf_to_ss<T, N>(b_c, a_c)) == Stability::Unstable)
141 // {
142 // throw "constfilt: unstable analog filter";
143 // }
144 return analog_to_digital<T, N>(
145 b_c, a_c, static_cast<T>(1) / sample_rate_hz, method_tag);
146 }
147
148 static constexpr TransferFunction<T, N + 1u, N + 1u>
149 checked_discretize_factored(const T (&b_c)[N + 1u], const T (&a_c)[N + 1u],
150 const FactoredTF<T, N> &factored_tf,
151 T sample_rate_hz, BoundMethod method_tag)
152 {
153 return discretize_with_factored<T, N>(
154 b_c, a_c, factored_tf, static_cast<T>(1) / sample_rate_hz,
155 method_tag);
156 }
157};
158
159} // namespace constfilt
160
161#endif // CONSTFILT_ANALOG_FILTER_HPP