Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
rng.hpp
Go to the documentation of this file.
1#pragma once
2
19
20#include <algorithm>
21#include <cmath>
22#include <cstdint>
23
24namespace pulp::signal {
25
34public:
37 static constexpr std::uint32_t kDefaultSeed = 0x9E3779B9u;
38
39 constexpr Xorshift32() = default;
40 constexpr explicit Xorshift32(std::uint32_t s)
41 : state_(s == 0u ? kDefaultSeed : s), seed_(state_) {}
42
43 void seed(std::uint32_t s) {
44 seed_ = (s == 0u ? kDefaultSeed : s);
45 state_ = seed_;
46 // The Box-Muller cache holds a deviate from the OLD stream. Leaving it
47 // would make the first gaussian() after a re-seed come from the
48 // sequence the caller just replaced.
49 cached_ = 0.0f;
50 has_cached_ = false;
51 }
52
54 void set_seed(std::uint32_t s) { seed(s); }
55
60 void reset() {
61 state_ = seed_ == 0u ? kDefaultSeed : seed_;
62 cached_ = 0.0f;
63 has_cached_ = false;
64 }
65
66 std::uint32_t next_u32() {
67 std::uint32_t x = state_;
68 if (x == 0u) x = kDefaultSeed;
69 x ^= x << 13;
70 x ^= x >> 17;
71 x ^= x << 5;
72 state_ = x;
73 return x;
74 }
75
77 std::uint32_t next_uint() { return next_u32(); }
78
81 float next_unipolar() {
82 return static_cast<float>(next_u32() >> 8) * (1.0f / 16777216.0f);
83 }
84
86 float next_bipolar() { return next_unipolar() * 2.0f - 1.0f; }
87
88 template <typename T>
90 return static_cast<T>(static_cast<double>(next_u32()) *
91 (1.0 / 2147483648.0) -
92 1.0);
93 }
94
96 float next_unit() { return next_unipolar(); }
97
98 template <typename T>
100 return static_cast<T>(static_cast<double>(next_u32()) *
101 (1.0 / 4294967296.0));
102 }
103
111 float gaussian() {
112 if (has_cached_) {
113 has_cached_ = false;
114 return cached_;
115 }
116 float u1 = next_unipolar();
117 if (u1 <= 0.0f) u1 = 1.0f / 16777216.0f;
118 const float u2 = next_unipolar();
119 const float radius = std::sqrt(-2.0f * std::log(u1));
120 const float angle = 6.2831853071795864769f * u2;
121 cached_ = radius * std::sin(angle);
122 has_cached_ = true;
123 return radius * std::cos(angle);
124 }
125
126 std::uint32_t state() const { return state_ == 0u ? kDefaultSeed : state_; }
127
128 std::uint32_t seed() const { return seed_ == 0u ? kDefaultSeed : seed_; }
129
130private:
131 std::uint32_t state_ = kDefaultSeed;
132 std::uint32_t seed_ = kDefaultSeed;
133 float cached_ = 0.0f;
134 bool has_cached_ = false;
135};
136
140constexpr std::uint64_t mix64(std::uint64_t z) {
141 z ^= z >> 30;
142 z *= 0xBF58476D1CE4E5B9ull;
143 z ^= z >> 27;
144 z *= 0x94D049BB133111EBull;
145 z ^= z >> 31;
146 return z;
147}
148
151constexpr std::uint64_t mix64(std::uint64_t seed,
152 std::uint64_t index,
153 std::uint64_t field = 0u) {
154 return mix64(seed + index * 0x9E3779B97F4A7C15ull +
155 field * 0xBF58476D1CE4E5B9ull);
156}
157
164inline float unit_from(std::uint64_t key) {
165 return static_cast<float>(mix64(key) >> 40) * (1.0f / 16777216.0f);
166}
167
168template <typename T>
169inline T unit_from(std::uint64_t key) {
170 return static_cast<T>(static_cast<double>(mix64(key) >> 11) *
171 (1.0 / 9007199254740992.0));
172}
173
174template <typename T = float>
175inline T bipolar_from(std::uint64_t key) {
176 return static_cast<T>(unit_from<double>(key) * 2.0 - 1.0);
177}
178
180inline constexpr std::uint64_t kGoldenGamma = 0x9E3779B97F4A7C15ull;
181
187constexpr std::uint64_t rng_key(std::uint64_t purpose, std::uint64_t index) {
188 return mix64(purpose * kGoldenGamma + index + kGoldenGamma);
189}
190
213template <typename SampleType = float>
214class OuWalkT {
215public:
219 static constexpr SampleType kClamp = SampleType{1.5};
220 static constexpr double kMinTheta = 1e-6;
221 static constexpr double kMaxTheta = 1.0;
222 static constexpr double kDefaultTheta = 0.05;
223
227 void prepare(double update_rate_hz) {
228 rate_ = update_rate_hz > 0.0 ? update_rate_hz : 1.0;
229 dt_ = 1.0 / rate_;
230 update_coefficients_();
231 }
232
233 void set_theta(double theta_per_second) {
234 if (!std::isfinite(theta_per_second)) return;
235 theta_ = std::clamp(theta_per_second, kMinTheta, kMaxTheta);
236 update_coefficients_();
237 }
238 void set_sigma(double sigma) {
239 if (!std::isfinite(sigma)) return;
240 sigma_ = std::max(0.0, sigma);
241 update_coefficients_();
242 }
243 void set_mu(double mu) {
244 if (std::isfinite(mu)) mu_ = mu;
245 }
246 void seed(std::uint32_t s) { rng_.seed(s); }
247 void set_seed(std::uint32_t s) { seed(s); }
248
249 void set_correlation_time(double seconds, double sample_rate) {
250 if (!(std::isfinite(seconds) && seconds > 0.0 &&
251 std::isfinite(sample_rate) && sample_rate > 0.0))
252 return;
253 prepare(sample_rate);
254 set_theta(1.0 / seconds);
255 }
256
257 void reset() {
258 value_ = mu_;
259 rng_.reset();
260 }
261
263 void reset_value() { value_ = mu_; }
264
265 SampleType next() {
266 // Exact transition: the decay is always in (0, 1), and the innovation
267 // scale preserves the requested stationary deviation at every rate.
268 const double diffusion = innovation_std_ * static_cast<double>(rng_.gaussian());
269 value_ = std::clamp(mu_ + decay_ * (value_ - mu_) + diffusion,
270 -static_cast<double>(kClamp),
271 static_cast<double>(kClamp));
272 return static_cast<SampleType>(value_);
273 }
274
275 SampleType current() const { return static_cast<SampleType>(value_); }
276 SampleType value() const { return current(); }
277 double theta() const { return theta_; }
278 double sigma() const { return sigma_; }
279
282 double stationary_std() const {
283 return sigma_;
284 }
285
286private:
287 void update_coefficients_() {
288 decay_ = std::exp(-theta_ * dt_);
289 // Exact discrete-time OU transition. `sigma_` is the requested
290 // stationary standard deviation, so changing update rate or theta does
291 // not silently change the modulation depth.
292 innovation_std_ = sigma_ * std::sqrt(std::max(0.0, 1.0 - decay_ * decay_));
293 }
294
295 Xorshift32 rng_{};
296 double value_ = 0.0;
297 double mu_ = 0.0;
298 double theta_ = kDefaultTheta;
299 double sigma_ = 0.1;
300 // An unprepared walk advances in unit time. Audio/control-rate users call
301 // `prepare(rate)`, while the direct per-step Round 2 surface can state
302 // theta without inheriting an arbitrary hidden 48 kHz assumption.
303 double rate_ = 1.0;
304 double dt_ = 1.0;
305 double decay_ = std::exp(-kDefaultTheta);
306 double innovation_std_ =
307 0.1 * std::sqrt(1.0 - std::exp(-2.0 * kDefaultTheta));
308};
309
312
332template <typename SampleType = float>
333class DriftT {
334public:
337 static constexpr int kDecimation = 32;
338 static constexpr double kDefaultDepthPercent = 0.1;
339 static constexpr double kDefaultCorrelationSeconds = 2.0;
340
341 void prepare(double sample_rate) {
342 const double sr = sample_rate > 0.0 ? sample_rate : 1.0;
343 const double control_rate = sr / static_cast<double>(kDecimation);
344 pitch_walk_.prepare(control_rate);
345 fraction_walk_.prepare(control_rate);
346 multiplier_walk_.prepare(control_rate);
347 multiplier_walk_.set_theta(1.0 / correlation_s_);
348 multiplier_walk_.set_sigma(depth_percent_ * 0.01);
349 reset();
350 }
351
353 void set_cents(double cents) { cents_ = cents; }
354
355 void set_theta(double theta_per_second) {
356 pitch_walk_.set_theta(theta_per_second);
357 fraction_walk_.set_theta(theta_per_second);
358 }
359
360 void set_sigma(double sigma) {
361 pitch_walk_.set_sigma(sigma);
362 fraction_walk_.set_sigma(sigma);
363 }
364
368 void seed(std::uint32_t s) {
369 pitch_walk_.seed(static_cast<std::uint32_t>(mix64(s) & 0xFFFFFFFFu));
370 fraction_walk_.seed(static_cast<std::uint32_t>(mix64(s ^ 0xA5A5A5A5u) >> 32));
371 multiplier_walk_.seed(static_cast<std::uint32_t>(mix64(s ^ 0xC6BC2796u) & 0xFFFFFFFFu));
372 }
373
374 void set_seed(std::uint32_t s) { seed(s); }
375
376 void set_depth_percent(double percent) {
377 if (!std::isfinite(percent)) return;
378 depth_percent_ = std::max(0.0, percent);
379 multiplier_walk_.set_sigma(depth_percent_ * 0.01);
380 }
381
382 void set_correlation_time(double seconds) {
383 if (!(std::isfinite(seconds) && seconds > 0.0)) return;
384 correlation_s_ = seconds;
385 multiplier_walk_.set_theta(1.0 / correlation_s_);
386 }
387
388 void reset() {
389 pitch_walk_.reset();
390 fraction_walk_.reset();
391 multiplier_walk_.reset();
392 counter_ = 0;
393 pitch_prev_ = pitch_next_ = pitch_walk_.current();
394 fraction_prev_ = fraction_next_ = fraction_walk_.current();
395 multiplier_prev_ = multiplier_next_ = multiplier_walk_.current();
396 pitch_now_ = pitch_prev_;
397 fraction_now_ = fraction_prev_;
398 multiplier_now_ = multiplier_prev_;
399 }
400
402 SampleType next() {
403 if (counter_ == 0) {
404 pitch_prev_ = pitch_next_;
405 fraction_prev_ = fraction_next_;
406 pitch_next_ = pitch_walk_.next();
407 fraction_next_ = fraction_walk_.next();
408 multiplier_prev_ = multiplier_next_;
409 multiplier_next_ = multiplier_walk_.next();
410 }
411 const SampleType t = static_cast<SampleType>(counter_)
412 / static_cast<SampleType>(kDecimation);
413 pitch_now_ = pitch_prev_ + (pitch_next_ - pitch_prev_) * t;
414 fraction_now_ = fraction_prev_ + (fraction_next_ - fraction_prev_) * t;
415 multiplier_now_ = multiplier_prev_ + (multiplier_next_ - multiplier_prev_) * t;
416 if (++counter_ >= kDecimation) counter_ = 0;
417 return static_cast<SampleType>(
418 std::max(kMinMultiplier, 1.0 + static_cast<double>(multiplier_now_)));
419 }
420
422 SampleType pitch_factor() const {
423 return static_cast<SampleType>(
424 std::exp2(cents_ * static_cast<double>(pitch_now_) / 1200.0));
425 }
426
431 SampleType fraction() const {
432 return std::clamp(fraction_now_, SampleType{-1}, SampleType{1});
433 }
434
435private:
436 static constexpr double kMinMultiplier = 1e-3;
437 OuWalkT<SampleType> pitch_walk_{};
438 OuWalkT<SampleType> fraction_walk_{};
439 OuWalkT<SampleType> multiplier_walk_{};
440 double cents_ = 5.0;
441 double depth_percent_ = kDefaultDepthPercent;
442 double correlation_s_ = kDefaultCorrelationSeconds;
443 int counter_ = 0;
444 SampleType pitch_prev_{};
445 SampleType pitch_next_{};
446 SampleType fraction_prev_{};
447 SampleType fraction_next_{};
448 SampleType pitch_now_{};
449 SampleType fraction_now_{};
450 SampleType multiplier_prev_{};
451 SampleType multiplier_next_{};
452 SampleType multiplier_now_{};
453};
454
457
458} // namespace pulp::signal
Two decorrelated OU walks presented as the two things drift is actually wired to: a pitch ratio and a...
Definition rng.hpp:333
SampleType fraction() const
Signed unit drift, independent of the pitch walk.
Definition rng.hpp:431
static constexpr double kDefaultCorrelationSeconds
Definition rng.hpp:339
SampleType next()
Advance one audio sample. Call once per sample; read the outputs after.
Definition rng.hpp:402
void set_cents(double cents)
Peak-ish pitch excursion in cents, scaled by the walk's unit output.
Definition rng.hpp:353
void reset()
Definition rng.hpp:388
void set_seed(std::uint32_t s)
Definition rng.hpp:374
void set_correlation_time(double seconds)
Definition rng.hpp:382
void set_sigma(double sigma)
Definition rng.hpp:360
void set_theta(double theta_per_second)
Definition rng.hpp:355
void prepare(double sample_rate)
Definition rng.hpp:341
void seed(std::uint32_t s)
The two walks are seeded from one user-facing seed through mix64 so they are decorrelated by construc...
Definition rng.hpp:368
static constexpr double kDefaultDepthPercent
Definition rng.hpp:338
void set_depth_percent(double percent)
Definition rng.hpp:376
static constexpr int kDecimation
Definition rng.hpp:337
SampleType pitch_factor() const
Multiplicative pitch ratio: 2 ^ (cents * x / 1200).
Definition rng.hpp:422
Ornstein-Uhlenbeck walk (Uhlenbeck & Ornstein, Phys.
Definition rng.hpp:214
void set_mu(double mu)
Definition rng.hpp:243
void set_correlation_time(double seconds, double sample_rate)
Definition rng.hpp:249
static constexpr float kClamp
Definition rng.hpp:219
static constexpr double kDefaultTheta
Definition rng.hpp:222
double sigma() const
Definition rng.hpp:278
void reset()
Definition rng.hpp:257
double stationary_std() const
Closed-form stationary standard deviation, for tests and for callers sizing a modulation depth from a...
Definition rng.hpp:282
static constexpr double kMinTheta
Definition rng.hpp:220
SampleType next()
Definition rng.hpp:265
void set_sigma(double sigma)
Definition rng.hpp:238
void set_theta(double theta_per_second)
Definition rng.hpp:233
void set_seed(std::uint32_t s)
Definition rng.hpp:247
void reset_value()
Reset to the mean without disturbing the stream position.
Definition rng.hpp:263
SampleType current() const
Definition rng.hpp:275
void seed(std::uint32_t s)
Definition rng.hpp:246
SampleType value() const
Definition rng.hpp:276
double theta() const
Definition rng.hpp:277
static constexpr double kMaxTheta
Definition rng.hpp:221
void prepare(double update_rate_hz)
Definition rng.hpp:227
std::uint32_t next_u32()
Definition rng.hpp:66
float next_bipolar()
Uniform in [-1, 1).
Definition rng.hpp:86
T next_bipolar()
Definition rng.hpp:89
constexpr Xorshift32(std::uint32_t s)
Definition rng.hpp:40
void seed(std::uint32_t s)
Definition rng.hpp:43
std::uint32_t seed() const
Definition rng.hpp:128
void reset()
Rewind to the seeded stream position.
Definition rng.hpp:60
float next_unit()
Uniform in [0, 1), with the caller-selected result precision.
Definition rng.hpp:96
float next_unipolar()
Uniform in [0, 1).
Definition rng.hpp:81
static constexpr std::uint32_t kDefaultSeed
Any nonzero constant works; this is the 32-bit golden-ratio odd constant, which is also what seed(0) ...
Definition rng.hpp:37
std::uint32_t state() const
Definition rng.hpp:126
std::uint32_t next_uint()
Compatibility spelling used by the Round 2 DSP blocks.
Definition rng.hpp:77
float gaussian()
Standard normal via the polar-free Box-Muller transform (Box & Muller, Ann.
Definition rng.hpp:111
T next_unit()
Definition rng.hpp:99
void set_seed(std::uint32_t s)
Compatibility spelling used by the Round 2 DSP blocks.
Definition rng.hpp:54
constexpr Xorshift32()=default
Definition additive_bank.hpp:218
constexpr std::uint64_t kGoldenGamma
The 64-bit golden-ratio odd constant that splitmix64 steps its state by.
Definition rng.hpp:180
OuWalkT< float > OuWalk
Definition rng.hpp:310
constexpr std::uint64_t mix64(std::uint64_t z)
splitmix64 finalizer (Steele, Lea & Flood, OOPSLA 2014).
Definition rng.hpp:140
constexpr std::uint64_t rng_key(std::uint64_t purpose, std::uint64_t index)
Combine a purpose tag and an index into a key for unit_from().
Definition rng.hpp:187
DriftT< double > Drift64
Definition rng.hpp:456
DriftT< float > Drift
Definition rng.hpp:455
@ u
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20
OuWalkT< double > OuWalk64
Definition rng.hpp:311
T bipolar_from(std::uint64_t key)
Definition rng.hpp:175
float unit_from(std::uint64_t key)
Stateless purpose-keyed draw in [0, 1).
Definition rng.hpp:164