Sound Byte Libs 0.5.1-121-g3358a44
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
critical_damp.hpp
Go to the documentation of this file.
1// sbl/dsp/critical_damp.hpp — Critically damped 2nd-order parameter smoother
2//
3// Two cascaded one-pole filters with identical coefficients, producing a
4// second-order system with repeated real poles. This gives:
5//
6// - No overshoot (critically damped, ζ = 1)
7// - Zero-velocity arrival (smooth settling, no abrupt stop)
8// - Faster initial response than a single one-pole at the same settling time
9//
10// The step response is: y[n] = 1 - (1 + a·n)·(1-a)^n
11//
12// Unlike OnePole, which puts the biggest step at the first sample (exactly
13// where clicks live for nonlinear parameters), CriticalDamp distributes
14// the change more evenly — the second stage smooths the first stage's
15// exponential attack into a gentle S-curve.
16//
17// Primary use: filter coefficient smoothing inside widgets (SVF, Ladder).
18// For simple linear parameters (gain, amplitude), OnePole is sufficient.
19//
20// Inspired by the Mutable Instruments parameter smoothing approach.
21
22#ifndef SBL_DSP_PRIMITIVES_CRITICAL_DAMP_HPP_
23#define SBL_DSP_PRIMITIVES_CRITICAL_DAMP_HPP_
24
25#include <cstdint>
26
28
29namespace sbl::dsp::primitives {
30
32public:
33 /// @note All public methods are ISR-safe — bounded computation, no I/O.
34
35 /**
36 * @brief Set settling time in milliseconds
37 * @param ms Time for 95% settling (0 = instant)
38 * @param rate_hz Update rate in Hz (e.g., 1000 for block-rate, 48000 for audio-rate)
39 *
40 * Computes the per-pole coefficient for a two-stage cascade.
41 * The combined system reaches 95% of a step change in `ms` milliseconds.
42 */
43 void set_time_ms(float ms, float rate_hz) {
44 if (ms <= 0.0f) {
45 coeff_ = 1.0f;
46 return;
47 }
48 float n = ms * rate_hz * 0.001f;
49 if (n < 0.5f) {
50 coeff_ = 1.0f;
51 return;
52 }
53 // k = 4.6 for 95% settling of cascaded 2nd-order system
54 // (vs k = 3.0 for single one-pole)
55 // Derived from: (1 + a·N)·(1-a)^N = 0.05
56 coeff_ = math::one_minus_exp_neg(4.6f / n);
57 }
58
59 /**
60 * @brief Set coefficient directly (advanced use)
61 * @param a Per-pole coefficient [0, 1]
62 */
63 void set_coefficient(float a) { coeff_ = a; }
64
65 /**
66 * @brief Process one sample toward target
67 * @param target The value to approach
68 * @return Smoothed value
69 */
70 float process(float target) {
71 state1_ += coeff_ * (target - state1_);
72 state2_ += coeff_ * (state1_ - state2_);
73 return state2_;
74 }
75
76 /**
77 * @brief Fill buffer with smoothed trajectory toward target
78 * @param buf Output buffer (filled with per-sample smoothed values)
79 * @param frames Number of samples
80 * @param target Target value
81 *
82 * Useful for generating per-sample coefficient ramps inside filter widgets.
83 */
84 void process(float* buf, uint16_t frames, float target) {
85 for (uint16_t i = 0; i < frames; ++i) {
86 buf[i] = process(target);
87 }
88 }
89
90 /** @brief Current output value */
91 float value() const { return state2_; }
92
93 /** @brief Current per-pole coefficient */
94 float coefficient() const { return coeff_; }
95
96 /** @brief Reset to zero */
97 void reset() {
98 state1_ = 0.0f;
99 state2_ = 0.0f;
100 }
101
102 /** @brief Reset to specific value (both stages) */
103 void reset(float val) {
104 state1_ = val;
105 state2_ = val;
106 }
107
108private:
109 float coeff_ = 0.0f;
110 float state1_ = 0.0f;
111 float state2_ = 0.0f;
112};
113
114} // namespace sbl::dsp::primitives
115
116#endif // SBL_DSP_PRIMITIVES_CRITICAL_DAMP_HPP_
float value() const
Current output value.
void set_time_ms(float ms, float rate_hz)
Set settling time in milliseconds.
void set_coefficient(float a)
Set coefficient directly (advanced use)
float process(float target)
Process one sample toward target.
void process(float *buf, uint16_t frames, float target)
Fill buffer with smoothed trajectory toward target.
float coefficient() const
Current per-pole coefficient.
void reset(float val)
Reset to specific value (both stages)
Fast analytical approximations (Audio Stack — Atoms)
constexpr float one_minus_exp_neg(float x)
Stateful, single-concern building blocks.