39#include "../Core/AudioBuffer.h"
40#include "../Core/AudioSpec.h"
41#include "../Core/Biquad.h"
42#include "../Core/DryWetMixer.h"
43#include "../Core/DspMath.h"
44#include "../Core/Oversampling.h"
45#include "../Core/Smoothers.h"
46#include "../Core/AnalogRandom.h"
47#include "../Core/SpscQueue.h"
48#include "../Core/SpinLock.h"
49#include "../Core/StateBlob.h"
56#include "../Core/detail/LogCosh.h"
68template <
typename SampleType>
class Saturation;
76 static constexpr int kAaCh = 16;
84 virtual void reset() noexcept = 0;
112 std::array<double, SaturationAlgorithm<T>::kAaCh> prevX_ {};
115 void reset() noexcept
override { prevX_.fill(0.0); }
120 T x = sample * drive;
121 T bias = character * T(0.3);
122 if (!this->
antialias_.load(std::memory_order_relaxed))
127 const double xd =
static_cast<double>(x);
128 const double b =
static_cast<double>(bias);
129 const double x0 = prevX_[
static_cast<size_t>(c)];
130 prevX_[
static_cast<size_t>(c)] = xd;
131 const double tb = std::tanh(b);
132 const double dx = xd - x0;
133 if (std::abs(dx) > 1e-7)
134 return static_cast<T
>((
logCosh(xd + b) -
logCosh(x0 + b)) / dx - tb);
135 return static_cast<T
>(std::tanh(0.5 * (xd + x0) + b) - tb);
143 std::array<double, SaturationAlgorithm<T>::kAaCh> prevX_ {};
145 static inline double f1(
double x,
double asym)
noexcept
151 void reset() noexcept
override { prevX_.fill(0.0); }
156 T x = sample * drive;
157 T asym = T(1.15) + character * T(0.5);
158 if (!this->
antialias_.load(std::memory_order_relaxed))
163 const double xd =
static_cast<double>(x);
164 const double a =
static_cast<double>(asym);
165 const double x0 = prevX_[
static_cast<size_t>(c)];
166 prevX_[
static_cast<size_t>(c)] = xd;
167 const double dx = xd - x0;
168 if (std::abs(dx) > 1e-7)
169 return static_cast<T
>((f1(xd, a) - f1(x0, a)) / dx);
170 const double m = 0.5 * (xd + x0);
171 return static_cast<T
>((m >= 0.0) ? std::tanh(m) : std::tanh(m * a));
179 std::array<T, SaturationAlgorithm<T>::kAaCh> prevX_ {};
182 static inline T g(T u)
noexcept
185 return (a <= T(1)) ? T(0.5) * u * u : a - T(0.5);
189 void reset() noexcept
override { prevX_.fill(T(0)); }
194 T bias = character * T(0.3);
195 T cb = std::clamp(bias, T(-1), T(1));
196 T d = sample * drive;
197 if (!this->
antialias_.load(std::memory_order_relaxed))
198 return std::clamp(d + bias, T(-1), T(1)) - cb;
204 T u = d + bias, u0 = d0 + bias;
205 if (u >= T(-1) && u <= T(1) && u0 >= T(-1) && u0 <= T(1))
208 if (std::abs(dd) > T(1e-5))
209 return (g(u) - g(u0)) / dd - cb;
210 return std::clamp(T(0.5) * (d + d0) + bias, T(-1), T(1)) - cb;
225 T x = std::clamp(sample * drive, T(-10), T(10));
228 T result = x + character * T(0.25) * x2 - T(0.15) * x3;
229 return std::clamp(result, T(-1), T(1));
237 static constexpr int kMaxCh = 16;
238 std::array<T, kMaxCh> lastX_ {};
242 void reset() noexcept
override { lastX_.fill(T(0)); }
258 const T bias = character * (pi<T> / T(4));
259 const T x = sample * drive + bias;
260 const T sb = std::sin(bias);
261 const int c = ch & (kMaxCh - 1);
267 const T half = T(0.5) * (x - lastX_[
static_cast<size_t>(c)]);
268 const T mid = T(0.5) * (x + lastX_[
static_cast<size_t>(c)]);
269 const T sinc = (std::abs(half) > T(1e-4)) ? std::sin(half) / half
270 : T(1) - half * half / T(6);
271 lastX_[
static_cast<size_t>(c)] = x;
272 return std::sin(mid) * sinc - sb;
284 uint32_t rngState_ = 0x9E3779B9u;
288 [[nodiscard]]
inline T nextRandom()
noexcept
290 rngState_ ^= rngState_ << 13;
291 rngState_ ^= rngState_ >> 17;
292 rngState_ ^= rngState_ << 5;
293 constexpr T scale = T(1) /
static_cast<T
>(0xFFFFFFFFu);
294 return static_cast<T
>(rngState_) * scale - T(0.5);
300 if (rngState_ == 0) rngState_ = 1;
307 T clamped = std::clamp(drive, T(1), T(100));
308 T bitDepth =
mapRange(clamped, T(1), T(100), T(16), T(2));
309 steps_ = std::pow(T(2), bitDepth);
310 invSteps_ = T(1) / steps_;
316 T dither = (nextRandom() - nextRandom()) * invSteps_;
317 return invSteps_ * std::round((sample + dither) * steps_);
325 static constexpr int kMaxCh = 16;
326 std::array<Biquad<T, 1>, kMaxCh> preFilters_;
327 std::array<Biquad<T, 1>, kMaxCh> postFilters_;
328 std::array<T, kMaxCh> M_ {};
329 int numChannels_ = 0;
330 T lastDrive_ = T(-1);
331 double lastSampleRate_ = 0.0;
336 static inline T langevin(T x)
noexcept
338 const T ax = std::abs(x);
342 return x * (T(1) / T(3) - x2 * (T(1) / T(45) - x2 * (T(2) / T(945))));
344 if (ax > T(20))
return std::copysign(T(1), x) - T(1) / x;
345 return T(1) / std::tanh(x) - T(1) / x;
349 static inline T langevinDeriv(T x)
noexcept
351 const T ax = std::abs(x);
355 return T(1) / T(3) - x2 * (T(1) / T(15) - x2 * (T(2) / T(189)));
357 if (ax > T(20))
return T(1) / (x * x);
358 const T s = std::sinh(x);
359 return T(1) / (x * x) - T(1) / (s * s);
365 numChannels_ = std::min(spec.numChannels, kMaxCh);
370 lastSampleRate_ = 0.0;
375 for (
auto& f : preFilters_) f.reset();
376 for (
auto& f : postFilters_) f.reset();
385 if (drive == lastDrive_ && spec.sampleRate == lastSampleRate_)
return;
387 lastSampleRate_ = spec.sampleRate;
390 T bumpGain = T(1.5) + std::min(driveDb * T(0.05), T(3.0));
392 auto lpFreq = std::max(6000.0, 19000.0 -
static_cast<double>(driveDb) * 200.0);
395 for (
int ch = 0; ch < numChannels_; ++ch)
397 preFilters_[ch].setCoeffs(peakCoeffs);
398 postFilters_[ch].setCoeffs(lpCoeffs);
419 const T filtered = preFilters_[ch].processSample(sample, 0);
420 const T H = filtered * drive;
424 const T alpha = T(0.35) + T(0.15) * character;
425 const T a = T(1) / (T(3) * (T(1) - alpha));
426 const T Ms = T(3) * a;
429 for (
int it = 0; it < 3; ++it)
431 const T x = (H + alpha * M) / a;
432 const T f = M - Ms * langevin(x);
433 const T fp = T(1) - T(3) * alpha * langevinDeriv(x);
436 M = std::clamp(M, -Ms, Ms);
439 return postFilters_[ch].processSample((T(1) - alpha) * M, 0);
447 static constexpr int kMaxCh = 16;
448 std::array<Biquad<T, 1>, kMaxCh> lpFilters_;
449 int numChannels_ = 0;
450 double lastSampleRate_ = 0.0;
455 numChannels_ = std::min(spec.numChannels, kMaxCh);
456 lastSampleRate_ = 0.0;
461 for (
auto& f : lpFilters_) f.reset();
469 if (spec.sampleRate == lastSampleRate_)
return;
470 lastSampleRate_ = spec.sampleRate;
473 for (
int ch = 0; ch < numChannels_; ++ch)
474 lpFilters_[ch].setCoeffs(c);
489 const T low = lpFilters_[ch].processSample(sample, 0);
490 const T high = sample - low;
491 const T bias = character * T(0.2);
492 const T kLo = drive * T(1.4);
493 const T kHi = drive * T(0.85);
494 const T satLow = (
fastTanh((low + bias) * kLo) -
fastTanh(bias * kLo)) / T(1.4);
495 const T satHigh = (
fastTanh((high + bias) * kHi) -
fastTanh(bias * kHi)) / T(0.85);
496 return satLow + satHigh;
504 static constexpr int kMaxCh = 16;
505 std::array<Biquad<T, 1>, kMaxCh> aaFilters_;
506 std::array<T, kMaxCh> lastSample_ {};
507 std::array<int, kMaxCh> counter_ {};
508 int numChannels_ = 0;
514 numChannels_ = std::min(spec.numChannels, kMaxCh);
519 for (
auto& f : aaFilters_) f.reset();
520 lastSample_.fill(T(0));
527 T clamped = std::clamp(drive, T(1), T(100));
528 reduction_ = std::max(1,
static_cast<int>(
mapRange(clamped, T(1), T(100), T(1), T(50))));
531 for (
int ch = 0; ch < numChannels_; ++ch)
532 aaFilters_[ch].setCoeffs(c);
537 T filtered = aaFilters_[ch].processSample(sample, 0);
538 if (++counter_[ch] >= reduction_)
541 lastSample_[ch] = filtered;
543 return lastSample_[ch];
570 tube_.setAntialias(this->
antialias_.load(std::memory_order_relaxed));
571 tape_.
update(drive * T(0.6), character, spec);
572 xfmr_.
update(drive * T(0.8), character, spec);
581 T tubeOut = tube_.processSample(sample, drive * T(0.5), character, ch) * T(2);
582 T tapeOut = tape_.
processSample(tubeOut, drive * T(0.6), character, ch) * (T(1) / T(0.6));
583 return xfmr_.
processSample(tapeOut, drive * T(0.8), character, ch);
608template <
typename SampleType>
611 static_assert(std::is_floating_point_v<SampleType>,
612 "Saturation: SampleType must be float or double.");
644 pool_[0] = std::make_unique<detail::TubeAlgorithm<SampleType>>();
645 pool_[1] = std::make_unique<detail::TapeAlgorithm<SampleType>>();
646 pool_[2] = std::make_unique<detail::TransformerAlgorithm<SampleType>>();
647 pool_[3] = std::make_unique<detail::TanhAlgorithm<SampleType>>();
648 pool_[4] = std::make_unique<detail::HardClipAlgorithm<SampleType>>();
649 pool_[5] = std::make_unique<detail::ExciterAlgorithm<SampleType>>();
650 pool_[6] = std::make_unique<detail::WavefolderAlgorithm<SampleType>>();
651 pool_[7] = std::make_unique<detail::BitcrusherAlgorithm<SampleType>>();
652 pool_[8] = std::make_unique<detail::DownsampleAlgorithm<SampleType>>();
653 pool_[9] = std::make_unique<detail::MultiStageAlgorithm<SampleType>>();
656 next_.store(
nullptr);
678 for (
auto& algo :
pool_)
679 if (algo) algo->prepare(spec);
745 if (
auto* pending =
next_.load())
748 next_.store(
nullptr);
751 for (
auto& algo :
pool_)
752 if (algo) algo->reset();
804 const int numSamples = buffer.getNumSamples();
805 constexpr int kCoefRefresh = 16;
806 for (
int i = 0; i < numSamples; i += kCoefRefresh)
808 const int chunk = std::min(kCoefRefresh, numSamples - i);
810 for (
int k = 0; k < chunk; ++k)
819 auto subView = buffer.getSubView(i, chunk);
835 const int keepChannel =
844 if (keepChannel >= 0 && keepChannel < upView.getNumChannels() && upSamples > 0)
846 static_cast<std::size_t
>(upSamples) *
sizeof(SampleType));
850 if (keepChannel >= 0 && keepChannel < upView.getNumChannels() && upSamples > 0)
852 static_cast<std::size_t
>(upSamples) *
sizeof(SampleType));
859 if (keepChannel >= 0 && keepChannel < buffer.getNumChannels() && baseSamples > 0)
861 static_cast<std::size_t
>(baseSamples) *
sizeof(SampleType));
865 if (keepChannel >= 0 && keepChannel < buffer.getNumChannels() && baseSamples > 0)
867 static_cast<std::size_t
>(baseSamples) *
sizeof(SampleType));
879 const int numSamples = buffer.getNumSamples();
880 constexpr int kCoefRefresh = 16;
881 for (
int i = 0; i < numSamples; i += kCoefRefresh)
883 const int chunk = std::min(kCoefRefresh, numSamples - i);
886 for (
int k = 0; k < chunk; ++k)
902 auto subView = buffer.getSubView(i, chunk);
916 for (
int ch = 0; ch < nCh; ++ch)
918 SampleType* wet = buffer.getChannel(ch);
920 for (
int i = 0; i < nS; ++i) wet[i] -= dry[i];
1029 for (
auto& a :
pool_)
if (a) a->setAntialias(on);
1040 if (!std::isfinite(amount))
return;
1041 slewSensitivity_.store(std::clamp(amount, SampleType(0), SampleType(1)), std::memory_order_relaxed);
1057 pushParam([&](
auto& p){ p.postFilterTiltFreq = centerHz; p.postFilterTiltGain = amountDb; });
1075 if (factor < 1 || (factor & (factor - 1)) != 0)
return;
1079 oversampler_ = std::make_unique<Oversampling<SampleType>>(factor);
1151 w.
write(
"mix",
static_cast<float>(p.
mix));
1176 setDrive(
static_cast<SampleType
>(r.
read(
"drive", 0.0f)));
1177 setMix(
static_cast<SampleType
>(r.
read(
"mix", 1.0f)));
1182 static_cast<SampleType
>(r.
read(
"tiltGain", 0.0f)));
1199 SampleType
mix = SampleType(1);
1217 const auto keepFinite = [](SampleType& v, SampleType old)
noexcept
1219 if (!std::isfinite(v)) v = old;
1221 keepFinite(p.driveDb, prev.driveDb);
1222 keepFinite(p.mix, prev.mix);
1223 keepFinite(p.character, prev.character);
1224 keepFinite(p.analogDrift, prev.analogDrift);
1225 keepFinite(p.preFilterHpFreq, prev.preFilterHpFreq);
1226 keepFinite(p.postFilterTiltFreq, prev.postFilterTiltFreq);
1227 keepFinite(p.postFilterTiltGain, prev.postFilterTiltGain);
1228 keepFinite(p.outputGain, prev.outputGain);
1230 const auto clampEnum = [](
auto& e,
int hi)
noexcept
1232 using E = std::remove_reference_t<
decltype(e)>;
1233 e =
static_cast<E
>(std::clamp(
static_cast<int>(e), 0, hi));
1240 template <
typename Fn>
1298 auto* nxt =
next_.load();
1299 if (requested == act)
1315 next_.store(
nullptr);
1319 else if (requested == nxt)
1334 next_.store(requested);
1342 auto* primary =
active_.load();
1343 auto* secondary =
next_.load();
1353 const int nS = buffer.getNumSamples();
1362 if (primary) primary->update(driveGainTarget, characterTarget, updateSpec);
1363 if (secondary) secondary->update(driveGainTarget, characterTarget, updateSpec);
1365 SampleType peakInOriginal = SampleType(0);
1366 for (
int ch = 0; ch < nCh; ++ch) {
1367 const SampleType* d = buffer.getChannel(ch);
1368 for (
int i = 0; i < nS; ++i) peakInOriginal = std::max(peakInOriginal, std::abs(d[i]));
1373 if (slewAmt > SampleType(0))
1375 for (
int ch = 0; ch < nCh; ++ch) {
1376 SampleType* data = buffer.getChannel(ch);
1377 for (
int i = 0; i < nS; ++i) {
1378 SampleType dry = data[i];
1380 data[i] += std::tanh(std::abs(delta)) * slewAmt * dry;
1388 bool useDrift = driftIntensity > 0.01f;
1392 for (
int i = 0; i < nS; ++i) {
1401 for (
int ch = 0; ch < nCh; ++ch) {
1402 driftView.getChannel(ch)[i] =
1403 SampleType(1) +
static_cast<SampleType
>(driftS) * (ch == 0 ? noiseL : noiseR);
1412 for (
int ch = 0; ch < nCh; ++ch)
1413 std::memcpy(tempView.getChannel(ch), buffer.getChannel(ch),
static_cast<std::size_t
>(nS) *
sizeof(SampleType));
1418 for (
int i = 0; i < nS; ++i) {
1420 for (
int ch = 0; ch < nCh; ++ch) {
1421 SampleType* out = buffer.getChannel(ch);
1422 const SampleType* alt = tempView.getChannel(ch);
1423 out[i] = out[i] * fade + alt[i] * (SampleType(1) - fade);
1431 next_.store(
nullptr);
1432 if (primary) primary->reset();
1437 next_.store(
nullptr);
1451 SampleType peakOut = SampleType(0);
1452 for (
int ch = 0; ch < nCh; ++ch) {
1453 const SampleType* d = buffer.getChannel(ch);
1454 for (
int i = 0; i < nS; ++i) peakOut = std::max(peakOut, std::abs(d[i]));
1457 SampleType peakInDriven = peakInOriginal * driveGainTarget;
1458 if (peakInDriven > SampleType(1e-6)) {
1459 SampleType ratio = std::min(peakOut / peakInDriven, SampleType(1));
1471 const int nCh = std::min({ buffer.getNumChannels(),
1473 const int nS = std::min(buffer.getNumSamples(),
1476 for (
int ch = 0; ch < nCh; ++ch)
1478 SampleType* wetData = buffer.getChannel(ch);
1480 for (
int i = 0; i < nS; ++i)
1482 SampleType dry = dryData[i];
1483 SampleType wet = wetData[i];
1484 SampleType avg = (std::abs(
prevBlendSample_[ch]) + std::abs(dry)) * SampleType(0.5);
1485 SampleType apply = std::clamp(avg, SampleType(0), SampleType(1));
1486 wetData[i] = dry * (SampleType(1) - apply) + wet * apply;
1495 if (!baseAlgo)
return;
1496 switch (baseAlgo->getType())
1511 template <
typename ExactAlgo>
1517 const int nS = buffer.getNumSamples();
1520 for (
int i = 0; i < nS; ++i)
1525 for (
int ch = 0; ch < nCh; ++ch)
1527 SampleType drift = useDrift ? driftView.getChannel(ch)[i] : SampleType(1);
1528 SampleType* data = buffer.getChannel(ch);
1529 data[i] = algo->processSample(data[i], driveGainS * drift, charS * drift, ch);
1539 buffer.applyGain(
decibelsToGain(
static_cast<SampleType
>(targetGainDb)));
1543 const int nCh = buffer.getNumChannels();
1544 const int nS = buffer.getNumSamples();
1545 for (
int i = 0; i < nS; ++i)
1548 for (
int ch = 0; ch < nCh; ++ch) buffer.getChannel(ch)[i] *= gain;
1558 std::atomic<detail::SaturationAlgorithm<SampleType>*>
active_ {
nullptr };
1559 std::atomic<detail::SaturationAlgorithm<SampleType>*>
next_ {
nullptr };
Main generator class for analog-style random modulation.
void setSmoothing(bool shouldBeEnabled, Real timeInMs=static_cast< Real >(50.0)) noexcept
Enables one-pole smoothing of the held targets.
void reseed(std::uint64_t newSeed) noexcept
Request a lock-free reseed of the internal PRNG.
void prepare(double sampleRate) noexcept
Prepare the generator with the audio sample rate.
Real getNextSample() noexcept
Generate and return the next modulation sample.
Non-owning view over audio channel data.
Owning audio buffer with contiguous, 32-byte aligned storage.
AudioBufferView< T, MaxChannels > toView() noexcept
Returns a non-owning mutable view of this buffer. The view's channel capacity is propagated from MaxC...
T * getChannel(int ch) noexcept
Returns a pointer to the sample data.
int getNumSamples() const noexcept
Returns the number of samples per channel.
void resize(int numChannels, int numSamples)
Allocates the buffer for the given dimensions.
Biquad filter using Transposed Direct Form II (TDF-II) with thread-safe updates.
void setCoeffs(const BiquadCoeffs &c) noexcept
Sets the filter coefficients asynchronously (control thread).
void reset() noexcept
Resets all per-channel filter states to zero to avoid ringing/clicks.
void processBlock(AudioBufferView< T > buffer) noexcept
Processes a full audio buffer in-place.
DC blocking filter with configurable Butterworth order (1-10).
void prepare(double sampleRate, int numChannels=2, double cutoffHz=-1.0)
Prepares the DC blocker, resetting internal states and precalculating coefficients.
void setOrder(int order) noexcept
Sets the filter order (1-10). Thread-safe.
void reset() noexcept
Clears the internal history states to zero.
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an AudioBufferView in-place.
Pre-allocated, SIMD-friendly dry/wet blender for real-time audio.
void setLatencyCompensation(int samples)
Delays the captured dry signal to compensate for an effect's internal latency (e.g....
int getDryCapturedSamples() const noexcept
Returns the number of samples valid from the last pushDry() call.
void mixWet(AudioBufferView< T > wetBuffer, T targetMix) noexcept
Blends the stored dry signal with the current (wet) buffer in-place.
void reset() noexcept
Resets the internal buffer and smoothing states to zero.
int getDryNumChannels() const noexcept
Returns the internal capacity of channels in the dry buffer.
void pushDry(const AudioBufferView< const T > &input) noexcept
Captures a snapshot of the dry (unprocessed) signal.
void prepare(const AudioSpec &spec)
Allocates the internal dry buffer for the given audio spec.
const T * getDryChannel(int ch) const noexcept
Retrieves a read-only pointer to the captured dry channel data.
Professional multi-algorithm saturation processor with analog simulation.
void pushParam(Fn &&mutate)
void dispatchSaturator(detail::SaturationAlgorithm< SampleType > *baseAlgo, AudioBufferView< SampleType > buffer, bool useDrift) noexcept
Smoothers::StateVariableSmoother driveSmoother_
std::atomic< SampleType > gainReductionDb_
void setProcessingMode(ProcessingMode m)
Sets the routing configuration for multi-channel processing.
void setMix(SampleType mix01)
Sets the global Dry/Wet blend.
std::atomic< bool > adaptiveBlend_
std::atomic< SampleType > slewSensitivity_
std::atomic< bool > paramsPending_
AnalogRandom::Generator< SampleType > rightDrift_
void setAlgorithm(Algorithm algo)
Sets the saturation algorithm topology.
void setOversampling(int factor)
Configures internal polyphase oversampling to reduce aliasing.
void applyOutputGain(AudioBufferView< SampleType > buffer) noexcept
AudioBuffer< SampleType > msKeepBuffer_
MidOnly/SideOnly channel snapshot.
void setCharacter(SampleType c)
Adjusts the specific character/bias of the selected algorithm.
std::unique_ptr< Oversampling< SampleType > > oversampler_
std::atomic< detail::SaturationAlgorithm< SampleType > * > active_
std::atomic< detail::SaturationAlgorithm< SampleType > * > next_
void setOutputGain(SampleType dB)
Sets the post-saturation make-up or trim gain.
std::atomic< Algorithm > currentAlgoType_
std::array< SampleType, kMaxCh > prevSlewSample_
static constexpr int kNumAlgorithms
SpscQueue< Params > paramQueue_
AudioBuffer< SampleType > tempBuffer_
Smoothers::LinearSmoother crossfader_
DryWetMixer< SampleType > dryWetMixer_
Smoothers::LinearSmoother mixSmoother_
AudioBuffer< SampleType > driftBuffer_
Biquad< SampleType > preFilter_
Smoothers::StateVariableSmoother postTiltFreqSmoother_
SampleType getGainReductionDb() const noexcept
Calculates the peak gain reduction (clipping amount) for metering.
void processSaturationPipeline(AudioBufferView< SampleType > buffer) noexcept
void setAntialiasing(bool on) noexcept
Enables antiderivative anti-aliasing (ADAA) on the memoryless curves (SoftClip / Tube / HardClip),...
Algorithm
Defines the harmonic generation topology.
Smoothers::StateVariableSmoother preHpSmoother_
std::atomic< bool > antialiasShadow_
Mirror for getState.
void setPreFilterHpFrequency(SampleType hz)
Configures a pre-saturation high-pass filter.
void reset() noexcept
Clears all internal states, phase memory, and history buffers.
void setAdaptiveBlend(bool on) noexcept
Enables program-dependent saturation density.
int getOversamplingFactor() const noexcept
Retrieves the current oversampling factor.
OutputMode
Determines the final output signal routing.
void applyAdaptiveBlend(AudioBufferView< SampleType > buffer) noexcept
Program-dependent dry/wet density blend (base rate, L/R domain). The dry reference is the DryWetMixer...
void applyParamSnapshot(const Params &p)
void processCore(ExactAlgo *algo, AudioBufferView< SampleType > buffer, bool useDrift) noexcept
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (setup/UI threads: it forwards the stored oversampling factor to setO...
void process(AudioBufferView< SampleType > buffer) noexcept
Compatibility alias of processBlock(), with the same thread and preparation contract....
void setOutputMode(OutputMode m)
Sets the output signal path.
Smoothers::LinearSmoother postTiltGainSmoother_
ProcessingMode
Determines how the stereo field is processed.
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
static constexpr int kMaxCh
int oversamplingFactor_
2x by default: the curves alias at 1x (see setOversampling()).
void processBlock(AudioBufferView< SampleType > buffer) noexcept
Processes an audio block in-place (AudioProcessor standard contract).
int getLatency() const noexcept
Reports the processor's algorithmic latency in samples.
DCBlocker< SampleType > dcBlocker_
AnalogRandom::Generator< SampleType > leftDrift_
Smoothers::LinearSmoother driftSmoother_
Smoothers::LinearSmoother characterSmoother_
void setPostFilterTilt(SampleType centerHz, SampleType amountDb)
Configures a post-saturation first-order tilt EQ.
Saturation & operator=(const Saturation &)=delete
void handleParameterChanges()
void setDcBlocking(bool on)
Enables or disables the fixed 10Hz DC Blocker.
int getLatencySamples() const noexcept
Compatibility alias of getLatency(), in prepared-rate samples.
Smoothers::LinearSmoother outputGainSmoother_
void prepare(const AudioSpec &spec)
Prepares all internal resources, filters, and buffers.
std::array< std::unique_ptr< detail::SaturationAlgorithm< SampleType > >, kNumAlgorithms > pool_
void setAnalogDrift(SampleType i)
Injects true-stereo pseudo-random low-frequency modulation (drift) into the saturation drive.
void setDrive(SampleType dB)
Sets the input drive gain.
void setSlewSensitivity(SampleType amount) noexcept
Sets a derivative-based (slew rate) saturation multiplier.
std::array< SampleType, kMaxCh > prevBlendSample_
Biquad< SampleType > postFilter_
static void sanitizeParams(Params &p, const Params &prev) noexcept
Saturation(const Saturation &)=delete
Algorithm getCurrentAlgorithm() const noexcept
Retrieves the currently active underlying algorithm.
RAII wrapper that acquires the lock on construction and releases on destruction.
RAII wrapper that tries to acquire the lock without blocking.
bool isLocked() const noexcept
Queries whether the lock acquisition was successful.
A minimal, real-time safe spin lock with a TTAS wait loop.
Tolerant reader: missing keys yield defaults, unknown keys are skipped.
float read(const char *key, float defaultValue) const
Reads a float, or defaultValue when the key is absent.
bool isValid() const noexcept
uint32_t processorId() const noexcept
Serializes key/value parameters into a versioned blob.
std::vector< uint8_t > blob() const
Finalizes and returns the blob.
void write(const char *key, float value)
Writes a float parameter.
void reset() noexcept override
Resets internal states (filters, phase, memory).
void update(T drive, T, const AudioSpec &) noexcept override
Updates internal coefficients dependent on block-rate parameters.
Saturation< T >::Algorithm getType() const noexcept override
Identifies the exact algorithm type for CRTP static dispatch.
T processSample(T sample, T, T, int) noexcept
void prepare(const AudioSpec &) noexcept override
Prepares the algorithm with the current audio specification.
T processSample(T sample, T, T, int ch) noexcept
void reset() noexcept override
Resets internal states (filters, phase, memory).
void update(T drive, T, const AudioSpec &spec) noexcept override
Updates internal coefficients dependent on block-rate parameters.
void prepare(const AudioSpec &spec) noexcept override
Prepares the algorithm with the current audio specification.
Saturation< T >::Algorithm getType() const noexcept override
Identifies the exact algorithm type for CRTP static dispatch.
T processSample(T sample, T drive, T character, int) noexcept
Saturation< T >::Algorithm getType() const noexcept override
Identifies the exact algorithm type for CRTP static dispatch.
void reset() noexcept override
Resets internal states (filters, phase, memory).
void prepare(const AudioSpec &) noexcept override
Prepares the algorithm with the current audio specification.
T processSample(T sample, T drive, T character, int ch) noexcept
void reset() noexcept override
Resets internal states (filters, phase, memory).
Saturation< T >::Algorithm getType() const noexcept override
Identifies the exact algorithm type for CRTP static dispatch.
void prepare(const AudioSpec &) noexcept override
Prepares the algorithm with the current audio specification.
void reset() noexcept override
Resets internal states (filters, phase, memory).
void prepare(const AudioSpec &spec) noexcept override
Prepares the algorithm with the current audio specification.
Saturation< T >::Algorithm getType() const noexcept override
Identifies the exact algorithm type for CRTP static dispatch.
T processSample(T sample, T drive, T character, int ch) noexcept
void update(T drive, T character, const AudioSpec &spec) noexcept override
Updates internal coefficients dependent on block-rate parameters.
virtual void reset() noexcept=0
Resets internal states (filters, phase, memory).
std::atomic< bool > antialias_
virtual void update(T, T, const AudioSpec &) noexcept
Updates internal coefficients dependent on block-rate parameters.
static constexpr int kAaCh
virtual Saturation< T >::Algorithm getType() const noexcept=0
Identifies the exact algorithm type for CRTP static dispatch.
virtual void prepare(const AudioSpec &spec) noexcept=0
Prepares the algorithm with the current audio specification.
virtual ~SaturationAlgorithm()=default
void setAntialias(bool on) noexcept
Enables 1st-order antiderivative anti-aliasing (ADAA) on the memoryless curves (Tanh/Tube/HardClip)....
Saturation< T >::Algorithm getType() const noexcept override
Identifies the exact algorithm type for CRTP static dispatch.
void reset() noexcept override
Resets internal states (filters, phase, memory).
T processSample(T sample, T drive, T character, int ch) noexcept
void prepare(const AudioSpec &) noexcept override
Prepares the algorithm with the current audio specification.
void reset() noexcept override
Resets internal states (filters, phase, memory).
void prepare(const AudioSpec &spec) noexcept override
Prepares the algorithm with the current audio specification.
T processSample(T sample, T drive, T character, int ch) noexcept
void update(T drive, T, const AudioSpec &spec) noexcept override
Updates internal coefficients dependent on block-rate parameters.
Saturation< T >::Algorithm getType() const noexcept override
Identifies the exact algorithm type for CRTP static dispatch.
void reset() noexcept override
Resets internal states (filters, phase, memory).
void prepare(const AudioSpec &) noexcept override
Prepares the algorithm with the current audio specification.
T processSample(T sample, T drive, T character, int ch) noexcept
Saturation< T >::Algorithm getType() const noexcept override
Identifies the exact algorithm type for CRTP static dispatch.
Saturation< T >::Algorithm getType() const noexcept override
Identifies the exact algorithm type for CRTP static dispatch.
void prepare(const AudioSpec &) noexcept override
Prepares the algorithm with the current audio specification.
void reset() noexcept override
Resets internal states (filters, phase, memory).
T processSample(T sample, T drive, T character, int ch) noexcept
Main namespace for the DSPark framework.
T decibelsToGain(T dB, T minusInfinityDb=T(-100)) noexcept
Converts a value in decibels to linear gain.
T mapRange(T value, T inMin, T inMax, T outMin, T outMax) noexcept
Maps a value from one range to another (linear interpolation).
T gainToDecibels(T gain, T minusInfinityDb=T(-100)) noexcept
Converts a linear gain value to decibels.
T fastTanh(T x) noexcept
Fast tanh approximation using Pade rational function.
constexpr uint32_t stateId(const char(&tag)[5]) noexcept
Builds a FOURCC processor id, e.g. dspark::stateId("COMP").
Describes the audio environment for a DSP processor.
constexpr bool isValid() const noexcept
Checks if the specification contains valid, processable parameters.
int numChannels
Number of audio channels (e.g., 1 = mono, 2 = stereo).
int maxBlockSize
Maximum number of samples per processing block.
double sampleRate
Sample rate in Hz.
static BiquadCoeffs makeHighPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
High-pass filter.
static BiquadCoeffs makePeak(double sampleRate, double freq, double Q, double gainDb) noexcept
Peak (parametric EQ) filter.
static BiquadCoeffs makeTilt(double sampleRate, double pivotFreq, double gainDb) noexcept
Creates a first-order tilt filter.
static BiquadCoeffs makeLowPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Low-pass filter.
Static utility for Mid/Side stereo encoding and decoding.
static void encode(AudioBufferView< T > buffer) noexcept
Encodes an entire stereo buffer from Left/Right to Mid/Side.
static void decode(AudioBufferView< T > buffer) noexcept
Decodes an entire stereo buffer from Mid/Side back to Left/Right.
ProcessingMode processingMode
SampleType postFilterTiltFreq
SampleType preFilterHpFreq
SampleType postFilterTiltGain
Linear ramp smoother for predictable, uniform interpolation.
void setCurrentAndTargetValue(float value) noexcept
void reset(double sampleRate, float rampTimeMilliseconds, float initialValue=0.0f) noexcept
float getTargetValue() const noexcept
float getNextValue() noexcept
void setTargetValue(float newTarget) noexcept
bool isSmoothing() const noexcept
float getCurrentValue() const noexcept
Second-order state variable filter (SVF) smoother (TPT implementation).
void reset(double sampleRate, float timeConstantMilliseconds, float q=0.707f, float initialValue=0.0f) noexcept
void setTargetValue(float newTarget) noexcept
float getNextValue() noexcept
float getTargetValue() const noexcept