Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
filter_morph.hpp
Go to the documentation of this file.
1#pragma once
2
5
8
9#include <algorithm>
10#include <cmath>
11#include <complex>
12#include <cstddef>
13#include <limits>
14#include <span>
15#include <type_traits>
16
17namespace pulp::signal {
18
20
21template <typename SampleType = float> class FilterMorphT {
22 static_assert(std::is_floating_point_v<SampleType>);
23 using InternalType = std::conditional_t<std::is_same_v<SampleType, float>, double, SampleType>;
24
25 public:
26 static constexpr SampleType min_frequency_hz = SampleType{20};
27 static constexpr SampleType min_q = SampleType{0.1};
28 static constexpr SampleType max_q = SampleType{20};
29 static constexpr SampleType max_sample_rate = SampleType{384000};
30
31 struct Endpoint {
33 SampleType frequency_hz = SampleType{1000};
34 SampleType q = SampleType{0.7071067811865476};
35 };
36
39 bool configure(SampleType sample_rate, Endpoint first, Endpoint second) noexcept {
40 if (!valid_sample_rate_(sample_rate) || !valid_endpoint_(first, sample_rate) ||
41 !valid_endpoint_(second, sample_rate))
42 return false;
43
44 const auto first_coefficients = design_(first, sample_rate);
45 const auto second_coefficients = design_(second, sample_rate);
46 if (!finite_coefficients_(first_coefficients) ||
47 !finite_coefficients_(second_coefficients) || !biquad_is_stable(first_coefficients) ||
49 return false;
50
51 first_filter_.set_coefficients(first_coefficients);
52 second_filter_.set_coefficients(second_coefficients);
53 sample_rate_ = sample_rate;
54 endpoints_[0] = first;
55 endpoints_[1] = second;
56 configured_ = true;
57 return true;
58 }
59
62 bool set_morph(SampleType amount) noexcept {
63 if (!std::isfinite(static_cast<double>(amount)) || amount < SampleType{0} ||
64 amount > SampleType{1})
65 return false;
66 morph_ = amount;
67 return true;
68 }
69
70 SampleType process(SampleType input) noexcept {
71 if (!std::isfinite(static_cast<double>(input))) {
72 reset();
73 return SampleType{0};
74 }
75 if (!configured_)
76 return input;
77
78 // Always advance both paths, including at exact endpoints, so a later
79 // morph change is independent of the preceding block partition.
80 const InternalType first = first_filter_.process(static_cast<InternalType>(input));
81 const InternalType second = second_filter_.process(static_cast<InternalType>(input));
82 const bool first_finite = std::isfinite(first);
83 const bool second_finite = std::isfinite(second);
84 if (morph_ == SampleType{0}) {
85 if (!second_finite)
86 second_filter_.reset();
87 if (!first_finite || !representable_(first)) {
88 first_filter_.reset();
89 return SampleType{0};
90 }
91 return static_cast<SampleType>(first);
92 }
93 if (morph_ == SampleType{1}) {
94 if (!first_finite)
95 first_filter_.reset();
96 if (!second_finite || !representable_(second)) {
97 second_filter_.reset();
98 return SampleType{0};
99 }
100 return static_cast<SampleType>(second);
101 }
102 if (!first_finite || !second_finite) {
103 if (!first_finite)
104 first_filter_.reset();
105 if (!second_finite)
106 second_filter_.reset();
107 return SampleType{0};
108 }
109 const InternalType output = std::lerp(first, second, static_cast<InternalType>(morph_));
110 if (!representable_(output)) {
111 reset();
112 return SampleType{0};
113 }
114 return static_cast<SampleType>(output);
115 }
116
117 bool process_block(const SampleType* input, SampleType* output, std::size_t frames) noexcept {
118 if ((input == nullptr || output == nullptr) && frames != 0)
119 return false;
120 for (std::size_t i = 0; i < frames; ++i)
121 output[i] = process(input[i]);
122 return true;
123 }
124
125 bool process_block(SampleType* samples, std::size_t frames) noexcept {
126 return process_block(samples, samples, frames);
127 }
128
129 void reset() noexcept {
130 first_filter_.reset();
131 second_filter_.reset();
132 }
133
134 [[nodiscard]] Endpoint first_endpoint() const noexcept {
135 return endpoints_[0];
136 }
137 [[nodiscard]] Endpoint second_endpoint() const noexcept {
138 return endpoints_[1];
139 }
140 [[nodiscard]] SampleType morph() const noexcept {
141 return morph_;
142 }
143 [[nodiscard]] SampleType sample_rate() const noexcept {
144 return sample_rate_;
145 }
146 [[nodiscard]] bool configured() const noexcept {
147 return configured_;
148 }
150 return narrow_coefficients_(first_filter_.coefficients());
151 }
153 return narrow_coefficients_(second_filter_.coefficients());
154 }
155
157 [[nodiscard]] double magnitude(double frequency_hz) const noexcept {
158 if (!configured_)
159 return 1.0;
160 const double omega = angular_frequency(frequency_hz, static_cast<double>(sample_rate_));
161 const auto first = complex_response_(first_filter_.coefficients(), omega);
162 const auto second = complex_response_(second_filter_.coefficients(), omega);
163 return std::abs(first + static_cast<double>(morph_) * (second - first));
164 }
165
166 [[nodiscard]] float magnitude_db(double frequency_hz) const noexcept {
167 return magnitude_to_db(magnitude(frequency_hz));
168 }
169
170 void response_curve_db(double min_hz, double max_hz, std::span<float> output) const noexcept {
171 for (std::size_t i = 0; i < output.size(); ++i)
172 output[i] = magnitude_db(log_frequency_at(i, output.size(), min_hz, max_hz));
173 }
174
175 static constexpr int latency_samples() noexcept {
176 return 0;
177 }
178
179 static constexpr int tail_samples() noexcept {
180 return -1;
181 }
182
183 private:
184 static bool valid_sample_rate_(SampleType sample_rate) noexcept {
185 return std::isfinite(static_cast<double>(sample_rate)) &&
187 }
188
189 static bool valid_endpoint_(const Endpoint& endpoint, SampleType sample_rate) noexcept {
190 if (!std::isfinite(static_cast<double>(endpoint.frequency_hz)) ||
191 !std::isfinite(static_cast<double>(endpoint.q)) ||
192 endpoint.frequency_hz < min_frequency_hz ||
193 endpoint.frequency_hz > sample_rate * SampleType{0.45} || endpoint.q < min_q ||
194 endpoint.q > max_q)
195 return false;
196 switch (endpoint.type) {
201 return true;
202 }
203 return false;
204 }
205
206 static BiquadCoefficientsT<SampleType> design_(Endpoint endpoint,
207 SampleType sample_rate) noexcept {
208 // Design in double even for FilterMorph<float>. At the documented
209 // 20 Hz / high-rate corner, float cos(w) can round to one and erase a
210 // low-pass numerator before normalization.
211 BiquadT<double> designer;
212 designer.set_coefficients(
213 to_biquad_type_double_(endpoint.type), static_cast<double>(endpoint.frequency_hz),
214 static_cast<double>(endpoint.q), static_cast<double>(sample_rate));
215 const auto c = designer.coefficients();
216 return {static_cast<SampleType>(c.b0), static_cast<SampleType>(c.b1),
217 static_cast<SampleType>(c.b2), static_cast<SampleType>(c.a1),
218 static_cast<SampleType>(c.a2)};
219 }
220
221 static BiquadT<double>::Type to_biquad_type_double_(MorphFilterType type) noexcept {
222 switch (type) {
231 }
233 }
234
235 static bool finite_coefficients_(const BiquadCoefficientsT<SampleType>& c) noexcept {
236 return std::isfinite(static_cast<double>(c.b0)) &&
237 std::isfinite(static_cast<double>(c.b1)) &&
238 std::isfinite(static_cast<double>(c.b2)) &&
239 std::isfinite(static_cast<double>(c.a1)) && std::isfinite(static_cast<double>(c.a2));
240 }
241
242 template <typename CoefficientType>
243 static std::complex<double> complex_response_(const BiquadCoefficientsT<CoefficientType>& c,
244 double omega) noexcept {
245 const auto z1 = std::polar(1.0, -omega);
246 const auto z2 = z1 * z1;
247 const std::complex<double> numerator = static_cast<double>(c.b0) +
248 static_cast<double>(c.b1) * z1 +
249 static_cast<double>(c.b2) * z2;
250 const std::complex<double> denominator =
251 1.0 + static_cast<double>(c.a1) * z1 + static_cast<double>(c.a2) * z2;
252 if (!(std::abs(denominator) > 0.0))
253 return {};
254 return numerator / denominator;
255 }
256
257 static bool representable_(InternalType value) noexcept {
258 return std::isfinite(value) &&
259 std::abs(value) <= static_cast<InternalType>(std::numeric_limits<SampleType>::max());
260 }
261
262 static BiquadCoefficientsT<SampleType>
263 narrow_coefficients_(const BiquadCoefficientsT<InternalType>& c) noexcept {
264 return {static_cast<SampleType>(c.b0), static_cast<SampleType>(c.b1),
265 static_cast<SampleType>(c.b2), static_cast<SampleType>(c.a1),
266 static_cast<SampleType>(c.a2)};
267 }
268
269 BiquadT<InternalType> first_filter_{};
270 BiquadT<InternalType> second_filter_{};
271 Endpoint endpoints_[2]{};
272 SampleType sample_rate_ = SampleType{48000};
273 SampleType morph_ = SampleType{0};
274 bool configured_ = false;
275};
276
279
280} // namespace pulp::signal
Type
Definition biquad.hpp:58
@ notch
Definition biquad.hpp:58
@ highpass
Definition biquad.hpp:58
@ bandpass
Definition biquad.hpp:58
@ lowpass
Definition biquad.hpp:58
Definition filter_morph.hpp:21
bool configured() const noexcept
Definition filter_morph.hpp:146
BiquadCoefficientsT< float > first_coefficients() const noexcept
Definition filter_morph.hpp:149
bool configure(SampleType sample_rate, Endpoint first, Endpoint second) noexcept
Transactionally design both endpoints.
Definition filter_morph.hpp:39
BiquadCoefficientsT< float > second_coefficients() const noexcept
Definition filter_morph.hpp:152
void reset() noexcept
Definition filter_morph.hpp:129
void response_curve_db(double min_hz, double max_hz, std::span< float > output) const noexcept
Definition filter_morph.hpp:170
static constexpr float max_q
Definition filter_morph.hpp:28
static constexpr float max_sample_rate
Definition filter_morph.hpp:29
SampleType morph() const noexcept
Definition filter_morph.hpp:140
bool process_block(const SampleType *input, SampleType *output, std::size_t frames) noexcept
Definition filter_morph.hpp:117
static constexpr int tail_samples() noexcept
Recursive biquads have an asymptotic (formally unbounded) tail.
Definition filter_morph.hpp:179
static constexpr int latency_samples() noexcept
Definition filter_morph.hpp:175
Endpoint second_endpoint() const noexcept
Definition filter_morph.hpp:137
SampleType process(SampleType input) noexcept
Definition filter_morph.hpp:70
bool process_block(SampleType *samples, std::size_t frames) noexcept
Definition filter_morph.hpp:125
Endpoint first_endpoint() const noexcept
Definition filter_morph.hpp:134
float sample_rate() const noexcept
Definition filter_morph.hpp:143
double magnitude(double frequency_hz) const noexcept
Exact complex parallel sum of the two live sections.
Definition filter_morph.hpp:157
bool set_morph(SampleType amount) noexcept
Set the linear-amplitude morph position.
Definition filter_morph.hpp:62
float magnitude_db(double frequency_hz) const noexcept
Definition filter_morph.hpp:166
static constexpr float min_q
Definition filter_morph.hpp:27
static constexpr float min_frequency_hz
Definition filter_morph.hpp:26
Evaluate what a biquad (or a cascade of them) does to the spectrum.
Definition additive_bank.hpp:218
float magnitude_to_db(double magnitude)
Convert a linear magnitude to dB, floored at min_response_db.
Definition frequency_response.hpp:35
@ first
Definition dither.hpp:23
@ second
Definition dither.hpp:24
FilterMorphT< double > FilterMorph64
Definition filter_morph.hpp:278
@ c
Definition spectrum_trace.hpp:35
bool biquad_is_stable(const BiquadCoefficientsT< SampleType > &c) noexcept
Return whether a normalized real biquad's poles are strictly inside the unit circle.
Definition biquad.hpp:34
double log_frequency_at(std::size_t index, std::size_t count, double min_hz, double max_hz)
Frequency of sample index of count, log-spaced across [min_hz, max_hz].
Definition frequency_response.hpp:152
FilterMorphT< float > FilterMorph
Definition filter_morph.hpp:277
@ i
Definition formant_filter_bank.hpp:20
MorphFilterType
Definition filter_morph.hpp:19
@ notch
Definition filter_morph.hpp:19
@ highpass
Definition filter_morph.hpp:19
@ bandpass
Definition filter_morph.hpp:19
@ lowpass
Definition filter_morph.hpp:19
double angular_frequency(double freq_hz, double sample_rate)
Angular frequency (radians/sample) of freq_hz at sample_rate, clamped to [0, π].
Definition frequency_response.hpp:97
Definition biquad.hpp:24
Definition filter_morph.hpp:31
MorphFilterType type
Definition filter_morph.hpp:32
SampleType frequency_hz
Definition filter_morph.hpp:33
SampleType q
Definition filter_morph.hpp:34