42 SampleType
q = SampleType{1};
70template <
typename SampleType =
float, std::
size_t MaxBands = 31>
class GraphicEqT {
71 static_assert(std::is_floating_point_v<SampleType>,
72 "GraphicEqT requires a floating-point sample type");
73 static_assert(MaxBands > 0,
"GraphicEqT requires non-zero band storage");
85 static constexpr SampleType
minimum_q = SampleType{0.1};
86 static constexpr SampleType
maximum_q = SampleType{12};
89 std::size_t band_capacity = MaxBands)
noexcept {
93 if (band_capacity == 0 || band_capacity > MaxBands)
98 (void)cascades_[0].
prepare(band_capacity);
99 (void)cascades_[1].
prepare(band_capacity);
101 capacity_ = band_capacity;
104 requested_bands_ = {};
105 requested_coefficients_ = {};
107 requested_count_ = 0;
108 transition_total_ = 0;
109 transition_remaining_ = 0;
116 std::size_t transition_samples = 0) noexcept {
121 if (bands.size() > capacity_)
123 if (transition_samples == std::numeric_limits<std::size_t>::max())
126 std::array<Band, MaxBands> candidate_bands{};
127 std::array<Coefficients, MaxBands> candidate_coefficients{};
128 SampleType previous_frequency = SampleType{0};
131 for (std::size_t index = 0; index < bands.size(); ++index) {
133 if (!(std::isfinite(
band.frequency_hz) && std::isfinite(
band.gain_db) &&
134 std::isfinite(
band.q)))
138 if (index != 0 && !(
band.frequency_hz > previous_frequency))
145 candidate_bands[index] =
band;
146 candidate_coefficients[index] = design_band(
band);
147 if (!coefficients_are_finite(candidate_coefficients[index]))
151 previous_frequency =
band.frequency_hz;
154 if (same_requested_configuration(bands))
157 const std::size_t staging_index = 1 - active_index_;
158 const auto installed = cascades_[staging_index].set_coefficients(
159 std::span<const Coefficients>(candidate_coefficients.data(), bands.size()));
173 requested_bands_ = candidate_bands;
174 requested_coefficients_ = candidate_coefficients;
175 requested_count_ = bands.size();
177 if (transition_samples == 0) {
178 active_index_ = staging_index;
179 commit_requested_endpoint();
180 cascades_[1 - active_index_].reset();
182 transition_total_ = transition_samples;
183 transition_remaining_ = transition_samples;
189 SampleType
process(SampleType input)
noexcept {
192 if (!std::isfinite(input))
193 return recover_from_fault();
195 const SampleType from = cascades_[active_index_].process(input);
196 SampleType output = from;
198 const SampleType to = cascades_[1 - active_index_].process(input);
199 const std::size_t completed = transition_total_ - transition_remaining_ + 1;
200 const SampleType mix =
201 static_cast<SampleType
>(completed) /
static_cast<SampleType
>(transition_total_);
206 output = transition_remaining_ == 1 ? to : std::lerp(from, to, mix);
207 if (--transition_remaining_ == 0) {
208 active_index_ = 1 - active_index_;
209 commit_requested_endpoint();
210 cascades_[1 - active_index_].reset();
214 if (!std::isfinite(output))
215 return recover_from_fault();
233 if (samples ==
nullptr && frames != 0)
239 [&](std::size_t
i) { samples[
i] =
process(samples[
i]); },
240 [&](std::size_t
first, std::size_t count) { process_steady(samples +
first, count); });
250 active_index_ = 1 - active_index_;
251 commit_requested_endpoint();
253 cascades_[0].reset();
254 cascades_[1].reset();
255 transition_total_ = 0;
256 transition_remaining_ = 0;
261 return capacity_ != 0;
264 return transition_remaining_ != 0;
276 return requested_count_;
285 return index < requested_count_ ? requested_bands_[index] :
Band{};
288 return index < requested_count_ ? requested_coefficients_[index] :
Coefficients{};
298 const auto has_non_neutral_band = [](
const auto& bands, std::size_t count) {
299 for (std::size_t index = 0; index < count; ++index)
300 if (bands[index].gain_db != SampleType{0})
304 return has_non_neutral_band(active_bands_, active_count_) ||
305 has_non_neutral_band(requested_bands_, requested_count_)
313 if (!
prepared() || !std::isfinite(frequency_hz) || frequency_hz < 0.0 ||
314 frequency_hz >
static_cast<double>(sample_rate_) * 0.5)
315 return std::numeric_limits<double>::quiet_NaN();
318 const double omega =
angular_frequency(frequency_hz,
static_cast<double>(sample_rate_));
319 for (std::size_t index = 0; index < requested_count_; ++index)
326 if (!std::isfinite(
linear))
330 const double db = 20.0 * std::log10(
linear);
341 static bool coefficients_are_finite(
const Coefficients&
c)
noexcept {
342 return std::isfinite(
c.b0) && std::isfinite(
c.b1) && std::isfinite(
c.b2) &&
343 std::isfinite(
c.a1) && std::isfinite(
c.a2);
349 BiquadT<double> designer;
352 static_cast<double>(
band.
q),
static_cast<double>(sample_rate_),
354 const auto c = designer.coefficients();
355 return {
static_cast<SampleType
>(
c.b0),
static_cast<SampleType
>(
c.b1),
356 static_cast<SampleType
>(
c.b2),
static_cast<SampleType
>(
c.a1),
357 static_cast<SampleType
>(
c.a2)};
360 bool same_requested_configuration(std::span<const Band> bands)
const noexcept {
361 if (bands.size() != requested_count_)
363 for (std::size_t index = 0;
index < bands.size(); ++
index)
364 if (!(bands[index] == requested_bands_[index]))
369 void commit_requested_endpoint() noexcept {
370 active_bands_ = requested_bands_;
371 active_count_ = requested_count_;
372 transition_total_ = 0;
373 transition_remaining_ = 0;
377 void process_steady(SampleType* samples, std::size_t frames)
noexcept {
378 constexpr SampleType kMaxFinite = std::numeric_limits<SampleType>::max();
379 std::size_t frame = 0;
380 while (frame < frames) {
382 SampleType* chunk = samples + frame;
385 std::size_t finite = 0;
386 while (finite < n && std::abs(chunk[finite]) <= kMaxFinite)
394 std::copy(chunk, chunk + finite, input);
395 cascades_[active_index_].process(chunk, finite);
396 std::size_t good = 0;
397 while (good < finite && std::abs(chunk[good]) <= kMaxFinite)
401 if (good == finite) {
407 chunk[good] = recover_from_fault();
408 std::copy(input + good + 1, input + finite, chunk + good + 1);
413 SampleType recover_from_fault() noexcept {
414 cascades_[0].reset();
415 cascades_[1].reset();
418 return SampleType{0};
421 std::array<SosCascadeT<SampleType, MaxBands>, 2> cascades_{};
422 std::array<Band, MaxBands> active_bands_{};
423 std::array<Band, MaxBands> requested_bands_{};
424 std::array<Coefficients, MaxBands> requested_coefficients_{};
425 SampleType sample_rate_ = SampleType{0};
426 std::size_t capacity_ = 0;
427 std::size_t active_index_ = 0;
428 std::size_t active_count_ = 0;
429 std::size_t requested_count_ = 0;
430 std::size_t transition_total_ = 0;
431 std::size_t transition_remaining_ = 0;
432 std::size_t fault_count_ = 0;
433 bool healthy_ =
true;
@ peaking
Definition biquad.hpp:58
A bounded cascade of configurable peaking bands for one audio channel.
Definition graphic_eq.hpp:70
bool prepared() const noexcept
Definition graphic_eq.hpp:260
GraphicEqPrepareStatus prepare(SampleType sample_rate, std::size_t band_capacity=MaxBands) noexcept
Definition graphic_eq.hpp:88
int tail_samples() const noexcept
Definition graphic_eq.hpp:297
Coefficients coefficients(std::size_t index) const noexcept
Definition graphic_eq.hpp:287
GraphicEqConfigureStatus configure(std::span< const Band > bands, std::size_t transition_samples=0) noexcept
Definition graphic_eq.hpp:115
bool healthy() const noexcept
Definition graphic_eq.hpp:266
double magnitude(double frequency_hz) const noexcept
Stationary response of the requested endpoint.
Definition graphic_eq.hpp:312
SampleType supported_frequency_ceiling_hz() const noexcept
Definition graphic_eq.hpp:281
static constexpr float maximum_sample_rate_hz
Definition graphic_eq.hpp:80
static constexpr float minimum_sample_rate_hz
Definition graphic_eq.hpp:79
static constexpr float minimum_frequency_hz
Definition graphic_eq.hpp:81
static constexpr int latency_samples() noexcept
The cascade is causal IIR and adds no bulk delay.
Definition graphic_eq.hpp:294
std::size_t band_count() const noexcept
Definition graphic_eq.hpp:275
SampleType process(SampleType input) noexcept
Definition graphic_eq.hpp:189
Band band(std::size_t index) const noexcept
Definition graphic_eq.hpp:284
bool process_block(SampleType *samples, std::size_t frames) noexcept
Process caller-owned storage in place.
Definition graphic_eq.hpp:232
GraphicEqBandT< SampleType > Band
Definition graphic_eq.hpp:76
static constexpr float maximum_gain_db
Definition graphic_eq.hpp:84
static constexpr float maximum_frequency_hz
Definition graphic_eq.hpp:82
BiquadCoefficientsT< SampleType > Coefficients
Definition graphic_eq.hpp:77
void reset() noexcept
Clear recursive history and, if a transition is active, select its requested endpoint.
Definition graphic_eq.hpp:246
static constexpr float minimum_q
Definition graphic_eq.hpp:85
std::size_t fault_count() const noexcept
Definition graphic_eq.hpp:269
static constexpr float maximum_q
Definition graphic_eq.hpp:86
static constexpr float minimum_gain_db
Definition graphic_eq.hpp:83
std::size_t capacity() const noexcept
Definition graphic_eq.hpp:272
float sample_rate() const noexcept
Definition graphic_eq.hpp:278
static constexpr std::size_t storage_capacity() noexcept
Definition graphic_eq.hpp:336
double magnitude_db(double frequency_hz) const noexcept
Definition graphic_eq.hpp:324
bool transitioning() const noexcept
Definition graphic_eq.hpp:263
Evaluate what a biquad (or a cascade of them) does to the spectrum.
@ 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
constexpr float min_response_db
Floor applied to dB conversions.
Definition frequency_response.hpp:32
double section_magnitude(const BiquadCoefficientsT< SampleType > &c, double omega)
Linear magnitude |H(e^jω)| of one section at normalized angular frequency ω = 2π·f/fs (radians/sample...
Definition frequency_response.hpp:45
@ c
Definition spectrum_trace.hpp:35
GraphicEqBandT< float > GraphicEqBand
Definition graphic_eq.hpp:47
constexpr std::size_t kSnapChunkSamples
Samples a block path runs between denormal snaps of its recursive state.
Definition denormal.hpp:113
GraphicEqT< float > GraphicEq
Definition graphic_eq.hpp:436
@ 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
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
GraphicEqConfigureStatus
Definition graphic_eq.hpp:25
@ frequency_out_of_range
Definition graphic_eq.hpp:31
@ non_finite
Definition graphic_eq.hpp:30
@ over_capacity
Definition graphic_eq.hpp:28
@ configured
Definition graphic_eq.hpp:26
@ q_out_of_range
Definition graphic_eq.hpp:34
@ unstable
Definition graphic_eq.hpp:35
@ frequencies_not_strictly_increasing
Definition graphic_eq.hpp:32
@ invalid_transition
Definition graphic_eq.hpp:29
@ not_prepared
Definition graphic_eq.hpp:27
@ transition_in_progress
Definition graphic_eq.hpp:36
@ gain_out_of_range
Definition graphic_eq.hpp:33
@ i
Definition formant_filter_bank.hpp:20
@ linear
2-point. Cheapest; audible imaging when pitching up.
Definition granular.hpp:133
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
GraphicEqT< double > GraphicEq64
Definition graphic_eq.hpp:437
@ prepared
Definition finite_stretch_builder.hpp:19
GraphicEqPrepareStatus
Definition graphic_eq.hpp:19
@ invalid_sample_rate
Definition graphic_eq.hpp:21
@ prepared
Definition graphic_eq.hpp:20
@ invalid_capacity
Definition graphic_eq.hpp:22
@ non_finite
Definition formant_filter_bank.hpp:26
@ configured
Definition formant_filter_bank.hpp:23
@ unstable
Definition formant_filter_bank.hpp:29
@ n
Definition input_events.hpp:40
Fixed-capacity runtime executor for cascades of second-order IIR sections.
Definition graphic_eq.hpp:39
float gain_db
Definition graphic_eq.hpp:41
float q
Definition graphic_eq.hpp:42
float frequency_hz
Definition graphic_eq.hpp:40
friend constexpr bool operator==(const GraphicEqBandT &, const GraphicEqBandT &)=default