Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
ladder_filter.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <cmath>
7#include <cstddef>
8#include <algorithm>
9#include <type_traits>
10
11namespace pulp::signal {
12
13// Nonlinear ladder filter (4-pole, 24 dB/octave)
14// Non-linear, self-oscillating at high resonance.
15//
16// RT contract: setters, process paths, and reset are scalar/fixed-array only
17// and allocate no memory.
18//
19// One voice is bound by the latency of its serial stage chain, so separate
20// instances in a voice loop are fast only when the compiler happens to
21// vectorize across them (it may not without FMA contraction). For many voices
22// in lockstep, LadderBankT runs them as explicit SIMD lanes with identical
23// output.
24template <typename SampleType = float>
26public:
27 void set_sample_rate(SampleType sr) { sample_rate_ = sr; }
28 void set_frequency(SampleType hz) { cutoff_ = hz; update(); }
29 void set_resonance(SampleType r) {
30 resonance_ = std::clamp(r, SampleType{0.0f}, SampleType{1.0f});
31 update();
32 }
33
35 SampleType process(SampleType input) {
36 return process_with_profile<kDefaultLadderSaturation>(input);
37 }
38
40 SampleType process_reference(SampleType input) {
41 return process_with_profile<LadderSaturation::exact>(input);
42 }
43
45 SampleType process_realtime_efficient(SampleType input) {
46 return process_with_profile<LadderSaturation::realtime_efficient>(input);
47 }
48
50 void process(SampleType* buffer, int num_samples) {
51 for (int i = 0; i < num_samples; ++i)
52 buffer[i] = process(buffer[i]);
53 }
54
56 void process_reference(SampleType* buffer, std::size_t num_samples) {
57 for (std::size_t i = 0; i < num_samples; ++i)
59 }
60
62 void process_realtime_efficient(SampleType* buffer, std::size_t num_samples) {
63 for (std::size_t i = 0; i < num_samples; ++i)
65 }
66
67 void reset() {
68 for (auto& s : stage_) s = 0;
69 for (auto& s : saturated_stage_) s = 0;
70 }
71
75 static SampleType stage_coefficient(SampleType cutoff_hz, SampleType sample_rate) {
76 return SampleType{1.0f} -
77 std::exp(SampleType{-2.0f} * SampleType{3.14159265f} * cutoff_hz / sample_rate);
78 }
79
85 template <LadderSaturation Saturation> static SampleType saturate(SampleType x) {
86 if constexpr (std::is_same_v<SampleType, float>)
88 else
89 return std::tanh(x);
90 }
91
92 private:
93 template <LadderSaturation Saturation> SampleType process_with_profile(SampleType input) {
94 // Non-linear feedback
95 SampleType feedback =
96 resonance_ * SampleType{4.0f} * (stage_[3] - input * SampleType{0.5f});
97 SampleType x = input - feedback;
98
99 // Cache each snapped stage's saturation. The next stage and next sample
100 // reuse exactly that value, avoiding repeated transcendental calls.
101 SampleType saturated_prev = saturate<Saturation>(x);
102 for (int i = 0; i < 4; ++i) {
103 // Snap each ladder stage: at high resonance the stages self-
104 // oscillate and their tails otherwise decay into denormals with
105 // no FTZ guard. No-op above 1e-15.
106 stage_[i] = snap_to_zero(
107 stage_[i] + g_ * (saturated_prev - saturated_stage_[i]));
108 saturated_stage_[i] = saturate<Saturation>(stage_[i]);
109 saturated_prev = saturated_stage_[i];
110 }
111
112 return stage_[3];
113 }
114
115 SampleType sample_rate_ = SampleType{44100.0f};
116 SampleType cutoff_ = SampleType{1000.0f};
117 SampleType resonance_ = SampleType{0.0f};
118 SampleType g_ = 0;
119 SampleType stage_[4] = {};
120 SampleType saturated_stage_[4] = {};
121
122 void update() {
123 g_ = stage_coefficient(cutoff_, sample_rate_);
124 }
125};
126
129
130} // namespace pulp::signal
Definition ladder_filter.hpp:25
void process_realtime_efficient(SampleType *buffer, std::size_t num_samples)
Process a buffer in place with the Padé FastMath::tanh stages.
Definition ladder_filter.hpp:62
void set_frequency(SampleType hz)
Definition ladder_filter.hpp:28
void process(SampleType *buffer, int num_samples)
Process a buffer in place with the default saturator.
Definition ladder_filter.hpp:50
void reset()
Definition ladder_filter.hpp:67
SampleType process_reference(SampleType input)
Process one sample with exact std::tanh stages.
Definition ladder_filter.hpp:40
void set_sample_rate(SampleType sr)
Definition ladder_filter.hpp:27
void set_resonance(SampleType r)
Definition ladder_filter.hpp:29
SampleType process(SampleType input)
Process one sample with the default saturator (see LadderSaturation).
Definition ladder_filter.hpp:35
SampleType process_realtime_efficient(SampleType input)
Process one sample with the Padé FastMath::tanh stages.
Definition ladder_filter.hpp:45
static SampleType stage_coefficient(SampleType cutoff_hz, SampleType sample_rate)
One-pole stage coefficient for a cutoff and sample rate: g = 1 - exp(-2 pi fc / fs).
Definition ladder_filter.hpp:75
static SampleType saturate(SampleType x)
Saturating non-linearity.
Definition ladder_filter.hpp:85
void process_reference(SampleType *buffer, std::size_t num_samples)
Process a buffer in place with exact std::tanh stages.
Definition ladder_filter.hpp:56
Scalar math helpers for real-time audio DSP.
Definition additive_bank.hpp:218
@ feedback
Definition comb_filter.hpp:18
@ buffer
A borrowed static buffer with a traversable playhead.
Definition granular.hpp:110
LadderFilterT< double > LadderFilter64
Definition ladder_filter.hpp:128
LadderFilterT< float > LadderFilter
Definition ladder_filter.hpp:127
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
@ i
Definition formant_filter_bank.hpp:20
float ladder_tanh_profiled(float x)
The ladder's float saturator for an explicit choice.
Definition fast_math.hpp:668