Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
linkwitz_riley.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <algorithm>
6#include <array>
7#include <cmath>
8#include <complex>
9#include <cstddef>
10#include <cstdint>
11#include <limits>
12#include <span>
13#include <type_traits>
14
15namespace pulp::signal {
16
17// Linkwitz-Riley crossover filter (4th order, -6dB at crossover)
18// Provides lowpass and highpass outputs that sum flat.
19//
20// RT contract: coefficient updates, process, and reset are fixed-state only and
21// allocate no memory.
22template <typename SampleType = float> class LinkwitzRileyT {
23 public:
24 static constexpr SampleType legacy_butterworth_q = SampleType{0.707f};
25 static constexpr SampleType exact_butterworth_q =
26 static_cast<SampleType>(0.707106781186547524400844362104849039L);
27
32
33 void set_frequency(SampleType hz, SampleType sample_rate) {
34 set_frequency_with_q(hz, sample_rate, legacy_butterworth_q);
35 }
36
40 void set_frequency_precise(SampleType hz, SampleType sample_rate) {
41 set_frequency_with_q(hz, sample_rate, exact_butterworth_q);
42 }
43
45 SectionCoefficients section_coefficients() const {
46 return {lp1_.coefficients(), hp1_.coefficients()};
47 }
48
51 void set_section_coefficients(const SectionCoefficients& coefficients) {
52 lp1_.set_coefficients(coefficients.lowpass);
53 lp2_.set_coefficients(coefficients.lowpass);
54 hp1_.set_coefficients(coefficients.highpass);
55 hp2_.set_coefficients(coefficients.highpass);
56 }
57
58 private:
59 void set_frequency_with_q(SampleType hz, SampleType sample_rate, SampleType q) {
62 lp2_.set_coefficients(lp1_.coefficients());
63 hp2_.set_coefficients(hp1_.coefficients());
64 }
65
66 public:
67 struct BandSplit {
68 SampleType low, high;
69 };
70
71 BandSplit process(SampleType input) {
72 SampleType low = lp2_.process(lp1_.process(input));
73 SampleType high = hp2_.process(hp1_.process(input));
74 return {low, high};
75 }
76
77 void reset() {
78 lp1_.reset();
79 lp2_.reset();
80 hp1_.reset();
81 hp2_.reset();
82 }
83
84 private:
85 BiquadT<SampleType> lp1_, lp2_, hp1_, hp2_;
86};
87
90
103template <typename SampleType = float, std::size_t MaxBands = 8> class LinkwitzRileyCrossoverT {
104 static_assert(std::is_floating_point_v<SampleType>);
105 static_assert(MaxBands >= 2);
106
107 public:
108 static constexpr std::size_t max_bands = MaxBands;
109
110 struct Frame {
111 std::array<SampleType, MaxBands> bands{};
112 std::size_t count = 0;
113 bool healthy = false;
114 };
115
118 bool prepare(SampleType sample_rate, std::span<const SampleType> cutoffs) noexcept {
119 if (!valid_configuration(sample_rate, cutoffs))
120 return false;
121
122 sample_rate_ = sample_rate;
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_;
129 bank_.reset();
130 transition_position_ = 0;
131 transition_length_ = 0;
132 prepared_ = true;
133 healthy_ = true;
134 return true;
135 }
136
143 bool set_cutoffs(std::span<const SampleType> cutoffs,
144 std::size_t transition_samples = 0) noexcept {
145 if (!prepared_ || transition_length_ != 0 || cutoffs.size() != cutoff_count_ ||
146 !valid_configuration(sample_rate_, cutoffs))
147 return false;
148 if (transition_samples == std::numeric_limits<std::size_t>::max())
149 return false;
150
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_);
163 bank_.reset();
164 return true;
165 }
166
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)
169 return false;
170
171 bool changes_cutoff = false;
172 for (std::size_t split = 0; split < cutoff_count_; ++split) {
173 changes_cutoff =
174 changes_cutoff || proposed_warped_cutoffs[split] != current_warped_cutoffs_[split];
175 }
176 if (!changes_cutoff)
177 return true;
178
179 std::array<double, MaxBands - 1> proposed_multipliers{};
180 if (!transition_is_representable(proposed_warped_cutoffs, transition_samples,
181 &proposed_multipliers))
182 return false;
183
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;
189 return true;
190 }
191
192 Frame process(SampleType input) noexcept {
193 Frame result;
194 result.count = prepared_ ? cutoff_count_ + 1 : 0;
195 if (!prepared_ || !std::isfinite(input))
196 return recover(result);
197
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]);
203
204 for (std::size_t band = 0; band < result.count; ++band) {
205 if (!std::isfinite(result.bands[band]))
206 return recover(result);
207 }
208 healthy_ = true;
209 result.healthy = true;
210 return result;
211 }
212
213 void reset() noexcept {
214 finish_transition();
215 bank_.reset();
216 transition_position_ = 0;
217 transition_length_ = 0;
218 healthy_ = prepared_;
219 }
220
221 std::size_t band_count() const noexcept {
222 return prepared_ ? cutoff_count_ + 1 : 0;
223 }
224 std::size_t cutoff_count() const noexcept {
225 return prepared_ ? cutoff_count_ : 0;
226 }
227 SampleType cutoff(std::size_t index) const noexcept {
228 return index < cutoff_count_ ? static_cast<SampleType>(cutoffs_[index]) : SampleType{0};
229 }
230 SampleType sample_rate() const noexcept {
231 return prepared_ ? sample_rate_ : SampleType{0};
232 }
233 bool transitioning() const noexcept {
234 return transition_length_ != 0;
235 }
236 bool healthy() const noexcept {
237 return healthy_;
238 }
239 std::uint64_t fault_count() const noexcept {
240 return fault_count_;
241 }
242
246 static constexpr double maximum_downward_log_slew_nepers_per_second() noexcept {
247 return 20.0;
248 }
249
253 std::size_t minimum_transition_samples(std::span<const SampleType> cutoffs) const noexcept {
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_,
261 warped);
262 return minimum_transition_samples_from_warped(warped);
263 }
264 static constexpr int latency_samples() noexcept {
265 return 0;
266 }
267
271 static bool supports_configuration(SampleType sample_rate,
272 std::span<const SampleType> cutoffs) noexcept {
273 return valid_configuration(sample_rate, cutoffs);
274 }
275
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 &&
283 frequency_hz <= sample_rate * 0.5))
284 return {};
285
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);
289
290 if (band < cutoffs.size())
291 response *= split_response(cutoffs[band], frequency_hz, sample_rate, true);
292
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);
296 }
297 return response;
298 }
299
300 static std::complex<double> reconstruction_response(std::span<const SampleType> cutoffs,
301 double frequency_hz,
302 double sample_rate) noexcept {
303 std::complex<double> response{};
304 for (std::size_t band = 0; band <= cutoffs.size(); ++band)
305 response += band_response(cutoffs, band, frequency_hz, sample_rate);
306 return response;
307 }
308
309 private:
310 using SectionCoefficients = LinkwitzRileyT<double>::SectionCoefficients;
311
312 struct TptCoefficients {
313 double a1 = 1.0;
314 double a2 = 0.0;
315 double a3 = 0.0;
316 };
317
318 struct TptSection {
319 struct Outputs {
320 double low = 0.0;
321 double high = 0.0;
322 };
323
324 Outputs process(double input, const TptCoefficients& coefficients) noexcept {
325 constexpr double inverse_q = 1.0 / LinkwitzRileyT<double>::exact_butterworth_q;
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;
329 state1 = snap_recursion_state(2.0 * v1 - state1);
330 state2 = snap_recursion_state(2.0 * v2 - state2);
331 return {v2, input - inverse_q * v1 - v2};
332 }
333
334 void reset() noexcept {
335 state1 = 0.0;
336 state2 = 0.0;
337 }
338
339 double state1 = 0.0;
340 double state2 = 0.0;
341 };
342
343 struct ModulatedLinkwitzRiley {
344 LinkwitzRileyT<double>::BandSplit process(double input,
345 const TptCoefficients& coefficients) noexcept {
346 const double low =
347 low2.process(low1.process(input, coefficients).low, coefficients).low;
348 const double high =
349 high2.process(high1.process(input, coefficients).high, coefficients).high;
350 return {low, high};
351 }
352
353 void reset() noexcept {
354 low1.reset();
355 low2.reset();
356 high1.reset();
357 high2.reset();
358 }
359
360 TptSection low1, low2, high1, high2;
361 };
362
363 struct Bank {
364 std::array<ModulatedLinkwitzRiley, MaxBands - 1> splitters{};
365 std::array<std::array<ModulatedLinkwitzRiley, MaxBands - 1>, MaxBands - 1>
366 phase_compensators{};
367
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);
372 }
373 }
374
375 static SectionCoefficients coefficients_from_warped_cutoff(double warped) noexcept {
376 const double square = warped * warped;
377 const double inverse_q = 1.0 / LinkwitzRileyT<double>::exact_butterworth_q;
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}};
385 }
386
387 static void
388 design_coefficients(const std::array<double, MaxBands - 1>& warped_cutoffs,
389 std::size_t cutoff_count,
390 std::array<TptCoefficients, MaxBands - 1>& destination) noexcept {
391 for (std::size_t split = 0; split < cutoff_count; ++split) {
392 constexpr double inverse_q = 1.0 / LinkwitzRileyT<double>::exact_butterworth_q;
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};
396 }
397 }
398
399 void reset() noexcept {
400 for (auto& splitter : splitters)
401 splitter.reset();
402 for (auto& row : phase_compensators)
403 for (auto& compensator : row)
404 compensator.reset();
405 }
406
407 std::array<double, MaxBands>
408 process(double input, std::size_t cutoff_count,
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;
416 }
417 bands[cutoff_count] = remainder;
418
419 for (std::size_t band = 0; band < cutoff_count; ++band) {
420 for (std::size_t split = band + 1; split < cutoff_count; ++split) {
421 const auto aligned =
422 phase_compensators[band][split].process(bands[band], coefficients[split]);
423 bands[band] = aligned.low + aligned.high;
424 }
425 }
426 return bands;
427 }
428 };
429
430 static bool valid_configuration(SampleType sample_rate,
431 std::span<const SampleType> cutoffs) noexcept {
432 if (!(std::isfinite(sample_rate) && sample_rate > SampleType{0}) || cutoffs.empty() ||
433 cutoffs.size() >= MaxBands)
434 return false;
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),
440 static_cast<double>(sample_rate)))
441 return false;
442 previous = cutoff_hz;
443 }
444 return true;
445 }
446
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);
453 }
454
455 static std::complex<double> split_response(SampleType cutoff_hz, double frequency_hz,
456 double sample_rate, bool lowpass) noexcept {
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,
461 frequency_hz, sample_rate);
462 return once * once;
463 }
464
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);
473 };
474 if (!finite(lp) || !finite(hp) || !(lp.b0 > 0.0) || !(hp.b0 > 0.0))
475 return false;
476
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;
483 }
484
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]);
489 }
490
491 std::size_t minimum_transition_samples_from_warped(
492 const std::array<double, MaxBands - 1>& target) const noexcept {
493 // TPT state is invariant under coefficient changes, but a rapid downward
494 // move can still release stored integrator energy into a much slower
495 // pole. Limit that parameter motion explicitly. Signal peaks also depend
496 // on prior input and are intentionally not described by this rate bound.
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]));
503 }
504 }
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);
511 if (candidate == 0)
512 return 0;
513
514 // ceil() normally leaves enough room for floating-point roundoff. At
515 // exact integer boundaries, advance a few samples until the realized
516 // multiplier and accumulated error fit the same public rate bound.
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))
520 return candidate;
521 if (candidate == std::numeric_limits<std::size_t>::max() - 1)
522 break;
523 ++candidate;
524 }
525 return std::numeric_limits<std::size_t>::max();
526 }
527
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())
533 return false;
534
535 // A product of n rounded positive factors has relative error bounded by
536 // gamma_n = n*epsilon/(1-n*epsilon). Work in log space and reserve a
537 // conservative full epsilon per multiplication. Requests too long for
538 // double precision to substantiate are rejected during configuration.
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))
542 return false;
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 =
548
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))
557 return false;
558
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)))
567 return false;
568
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))
572 return false;
573
574 if (multipliers != nullptr)
575 (*multipliers)[split] = multiplier;
576 }
577 return true;
578 }
579
580 void advance_transition() noexcept {
581 if (transition_length_ == 0)
582 return;
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;
591 }
592 Bank::design_coefficients(current_warped_cutoffs_, cutoff_count_, current_coefficients_);
593 }
594
595 void finish_transition() noexcept {
596 if (transition_length_ == 0)
597 return;
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;
603 }
604
605 Frame recover(Frame result) noexcept {
606 finish_transition();
607 bank_.reset();
608 transition_position_ = 0;
609 transition_length_ = 0;
610 healthy_ = false;
611 ++fault_count_;
612 result.bands.fill(SampleType{0});
613 result.healthy = false;
614 return result;
615 }
616
617 Bank bank_{};
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;
631};
632
635
636} // namespace pulp::signal
Definition biquad.hpp:53
@ highpass
Definition biquad.hpp:58
@ lowpass
Definition biquad.hpp:58
void set_coefficients(const BiquadCoefficientsT< U > &c)
Definition biquad.hpp:69
Coefficients coefficients() const
Definition biquad.hpp:81
Fixed-capacity Linkwitz-Riley crossover for two or more ordered bands.
Definition linkwitz_riley.hpp:103
bool healthy() const noexcept
Definition linkwitz_riley.hpp:236
std::uint64_t fault_count() const noexcept
Definition linkwitz_riley.hpp:239
static constexpr std::size_t max_bands
Definition linkwitz_riley.hpp:108
std::size_t minimum_transition_samples(std::span< const SampleType > cutoffs) const noexcept
Minimum safe nonzero transition for the requested cutoff set.
Definition linkwitz_riley.hpp:253
static std::complex< double > band_response(std::span< const SampleType > cutoffs, std::size_t band, double frequency_hz, double sample_rate) noexcept
Exact complex response of one configured band, including the all-pass compensation that makes the com...
Definition linkwitz_riley.hpp:278
bool prepare(SampleType sample_rate, std::span< const SampleType > cutoffs) noexcept
Establish the sample rate, band count, and initial cutoff set.
Definition linkwitz_riley.hpp:118
SampleType cutoff(std::size_t index) const noexcept
Definition linkwitz_riley.hpp:227
std::size_t cutoff_count() const noexcept
Definition linkwitz_riley.hpp:224
bool set_cutoffs(std::span< const SampleType > cutoffs, std::size_t transition_samples=0) noexcept
Retune all cutoffs together.
Definition linkwitz_riley.hpp:143
Frame process(SampleType input) noexcept
Definition linkwitz_riley.hpp:192
static constexpr double maximum_downward_log_slew_nepers_per_second() noexcept
Maximum accepted downward motion in the logarithm of the bilinear-warped cutoff.
Definition linkwitz_riley.hpp:246
void reset() noexcept
Definition linkwitz_riley.hpp:213
static bool supports_configuration(SampleType sample_rate, std::span< const SampleType > cutoffs) noexcept
True when the exact double-precision internal realization has finite, non-degenerate coefficients and...
Definition linkwitz_riley.hpp:271
std::size_t band_count() const noexcept
Definition linkwitz_riley.hpp:221
bool transitioning() const noexcept
Definition linkwitz_riley.hpp:233
float sample_rate() const noexcept
Definition linkwitz_riley.hpp:230
static std::complex< double > reconstruction_response(std::span< const SampleType > cutoffs, double frequency_hz, double sample_rate) noexcept
Definition linkwitz_riley.hpp:300
static constexpr int latency_samples() noexcept
Definition linkwitz_riley.hpp:264
Definition linkwitz_riley.hpp:22
void set_frequency_precise(SampleType hz, SampleType sample_rate)
Exact Butterworth alignment for new LR4 constructions.
Definition linkwitz_riley.hpp:40
void reset()
Definition linkwitz_riley.hpp:77
static constexpr float exact_butterworth_q
Definition linkwitz_riley.hpp:25
void set_section_coefficients(const SectionCoefficients &coefficients)
Change coefficients without clearing recursive state.
Definition linkwitz_riley.hpp:51
SectionCoefficients section_coefficients() const
Coefficients shared by both cascaded sections of each LR4 branch.
Definition linkwitz_riley.hpp:45
void set_frequency(SampleType hz, SampleType sample_rate)
Definition linkwitz_riley.hpp:33
BandSplit process(SampleType input)
Definition linkwitz_riley.hpp:71
static constexpr float legacy_butterworth_q
Definition linkwitz_riley.hpp:24
Definition additive_bank.hpp:218
@ c
Definition spectrum_trace.hpp:35
LinkwitzRileyCrossoverT< double > LinkwitzRileyCrossover64
Definition linkwitz_riley.hpp:634
LinkwitzRileyT< double > LinkwitzRiley64
Definition linkwitz_riley.hpp:89
@ v2
Medium vibrato.
Definition scanner_vibrato.hpp:24
@ v3
Full vibrato.
Definition scanner_vibrato.hpp:25
@ v1
Shallow vibrato — scanner output only.
Definition scanner_vibrato.hpp:23
LinkwitzRileyCrossoverT< float > LinkwitzRileyCrossover
Definition linkwitz_riley.hpp:633
@ i
Definition formant_filter_bank.hpp:20
LinkwitzRileyT< float > LinkwitzRiley
Definition linkwitz_riley.hpp:88
@ lowpass
Definition filter_morph.hpp:19
@ square
Definition lfo_sampling.hpp:58
T snap_recursion_state(T x)
Snap a per-sample recursion's state: snap_to_zero() where subnormals are slow, the identity where kSu...
Definition denormal.hpp:84
@ candidate
Definition waveform_editor_primitives.hpp:193
Definition biquad.hpp:24
Definition linkwitz_riley.hpp:110
bool healthy
Definition linkwitz_riley.hpp:113
std::array< SampleType, MaxBands > bands
Definition linkwitz_riley.hpp:111
std::size_t count
Definition linkwitz_riley.hpp:112
double low
Definition linkwitz_riley.hpp:320
double high
Definition linkwitz_riley.hpp:321
Definition linkwitz_riley.hpp:67
SampleType high
Definition linkwitz_riley.hpp:68
SampleType low
Definition linkwitz_riley.hpp:68
BiquadCoefficientsT< SampleType > lowpass
Definition linkwitz_riley.hpp:29
BiquadCoefficientsT< SampleType > highpass
Definition linkwitz_riley.hpp:30