Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
parametric_eq.hpp
Go to the documentation of this file.
1#pragma once
2
5
8
9#include <algorithm>
10#include <array>
11#include <cmath>
12#include <cstddef>
13#include <limits>
14#include <span>
15#include <type_traits>
16
17namespace pulp::signal {
18
19enum class ParametricEqBandType : std::uint8_t { low_shelf, peaking, high_shelf };
20
21template <typename SampleType> struct ParametricEqBandT {
23 SampleType frequency_hz = SampleType{1000};
24 SampleType gain_db = SampleType{0};
25 SampleType q = SampleType{1};
26 bool enabled = true;
27 bool solo = false;
28
29 friend constexpr bool operator==(const ParametricEqBandT&, const ParametricEqBandT&) = default;
30};
31
33
35
50
58template <typename SampleType = float, std::size_t MaxBands = 31> class ParametricEqT {
59 static_assert(std::is_floating_point_v<SampleType>, "ParametricEqT requires floating point");
60 static_assert(MaxBands > 0, "ParametricEqT requires non-zero band storage");
61
62 public:
63 using Sample = SampleType;
66
67 static constexpr SampleType minimum_sample_rate_hz = SampleType{8000};
68 static constexpr SampleType maximum_sample_rate_hz = SampleType{384000};
69 static constexpr SampleType minimum_frequency_hz = SampleType{20};
70 static constexpr SampleType maximum_frequency_hz = SampleType{20000};
71 static constexpr SampleType minimum_gain_db = SampleType{-24};
72 static constexpr SampleType maximum_gain_db = SampleType{24};
73 static constexpr SampleType minimum_q = SampleType{0.1};
74 static constexpr SampleType maximum_q = SampleType{12};
75
77 std::size_t band_capacity = MaxBands) noexcept {
78 if (!(std::isfinite(sample_rate) && sample_rate >= minimum_sample_rate_hz &&
81 if (band_capacity == 0 || band_capacity > MaxBands)
83 (void)cascades_[0].prepare(band_capacity);
84 (void)cascades_[1].prepare(band_capacity);
85 sample_rate_ = sample_rate;
86 capacity_ = band_capacity;
87 active_index_ = 0;
88 active_count_ = requested_count_ = 0;
89 transition_total_ = transition_remaining_ = 0;
90 requested_bands_ = {};
91 active_bands_ = {};
92 requested_coefficients_ = {};
94 }
95
96 ParametricEqConfigureStatus configure(std::span<const Band> bands,
97 std::size_t transition_samples = 0) noexcept {
98 if (!prepared())
100 if (transitioning())
102 if (bands.size() > capacity_)
104 if (transition_samples == std::numeric_limits<std::size_t>::max())
106
107 std::array<Coefficients, MaxBands> candidate{};
108 std::array<Band, MaxBands> selected{};
109 std::size_t selected_count = 0;
110 bool has_solo = false;
111 SampleType previous_frequency = SampleType{0};
112 const SampleType ceiling = supported_frequency_ceiling_hz();
113 for (std::size_t i = 0; i < bands.size(); ++i) {
114 const Band band = bands[i];
115 if (band.solo && !band.enabled)
117 if (!(std::isfinite(band.frequency_hz) && std::isfinite(band.gain_db) &&
118 std::isfinite(band.q)))
120 if (band.frequency_hz < minimum_frequency_hz || band.frequency_hz > ceiling)
122 if (i != 0 && !(band.frequency_hz > previous_frequency))
124 if (band.gain_db < minimum_gain_db || band.gain_db > maximum_gain_db)
126 if (band.q < minimum_q || band.q > maximum_q)
128 has_solo = has_solo || band.solo;
129 previous_frequency = band.frequency_hz;
130 }
131 for (const Band band : bands) {
132 if (!band.enabled || (has_solo && !band.solo))
133 continue;
134 selected[selected_count] = band;
135 candidate[selected_count] = design_band(band);
136 if (!finite(candidate[selected_count]))
138 if (!biquad_is_stable(candidate[selected_count]))
140 ++selected_count;
141 }
142
143 const std::size_t staging = 1 - active_index_;
144 const auto installed = cascades_[staging].set_coefficients(
145 std::span<const Coefficients>(candidate.data(), selected_count),
155
156 requested_bands_ = selected;
157 requested_coefficients_ = candidate;
158 requested_count_ = selected_count;
159 if (transition_samples == 0) {
160 active_index_ = staging;
161 active_count_ = selected_count;
162 active_bands_ = selected;
163 cascades_[1 - active_index_].reset();
164 } else {
165 transition_total_ = transition_samples;
166 transition_remaining_ = transition_samples;
167 }
169 }
170
171 SampleType process(SampleType input) noexcept {
172 if (!prepared())
173 return input;
174 const SampleType from = cascades_[active_index_].process(input);
175 if (!transitioning())
176 return std::isfinite(from) ? from : SampleType{};
177 const SampleType to = cascades_[1 - active_index_].process(input);
178 const std::size_t completed = transition_total_ - transition_remaining_ + 1;
179 const SampleType mix =
180 static_cast<SampleType>(completed) / static_cast<SampleType>(transition_total_);
181 const SampleType output = transition_remaining_ == 1 ? to : std::lerp(from, to, mix);
182 if (--transition_remaining_ == 0) {
183 active_index_ = 1 - active_index_;
184 active_count_ = requested_count_;
185 active_bands_ = requested_bands_;
186 cascades_[1 - active_index_].reset();
187 }
188 return std::isfinite(output) ? output : SampleType{};
189 }
190
195 bool process_block(SampleType* samples, std::size_t frames) noexcept {
196 if (samples == nullptr && frames != 0)
197 return false;
198 if (!prepared())
199 return true;
201 frames, [&] { return transitioning(); },
202 [&](std::size_t i) { samples[i] = process(samples[i]); },
203 [&](std::size_t first, std::size_t count) {
204 cascades_[active_index_].process(samples + first, count);
205 for (std::size_t i = first; i < first + count; ++i)
206 samples[i] = std::isfinite(samples[i]) ? samples[i] : SampleType{};
207 });
208 return true;
209 }
210
211 void reset() noexcept {
212 cascades_[0].reset();
213 cascades_[1].reset();
214 if (transitioning()) {
215 active_index_ = 1 - active_index_;
216 active_count_ = requested_count_;
217 active_bands_ = requested_bands_;
218 }
219 transition_total_ = transition_remaining_ = 0;
220 }
221
222 bool prepared() const noexcept {
223 return capacity_ != 0;
224 }
225 bool transitioning() const noexcept {
226 return transition_remaining_ != 0;
227 }
228 std::size_t capacity() const noexcept {
229 return capacity_;
230 }
231 std::size_t band_count() const noexcept {
232 return requested_count_;
233 }
234 SampleType sample_rate() const noexcept {
235 return sample_rate_;
236 }
237 static constexpr std::size_t storage_capacity() noexcept {
238 return MaxBands;
239 }
240 Band band(std::size_t index) const noexcept {
241 return index < requested_count_ ? requested_bands_[index] : Band{};
242 }
243 Coefficients coefficients(std::size_t index) const noexcept {
244 return index < requested_count_ ? requested_coefficients_[index] : Coefficients{};
245 }
246
248 double magnitude(double frequency_hz) const noexcept {
249 if (!prepared() || !std::isfinite(frequency_hz) || frequency_hz < 0.0 ||
250 frequency_hz > static_cast<double>(sample_rate_) * 0.5)
251 return std::numeric_limits<double>::quiet_NaN();
252 return cascade_magnitude(
253 std::span<const Coefficients>(requested_coefficients_.data(), requested_count_),
254 angular_frequency(frequency_hz, static_cast<double>(sample_rate_)));
255 }
256 float magnitude_db(double frequency_hz) const noexcept {
257 return magnitude_to_db(magnitude(frequency_hz));
258 }
259 void response_curve_db(double min_hz, double max_hz, std::span<float> out) const noexcept {
261 std::span<const Coefficients>(requested_coefficients_.data(), requested_count_), min_hz,
262 max_hz, static_cast<double>(sample_rate_), out);
263 }
264
265 private:
266 static bool finite(const Coefficients& c) noexcept {
267 return std::isfinite(c.b0) && std::isfinite(c.b1) && std::isfinite(c.b2) &&
268 std::isfinite(c.a1) && std::isfinite(c.a2);
269 }
270 SampleType supported_frequency_ceiling_hz() const noexcept {
271 return std::min(maximum_frequency_hz, sample_rate_ * SampleType{0.49});
272 }
273 static Coefficients design_band(const Band& band, SampleType sample_rate) noexcept {
274 BiquadT<double> designer;
275 const auto type =
279 designer.set_coefficients(type, static_cast<double>(band.frequency_hz),
280 static_cast<double>(band.q), static_cast<double>(sample_rate),
281 static_cast<double>(band.gain_db));
282 const auto c = designer.coefficients();
283 return {static_cast<SampleType>(c.b0), static_cast<SampleType>(c.b1),
284 static_cast<SampleType>(c.b2), static_cast<SampleType>(c.a1),
285 static_cast<SampleType>(c.a2)};
286 }
287 Coefficients design_band(const Band& band) const noexcept {
288 return design_band(band, sample_rate_);
289 }
290
291 std::array<SosCascadeT<SampleType, MaxBands>, 2> cascades_{};
292 std::array<Band, MaxBands> requested_bands_{};
293 std::array<Band, MaxBands> active_bands_{};
294 std::array<Coefficients, MaxBands> requested_coefficients_{};
295 SampleType sample_rate_ = SampleType{};
296 std::size_t capacity_ = 0;
297 std::size_t active_index_ = 0;
298 std::size_t active_count_ = 0;
299 std::size_t requested_count_ = 0;
300 std::size_t transition_total_ = 0;
301 std::size_t transition_remaining_ = 0;
302};
303
306
307} // namespace pulp::signal
@ peaking
Definition biquad.hpp:58
@ high_shelf
Definition biquad.hpp:58
@ low_shelf
Definition biquad.hpp:58
A bounded series cascade of arbitrary parametric bands.
Definition parametric_eq.hpp:58
static constexpr float maximum_sample_rate_hz
Definition parametric_eq.hpp:68
bool process_block(SampleType *samples, std::size_t frames) noexcept
Process a block in place.
Definition parametric_eq.hpp:195
float sample_rate() const noexcept
Definition parametric_eq.hpp:234
ParametricEqPrepareStatus prepare(SampleType sample_rate, std::size_t band_capacity=MaxBands) noexcept
Definition parametric_eq.hpp:76
static constexpr float minimum_sample_rate_hz
Definition parametric_eq.hpp:67
std::size_t band_count() const noexcept
Definition parametric_eq.hpp:231
ParametricEqConfigureStatus configure(std::span< const Band > bands, std::size_t transition_samples=0) noexcept
Definition parametric_eq.hpp:96
static constexpr float maximum_gain_db
Definition parametric_eq.hpp:72
static constexpr float minimum_gain_db
Definition parametric_eq.hpp:71
double magnitude(double frequency_hz) const noexcept
Stationary response of the requested endpoint, in linear magnitude.
Definition parametric_eq.hpp:248
SampleType Sample
Definition parametric_eq.hpp:63
void reset() noexcept
Definition parametric_eq.hpp:211
Coefficients coefficients(std::size_t index) const noexcept
Definition parametric_eq.hpp:243
bool prepared() const noexcept
Definition parametric_eq.hpp:222
BiquadCoefficientsT< SampleType > Coefficients
Definition parametric_eq.hpp:65
static constexpr float maximum_frequency_hz
Definition parametric_eq.hpp:70
static constexpr std::size_t storage_capacity() noexcept
Definition parametric_eq.hpp:237
SampleType process(SampleType input) noexcept
Definition parametric_eq.hpp:171
float magnitude_db(double frequency_hz) const noexcept
Definition parametric_eq.hpp:256
static constexpr float minimum_frequency_hz
Definition parametric_eq.hpp:69
static constexpr float minimum_q
Definition parametric_eq.hpp:73
bool transitioning() const noexcept
Definition parametric_eq.hpp:225
void response_curve_db(double min_hz, double max_hz, std::span< float > out) const noexcept
Definition parametric_eq.hpp:259
static constexpr float maximum_q
Definition parametric_eq.hpp:74
ParametricEqBandT< SampleType > Band
Definition parametric_eq.hpp:64
std::size_t capacity() const noexcept
Definition parametric_eq.hpp:228
Band band(std::size_t index) const noexcept
Definition parametric_eq.hpp:240
Evaluate what a biquad (or a cascade of them) does to the spectrum.
void transition_then_block(std::size_t frames, Transitioning &&transitioning, Sample &&sample, Block &&block)
The block path the cascade EQs share: a design transition (crossfade or coefficient ramp) runs sample...
Definition sos_cascade.hpp:39
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
double cascade_magnitude(std::span< const BiquadCoefficientsT< SampleType >, Extent > sos, double omega)
Linear magnitude |H| of a cascade at angular frequency ω.
Definition frequency_response.hpp:80
@ reset_state
Definition sos_cascade.hpp:27
@ c
Definition spectrum_trace.hpp:35
ParametricEqBandType
Definition parametric_eq.hpp:19
@ peaking
Definition parametric_eq.hpp:19
@ high_shelf
Definition parametric_eq.hpp:19
@ low_shelf
Definition parametric_eq.hpp:19
@ not_prepared
Definition comb_filter.hpp:33
@ non_finite
Definition sos_cascade.hpp:20
@ over_capacity
Definition sos_cascade.hpp:19
@ installed
Definition sos_cascade.hpp:17
@ unstable
Definition sos_cascade.hpp:21
@ not_prepared
Definition sos_cascade.hpp:18
@ invalid_sample_rate
Definition auto_ducked_send.hpp:18
ParametricEqBandT< float > ParametricEqBand
Definition parametric_eq.hpp:32
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
@ frequency_out_of_range
Definition graphic_eq.hpp:31
@ over_capacity
Definition graphic_eq.hpp:28
@ q_out_of_range
Definition graphic_eq.hpp:34
@ frequencies_not_strictly_increasing
Definition graphic_eq.hpp:32
@ invalid_transition
Definition graphic_eq.hpp:29
@ transition_in_progress
Definition graphic_eq.hpp:36
@ gain_out_of_range
Definition graphic_eq.hpp:33
@ i
Definition formant_filter_bank.hpp:20
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
@ prepared
Definition finite_stretch_builder.hpp:19
@ invalid_capacity
Definition graphic_eq.hpp:22
ParametricEqT< float > ParametricEq
Definition parametric_eq.hpp:304
ParametricEqPrepareStatus
Definition parametric_eq.hpp:34
@ invalid_sample_rate
Definition parametric_eq.hpp:34
@ prepared
Definition parametric_eq.hpp:34
@ invalid_capacity
Definition parametric_eq.hpp:34
ParametricEqConfigureStatus
Definition parametric_eq.hpp:36
@ frequency_out_of_range
Definition parametric_eq.hpp:42
@ invalid_solo
Definition parametric_eq.hpp:46
@ non_finite
Definition parametric_eq.hpp:41
@ over_capacity
Definition parametric_eq.hpp:39
@ configured
Definition parametric_eq.hpp:37
@ q_out_of_range
Definition parametric_eq.hpp:45
@ unstable
Definition parametric_eq.hpp:47
@ frequencies_not_strictly_increasing
Definition parametric_eq.hpp:43
@ invalid_transition
Definition parametric_eq.hpp:40
@ not_prepared
Definition parametric_eq.hpp:38
@ transition_in_progress
Definition parametric_eq.hpp:48
@ gain_out_of_range
Definition parametric_eq.hpp:44
ParametricEqT< double > ParametricEq64
Definition parametric_eq.hpp:305
@ non_finite
Definition formant_filter_bank.hpp:26
@ configured
Definition formant_filter_bank.hpp:23
@ unstable
Definition formant_filter_bank.hpp:29
@ type
Built-in / common type name (e.g. int, Number).
Definition code_editor_tokenizer.hpp:39
Fixed-capacity runtime executor for cascades of second-order IIR sections.
Definition biquad.hpp:24
Definition parametric_eq.hpp:21
float q
Definition parametric_eq.hpp:25
float gain_db
Definition parametric_eq.hpp:24
friend constexpr bool operator==(const ParametricEqBandT &, const ParametricEqBandT &)=default
bool solo
Definition parametric_eq.hpp:27
ParametricEqBandType type
Definition parametric_eq.hpp:22
bool enabled
Definition parametric_eq.hpp:26
float frequency_hz
Definition parametric_eq.hpp:23