104 static_assert(std::is_floating_point_v<SampleType>);
105 static_assert(MaxBands >= 2);
111 std::array<SampleType, MaxBands>
bands{};
123 cutoff_count_ = cutoffs.size();
124 copy_cutoffs(cutoffs, cutoffs_);
125 Bank::warp_cutoffs(std::span<const double>(cutoffs_.data(), cutoff_count_), sample_rate_,
126 current_warped_cutoffs_);
127 Bank::design_coefficients(current_warped_cutoffs_, cutoff_count_, current_coefficients_);
128 target_warped_cutoffs_ = current_warped_cutoffs_;
130 transition_position_ = 0;
131 transition_length_ = 0;
144 std::size_t transition_samples = 0) noexcept {
145 if (!prepared_ || transition_length_ != 0 || cutoffs.size() != cutoff_count_ ||
146 !valid_configuration(sample_rate_, cutoffs))
148 if (transition_samples == std::numeric_limits<std::size_t>::max())
151 std::array<double, MaxBands - 1> proposed_cutoffs{};
152 std::array<double, MaxBands - 1> proposed_warped_cutoffs{};
153 copy_cutoffs(cutoffs, proposed_cutoffs);
154 Bank::warp_cutoffs(std::span<const double>(proposed_cutoffs.data(), cutoff_count_),
155 sample_rate_, proposed_warped_cutoffs);
156 if (transition_samples == 0) {
157 cutoffs_ = proposed_cutoffs;
158 target_cutoffs_ = proposed_cutoffs;
159 current_warped_cutoffs_ = proposed_warped_cutoffs;
160 target_warped_cutoffs_ = proposed_warped_cutoffs;
161 Bank::design_coefficients(current_warped_cutoffs_, cutoff_count_,
162 current_coefficients_);
167 const auto minimum = minimum_transition_samples_from_warped(proposed_warped_cutoffs);
168 if (minimum == std::numeric_limits<std::size_t>::max() || transition_samples < minimum)
171 bool changes_cutoff =
false;
172 for (std::size_t split = 0; split < cutoff_count_; ++split) {
174 changes_cutoff || proposed_warped_cutoffs[split] != current_warped_cutoffs_[split];
179 std::array<double, MaxBands - 1> proposed_multipliers{};
180 if (!transition_is_representable(proposed_warped_cutoffs, transition_samples,
181 &proposed_multipliers))
184 target_cutoffs_ = proposed_cutoffs;
185 target_warped_cutoffs_ = proposed_warped_cutoffs;
186 warped_cutoff_multipliers_ = proposed_multipliers;
187 transition_position_ = 0;
188 transition_length_ = transition_samples;
194 result.count = prepared_ ? cutoff_count_ + 1 : 0;
195 if (!prepared_ || !std::isfinite(input))
196 return recover(result);
198 advance_transition();
199 const auto rendered =
200 bank_.process(
static_cast<double>(input), cutoff_count_, current_coefficients_);
201 for (std::size_t band = 0; band < result.count; ++band)
202 result.bands[band] =
static_cast<SampleType
>(rendered[band]);
204 for (std::size_t band = 0; band < result.count; ++band) {
205 if (!std::isfinite(result.bands[band]))
206 return recover(result);
209 result.healthy =
true;
216 transition_position_ = 0;
217 transition_length_ = 0;
218 healthy_ = prepared_;
222 return prepared_ ? cutoff_count_ + 1 : 0;
225 return prepared_ ? cutoff_count_ : 0;
227 SampleType
cutoff(std::size_t index)
const noexcept {
228 return index < cutoff_count_ ? static_cast<SampleType>(cutoffs_[index]) : SampleType{0};
231 return prepared_ ? sample_rate_ : SampleType{0};
234 return transition_length_ != 0;
254 if (!prepared_ || cutoffs.size() != cutoff_count_ ||
255 !valid_configuration(sample_rate_, cutoffs))
256 return std::numeric_limits<std::size_t>::max();
257 std::array<double, MaxBands - 1> warped{};
258 std::array<double, MaxBands - 1> copied{};
259 copy_cutoffs(cutoffs, copied);
260 Bank::warp_cutoffs(std::span<const double>(copied.data(), cutoff_count_), sample_rate_,
262 return minimum_transition_samples_from_warped(warped);
272 std::span<const SampleType> cutoffs)
noexcept {
278 static std::complex<double>
band_response(std::span<const SampleType> cutoffs, std::size_t band,
279 double frequency_hz,
double sample_rate)
noexcept {
280 if (band > cutoffs.size() ||
281 !valid_configuration(
static_cast<SampleType
>(
sample_rate), cutoffs) ||
282 !(std::isfinite(frequency_hz) && frequency_hz >= 0.0 &&
286 std::complex<double> response{1.0, 0.0};
287 for (std::size_t split = 0; split < band; ++split)
288 response *= split_response(cutoffs[split], frequency_hz,
sample_rate,
false);
290 if (band < cutoffs.size())
291 response *= split_response(cutoffs[band], frequency_hz,
sample_rate,
true);
293 for (std::size_t split = band + 1; split < cutoffs.size(); ++split) {
294 response *= split_response(cutoffs[split], frequency_hz,
sample_rate,
true) +
295 split_response(cutoffs[split], frequency_hz,
sample_rate,
false);
303 std::complex<double> response{};
304 for (std::size_t band = 0; band <= cutoffs.size(); ++band)
312 struct TptCoefficients {
324 Outputs process(
double input,
const TptCoefficients& coefficients)
noexcept {
326 const double v3 = input - state2;
327 const double v1 = coefficients.a1 * state1 + coefficients.a2 *
v3;
328 const double v2 = state2 + coefficients.a2 * state1 + coefficients.a3 *
v3;
331 return {
v2, input - inverse_q *
v1 -
v2};
334 void reset() noexcept {
343 struct ModulatedLinkwitzRiley {
344 LinkwitzRileyT<double>::BandSplit process(
double input,
345 const TptCoefficients& coefficients)
noexcept {
347 low2.process(low1.process(input, coefficients).
low, coefficients).
low;
349 high2.process(high1.process(input, coefficients).
high, coefficients).
high;
353 void reset() noexcept {
360 TptSection low1, low2, high1, high2;
364 std::array<ModulatedLinkwitzRiley, MaxBands - 1> splitters{};
365 std::array<std::array<ModulatedLinkwitzRiley, MaxBands - 1>, MaxBands - 1>
366 phase_compensators{};
368 static void warp_cutoffs(std::span<const double> cutoffs,
double sample_rate,
369 std::array<double, MaxBands - 1>& destination)
noexcept {
370 for (std::size_t split = 0; split < cutoffs.size(); ++split) {
371 destination[split] = std::tan(std::acos(-1.0) * cutoffs[split] /
sample_rate);
375 static SectionCoefficients coefficients_from_warped_cutoff(
double warped)
noexcept {
376 const double square = warped * warped;
378 const double normalization = 1.0 / (1.0 + inverse_q * warped +
square);
379 const double low_b0 =
square * normalization;
380 const double high_b0 = normalization;
381 const double a1 = 2.0 * (
square - 1.0) * normalization;
382 const double a2 = (1.0 - inverse_q * warped +
square) * normalization;
383 return {{low_b0, 2.0 * low_b0, low_b0, a1, a2},
384 {high_b0, -2.0 * high_b0, high_b0, a1, a2}};
388 design_coefficients(
const std::array<double, MaxBands - 1>& warped_cutoffs,
390 std::array<TptCoefficients, MaxBands - 1>& destination)
noexcept {
391 for (std::size_t split = 0; split <
cutoff_count; ++split) {
393 const double warped = warped_cutoffs[split];
394 const double a1 = 1.0 / (1.0 + warped * (warped + inverse_q));
395 destination[split] = {a1, warped * a1, warped * warped * a1};
399 void reset() noexcept {
400 for (
auto& splitter : splitters)
402 for (
auto& row : phase_compensators)
403 for (
auto& compensator : row)
407 std::array<double, MaxBands>
409 const std::array<TptCoefficients, MaxBands - 1>& coefficients)
noexcept {
410 std::array<double, MaxBands> bands{};
411 double remainder = input;
412 for (std::size_t split = 0; split <
cutoff_count; ++split) {
413 const auto divided = splitters[split].process(remainder, coefficients[split]);
414 bands[split] = divided.low;
415 remainder = divided.high;
419 for (std::size_t band = 0; band <
cutoff_count; ++band) {
420 for (std::size_t split = band + 1; split <
cutoff_count; ++split) {
422 phase_compensators[band][split].process(bands[band], coefficients[split]);
423 bands[band] = aligned.low + aligned.high;
430 static bool valid_configuration(SampleType
sample_rate,
431 std::span<const SampleType> cutoffs)
noexcept {
433 cutoffs.size() >= MaxBands)
435 const SampleType nyquist =
sample_rate * SampleType{0.5};
436 SampleType previous = SampleType{0};
437 for (
const auto cutoff_hz : cutoffs) {
438 if (!(std::isfinite(cutoff_hz) && cutoff_hz > previous && cutoff_hz < nyquist) ||
439 !stable_section_design(
static_cast<double>(cutoff_hz),
442 previous = cutoff_hz;
447 static std::complex<double> section_response(
const BiquadCoefficientsT<double>&
c,
448 double frequency_hz,
double sample_rate)
noexcept {
449 const double omega = 2.0 * std::acos(-1.0) * frequency_hz /
sample_rate;
450 const auto z1 = std::polar(1.0, -omega);
451 const auto z2 = z1 * z1;
452 return (
c.b0 +
c.b1 * z1 +
c.b2 * z2) / (1.0 +
c.a1 * z1 +
c.a2 * z2);
455 static std::complex<double> split_response(SampleType cutoff_hz,
double frequency_hz,
457 const double warped =
458 std::tan(std::acos(-1.0) *
static_cast<double>(cutoff_hz) /
sample_rate);
459 const auto coefficients = Bank::coefficients_from_warped_cutoff(warped);
460 const auto once = section_response(
lowpass ? coefficients.lowpass : coefficients.highpass,
465 static bool stable_section_design(
double cutoff_hz,
double sample_rate)
noexcept {
466 const double warped = std::tan(std::acos(-1.0) * cutoff_hz /
sample_rate);
467 const auto coefficients = Bank::coefficients_from_warped_cutoff(warped);
468 const auto& lp = coefficients.lowpass;
469 const auto& hp = coefficients.highpass;
470 const auto finite = [](
const auto&
c) {
471 return std::isfinite(
c.b0) && std::isfinite(
c.b1) && std::isfinite(
c.b2) &&
472 std::isfinite(
c.a1) && std::isfinite(
c.a2);
474 if (!finite(lp) || !finite(hp) || !(lp.b0 > 0.0) || !(hp.b0 > 0.0))
477 const std::complex<double> discriminant{lp.a1 * lp.a1 - 4.0 * lp.a2, 0.0};
478 const auto root = std::sqrt(discriminant);
479 const double radius =
480 std::max(std::abs((-lp.a1 + root) * 0.5), std::abs((-lp.a1 - root) * 0.5));
481 constexpr double pole_margin = 64.0 * std::numeric_limits<double>::epsilon();
482 return std::isfinite(radius) && radius < 1.0 - pole_margin;
485 static void copy_cutoffs(std::span<const SampleType> cutoffs,
486 std::array<double, MaxBands - 1>& destination)
noexcept {
487 for (std::size_t
i = 0;
i < cutoffs.size(); ++
i)
488 destination[
i] =
static_cast<double>(cutoffs[
i]);
491 std::size_t minimum_transition_samples_from_warped(
492 const std::array<double, MaxBands - 1>& target)
const noexcept {
497 double largest_downward_distance = 0.0;
498 for (std::size_t split = 0; split < cutoff_count_; ++split) {
499 if (target[split] < current_warped_cutoffs_[split]) {
500 largest_downward_distance =
501 std::max(largest_downward_distance,
502 std::log(current_warped_cutoffs_[split] / target[split]));
505 const double required = std::ceil(sample_rate_ * largest_downward_distance /
507 if (!(std::isfinite(required) && required >= 0.0) ||
508 required >=
static_cast<double>(std::numeric_limits<std::size_t>::max()))
509 return std::numeric_limits<std::size_t>::max();
510 auto candidate =
static_cast<std::size_t
>(required);
517 constexpr std::size_t representability_search = 1024;
518 for (std::size_t attempt = 0; attempt < representability_search; ++attempt) {
519 if (transition_is_representable(target, candidate,
nullptr))
521 if (candidate == std::numeric_limits<std::size_t>::max() - 1)
525 return std::numeric_limits<std::size_t>::max();
528 bool transition_is_representable(
const std::array<double, MaxBands - 1>& target,
529 std::size_t transition_samples,
530 std::array<double, MaxBands - 1>* multipliers)
const noexcept {
531 if (transition_samples == 0 ||
532 transition_samples == std::numeric_limits<std::size_t>::max())
539 constexpr double epsilon = std::numeric_limits<double>::epsilon();
540 const double roundoff_product =
static_cast<double>(transition_samples - 1) * epsilon;
541 if (!(std::isfinite(roundoff_product) && roundoff_product < 0.5))
543 const double relative_roundoff_bound = roundoff_product / (1.0 - roundoff_product);
544 const double accumulated_log_roundoff = -std::log1p(-relative_roundoff_bound);
545 const double one_multiply_log_roundoff = -std::log1p(-epsilon);
546 const double maximum_downward_log_step =
549 for (std::size_t split = 0; split < cutoff_count_; ++split) {
550 const double total_log_distance =
551 std::log(target[split] / current_warped_cutoffs_[split]);
552 const double ideal_log_step =
553 total_log_distance /
static_cast<double>(transition_samples);
554 const double multiplier = std::exp(ideal_log_step);
555 if (!(std::isfinite(multiplier) && multiplier > 0.0) ||
556 (target[split] != current_warped_cutoffs_[split] && multiplier == 1.0))
559 const double realized_log_step = std::log(multiplier);
560 const double final_log_correction =
561 std::abs(total_log_distance -
562 static_cast<double>(transition_samples - 1) * realized_log_step);
563 const double bounded_final_log_correction =
564 final_log_correction + accumulated_log_roundoff;
565 if (!(std::isfinite(bounded_final_log_correction) &&
566 bounded_final_log_correction <= 2.0 * std::abs(ideal_log_step)))
569 if (total_log_distance < 0.0 &&
570 (-realized_log_step + one_multiply_log_roundoff > maximum_downward_log_step ||
571 bounded_final_log_correction > maximum_downward_log_step))
574 if (multipliers !=
nullptr)
575 (*multipliers)[split] = multiplier;
580 void advance_transition() noexcept {
581 if (transition_length_ == 0)
583 for (std::size_t split = 0; split < cutoff_count_; ++split)
584 current_warped_cutoffs_[split] *= warped_cutoff_multipliers_[split];
585 ++transition_position_;
586 if (transition_position_ == transition_length_) {
587 cutoffs_ = target_cutoffs_;
588 current_warped_cutoffs_ = target_warped_cutoffs_;
589 transition_position_ = 0;
590 transition_length_ = 0;
592 Bank::design_coefficients(current_warped_cutoffs_, cutoff_count_, current_coefficients_);
595 void finish_transition() noexcept {
596 if (transition_length_ == 0)
598 cutoffs_ = target_cutoffs_;
599 current_warped_cutoffs_ = target_warped_cutoffs_;
600 Bank::design_coefficients(current_warped_cutoffs_, cutoff_count_, current_coefficients_);
601 transition_position_ = 0;
602 transition_length_ = 0;
608 transition_position_ = 0;
609 transition_length_ = 0;
612 result.bands.fill(SampleType{0});
613 result.healthy =
false;
618 std::array<double, MaxBands - 1> cutoffs_{};
619 std::array<double, MaxBands - 1> target_cutoffs_{};
620 std::array<double, MaxBands - 1> current_warped_cutoffs_{};
621 std::array<double, MaxBands - 1> target_warped_cutoffs_{};
622 std::array<double, MaxBands - 1> warped_cutoff_multipliers_{};
623 std::array<TptCoefficients, MaxBands - 1> current_coefficients_{};
624 double sample_rate_ = 0.0;
625 std::size_t cutoff_count_ = 0;
626 std::size_t transition_position_ = 0;
627 std::size_t transition_length_ = 0;
628 std::uint64_t fault_count_ = 0;
629 bool prepared_ =
false;
630 bool healthy_ =
false;