DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
Loading...
Searching...
No Matches
AnalogRandom.h
Go to the documentation of this file.
1// DSPark -- Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi -- MIT License
3
4#pragma once
5
24#include <algorithm>
25#include <array>
26#include <atomic>
27#include <chrono>
28#include <cmath>
29#include <cstdint>
30#include <span>
31#include <type_traits>
32
33#include "AnalogConstants.h"
34
39namespace dspark
40{
45 namespace AnalogRandom
46 {
47 //==============================================================================
48 // PRNG and Internal Details
49 // (Reference constants live in Core/AnalogConstants.h, included above.)
50 //==============================================================================
51 namespace Detail
52 {
58 {
59 std::uint64_t s[4];
60
61 explicit Xoshiro256pp(std::uint64_t seed = 1) noexcept
62 {
63 reseed(seed);
64 }
65
66 void reseed(std::uint64_t seed) noexcept
67 {
68 if (seed == 0) seed = 1;
69 s[0] = splitmix64(seed + 0x9E3779B97F4A7C15ull);
70 s[1] = splitmix64(s[0]);
71 s[2] = splitmix64(s[1]);
72 s[3] = splitmix64(s[2]);
73 }
74
75 [[nodiscard]] std::uint64_t next() noexcept
76 {
77 const std::uint64_t result = rotl(s[0] + s[3], 23) + s[0];
78 const std::uint64_t t = s[1] << 17;
79 s[2] ^= s[0];
80 s[3] ^= s[1];
81 s[1] ^= s[2];
82 s[0] ^= s[3];
83 s[2] ^= t;
84 s[3] = rotl(s[3], 45);
85 return result;
86 }
87
88 [[nodiscard]] double next_double() noexcept
89 {
90 const std::uint64_t x = next();
91 constexpr double inv2pow53 = 1.0 / 9007199254740992.0;
92 return static_cast<double>(x >> 11) * inv2pow53;
93 }
94
95 private:
96 static std::uint64_t rotl(std::uint64_t x, int k) noexcept
97 {
98 return (x << k) | (x >> (64 - k));
99 }
100
101 static std::uint64_t splitmix64(std::uint64_t x) noexcept
102 {
103 x += 0x9E3779B97F4A7C15ull;
104 x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ull;
105 x = (x ^ (x >> 27)) * 0x94D049BB133111EBull;
106 return x ^ (x >> 31);
107 }
108 };
109
110 // NOTE: lock-free safety is checked at runtime per Generator instance
111 // (checkLockFree() -> isSafeToRun_). A preprocessor #warning here would
112 // fire on every translation unit unconditionally (the directive ignores
113 // the runtime `if`), so it is intentionally omitted.
114 }
115
116 //==============================================================================
117 // Public API
118 //==============================================================================
119
124 enum class NoiseType
125 {
126 Pink,
127 Brown,
128 White
129 };
130
137
142
148 template <typename Real = float>
150 {
151 static_assert(std::is_floating_point_v<Real>,
152 "AnalogRandom::Generator requires a floating-point Real type.");
153
154 public:
155 Generator() noexcept : prng_(generateUniqueSeed())
156 {
157 checkLockFree();
158 }
159
160 explicit Generator(std::uint64_t seed) noexcept : prng_(seed)
161 {
162 checkLockFree();
163 }
164
165 // std::atomic members are not move-constructible by default, but we
166 // own this Generator's state and need it to be storable in standard
167 // containers (e.g. std::vector). The atomic loads/stores below are
168 // safe because the source object is being moved-from; by contract
169 // it is not concurrently observed by any other thread.
170 Generator(Generator&& other) noexcept
171 : isSafeToRun_(other.isSafeToRun_),
172 prng_(other.prng_),
173 sampleRate_(other.sampleRate_),
174 phaseAccumulator_(other.phaseAccumulator_),
175 triggerNext_(other.triggerNext_.load(std::memory_order_relaxed)),
176 currentValue_(other.currentValue_),
177 targetValue_(other.targetValue_),
178 noiseType_(other.noiseType_.load(std::memory_order_relaxed)),
179 useBpmSync_(other.useBpmSync_.load(std::memory_order_relaxed)),
180 rateHz_(other.rateHz_.load(std::memory_order_relaxed)),
181 bpm_(other.bpm_.load(std::memory_order_relaxed)),
182 bpmDivision_(other.bpmDivision_.load(std::memory_order_relaxed)),
183 min_(other.min_.load(std::memory_order_relaxed)),
184 max_(other.max_.load(std::memory_order_relaxed)),
185 smoothingEnabled_(other.smoothingEnabled_.load(std::memory_order_relaxed)),
186 smoothingCoeff_(other.smoothingCoeff_.load(std::memory_order_relaxed)),
187 quantizationStep_(other.quantizationStep_.load(std::memory_order_relaxed)),
188 pendingSeed_(other.pendingSeed_.load(std::memory_order_relaxed)),
189 brownNoiseState_(other.brownNoiseState_),
190 pinkNoiseOctaves_(other.pinkNoiseOctaves_),
191 denormalFlip_(other.denormalFlip_)
192 {
193 }
194
195 Generator& operator=(Generator&& other) noexcept
196 {
197 if (this == &other) return *this;
198 isSafeToRun_ = other.isSafeToRun_;
199 prng_ = other.prng_;
200 sampleRate_ = other.sampleRate_;
201 phaseAccumulator_ = other.phaseAccumulator_;
202 triggerNext_.store(other.triggerNext_.load(std::memory_order_relaxed), std::memory_order_relaxed);
203 currentValue_ = other.currentValue_;
204 targetValue_ = other.targetValue_;
205 noiseType_.store (other.noiseType_.load(std::memory_order_relaxed), std::memory_order_relaxed);
206 useBpmSync_.store (other.useBpmSync_.load(std::memory_order_relaxed), std::memory_order_relaxed);
207 rateHz_.store (other.rateHz_.load(std::memory_order_relaxed), std::memory_order_relaxed);
208 bpm_.store (other.bpm_.load(std::memory_order_relaxed), std::memory_order_relaxed);
209 bpmDivision_.store(other.bpmDivision_.load(std::memory_order_relaxed), std::memory_order_relaxed);
210 min_.store (other.min_.load(std::memory_order_relaxed), std::memory_order_relaxed);
211 max_.store (other.max_.load(std::memory_order_relaxed), std::memory_order_relaxed);
212 smoothingEnabled_.store(other.smoothingEnabled_.load(std::memory_order_relaxed), std::memory_order_relaxed);
213 smoothingCoeff_.store (other.smoothingCoeff_.load(std::memory_order_relaxed), std::memory_order_relaxed);
214 quantizationStep_.store(other.quantizationStep_.load(std::memory_order_relaxed), std::memory_order_relaxed);
215 pendingSeed_.store(other.pendingSeed_.load(std::memory_order_relaxed), std::memory_order_relaxed);
216 brownNoiseState_ = other.brownNoiseState_;
217 pinkNoiseOctaves_ = other.pinkNoiseOctaves_;
218 denormalFlip_ = other.denormalFlip_;
219 return *this;
220 }
221
222 // Copy operations stay deleted: the atomics make a meaningful copy
223 // semantically ambiguous (concurrent observers of the source) and
224 // we don't need them.
225 Generator(const Generator&) = delete;
226 Generator& operator=(const Generator&) = delete;
227
232 void prepare(double sampleRate) noexcept
233 {
234 if (sampleRate > 0.0) sampleRate_ = sampleRate;
235 reset();
236 }
237
241 void reset() noexcept
242 {
243 phaseAccumulator_ = 0.0;
244 triggerNext_.store(true, std::memory_order_relaxed);
245 currentValue_ = static_cast<Real>(0);
246 targetValue_ = static_cast<Real>(0);
247 brownNoiseState_ = static_cast<Real>(0);
248 pinkNoiseOctaves_.fill(static_cast<Real>(0));
249 }
250
255 void reseed(std::uint64_t newSeed) noexcept
256 {
257 if (newSeed == 0) newSeed = 1;
258 pendingSeed_.store(newSeed, std::memory_order_release);
259 }
260
261 //----------------------------------------------------------------------
262 // Main audio API
263 //----------------------------------------------------------------------
264
270 [[nodiscard]] Real getNextSample() noexcept
271 {
272 if (!isSafeToRun_) [[unlikely]] return static_cast<Real>(0);
273
274 const auto pending = pendingSeed_.exchange(0, std::memory_order_acq_rel);
275 if (pending != 0)
276 {
277 prng_.reseed(pending);
278 reset();
279 }
280
281 const Real continuousNoise = tickContinuousNoise();
282 updatePhase();
283
284 if (triggerNext_.exchange(false, std::memory_order_acquire))
285 {
286 generateNewTarget(continuousNoise);
287 }
288
289 if (smoothingEnabled_.load(std::memory_order_relaxed))
290 {
291 const Real coeff = smoothingCoeff_.load(std::memory_order_relaxed);
292 currentValue_ += coeff * (targetValue_ - currentValue_);
293 }
294 else
295 {
296 currentValue_ = targetValue_;
297 }
298
299 // Professional DC-free denormal mitigation
300 denormalFlip_ = -denormalFlip_;
301 currentValue_ += denormalFlip_;
302
303 return currentValue_;
304 }
305
311 void getNextBlock(std::span<Real> outputBuffer) noexcept
312 {
313 if (!isSafeToRun_ || outputBuffer.empty()) [[unlikely]] return;
314
315 const auto pending = pendingSeed_.exchange(0, std::memory_order_acq_rel);
316 if (pending != 0)
317 {
318 prng_.reseed(pending);
319 reset();
320 }
321
322 // Cache atomics to local registers for the block duration
323 const bool smoothing = smoothingEnabled_.load(std::memory_order_relaxed);
324 const Real coeff = smoothingCoeff_.load(std::memory_order_relaxed);
325 const NoiseType noiseType = noiseType_.load(std::memory_order_relaxed);
326 const Real quantStep = quantizationStep_.load(std::memory_order_relaxed);
327 Real currentMin = min_.load(std::memory_order_relaxed);
328 Real currentMax = max_.load(std::memory_order_relaxed);
329
330 if (currentMin > currentMax) std::swap(currentMin, currentMax);
331
332 for (Real& sample : outputBuffer)
333 {
334 const Real white = static_cast<Real>(prng_.next_double() * 2.0 - 1.0);
335 Real continuousNoise = white;
336
337 if (noiseType == NoiseType::Pink) continuousNoise = tickPinkNoise(white);
338 else if (noiseType == NoiseType::Brown) continuousNoise = tickBrownNoise(white);
339
340 updatePhase();
341
342 if (triggerNext_.exchange(false, std::memory_order_acquire))
343 {
344 // Clamp to [-1,1] before mapping, matching generateNewTarget()
345 // (pink noise can momentarily exceed unity).
346 const Real cn = std::clamp(continuousNoise, static_cast<Real>(-1), static_cast<Real>(1));
347 targetValue_ = currentMin + ((cn * static_cast<Real>(0.5)) + static_cast<Real>(0.5)) * (currentMax - currentMin);
348 if (quantStep > static_cast<Real>(0)) targetValue_ = std::round(targetValue_ / quantStep) * quantStep;
349 }
350
351 if (smoothing)
352 {
353 currentValue_ += coeff * (targetValue_ - currentValue_);
354 }
355 else
356 {
357 currentValue_ = targetValue_;
358 }
359
360 denormalFlip_ = -denormalFlip_;
361 currentValue_ += denormalFlip_;
362
363 sample = currentValue_;
364 }
365 }
366
367 [[nodiscard]] Real getCurrentValue() const noexcept { return currentValue_; }
368 [[nodiscard]] Real getPhase() const noexcept { return static_cast<Real>(phaseAccumulator_); }
369
370 //----------------------------------------------------------------------
371 // Configuration API
372 //----------------------------------------------------------------------
373
374 void setNoiseType(NoiseType type) noexcept
375 {
376 noiseType_.store(type, std::memory_order_relaxed);
377 }
378
379 void setRateHz(Real rateInHz) noexcept
380 {
381 useBpmSync_.store(false, std::memory_order_relaxed);
382 rateHz_.store(static_cast<float>(rateInHz), std::memory_order_relaxed);
383 }
384
385 void setRateBPM(double bpm, BpmDivision division) noexcept
386 {
387 bpm_.store(static_cast<float>(bpm), std::memory_order_relaxed);
388 bpmDivision_.store(division, std::memory_order_relaxed);
389 useBpmSync_.store(true, std::memory_order_relaxed);
390 }
391
392 void updateBPM(double newBpm) noexcept
393 {
394 bpm_.store(static_cast<float>(newBpm), std::memory_order_relaxed);
395 }
396
400 template <typename T>
401 void setRange(T min, T max) noexcept
402 {
403 static_assert(std::is_floating_point_v<T>, "setRange only accepts floating-point types.");
404 if (min > max) std::swap(min, max);
405 min_.store(static_cast<Real>(min), std::memory_order_relaxed);
406 max_.store(static_cast<Real>(max), std::memory_order_relaxed);
407 }
408
409 void setSmoothing(bool shouldBeEnabled, Real timeInMs = static_cast<Real>(50.0)) noexcept
410 {
411 smoothingEnabled_.store(shouldBeEnabled, std::memory_order_relaxed);
412 if (sampleRate_ > 0 && timeInMs > static_cast<Real>(0))
413 {
414 const double coeff = std::exp(-1.0 / (sampleRate_ * (static_cast<double>(timeInMs) / 1000.0)));
415 smoothingCoeff_.store(static_cast<Real>(1.0 - coeff), std::memory_order_relaxed);
416 }
417 else
418 {
419 // A zero (or negative) smoothing time means instantaneous.
420 // Keeping the previous coefficient here (possibly the
421 // initial 0) would silently freeze the output short of
422 // every new target while smoothing is enabled.
423 smoothingCoeff_.store(static_cast<Real>(1), std::memory_order_relaxed);
424 }
425 }
426
427 void setQuantization(Real step) noexcept
428 {
429 if (std::isnan(step) || step < static_cast<Real>(0)) step = static_cast<Real>(0);
430 quantizationStep_.store(step, std::memory_order_relaxed);
431 }
432
433 void setAnalogDefault(AnalogComponent component) noexcept
434 {
435 switch (component)
436 {
440 setRateHz(0.5f);
441 setSmoothing(true, 100.0f);
442 break;
446 setRateHz(2.0f);
447 setSmoothing(true, 50.0f);
448 break;
452 setRateHz(1.0f);
453 setSmoothing(true, 75.0f);
454 break;
458 setRateHz(1.5f);
459 setSmoothing(true, 80.0f);
460 break;
464 setRateHz(0.8f);
465 setSmoothing(true, 120.0f);
466 break;
467 }
468 }
469
470 template <typename Int>
471 [[nodiscard]] Int getNextDiscrete(Int imin, Int imax) noexcept
472 {
473 static_assert(std::is_integral_v<Int>, "getNextDiscrete requires an integral type.");
474 if (imax <= imin) return imin;
475 const Real value = getNextSample();
476
477 Real minVal = min_.load(std::memory_order_relaxed);
478 Real maxVal = max_.load(std::memory_order_relaxed);
479 if (minVal > maxVal) std::swap(minVal, maxVal);
480 if (maxVal == minVal) return imin;
481
482 const double t = static_cast<double>((value - minVal) / (maxVal - minVal));
483 const double mapped = t * static_cast<double>(imax - imin) + static_cast<double>(imin);
484 // llround/long long, not lround/long: on LLP64 platforms
485 // (Windows) long is 32-bit and would truncate 64-bit Int.
486 return static_cast<Int>(std::clamp(std::llround(mapped),
487 static_cast<long long>(imin),
488 static_cast<long long>(imax)));
489 }
490
491 [[nodiscard]] int getNextDiscreteInt(int imin, int imax) noexcept
492 {
493 return static_cast<int>(getNextDiscrete<int>(imin, imax));
494 }
495
496 private:
497 void checkLockFree() noexcept
498 {
499 // Guard against the atomics this class actually uses. On every
500 // supported platform these are all lock-free; a hypothetical
501 // platform where they are not degrades to silence instead of
502 // risking a blocking atomic on the audio thread.
503 isSafeToRun_ = std::atomic<Real>::is_always_lock_free
504 && std::atomic<float>::is_always_lock_free
505 && std::atomic<std::uint64_t>::is_always_lock_free
506 && std::atomic<NoiseType>::is_always_lock_free
507 && std::atomic<BpmDivision>::is_always_lock_free
508 && std::atomic<bool>::is_always_lock_free;
509 }
510
514 [[nodiscard]] Real tickContinuousNoise() noexcept
515 {
516 const Real white = static_cast<Real>(prng_.next_double() * 2.0 - 1.0);
517 switch (noiseType_.load(std::memory_order_relaxed))
518 {
519 case NoiseType::White: return white;
520 case NoiseType::Pink: return tickPinkNoise(white);
521 case NoiseType::Brown: return tickBrownNoise(white);
522 default: return white;
523 }
524 }
525
529 void generateNewTarget(Real sampledNoise) noexcept
530 {
531 sampledNoise = std::clamp(sampledNoise, static_cast<Real>(-1), static_cast<Real>(1));
532
533 Real currentMin = min_.load(std::memory_order_relaxed);
534 Real currentMax = max_.load(std::memory_order_relaxed);
535 if (currentMin > currentMax) std::swap(currentMin, currentMax);
536
537 targetValue_ = currentMin + ((sampledNoise * static_cast<Real>(0.5)) + static_cast<Real>(0.5)) * (currentMax - currentMin);
538
539 const Real quantStep = quantizationStep_.load(std::memory_order_relaxed);
540 if (quantStep > static_cast<Real>(0))
541 {
542 targetValue_ = std::round(targetValue_ / quantStep) * quantStep;
543 }
544 }
545
546 void updatePhase() noexcept
547 {
548 Real rate = static_cast<Real>(0);
549 if (useBpmSync_.load(std::memory_order_relaxed))
550 {
551 const float bpm = bpm_.load(std::memory_order_relaxed);
552 if (bpm > 0.0f)
553 {
554 const double noteLengthInBeats = 4.0 / getBpmDivisionMultiplier(bpmDivision_.load(std::memory_order_relaxed));
555 const double periodInSeconds = (60.0 / static_cast<double>(bpm)) * noteLengthInBeats;
556 if (periodInSeconds > 0.0) rate = static_cast<Real>(1.0 / periodInSeconds);
557 }
558 }
559 else
560 {
561 rate = static_cast<Real>(rateHz_.load(std::memory_order_relaxed));
562 }
563
564 if (rate <= static_cast<Real>(0)) return;
565
566 phaseAccumulator_ += static_cast<double>(rate) / sampleRate_;
567 if (phaseAccumulator_ >= 1.0)
568 {
569 // floor(), not a single -1.0: with rates at or above the
570 // sample rate the fixed decrement would let the
571 // accumulator grow without bound.
572 phaseAccumulator_ -= std::floor(phaseAccumulator_);
573 triggerNext_.store(true, std::memory_order_release);
574 }
575 }
576
577 [[nodiscard]] Real tickPinkNoise(Real white) noexcept
578 {
579 // Paul Kellett's refined 7-state pink-noise filter (public-domain
580 // "instrumentation-grade" variant): parallel one-pole bank whose
581 // summed response tracks -3 dB/oct across the audio band to
582 // within ~0.5 dB. The previous 3-pole truncation measured ~-5
583 // dB/oct (M-003 audit), violating the documented pink slope.
584 Real b0 = pinkNoiseOctaves_[0];
585 Real b1 = pinkNoiseOctaves_[1];
586 Real b2 = pinkNoiseOctaves_[2];
587 Real b3 = pinkNoiseOctaves_[3];
588 Real b4 = pinkNoiseOctaves_[4];
589 Real b5 = pinkNoiseOctaves_[5];
590 Real b6 = pinkNoiseOctaves_[6];
591
592 b0 = static_cast<Real>(0.99886) * b0 + white * static_cast<Real>(0.0555179);
593 b1 = static_cast<Real>(0.99332) * b1 + white * static_cast<Real>(0.0750759);
594 b2 = static_cast<Real>(0.96900) * b2 + white * static_cast<Real>(0.1538520);
595 b3 = static_cast<Real>(0.86650) * b3 + white * static_cast<Real>(0.3104856);
596 b4 = static_cast<Real>(0.55000) * b4 + white * static_cast<Real>(0.5329522);
597 b5 = static_cast<Real>(-0.7616) * b5 - white * static_cast<Real>(0.0168980);
598
599 const Real pink = b0 + b1 + b2 + b3 + b4 + b5 + b6
600 + white * static_cast<Real>(0.5362);
601
602 pinkNoiseOctaves_[0] = b0;
603 pinkNoiseOctaves_[1] = b1;
604 pinkNoiseOctaves_[2] = b2;
605 pinkNoiseOctaves_[3] = b3;
606 pinkNoiseOctaves_[4] = b4;
607 pinkNoiseOctaves_[5] = b5;
608 pinkNoiseOctaves_[6] = white * static_cast<Real>(0.115926);
609
610 // Normalisation keeps the peak below unity for white in [-1,1].
611 // The peak grows with run length; measured worst case ~0.90 over
612 // 2^21 samples (RMS ~0.19), leaving headroom to full scale.
613 return pink * static_cast<Real>(0.11);
614 }
615
616 [[nodiscard]] Real tickBrownNoise(Real white) noexcept
617 {
618 brownNoiseState_ += white * static_cast<Real>(0.02);
619 brownNoiseState_ *= static_cast<Real>(0.995);
620 brownNoiseState_ = std::clamp(brownNoiseState_, static_cast<Real>(-1.0), static_cast<Real>(1.0));
621 return brownNoiseState_;
622 }
623
624 [[nodiscard]] static constexpr double getBpmDivisionMultiplier(BpmDivision division) noexcept
625 {
626 switch (division)
627 {
628 case BpmDivision::One: return 4.0;
629 case BpmDivision::Half: return 2.0;
630 case BpmDivision::Quarter: return 1.0;
631 case BpmDivision::Eighth: return 0.5;
632 case BpmDivision::Sixteenth: return 0.25;
633 case BpmDivision::ThirtySecond: return 0.125;
634 case BpmDivision::HalfTriplet: return 4.0 / 3.0;
635 case BpmDivision::QuarterTriplet: return 2.0 / 3.0;
636 case BpmDivision::EighthTriplet: return 1.0 / 3.0;
637 case BpmDivision::SixteenthTriplet: return 1.0 / 6.0;
638 case BpmDivision::DottedHalf: return 3.0;
639 case BpmDivision::DottedQuarter: return 1.5;
640 case BpmDivision::DottedEighth: return 0.75;
641 default: return 1.0;
642 }
643 }
644
645 [[nodiscard]] static std::uint64_t generateUniqueSeed() noexcept
646 {
647 static std::atomic_uint64_t instanceCounter{ 0 };
648 const auto instanceId = instanceCounter.fetch_add(1, std::memory_order_relaxed);
649 const auto thisPtrAddress = reinterpret_cast<std::uintptr_t>(&instanceCounter);
650 const auto now = static_cast<std::uint64_t>(std::chrono::steady_clock::now().time_since_epoch().count());
651 std::uint64_t seed = instanceId ^ (thisPtrAddress << 1) ^ (now + 0x9E3779B97F4A7C15ull);
652 return seed == 0 ? 1 : seed;
653 }
654
655 bool isSafeToRun_{ false };
656 Detail::Xoshiro256pp prng_;
657 double sampleRate_{ 44100.0 };
658 double phaseAccumulator_{ 0.0 };
659 std::atomic<bool> triggerNext_{ true };
660 Real currentValue_{ static_cast<Real>(0) };
661 Real targetValue_{ static_cast<Real>(0) };
662 std::atomic<NoiseType> noiseType_{ NoiseType::Pink };
663 std::atomic<bool> useBpmSync_{ false };
664 std::atomic<float> rateHz_{ 1.0f };
665 std::atomic<float> bpm_{ 120.0f };
666 std::atomic<BpmDivision> bpmDivision_{ BpmDivision::Quarter };
667 // Value-domain atomics use Real: with Real = double, storing them
668 // as float would silently round the user's range/quantization.
669 // Time-domain ones (rateHz_, bpm_) stay float on purpose.
670 std::atomic<Real> min_{ static_cast<Real>(-1) };
671 std::atomic<Real> max_{ static_cast<Real>(1) };
672 std::atomic<bool> smoothingEnabled_{ false };
673 std::atomic<Real> smoothingCoeff_{ static_cast<Real>(0) };
674 std::atomic<Real> quantizationStep_{ static_cast<Real>(0) };
675 std::atomic<std::uint64_t> pendingSeed_{ 0 };
676 Real brownNoiseState_{ static_cast<Real>(0) };
677 std::array<Real, 7> pinkNoiseOctaves_{};
678 Real denormalFlip_{ static_cast<Real>(1e-18) }; // DC-Free denormal mitigation state
679 };
680 } // namespace AnalogRandom
681} // namespace dspark
Reference constants from analog-hardware research and measurement.
Main generator class for analog-style random modulation.
void reset() noexcept
Reset internal state (phase, smoothing, noise states).
void setRateBPM(double bpm, BpmDivision division) noexcept
void setRateHz(Real rateInHz) noexcept
void setNoiseType(NoiseType type) noexcept
int getNextDiscreteInt(int imin, int imax) noexcept
void setSmoothing(bool shouldBeEnabled, Real timeInMs=static_cast< Real >(50.0)) noexcept
void reseed(std::uint64_t newSeed) noexcept
Request a lock-free reseed of the internal PRNG.
Int getNextDiscrete(Int imin, Int imax) noexcept
void prepare(double sampleRate) noexcept
Prepare the generator with the audio sample rate.
void setRange(T min, T max) noexcept
Set the output range for values. Safe from tearing.
Generator(const Generator &)=delete
Generator & operator=(const Generator &)=delete
Generator(Generator &&other) noexcept
void getNextBlock(std::span< Real > outputBuffer) noexcept
Generate a block of modulation samples.
Real getCurrentValue() const noexcept
Real getPhase() const noexcept
void setQuantization(Real step) noexcept
void setAnalogDefault(AnalogComponent component) noexcept
Real getNextSample() noexcept
Generate and return the next modulation sample.
Generator(std::uint64_t seed) noexcept
Generator & operator=(Generator &&other) noexcept
void updateBPM(double newBpm) noexcept
Parametric multi-band EQ using cascaded biquads or FFT overlap-save convolution.
Definition Equalizer.h:57
Main namespace for the analog random generation library.
NoiseType
Defines the spectral distribution of the random fluctuations.
@ White
Uncorrelated noise for "sample-and-hold" effects.
@ Pink
1/f noise for organic drift.
@ Brown
1/f^2 noise for slow "wow/flutter".
Main namespace for the DSPark framework.
A compact xoshiro256++ PRNG implementation (non-threadsafe).
void reseed(std::uint64_t seed) noexcept
Xoshiro256pp(std::uint64_t seed=1) noexcept