Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
six_band_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 <complex>
13#include <cstddef>
14#include <span>
15#include <type_traits>
16
17namespace pulp::signal {
18
27template <typename SampleType = float, std::size_t Channels = 2>
29 static_assert(std::is_floating_point_v<SampleType>,
30 "SixBandEqT requires a floating-point sample type");
31 static_assert(Channels > 0, "SixBandEqT requires at least one channel");
32
33public:
34 static constexpr std::size_t band_count = 6;
35 static constexpr std::size_t channel_count = Channels;
36 static constexpr SampleType min_frequency_hz = SampleType{20};
37 static constexpr SampleType max_frequency_hz = SampleType{20000};
38 static constexpr SampleType min_gain_db = SampleType{-18};
39 static constexpr SampleType max_gain_db = SampleType{18};
40 static constexpr SampleType min_q = SampleType{0.1};
41 static constexpr SampleType max_q = SampleType{12};
42 static constexpr SampleType default_sample_rate = SampleType{48000};
43
45 using Type = typename Filter::Type;
47
48 struct Parameters {
49 SampleType frequency_hz = SampleType{1000};
50 SampleType gain_db = SampleType{0};
51 SampleType q = SampleType{1};
52 };
53
54 SixBandEqT() { apply_all_parameters_immediate(); }
55
59 void prepare(SampleType sample_rate) {
60 sample_rate_ = valid_sample_rate(sample_rate) ? sample_rate : default_sample_rate;
61 apply_all_parameters_immediate();
62 reset();
63 }
64
68 void set_transition_samples(std::size_t samples) {
69 if (samples == transition_samples_) return;
70 transition_samples_ = samples;
71 }
72
73 std::size_t transition_samples() const { return transition_samples_; }
74 bool transition_active(std::size_t channel) const {
75 return channel < Channels && transition_remaining_[channel] != 0;
76 }
77 bool transition_active() const {
78 return std::any_of(transition_remaining_.begin(), transition_remaining_.end(),
79 [](std::size_t remaining) { return remaining != 0; });
80 }
81
84 bool set_band(std::size_t band, Parameters parameters) {
85 if (band >= band_count) return false;
86 parameters = sanitize(band, parameters);
87 if (parameters_equal(parameters_[band], parameters)) return true;
88 parameters_[band] = parameters;
89 design_parameters(band);
90 schedule_band(band);
91 return true;
92 }
93
97 void set_bands(const std::array<Parameters, band_count>& parameters) {
98 std::array<Parameters, band_count> sanitized{};
99 bool changed = false;
100 for (std::size_t band = 0; band < band_count; ++band) {
101 sanitized[band] = sanitize(band, parameters[band]);
102 changed = changed || !parameters_equal(parameters_[band], sanitized[band]);
103 }
104 if (!changed) return;
105 for (std::size_t band = 0; band < band_count; ++band) {
106 parameters_[band] = sanitized[band];
107 design_parameters(band);
108 }
109 schedule_all_bands();
110 }
111
114 Parameters band(std::size_t index) const {
115 return index < band_count ? parameters_[index] : Parameters{};
116 }
117 SampleType sample_rate() const { return sample_rate_; }
118
119 static constexpr Type band_type(std::size_t index) {
120 return index < band_count ? types_[index] : Type::peaking;
121 }
122
123 static constexpr Parameters default_band(std::size_t index) {
124 return index < band_count ? defaults_[index] : Parameters{};
125 }
126
130 Coefficients coefficients(std::size_t band) const {
131 return band < band_count ? requested_coefficients_[band] : Coefficients{};
132 }
133
135 SampleType process(SampleType input, std::size_t channel = 0) {
136 if (channel >= Channels) return input;
137 if (!transition_active(channel)) {
138 for (auto& section : filters_[channel]) input = section.process(input);
139 return input;
140 }
141 SampleType from = input;
142 SampleType to = input;
143 for (auto& section : filters_[channel]) from = section.process(from);
144 for (auto& section : transition_filters_[channel]) to = section.process(to);
145 const std::size_t completed = transition_total_[channel] - transition_remaining_[channel] + 1;
146 const SampleType mix = static_cast<SampleType>(completed) /
147 static_cast<SampleType>(transition_total_[channel]);
148 const SampleType output = from + (to - from) * mix;
149 if (--transition_remaining_[channel] == 0) {
150 filters_[channel] = transition_filters_[channel];
151 if (queued_transition_[channel]) {
152 const std::size_t queued_samples = queued_transition_samples_[channel];
153 queued_transition_[channel] = false;
154 queued_transition_samples_[channel] = 0;
155 begin_transition(channel, queued_samples);
156 }
157 }
158 return output;
159 }
160
168 bool process_block(SampleType* samples, std::size_t frames, std::size_t channel = 0) {
169 if (channel >= Channels || (samples == nullptr && frames != 0)) return false;
171 frames, [&] { return transition_active(channel); },
172 [&](std::size_t i) { samples[i] = process(samples[i], channel); },
173 [&](std::size_t first, std::size_t count) {
174 Filter::process_chain(filters_[channel].data(), band_count, samples + first, count);
175 });
176 return true;
177 }
178
180 void reset() {
181 for (auto& channel : filters_)
182 for (auto& section : channel) section.reset();
183 for (auto& channel : transition_filters_)
184 for (auto& section : channel) section.reset();
185 }
186
189 double magnitude(double frequency_hz) const {
190 const double omega = query_omega(frequency_hz);
191 const std::complex<double> z1 = std::polar(1.0, -omega);
192 const std::complex<double> z2 = z1 * z1;
193 double result = 1.0;
194 for (std::size_t band = 0; band < band_count; ++band) {
195 const auto c = coefficients(band);
196 const auto numerator = static_cast<double>(c.b0) + static_cast<double>(c.b1) * z1 +
197 static_cast<double>(c.b2) * z2;
198 const auto denominator = 1.0 + static_cast<double>(c.a1) * z1 +
199 static_cast<double>(c.a2) * z2;
200 const double denominator_magnitude = std::abs(denominator);
201 if (!(denominator_magnitude > 0.0)) return 0.0;
202 result *= std::abs(numerator) / denominator_magnitude;
203 }
204 return result;
205 }
206
208 double magnitude_db(double frequency_hz) const {
209 const double linear = magnitude(frequency_hz);
210 if (!(linear > 0.0)) return -200.0;
211 const double db = 20.0 * std::log10(linear);
212 return std::isfinite(db) ? std::max(db, -200.0) : -200.0;
213 }
214
217 void response_curve_db(double min_hz, double max_hz, std::span<SampleType> output) const {
218 if (output.empty()) return;
219 const double nyquist = static_cast<double>(sample_rate_) * 0.5;
220 const double lo = sanitize_query(min_hz, nyquist);
221 const double hi = std::max(lo, sanitize_query(max_hz, nyquist));
222 for (std::size_t i = 0; i < output.size(); ++i) {
223 const double t = output.size() == 1
224 ? 0.0
225 : static_cast<double>(i) / static_cast<double>(output.size() - 1);
226 const double frequency = lo > 0.0 && hi > 0.0
227 ? std::exp(std::log(lo) + t * (std::log(hi) - std::log(lo)))
228 : 0.0;
229 output[i] = static_cast<SampleType>(magnitude_db(frequency));
230 }
231 }
232
233private:
234 static constexpr std::array<Type, band_count> types_ = {
235 Type::low_shelf, Type::peaking, Type::peaking,
236 Type::peaking, Type::peaking, Type::high_shelf};
237
238 static constexpr std::array<Parameters, band_count> defaults_ = {{
239 {SampleType{80}, SampleType{0}, SampleType{0.707}},
240 {SampleType{250}, SampleType{0}, SampleType{1}},
241 {SampleType{700}, SampleType{0}, SampleType{1.2}},
242 {SampleType{2000}, SampleType{0}, SampleType{1.2}},
243 {SampleType{5000}, SampleType{0}, SampleType{1}},
244 {SampleType{12000}, SampleType{0}, SampleType{0.707}},
245 }};
246
247 static bool valid_sample_rate(SampleType sample_rate) {
248 return std::isfinite(sample_rate) &&
249 sample_rate * SampleType{0.49} >= min_frequency_hz;
250 }
251
252 Parameters sanitize(std::size_t index, Parameters parameters) const {
253 const Parameters fallback = defaults_[index];
254 const SampleType frequency_ceiling =
255 std::min(max_frequency_hz, sample_rate_ * SampleType{0.49});
256 if (!std::isfinite(parameters.frequency_hz)) parameters.frequency_hz = fallback.frequency_hz;
257 if (!std::isfinite(parameters.gain_db)) parameters.gain_db = fallback.gain_db;
258 if (!std::isfinite(parameters.q)) parameters.q = fallback.q;
259 parameters.frequency_hz =
260 std::clamp(parameters.frequency_hz, min_frequency_hz, frequency_ceiling);
261 parameters.gain_db = std::clamp(parameters.gain_db, min_gain_db, max_gain_db);
262 parameters.q = std::clamp(parameters.q, min_q, max_q);
263 return parameters;
264 }
265
266 static bool parameters_equal(const Parameters& lhs, const Parameters& rhs) {
267 return lhs.frequency_hz == rhs.frequency_hz && lhs.gain_db == rhs.gain_db &&
268 lhs.q == rhs.q;
269 }
270
271 void design_parameters(std::size_t band) {
272 Filter designer;
273 const auto& parameters = parameters_[band];
274 designer.set_coefficients(types_[band], parameters.frequency_hz, parameters.q,
275 sample_rate_, parameters.gain_db);
276 requested_coefficients_[band] = designer.coefficients();
277 }
278
279 void apply_all_parameters_immediate() {
280 for (std::size_t band = 0; band < band_count; ++band) {
281 parameters_[band] = sanitize(band, parameters_[band]);
282 design_parameters(band);
283 }
284 for (std::size_t channel = 0; channel < Channels; ++channel) {
285 for (std::size_t band = 0; band < band_count; ++band)
286 filters_[channel][band].set_coefficients(requested_coefficients_[band]);
287 transition_remaining_[channel] = 0;
288 transition_total_[channel] = 0;
289 queued_transition_[channel] = false;
290 queued_transition_samples_[channel] = 0;
291 }
292 }
293
294 void load_requested(std::array<Filter, band_count>& cascade) {
295 for (std::size_t band = 0; band < band_count; ++band)
296 cascade[band].set_coefficients(requested_coefficients_[band]);
297 }
298
299 void begin_transition(std::size_t channel, std::size_t samples) {
300 transition_filters_[channel] = filters_[channel];
301 load_requested(transition_filters_[channel]);
302 transition_total_[channel] = samples;
303 transition_remaining_[channel] = samples;
304 }
305
306 void queue_transition(std::size_t channel) {
307 if (queued_transition_[channel]) return;
308 queued_transition_[channel] = true;
309 // A request made after immediate mode was selected still completes via
310 // a bounded continuation: cold-retuning the audible source cascade
311 // would defeat the click-safe contract of the transition in flight.
312 queued_transition_samples_[channel] =
313 transition_samples_ != 0 ? transition_samples_ : transition_total_[channel];
314 }
315
316 void schedule_band(std::size_t band) {
317 for (std::size_t channel = 0; channel < Channels; ++channel) {
318 if (!transition_active(channel)) {
319 if (transition_samples_ == 0)
320 filters_[channel][band].set_coefficients(requested_coefficients_[band]);
321 else
322 begin_transition(channel, transition_samples_);
323 }
324 else if (transition_remaining_[channel] == transition_total_[channel])
325 transition_filters_[channel][band].set_coefficients(requested_coefficients_[band]);
326 else
327 queue_transition(channel);
328 }
329 }
330
331 void schedule_all_bands() {
332 for (std::size_t channel = 0; channel < Channels; ++channel) {
333 if (!transition_active(channel)) {
334 if (transition_samples_ == 0)
335 load_requested(filters_[channel]);
336 else
337 begin_transition(channel, transition_samples_);
338 }
339 else if (transition_remaining_[channel] == transition_total_[channel])
340 load_requested(transition_filters_[channel]);
341 else
342 queue_transition(channel);
343 }
344 }
345
346 static double sanitize_query(double frequency_hz, double nyquist) {
347 if (!std::isfinite(frequency_hz)) return 0.0;
348 return std::clamp(frequency_hz, 0.0, nyquist);
349 }
350
351 double query_omega(double frequency_hz) const {
352 const double nyquist = static_cast<double>(sample_rate_) * 0.5;
353 const double frequency = sanitize_query(frequency_hz, nyquist);
354 return std::acos(-1.0) * frequency / nyquist;
355 }
356
357 SampleType sample_rate_ = default_sample_rate;
358 std::array<Parameters, band_count> parameters_ = defaults_;
359 std::array<Coefficients, band_count> requested_coefficients_{};
360 std::array<std::array<Filter, band_count>, Channels> filters_{};
361 std::array<std::array<Filter, band_count>, Channels> transition_filters_{};
362 std::array<std::size_t, Channels> transition_total_{};
363 std::array<std::size_t, Channels> transition_remaining_{};
364 std::array<bool, Channels> queued_transition_{};
365 std::array<std::size_t, Channels> queued_transition_samples_{};
366 std::size_t transition_samples_ = 0;
367};
368
371
372} // namespace pulp::signal
Definition biquad.hpp:53
Type
Definition biquad.hpp:58
static void process_chain(BiquadT *sections, std::size_t count, SampleType *buffer, std::size_t num_samples) noexcept
Definition biquad.hpp:320
A low shelf, four peaking sections, and a high shelf in series.
Definition six_band_eq.hpp:28
static constexpr std::size_t band_count
Definition six_band_eq.hpp:34
std::size_t transition_samples() const
Definition six_band_eq.hpp:73
double magnitude_db(double frequency_hz) const
Decibel magnitude of the requested endpoint cascade, floored at -200 dB.
Definition six_band_eq.hpp:208
static constexpr float min_gain_db
Definition six_band_eq.hpp:38
void reset()
Clear recursive history while preserving rate and effective controls.
Definition six_band_eq.hpp:180
static constexpr Parameters default_band(std::size_t index)
Definition six_band_eq.hpp:123
static constexpr Type band_type(std::size_t index)
Definition six_band_eq.hpp:119
SampleType process(SampleType input, std::size_t channel=0)
Process one sample. An invalid channel fails transparent.
Definition six_band_eq.hpp:135
double magnitude(double frequency_hz) const
Linear magnitude of the requested endpoint coefficient cascade.
Definition six_band_eq.hpp:189
bool transition_active(std::size_t channel) const
Definition six_band_eq.hpp:74
static constexpr float min_q
Definition six_band_eq.hpp:40
static constexpr float max_q
Definition six_band_eq.hpp:41
BiquadCoefficientsT< SampleType > Coefficients
Definition six_band_eq.hpp:46
bool transition_active() const
Definition six_band_eq.hpp:77
static constexpr float max_frequency_hz
Definition six_band_eq.hpp:37
SixBandEqT()
Definition six_band_eq.hpp:54
void prepare(SampleType sample_rate)
Set the processing rate, re-clamp controls, derive coefficients, and clear all recursive history.
Definition six_band_eq.hpp:59
bool process_block(SampleType *samples, std::size_t frames, std::size_t channel=0)
Process one caller-owned channel in place.
Definition six_band_eq.hpp:168
void response_curve_db(double min_hz, double max_hz, std::span< SampleType > output) const
Fill caller-owned storage with a log-spaced response.
Definition six_band_eq.hpp:217
BiquadT< SampleType > Filter
Definition six_band_eq.hpp:44
static constexpr float min_frequency_hz
Definition six_band_eq.hpp:36
static constexpr std::size_t channel_count
Definition six_band_eq.hpp:35
bool set_band(std::size_t band, Parameters parameters)
Change one band's plain-domain controls without clearing filter state.
Definition six_band_eq.hpp:84
Coefficients coefficients(std::size_t band) const
Exact normalized coefficients at the latest requested transition endpoint.
Definition six_band_eq.hpp:130
Parameters band(std::size_t index) const
Definition six_band_eq.hpp:114
static constexpr float default_sample_rate
Definition six_band_eq.hpp:42
void set_transition_samples(std::size_t samples)
Set the length of future coefficient transitions.
Definition six_band_eq.hpp:68
static constexpr float max_gain_db
Definition six_band_eq.hpp:39
float sample_rate() const
Definition six_band_eq.hpp:117
typename Filter::Type Type
Definition six_band_eq.hpp:45
void set_bands(const std::array< Parameters, band_count > &parameters)
Atomically request all six bands.
Definition six_band_eq.hpp:97
@ index
Definition gpu_pass.hpp:140
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
@ first
Definition dither.hpp:23
SixBandEqT< float > SixBandEq
Definition six_band_eq.hpp:369
@ c
Definition spectrum_trace.hpp:35
@ i
Definition formant_filter_bank.hpp:20
SixBandEqT< double > SixBandEq64
Definition six_band_eq.hpp:370
@ linear
2-point. Cheapest; audible imaging when pitching up.
Definition granular.hpp:133
@ fallback
Definition health_read_result.hpp:30
Fixed-capacity runtime executor for cascades of second-order IIR sections.
Definition biquad.hpp:24
Definition six_band_eq.hpp:48
SampleType q
Definition six_band_eq.hpp:51
SampleType gain_db
Definition six_band_eq.hpp:50
SampleType frequency_hz
Definition six_band_eq.hpp:49