Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
saturator.hpp
Go to the documentation of this file.
1#pragma once
2
121
122#include <pulp/signal/biquad.hpp>
125#include <pulp/signal/units.hpp>
126
127#include <algorithm>
128#include <cmath>
129#include <cstddef>
130#include <cstdint>
131#include <array>
132
133namespace pulp::signal {
134
142
150
151namespace detail {
152
165 static double f(SaturatorShape shape, double u) {
166 switch (shape) {
167 case SaturatorShape::tanh_soft: return std::tanh(u);
168 case SaturatorShape::atan_soft: return std::atan(u);
170 if (u > 1.0) return 2.0 / 3.0;
171 if (u < -1.0) return -2.0 / 3.0;
172 return u - u * u * u / 3.0;
173 case SaturatorShape::sinh_arc: return std::asinh(u);
174 }
175 return u;
176 }
177
180 static double df(SaturatorShape shape, double u) {
181 switch (shape) {
183 const double c = std::cosh(u);
184 return 1.0 / (c * c);
185 }
186 case SaturatorShape::atan_soft: return 1.0 / (1.0 + u * u);
187 case SaturatorShape::cubic_soft: return std::abs(u) < 1.0 ? 1.0 - u * u : 0.0;
188 case SaturatorShape::sinh_arc: return 1.0 / std::sqrt(1.0 + u * u);
189 }
190 return 1.0;
191 }
192
195 static double antiderivative(SaturatorShape shape, double u) {
196 switch (shape) {
198 // ln(cosh u), computed so large |u| does not overflow cosh:
199 // ln(cosh u) = |u| + ln((1 + e^(−2|u|)) / 2).
200 {
201 const double a = std::abs(u);
202 return a + std::log1p(std::exp(-2.0 * a)) - 0.6931471805599453;
203 }
205 return u * std::atan(u) - 0.5 * std::log1p(u * u);
207 const double a = std::abs(u);
208 if (a < 1.0) return u * u * 0.5 - u * u * u * u / 12.0;
209 // Even, not odd: integrating the constant ±2/3 outward from
210 // |u| = 1 in both directions gives the same magnitude. The
211 // 5/12 matches the interior branch at |u| = 1 (1/2 − 1/12), so
212 // F1 is continuous there.
213 return (2.0 / 3.0) * (a - 1.0) + 5.0 / 12.0;
214 }
216 return u * std::asinh(u) - std::sqrt(1.0 + u * u);
217 }
218 return 0.5 * u * u;
219 }
220
231 static double second_antiderivative(SaturatorShape shape, double u) {
232 switch (shape) {
236 return 0.5 * (u * u - 1.0) * std::atan(u) - 0.5 * u * std::log1p(u * u) + 0.5 * u;
238 const double a = std::abs(u);
239 if (a < 1.0)
240 return u * u * u / 6.0 - u * u * u * u * u / 60.0;
241 // Odd continuation of the clipped branch: integrating
242 // F1 = (2/3)(a − 1) + 5/12 outward from F2(1) = 3/20.
243 const double r = a - 1.0;
244 return std::copysign(r * r / 3.0 + (5.0 / 12.0) * r + 3.0 / 20.0, u);
245 }
247 const double root = std::sqrt(1.0 + u * u);
248 return 0.25 * (2.0 * u * u - 1.0) * std::asinh(u) - 0.75 * u * root;
249 }
250 }
251 return u * u * u / 6.0;
252 }
253
268 static double tanh_second_antiderivative(double u) {
269 const double a = std::abs(u);
270 const double s = std::log1p(std::exp(-2.0 * a));
271 const double s2 = s * s;
272 // Li₂(w) = s − s²/4 + s³·P(s²), P from B₂ … B₁₆.
273 const double odd =
274 1.0 / 36.0 +
275 s2 * (-1.0 / 3600.0 +
276 s2 * (1.0 / 211680.0 + s2 * (-1.0 / 10886400.0 +
277 s2 * (1.0 / 526901760.0 +
278 s2 * (-4.0647616451442256e-11 +
279 s2 * (8.921691020456452e-13 +
280 s2 * -1.9939295860721074e-14))))));
281 const double li2_w = s - 0.25 * s2 + s * s2 * odd;
282 constexpr double kLn2 = 0.69314718055994530942;
283 constexpr double kPiSquaredOver24 = 0.41123351671205660911;
284 const double magnitude =
285 0.5 * a * a - kLn2 * a - 0.5 * li2_w - 0.25 * s2 + kPiSquaredOver24;
286 return std::copysign(magnitude, u);
287 }
288};
289
290} // namespace detail
291
304inline double adaa_linear_magnitude(int order, double cycles_per_sample) noexcept {
305 constexpr double kPi = 3.14159265358979323846;
306 if (order <= 0)
307 return 1.0;
308 if (order == 1)
309 return std::abs(std::cos(kPi * cycles_per_sample));
310 return std::abs(1.0 + 2.0 * std::cos(2.0 * kPi * cycles_per_sample)) / 3.0;
311}
312
336 public:
339 static constexpr double kTolerance = 1e-4;
340
345 void reset(SaturatorShape shape, double previous, double before) {
346 u_[0] = previous;
347 u_[1] = before;
350 }
351
354 double step(SaturatorShape shape, double u) {
356 // Sort (u, u_[0], u_[1]) with their F2 values.
357 double p[3] = {u, u_[0], u_[1]};
358 double q[3] = {f2, f2_[0], f2_[1]};
359 u_[1] = u_[0];
360 f2_[1] = f2_[0];
361 u_[0] = u;
362 f2_[0] = f2;
363 if (p[0] > p[1])
364 swap_pair(p, q, 0, 1);
365 if (p[1] > p[2])
366 swap_pair(p, q, 1, 2);
367 if (p[0] > p[1])
368 swap_pair(p, q, 0, 1);
369
370 const double spread = p[2] - p[0];
371 const double scale = std::max(1.0, std::max(std::abs(p[0]), std::abs(p[2])));
372 const double tolerance = kTolerance * scale;
373 if (spread <= tolerance)
374 return detail::SaturatorShapeMath::f(shape, (p[0] + p[1] + p[2]) / 3.0);
375 const double upper = first_difference(shape, p[2], p[1], q[2], q[1], tolerance);
376 const double lower = first_difference(shape, p[1], p[0], q[1], q[0], tolerance);
377 return 2.0 * (upper - lower) / spread;
378 }
379
380 private:
381 static void swap_pair(double* p, double* q, int i, int j) {
382 std::swap(p[i], p[j]);
383 std::swap(q[i], q[j]);
384 }
385
388 static double first_difference(SaturatorShape shape, double hi, double lo, double f2_hi,
389 double f2_lo, double tolerance) {
390 const double delta = hi - lo;
391 if (delta > tolerance)
392 return (f2_hi - f2_lo) / delta;
393 const double mid = 0.5 * (hi + lo);
395 delta * delta * detail::SaturatorShapeMath::df(shape, mid) / 24.0;
396 }
397
398 double u_[2] = {0.0, 0.0};
399 double f2_[2] = {0.0, 0.0};
400};
401
403template <typename SampleType = float>
405public:
408
409 // ── Design parameters (the complete roster; see the file doc block) ────
410
415 static constexpr double kDriveDbMin = -12.0;
416 static constexpr double kDriveDbMax = 36.0;
417 static constexpr double kDriveDbDefault = 0.0;
418
420 static constexpr double kBiasMax = 1.0;
421
426 static constexpr double kCubicBiasClamp = 0.98;
427
430 static constexpr double kTonePreHzDefault = 3000.0;
431 static constexpr double kTonePreHzMax = 8000.0;
432
435 static constexpr double kTonePreHzFloor = 20.0;
436
439 static constexpr double kPreBoostDbDefault = 9.0;
440 static constexpr double kPreBoostDbMax = 18.0;
441
444 static constexpr double kShelfQ = 0.70710678118654752;
445
455 static constexpr double kAdaaEpsilon = 1e-6;
456
458 static constexpr double kOutputTrimDbMax = 24.0;
459
460 // ── Lifecycle ─────────────────────────────────────────────────────────
461
462 SaturatorT() { update_shaper(); }
463
465 void prepare(double sample_rate) {
466 sample_rate_ = sample_rate > 0.0 ? sample_rate : sample_rate_;
467
468 // The house oversampler, at 2x with the linear-phase FIR kind. Composed
469 // rather than reimplemented: its x2 standard-quality prototype is 129
470 // taps, whose polyphase halves give exactly 64 samples of group delay at
471 // the input rate — the value `latency_samples()` reports.
472 using Os = OversamplerT<SampleType>;
473 oversampler_.set_kind(Os::Kind::linear_phase_fir);
474 oversampler_.set_quality(Os::Quality::standard);
475 oversampler_.set_factor(Os::Factor::x2);
476 oversampler_.set_sample_rate(static_cast<SampleType>(sample_rate_));
477 oversample_latency_ = oversampler_.latency_samples();
478
479 // The dry path is delayed to match the wet path (or the dry/wet mix
480 // becomes a comb filter) by a fixed-capacity ring, so an unprepared
481 // instance aligns adaa2's one sample too and switching policy never
482 // allocates.
483 oversample_latency_ =
484 std::min(oversample_latency_, static_cast<int>(kDryDelayCapacity) - 1);
485
486 update_tone();
487 reset();
488 }
489
491 shape_ = shape;
492 update_shaper();
493 }
494
495 Shape shape() const { return shape_; }
496
497 void set_drive_db(double db) {
498 if (!std::isfinite(db)) return;
499 drive_db_ = std::clamp(db, kDriveDbMin, kDriveDbMax);
500 update_shaper();
501 }
502
503 double drive_db() const { return drive_db_; }
504
507 void set_bias(double normalized) {
508 if (!std::isfinite(normalized)) return;
509 bias_request_ = std::clamp(normalized, -kBiasMax, kBiasMax);
510 update_shaper();
511 }
512
515 double bias() const { return bias_; }
516
518 void set_tone_pre_hz(double hz) {
519 if (!std::isfinite(hz)) return;
520 tone_pre_hz_ = std::clamp(hz, 0.0, kTonePreHzMax);
521 update_tone();
522 }
523
527 void set_tone_tracking(bool tracking) {
528 tone_tracking_ = tracking;
529 update_tone();
530 }
531
533 void set_tone_de_hz(double hz) {
534 if (!std::isfinite(hz)) return;
535 tone_de_hz_ = std::clamp(hz, 0.0, kTonePreHzMax);
536 update_tone();
537 }
538
539 void set_pre_boost_db(double db) {
540 if (!std::isfinite(db)) return;
541 pre_boost_db_ = std::clamp(db, 0.0, kPreBoostDbMax);
542 update_tone();
543 }
544
549 if (policy == alias_policy_)
550 return;
551 alias_policy_ = policy;
552 adaa_previous_ = input_history_[0];
553 adaa2_history_[0] = input_history_[0];
554 adaa2_history_[1] = input_history_[1];
555 reseed_adaa();
556 }
557 AliasPolicy alias_policy() const { return alias_policy_; }
558
559 void set_mix(double wet01) {
560 if (!std::isfinite(wet01)) return;
561 mix_ = std::clamp(wet01, 0.0, 1.0);
562 }
563
564 void set_output_trim_db(double db) {
565 if (!std::isfinite(db)) return;
566 output_trim_ = units::db_to_linear(std::clamp(db, -kOutputTrimDbMax, kOutputTrimDbMax));
567 }
568
574 int latency_samples() const noexcept {
575 switch (alias_policy_) {
576 case AliasPolicy::oversample_2x:
577 return oversample_latency_;
578 case AliasPolicy::adaa2:
579 return 1;
580 case AliasPolicy::adaa:
581 case AliasPolicy::off:
582 return 0;
583 }
584 return 0;
585 }
586
590 double group_delay_samples() const noexcept {
591 switch (alias_policy_) {
592 case AliasPolicy::oversample_2x:
593 return oversampler_.latency().exact_input_samples;
594 case AliasPolicy::adaa2:
595 return 1.0;
596 case AliasPolicy::adaa:
597 return 0.5;
598 case AliasPolicy::off:
599 return 0.0;
600 }
601 return 0.0;
602 }
603
605 void reset() {
606 adaa_previous_ = 0.0;
607 adaa2_history_[0] = adaa2_history_[1] = 0.0;
608 input_history_[0] = input_history_[1] = 0.0;
609 reseed_adaa();
610 pre_filter_.reset();
611 de_filter_.reset();
612 oversampler_.reset();
613 dry_delay_.fill(SampleType{0});
614 dry_write_ = 0;
615 dry_valid_ = 0;
616 }
617
620 SampleType process(SampleType input) {
621 if (!std::isfinite(static_cast<double>(input))) {
622 recover_audio_fault();
623 return SampleType{0};
624 }
625 const double dry = delayed_dry(static_cast<double>(input));
626
627 double x = static_cast<double>(input);
628 if (tone_active_) x = pre_filter_.process(static_cast<SampleType>(x));
629 input_history_[1] = input_history_[0];
630 input_history_[0] = x;
631
632 double wet = 0.0;
633 switch (alias_policy_) {
634 case AliasPolicy::off:
635 wet = shaped(x);
636 break;
637 case AliasPolicy::adaa:
638 wet = shaped_adaa(x);
639 break;
640 case AliasPolicy::adaa2:
641 wet = shaped_adaa2(x);
642 break;
643 case AliasPolicy::oversample_2x:
644 wet = oversampler_.process(static_cast<SampleType>(x), [this](SampleType s) {
645 return static_cast<SampleType>(shaped(static_cast<double>(s)));
646 });
647 break;
648 }
649
650 if (tone_active_) wet = de_filter_.process(static_cast<SampleType>(wet));
651
652 const double mixed = dry + mix_ * (wet - dry);
653 const double output = snap_to_zero(mixed * output_trim_);
654 if (!std::isfinite(output)) {
655 recover_audio_fault();
656 return SampleType{0};
657 }
658 return static_cast<SampleType>(output);
659 }
660
670 double worst_case_gain() const { return max_gain_; }
671
675 double shaped(double x) const {
676 const double u = drive_ * x + bias_;
677 return (detail::SaturatorShapeMath::f(shape_, u) - bias_offset_) * inv_drive_slope_;
678 }
679
680private:
681 void recover_audio_fault() noexcept {
682 adaa_previous_ = 0.0;
683 adaa2_history_[0] = adaa2_history_[1] = 0.0;
684 input_history_[0] = input_history_[1] = 0.0;
685 reseed_adaa();
686 pre_filter_.reset();
687 de_filter_.reset();
688 oversampler_.reset();
689 dry_write_ = 0;
690 dry_valid_ = 0;
691 }
692
698 double antiderivative(double x) const {
699 const double u = drive_ * x + bias_;
701 drive_ * bias_offset_ * x) *
702 inv_drive_sq_slope_;
703 }
704
707 double shaped_adaa(double x) {
708 const double previous = adaa_previous_;
709 const double previous_antiderivative = adaa_previous_antiderivative_;
710 const double current_antiderivative = antiderivative(x);
711 adaa_previous_ = x;
712 adaa_previous_antiderivative_ = current_antiderivative;
713 const double delta = x - previous;
714 if (std::abs(delta) > kAdaaEpsilon)
715 return (current_antiderivative - previous_antiderivative) / delta;
716 // The difference quotient is numerically meaningless here; the limit it
717 // is approximating is the curve evaluated at the midpoint.
718 return shaped(0.5 * (x + previous));
719 }
720
725 double shaped_adaa2(double x) {
726 adaa2_history_[1] = adaa2_history_[0];
727 adaa2_history_[0] = x;
728 const double v = adaa2_.step(shape_, drive_ * x + bias_);
729 return (v - bias_offset_) * inv_drive_slope_;
730 }
731
735 void reseed_adaa() {
736 adaa_previous_antiderivative_ = antiderivative(adaa_previous_);
737 adaa2_.reset(shape_, drive_ * adaa2_history_[0] + bias_,
738 drive_ * adaa2_history_[1] + bias_);
739 }
740
743 double delayed_dry(double input) {
744 const int latency = latency_samples();
745 if (latency <= 0)
746 return input;
747 const std::size_t size = dry_delay_.size();
748 const std::size_t read = (dry_write_ + size - static_cast<std::size_t>(latency)) % size;
749 const double out = dry_valid_ >= static_cast<std::size_t>(latency)
750 ? static_cast<double>(dry_delay_[read])
751 : 0.0;
752 dry_delay_[dry_write_] = static_cast<SampleType>(input);
753 dry_write_ = (dry_write_ + 1) % size;
754 if (dry_valid_ < size) ++dry_valid_;
755 return out;
756 }
757
758 void update_shaper() {
759 drive_ = units::db_to_linear(drive_db_);
760
761 // The cubic's slope reaches zero at |b| = 1, and the normaliser divides
762 // by it. Clamp per shape rather than globally so the other three keep
763 // their full range.
764 const double limit = shape_ == Shape::cubic_soft ? kCubicBiasClamp : kBiasMax;
765 bias_ = std::clamp(bias_request_, -limit, limit);
766
767 bias_offset_ = detail::SaturatorShapeMath::f(shape_, bias_);
768 const double slope = detail::SaturatorShapeMath::df(shape_, bias_);
769 // Guarded, though the per-shape clamp above should make it unreachable:
770 // a zero here would be a silent inf rather than a loud failure.
771 const double safe_slope = slope > 1e-12 ? slope : 1e-12;
772 inv_drive_slope_ = 1.0 / (drive_ * safe_slope);
773 inv_drive_sq_slope_ = 1.0 / (drive_ * drive_ * safe_slope);
774 // Every shape's steepest slope is at the origin and equals 1, so the
775 // largest gain the biased, slope-normalised curve can present is
776 // f'(0)/f'(b) = 1/f'(b). Drive cancels out of the ratio.
777 max_gain_ = 1.0 / safe_slope;
778 reseed_adaa();
779 }
780
781 void update_tone() {
782 tone_active_ = tone_pre_hz_ >= kTonePreHzFloor && pre_boost_db_ > 0.0;
783 if (!tone_active_) return;
784
785 const double de_hz = tone_tracking_ ? tone_pre_hz_
786 : std::max(tone_de_hz_, kTonePreHzFloor);
787 // An RBJ high shelf at −G is the exact algebraic reciprocal of the same
788 // shelf at +G, so a tracked pair cancels to numerical precision rather
789 // than to within a fitted tolerance.
790 pre_filter_.set_coefficients(BiquadT<SampleType>::Type::high_shelf,
791 static_cast<SampleType>(tone_pre_hz_),
792 static_cast<SampleType>(kShelfQ),
793 static_cast<SampleType>(sample_rate_),
794 static_cast<SampleType>(pre_boost_db_));
795 de_filter_.set_coefficients(BiquadT<SampleType>::Type::high_shelf,
796 static_cast<SampleType>(de_hz),
797 static_cast<SampleType>(kShelfQ),
798 static_cast<SampleType>(sample_rate_),
799 static_cast<SampleType>(-pre_boost_db_));
800 }
801
802 double sample_rate_ = 44100.0;
803
804 Shape shape_ = Shape::tanh_soft;
805 AliasPolicy alias_policy_ = AliasPolicy::adaa;
806
807 double drive_db_ = kDriveDbDefault;
808 double bias_request_ = 0.0;
809 double tone_pre_hz_ = 0.0; // off by default; the node's table supplies 3000
810 double tone_de_hz_ = kTonePreHzDefault;
811 double pre_boost_db_ = kPreBoostDbDefault;
812 bool tone_tracking_ = true;
813 bool tone_active_ = false;
814 double mix_ = 1.0;
815 double output_trim_ = 1.0;
816
817 // Derived, recomputed only on a parameter change.
818 double drive_ = 1.0;
819 double bias_ = 0.0;
820 double bias_offset_ = 0.0;
821 double inv_drive_slope_ = 1.0;
822 double inv_drive_sq_slope_ = 1.0;
823 double max_gain_ = 1.0;
824
825 double adaa_previous_ = 0.0;
826 double adaa_previous_antiderivative_ = 0.0;
827 double adaa2_history_[2] = {0.0, 0.0};
828 // The two most recent shaper inputs, whatever the policy; a policy switch
829 // seeds the ADAA paths from them.
830 double input_history_[2] = {0.0, 0.0};
831 SaturatorAdaa2 adaa2_{};
832 BiquadT<SampleType> pre_filter_{};
833 BiquadT<SampleType> de_filter_{};
834 OversamplerT<SampleType> oversampler_{};
835 int oversample_latency_ = 0;
836 // Holds the largest dry alignment any policy needs: the composed x2
837 // standard-quality oversampler's 64 samples.
838 static constexpr std::size_t kDryDelayCapacity = 128;
839 std::array<SampleType, kDryDelayCapacity> dry_delay_{};
840 std::size_t dry_write_ = 0;
841 std::size_t dry_valid_ = 0;
842};
843
846
847} // namespace pulp::signal
void reset()
Definition biquad.hpp:377
@ high_shelf
Definition biquad.hpp:58
Definition oversampling.hpp:78
void set_kind(Kind kind)
Definition oversampling.hpp:121
void reset()
Definition oversampling.hpp:269
Second-order antiderivative antialiasing over one shape, in the curve's own argument domain (Bilbao,...
Definition saturator.hpp:335
double step(SaturatorShape shape, double u)
Consumes the newest argument and returns the antialiased curve value centred one sample back.
Definition saturator.hpp:354
void reset(SaturatorShape shape, double previous, double before)
Seeds the two-sample history: previous is the last argument consumed, before the one ahead of it.
Definition saturator.hpp:345
static constexpr double kTolerance
Relative spacing below which a divided difference is replaced by its series limit.
Definition saturator.hpp:339
The memoryless saturation stage.
Definition saturator.hpp:404
static constexpr double kTonePreHzDefault
Definition saturator.hpp:430
void set_alias_policy(AliasPolicy policy)
Select the alias policy.
Definition saturator.hpp:548
static constexpr double kDriveDbDefault
Definition saturator.hpp:417
void prepare(double sample_rate)
Sizes the oversampler and the dry-path alignment delay. May allocate.
Definition saturator.hpp:465
void set_bias(double normalized)
Bias as a normalised [-1, +1].
Definition saturator.hpp:507
SaturatorT()
Definition saturator.hpp:462
void set_drive_db(double db)
Definition saturator.hpp:497
static constexpr double kShelfQ
Definition saturator.hpp:444
SaturatorShape Shape
Definition saturator.hpp:406
SaturatorAliasPolicy AliasPolicy
Definition saturator.hpp:407
static constexpr double kDriveDbMin
Definition saturator.hpp:415
AliasPolicy alias_policy() const
Definition saturator.hpp:557
double bias() const
The bias actually in force after any shape-specific clamp — exposed so a caller can show the effectiv...
Definition saturator.hpp:515
void reset()
Never allocates; a zero-initialised instance is already in this state.
Definition saturator.hpp:605
void set_output_trim_db(double db)
Definition saturator.hpp:564
static constexpr double kCubicBiasClamp
Definition saturator.hpp:426
void set_tone_tracking(bool tracking)
When true (the default) the de-emphasis corner follows the pre-emphasis corner, so the pair cancels e...
Definition saturator.hpp:527
double group_delay_samples() const noexcept
The wet path's exact delay on the linear part of the curve, in samples: 0.5 for adaa (its two-tap ave...
Definition saturator.hpp:590
static constexpr double kOutputTrimDbMax
Definition saturator.hpp:458
void set_shape(Shape shape)
Definition saturator.hpp:490
double shaped(double x) const
The normalised shaper itself, exposed so a caller (or a test) can evaluate the curve without running ...
Definition saturator.hpp:675
double drive_db() const
Definition saturator.hpp:503
static constexpr double kTonePreHzMax
Definition saturator.hpp:431
static constexpr double kDriveDbMax
Definition saturator.hpp:416
double worst_case_gain() const
The supremum of |shaped(x) / x| over every input, at the current shape and bias — the bound Forge's r...
Definition saturator.hpp:670
void set_tone_pre_hz(double hz)
Pre-emphasis corner in Hz. 0 disables the pre/de pair.
Definition saturator.hpp:518
Shape shape() const
Definition saturator.hpp:495
static constexpr double kBiasMax
Definition saturator.hpp:420
void set_pre_boost_db(double db)
Definition saturator.hpp:539
static constexpr double kPreBoostDbMax
Definition saturator.hpp:440
static constexpr double kAdaaEpsilon
Definition saturator.hpp:455
static constexpr double kTonePreHzFloor
Definition saturator.hpp:435
static constexpr double kPreBoostDbDefault
Definition saturator.hpp:439
SampleType process(SampleType input)
One sample through the full chain: pre-emphasis, alias-safe shaping, de-emphasis, latency-aligned dry...
Definition saturator.hpp:620
int latency_samples() const noexcept
The integer delay the host should compensate, matched on the dry path.
Definition saturator.hpp:574
void set_mix(double wet01)
Definition saturator.hpp:559
void set_tone_de_hz(double hz)
De-emphasis corner, used only when tracking is off.
Definition saturator.hpp:533
@ size
Definition config.hpp:216
std::uint64_t read(std::span< const std::uint8_t > b, std::size_t p, unsigned n) noexcept
Definition spectral_frame_codec.hpp:111
T db_to_linear(T db)
Amplitude dB → linear gain. 0 dB → 1, −6.0206 dB → 0.5.
Definition units.hpp:71
Definition additive_bank.hpp:218
double adaa_linear_magnitude(int order, double cycles_per_sample) noexcept
The linear-path magnitude response of order-th-order antiderivative antialiasing (1 or 2) at cycles_p...
Definition saturator.hpp:304
SaturatorShape
Which member of the waveshaper family a SaturatorT evaluates.
Definition saturator.hpp:136
@ atan_soft
atan(u). Softer knee than tanh at the shoulder.
Definition saturator.hpp:138
@ sinh_arc
asinh(u). Unbounded and sub-linear; never hard-limits.
Definition saturator.hpp:140
@ tanh_soft
tanh(u). The canonical smooth saturator.
Definition saturator.hpp:137
@ cubic_soft
u − u³/3, clipping at |u| ≥ 1. Hardest character.
Definition saturator.hpp:139
SaturatorT< double > Saturator64
Definition saturator.hpp:845
@ c
Definition spectrum_trace.hpp:35
@ off
Definition nonlinear_shaping.hpp:24
@ oversample_2x
Definition nonlinear_shaping.hpp:23
SaturatorAliasPolicy
How a SaturatorT keeps the harmonics it generates from folding back.
Definition saturator.hpp:144
@ adaa
First-order antiderivative antialiasing. Half a sample of delay.
Definition saturator.hpp:145
@ adaa2
Second-order antiderivative antialiasing. One sample of delay.
Definition saturator.hpp:148
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
@ a
Definition formant_filter_bank.hpp:20
@ u
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20
SaturatorT< float > Saturator
Definition saturator.hpp:844
@ x
Definition input_events.hpp:41
@ v
Definition input_events.hpp:41
The waveshaper family's three closed forms per member: the curve f, its derivative f' (needed for the...
Definition saturator.hpp:163
static double antiderivative(SaturatorShape shape, double u)
F1(u) = ∫f(u)du, with an arbitrary constant — ADAA only ever takes differences of it,...
Definition saturator.hpp:195
static double tanh_second_antiderivative(double u)
∫₀ᵘ ln cosh v dv.
Definition saturator.hpp:268
static double df(SaturatorShape shape, double u)
f'(u).
Definition saturator.hpp:180
static double second_antiderivative(SaturatorShape shape, double u)
F2(u) = ∫F1(u)du, the second antiderivative second-order ADAA takes divided differences of.
Definition saturator.hpp:231
static double f(SaturatorShape shape, double u)
f(u).
Definition saturator.hpp:165
The real-unit vocabulary shared by every DSP block in the catalog.