38inline constexpr double kPi = 3.14159265358979323846;
66 explicit Xorshift32(std::uint32_t seed = 74207u) : state_(seed | 1u), seed_(seed | 1u) {}
68 void reseed(std::uint32_t seed)
noexcept {
79 std::uint32_t
next() noexcept {
80 state_ ^= state_ << 13;
81 state_ ^= state_ >> 17;
82 state_ ^= state_ << 5;
88 return static_cast<double>(
next()) /
static_cast<double>(UINT32_MAX);
100 gauss_valid_ =
false;
104 if (u1 <= 0.0) u1 = 1.0 /
static_cast<double>(UINT32_MAX);
106 const double r = std::sqrt(-2.0 * std::log(u1));
107 const double a = 2.0 *
kPi * u2;
108 gauss_spare_ = r * std::sin(
a);
110 return r * std::cos(
a);
114 std::uint32_t state_;
116 double gauss_spare_ = 0.0;
117 bool gauss_valid_ =
false;
124template <std::
size_t N>
126 const std::array<double, N>& values,
double c)
noexcept {
127 static_assert(N >= 2,
"a knot table needs at least two knots");
128 if (
c <= axis[0])
return values[0];
129 for (std::size_t
i = 1;
i < N; ++
i) {
131 const double span = axis[
i] - axis[
i - 1];
132 const double t = span > 0.0 ? (
c - axis[
i - 1]) / span : 0.0;
133 return values[
i - 1] + t * (values[
i] - values[
i - 1]);
136 return values[N - 1];
145 void configure(
double time_constant_s,
double fs)
noexcept {
146 coefficient_ = (time_constant_s <= 0.0 || fs <= 0.0)
148 : std::exp(-1.0 / (time_constant_s * fs));
151 void snap(
double v)
noexcept { current_ = v; }
152 double current() const noexcept {
return current_; }
155 current_ = target + coefficient_ * (current_ - target);
160 double coefficient_ = 0.0;
161 double current_ = 0.0;
181 capacity_ = std::max<std::size_t>(capacity_samples, 8u);
182 buffer_.assign(capacity_, 0.0f);
188 std::fill(buffer_.begin(), buffer_.end(), 0.0f);
196 std::size_t
capacity() const noexcept {
return capacity_; }
201 return capacity_ > 4 ?
static_cast<double>(capacity_ - 4) : 1.0;
205 buffer_[write_] =
static_cast<float>(x);
206 if (++write_ >= capacity_) write_ = 0;
207 if (valid_ < capacity_) ++valid_;
211 double tap(std::size_t m)
const noexcept {
212 if (m >= valid_)
return 0.0;
213 const std::size_t index = (write_ + capacity_ - 1u - m) % capacity_;
214 return static_cast<double>(buffer_[index]);
217 double read(
double delay_samples)
const noexcept {
218 const double d = std::clamp(delay_samples, 1.0,
max_delay());
219 const auto integer =
static_cast<std::size_t
>(d);
220 const double frac = d -
static_cast<double>(integer);
222 const double s0 =
tap(integer - 1u);
223 const double s1 =
tap(integer);
224 const double s2 =
tap(integer + 1u);
225 const double s3 =
tap(integer + 2u);
227 const double d0 = frac + 1.0;
228 const double d1 = frac;
229 const double d2 = frac - 1.0;
230 const double d3 = frac - 2.0;
232 return (-d1 * d2 * d3 / 6.0) * s0 + (d0 * d2 * d3 * 0.5) * s1 +
233 (-d0 * d1 * d3 * 0.5) * s2 + (d0 * d1 * d2 / 6.0) * s3;
237 std::vector<float> buffer_;
238 std::size_t capacity_ = 8;
239 std::size_t write_ = 0;
240 std::size_t valid_ = 0;
249 const double nyquist_guard = 0.49 * fs;
250 const double fc = std::clamp(cutoff_hz, 0.1, nyquist_guard);
251 const double g = std::tan(
kPi * fc / fs);
252 gain_ = g / (1.0 + g);
255 void reset() noexcept { state_ = 0.0; }
258 const double v = (x - state_) * gain_;
259 const double y = v + state_;
281 void set(
double corner_hz,
double gain_linear,
double fs)
noexcept {
283 pole_.set_cutoff(corner_hz, fs);
287 void set_inverse(
double corner_hz,
double gain_linear,
double fs)
noexcept {
288 const double g = (gain_linear > 1
e-9) ? gain_linear : 1
e-9;
289 set(corner_hz / g, 1.0 / g, fs);
292 void reset() noexcept { pole_.reset(); }
295 const double low = pole_.lowpass(x);
296 return low + gain_ * (x - low);
310 void set_lowpass(
double cutoff_hz,
double q,
double fs)
noexcept {
311 configure(std::tan(
kPi * clamp_cutoff(cutoff_hz, fs) / fs), 1.0 / std::max(q, 1
e-4));
318 configure(std::tan(
kPi * clamp_cutoff(cutoff_hz, fs) / fs), 1.0 / std::max(q, 1
e-4));
325 void set_bell(
double cutoff_hz,
double q,
double gain_db,
double fs)
noexcept {
326 const double a = std::pow(10.0, gain_db / 40.0);
327 configure(std::tan(
kPi * clamp_cutoff(cutoff_hz, fs) / fs),
328 1.0 / (std::max(q, 1
e-4) *
a));
330 m1_ = k_ * (
a *
a - 1.0);
340 const double v3 = v0 - ic2_;
341 const double v1 = a1_ * ic1_ + a2_ *
v3;
342 const double v2 = ic2_ + a2_ * ic1_ + a3_ *
v3;
345 return m0_ * v0 + m1_ *
v1 + m2_ *
v2;
349 static double clamp_cutoff(
double cutoff_hz,
double fs)
noexcept {
350 return std::clamp(cutoff_hz, 1.0, 0.49 * fs);
353 void configure(
double g,
double k)
noexcept {
356 a1_ = 1.0 / (1.0 + g * (g + k));
361 double g_ = 0.0, k_ = 1.0;
362 double a1_ = 1.0, a2_ = 0.0, a3_ = 0.0;
363 double m0_ = 0.0, m1_ = 0.0, m2_ = 1.0;
364 double ic1_ = 0.0, ic2_ = 0.0;
374 static constexpr std::array<double, 3>
kSectionQ = {0.5176380902, 0.7071067812,
378 for (std::size_t
i = 0;
i < sections_.size(); ++
i)
379 sections_[
i].set_lowpass(cutoff_hz,
kSectionQ[
i], fs);
383 for (
auto& s : sections_) s.reset();
387 for (
auto& s : sections_) x = s.process(x);
392 std::array<Svf2, 3> sections_;
407 capacity_ = std::max<std::size_t>(max_taps, 1u);
408 history_.assign(capacity_, 0.0);
414 std::fill(history_.begin(), history_.end(), 0.0);
423 history_[write_] = x;
424 if (++write_ >= capacity_) write_ = 0;
425 if (valid_ < capacity_) ++valid_;
428 double convolve(
const double* coefficients, std::size_t taps)
const noexcept {
429 const std::size_t n = std::min({taps, capacity_, valid_});
431 std::size_t index = write_;
432 for (std::size_t
i = 0;
i < n; ++
i) {
433 index = (index == 0) ? capacity_ - 1u : index - 1u;
434 sum += coefficients[
i] * history_[index];
440 std::vector<double> history_;
441 std::size_t capacity_ = 1;
442 std::size_t write_ = 0;
443 std::size_t valid_ = 0;
451 void configure(
double attack_s,
double release_s,
double rate_hz)
noexcept {
452 attack_ = (attack_s <= 0.0) ? 0.0 : std::exp(-1.0 / (attack_s * rate_hz));
453 release_ = (release_s <= 0.0) ? 0.0 : std::exp(-1.0 / (release_s * rate_hz));
457 double value() const noexcept {
return env_; }
460 const double c = (magnitude > env_) ? attack_ : release_;
461 env_ = magnitude +
c * (env_ - magnitude);
466 double attack_ = 0.0;
467 double release_ = 0.0;
Definition primitives.hpp:372
double process(double x) noexcept
Definition primitives.hpp:386
static constexpr std::array< double, 3 > kSectionQ
Definition primitives.hpp:374
void set_cutoff(double cutoff_hz, double fs) noexcept
Definition primitives.hpp:377
void reset() noexcept
Definition primitives.hpp:382
Definition primitives.hpp:449
double process(double magnitude) noexcept
Definition primitives.hpp:459
void reset(double value=0.0) noexcept
Definition primitives.hpp:456
double value() const noexcept
Definition primitives.hpp:457
void configure(double attack_s, double release_s, double rate_hz) noexcept
Definition primitives.hpp:451
Definition primitives.hpp:279
double process(double x) noexcept
Definition primitives.hpp:294
void set_inverse(double corner_hz, double gain_linear, double fs) noexcept
Configure as the exact inverse of set(corner_hz, gain_linear, fs).
Definition primitives.hpp:287
void set(double corner_hz, double gain_linear, double fs) noexcept
Definition primitives.hpp:281
void reset() noexcept
Definition primitives.hpp:292
Definition primitives.hpp:403
double convolve(const double *coefficients, std::size_t taps) const noexcept
Definition primitives.hpp:428
void reset() noexcept
Definition primitives.hpp:413
void push(double x) noexcept
Definition primitives.hpp:422
void discard_history() noexcept
Logically clear history without walking the allocation.
Definition primitives.hpp:420
void prepare(std::size_t max_taps)
Allocates. Control thread only.
Definition primitives.hpp:406
Definition primitives.hpp:177
std::size_t capacity() const noexcept
Definition primitives.hpp:196
void push(double x) noexcept
Definition primitives.hpp:204
void reset() noexcept
Definition primitives.hpp:187
void discard_history() noexcept
Logically clear history without walking the allocation.
Definition primitives.hpp:194
double read(double delay_samples) const noexcept
Definition primitives.hpp:217
void prepare(std::size_t capacity_samples)
Allocates. Control thread only.
Definition primitives.hpp:180
double tap(std::size_t m) const noexcept
Sample m samples in the past; m = 0 is the most recent push().
Definition primitives.hpp:211
double max_delay() const noexcept
Largest delay the Lagrange kernel can address without walking off the buffer (the top three slots are...
Definition primitives.hpp:200
Definition primitives.hpp:246
double highpass(double x) noexcept
Definition primitives.hpp:264
double lowpass(double x) noexcept
Definition primitives.hpp:257
void set_cutoff(double cutoff_hz, double fs) noexcept
Definition primitives.hpp:248
void reset() noexcept
Definition primitives.hpp:255
Definition primitives.hpp:143
void configure(double time_constant_s, double fs) noexcept
Definition primitives.hpp:145
double process(double target) noexcept
Definition primitives.hpp:154
double current() const noexcept
Definition primitives.hpp:152
void snap(double v) noexcept
Definition primitives.hpp:151
Definition primitives.hpp:308
double process(double v0) noexcept
Definition primitives.hpp:339
void set_bell(double cutoff_hz, double q, double gain_db, double fs) noexcept
Peaking ("bell") section: gain_db at cutoff_hz, unity far away.
Definition primitives.hpp:325
void set_lowpass(double cutoff_hz, double q, double fs) noexcept
Definition primitives.hpp:310
void set_highpass(double cutoff_hz, double q, double fs) noexcept
Definition primitives.hpp:317
void reset() noexcept
Definition primitives.hpp:334
Xorshift32(std::uint32_t seed=74207u)
Definition primitives.hpp:66
void reset() noexcept
Definition primitives.hpp:74
double gaussian() noexcept
Standard normal via Box-Muller (Box & Muller 1958).
Definition primitives.hpp:98
double bipolar() noexcept
Uniform in [-1, 1].
Definition primitives.hpp:92
double uniform() noexcept
Uniform in [0, 1].
Definition primitives.hpp:87
void reseed(std::uint32_t seed) noexcept
Definition primitives.hpp:68
std::uint32_t next() noexcept
Definition primitives.hpp:79
Scalar math helpers for real-time audio DSP.
double flush_denormal(double v) noexcept
Definition primitives.hpp:45
constexpr double kPi
Definition primitives.hpp:38
double fast_tanh(double v) noexcept
Definition primitives.hpp:57
double interpolate_knots(const std::array< double, N > &axis, const std::array< double, N > &values, double c) noexcept
Definition primitives.hpp:125
constexpr double kDenormalFloor
Definition primitives.hpp:43
double lipschitz_tanh(double v) noexcept
A [3/2] Padé tanh that is 1-LIPSCHITZ and bounded — the saturator to reach for inside a feedback loop...
Definition fast_math.hpp:710
@ c
Definition spectrum_trace.hpp:35
@ 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
@ a
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20