38#include "../Core/AudioBuffer.h"
39#include "../Core/AudioSpec.h"
40#include "../Core/Biquad.h"
41#include "../Core/DryWetMixer.h"
42#include "../Core/DspMath.h"
43#include "../Core/Oversampling.h"
44#include "../Core/Smoothers.h"
45#include "../Core/AnalogRandom.h"
46#include "../Core/SpscQueue.h"
47#include "../Core/SpinLock.h"
48#include "../Core/StateBlob.h"
65template <
typename SampleType>
class Saturation;
74 return a + std::log1p(std::exp(T(-2) * a)) - T(0.6931471805599453);
81 static constexpr int kAaCh = 16;
89 virtual void reset() noexcept = 0;
113 std::array<T, SaturationAlgorithm<T>::kAaCh> prevX_ {};
116 void reset() noexcept
override { prevX_.fill(T(0)); }
121 T x = sample * drive;
122 T bias = character * T(0.3);
123 if (!this->
antialias_.load(std::memory_order_relaxed))
130 T tb = std::tanh(bias);
132 if (std::abs(dx) > T(1e-5))
134 return std::tanh(T(0.5) * (x + x0) + bias) - tb;
142 std::array<T, SaturationAlgorithm<T>::kAaCh> prevX_ {};
144 static inline T f1(T x, T asym)
noexcept
150 void reset() noexcept
override { prevX_.fill(T(0)); }
155 T x = sample * drive;
156 T asym = T(1.15) + character * T(0.5);
157 if (!this->
antialias_.load(std::memory_order_relaxed))
165 if (std::abs(dx) > T(1e-5))
166 return (f1(x, asym) - f1(x0, asym)) / dx;
167 T m = T(0.5) * (x + x0);
168 return (m >= T(0)) ? std::tanh(m) : std::tanh(m * asym);
176 std::array<T, SaturationAlgorithm<T>::kAaCh> prevX_ {};
179 static inline T g(T u)
noexcept
182 return (a <= T(1)) ? T(0.5) * u * u : a - T(0.5);
186 void reset() noexcept
override { prevX_.fill(T(0)); }
191 T bias = character * T(0.3);
192 T cb = std::clamp(bias, T(-1), T(1));
193 T d = sample * drive;
194 if (!this->
antialias_.load(std::memory_order_relaxed))
195 return std::clamp(d + bias, T(-1), T(1)) - cb;
201 T u = d + bias, u0 = d0 + bias;
202 if (u >= T(-1) && u <= T(1) && u0 >= T(-1) && u0 <= T(1))
205 if (std::abs(dd) > T(1e-5))
206 return (g(u) - g(u0)) / dd - cb;
207 return std::clamp(T(0.5) * (d + d0) + bias, T(-1), T(1)) - cb;
222 T x = std::clamp(sample * drive, T(-10), T(10));
225 T result = x + character * T(0.25) * x2 - T(0.15) * x3;
226 return std::clamp(result, T(-1), T(1));
234 static constexpr int kMaxCh = 16;
235 std::array<T, kMaxCh> lastX_ {};
236 std::array<T, kMaxCh> lastF_ {};
260 const T bias = character * (pi<T> / T(4));
261 const T x = sample * drive + bias;
262 const T sb = std::sin(bias);
263 T F_x = -std::cos(x);
264 T diff = x - lastX_[ch];
267 if (std::abs(diff) > T(1e-5))
268 result = (F_x - lastF_[ch]) / diff - sb;
270 result = std::sin(x) - sb;
286 uint32_t rngState_ = 0x9E3779B9u;
290 [[nodiscard]]
inline T nextRandom()
noexcept
292 rngState_ ^= rngState_ << 13;
293 rngState_ ^= rngState_ >> 17;
294 rngState_ ^= rngState_ << 5;
295 constexpr T scale = T(1) /
static_cast<T
>(0xFFFFFFFFu);
296 return static_cast<T
>(rngState_) * scale - T(0.5);
302 if (rngState_ == 0) rngState_ = 1;
309 T clamped = std::clamp(drive, T(1), T(100));
310 T bitDepth =
mapRange(clamped, T(1), T(100), T(16), T(2));
311 steps_ = std::pow(T(2), bitDepth);
312 invSteps_ = T(1) / steps_;
318 T dither = (nextRandom() - nextRandom()) * invSteps_;
319 return invSteps_ * std::round((sample + dither) * steps_);
327 static constexpr int kMaxCh = 16;
328 std::array<Biquad<T, 1>, kMaxCh> preFilters_;
329 std::array<Biquad<T, 1>, kMaxCh> postFilters_;
330 std::array<T, kMaxCh> M_ {};
331 int numChannels_ = 0;
332 T lastDrive_ = T(-1);
333 double lastSampleRate_ = 0.0;
338 static inline T langevin(T x)
noexcept
340 const T ax = std::abs(x);
344 return x * (T(1) / T(3) - x2 * (T(1) / T(45) - x2 * (T(2) / T(945))));
346 if (ax > T(20))
return std::copysign(T(1), x) - T(1) / x;
347 return T(1) / std::tanh(x) - T(1) / x;
351 static inline T langevinDeriv(T x)
noexcept
353 const T ax = std::abs(x);
357 return T(1) / T(3) - x2 * (T(1) / T(15) - x2 * (T(2) / T(189)));
359 if (ax > T(20))
return T(1) / (x * x);
360 const T s = std::sinh(x);
361 return T(1) / (x * x) - T(1) / (s * s);
367 numChannels_ = std::min(spec.numChannels, kMaxCh);
372 lastSampleRate_ = 0.0;
377 for (
auto& f : preFilters_) f.reset();
378 for (
auto& f : postFilters_) f.reset();
387 if (drive == lastDrive_ && spec.sampleRate == lastSampleRate_)
return;
389 lastSampleRate_ = spec.sampleRate;
392 T bumpGain = T(1.5) + std::min(driveDb * T(0.05), T(3.0));
394 auto lpFreq = std::max(6000.0, 19000.0 -
static_cast<double>(driveDb) * 200.0);
397 for (
int ch = 0; ch < numChannels_; ++ch)
399 preFilters_[ch].setCoeffs(peakCoeffs);
400 postFilters_[ch].setCoeffs(lpCoeffs);
421 const T filtered = preFilters_[ch].processSample(sample, 0);
422 const T H = filtered * drive;
426 const T alpha = T(0.35) + T(0.15) * character;
427 const T a = T(1) / (T(3) * (T(1) - alpha));
428 const T Ms = T(3) * a;
431 for (
int it = 0; it < 3; ++it)
433 const T x = (H + alpha * M) / a;
434 const T f = M - Ms * langevin(x);
435 const T fp = T(1) - T(3) * alpha * langevinDeriv(x);
438 M = std::clamp(M, -Ms, Ms);
441 return postFilters_[ch].processSample((T(1) - alpha) * M, 0);
449 static constexpr int kMaxCh = 16;
450 std::array<Biquad<T, 1>, kMaxCh> lpFilters_;
451 int numChannels_ = 0;
452 double lastSampleRate_ = 0.0;
457 numChannels_ = std::min(spec.numChannels, kMaxCh);
458 lastSampleRate_ = 0.0;
463 for (
auto& f : lpFilters_) f.reset();
471 if (spec.sampleRate == lastSampleRate_)
return;
472 lastSampleRate_ = spec.sampleRate;
475 for (
int ch = 0; ch < numChannels_; ++ch)
476 lpFilters_[ch].setCoeffs(c);
491 const T low = lpFilters_[ch].processSample(sample, 0);
492 const T high = sample - low;
493 const T bias = character * T(0.2);
494 const T kLo = drive * T(1.4);
495 const T kHi = drive * T(0.85);
496 const T satLow = (
fastTanh((low + bias) * kLo) -
fastTanh(bias * kLo)) / T(1.4);
497 const T satHigh = (
fastTanh((high + bias) * kHi) -
fastTanh(bias * kHi)) / T(0.85);
498 return satLow + satHigh;
506 static constexpr int kMaxCh = 16;
507 std::array<Biquad<T, 1>, kMaxCh> aaFilters_;
508 std::array<T, kMaxCh> lastSample_ {};
509 std::array<int, kMaxCh> counter_ {};
510 int numChannels_ = 0;
516 numChannels_ = std::min(spec.numChannels, kMaxCh);
521 for (
auto& f : aaFilters_) f.reset();
522 lastSample_.fill(T(0));
529 T clamped = std::clamp(drive, T(1), T(100));
530 reduction_ = std::max(1,
static_cast<int>(
mapRange(clamped, T(1), T(100), T(1), T(50))));
533 for (
int ch = 0; ch < numChannels_; ++ch)
534 aaFilters_[ch].setCoeffs(c);
539 T filtered = aaFilters_[ch].processSample(sample, 0);
540 if (++counter_[ch] >= reduction_)
543 lastSample_[ch] = filtered;
545 return lastSample_[ch];
572 tube_.setAntialias(this->
antialias_.load(std::memory_order_relaxed));
573 tape_.
update(drive * T(0.6), character, spec);
574 xfmr_.
update(drive * T(0.8), character, spec);
583 T tubeOut = tube_.processSample(sample, drive * T(0.5), character, ch) * T(2);
584 T tapeOut = tape_.
processSample(tubeOut, drive * T(0.6), character, ch) * (T(1) / T(0.6));
585 return xfmr_.
processSample(tapeOut, drive * T(0.8), character, ch);
610template <
typename SampleType>
613 static_assert(std::is_floating_point_v<SampleType>,
614 "Saturation: SampleType must be float or double.");
646 pool_[0] = std::make_unique<detail::TubeAlgorithm<SampleType>>();
647 pool_[1] = std::make_unique<detail::TapeAlgorithm<SampleType>>();
648 pool_[2] = std::make_unique<detail::TransformerAlgorithm<SampleType>>();
649 pool_[3] = std::make_unique<detail::TanhAlgorithm<SampleType>>();
650 pool_[4] = std::make_unique<detail::HardClipAlgorithm<SampleType>>();
651 pool_[5] = std::make_unique<detail::ExciterAlgorithm<SampleType>>();
652 pool_[6] = std::make_unique<detail::WavefolderAlgorithm<SampleType>>();
653 pool_[7] = std::make_unique<detail::BitcrusherAlgorithm<SampleType>>();
654 pool_[8] = std::make_unique<detail::DownsampleAlgorithm<SampleType>>();
655 pool_[9] = std::make_unique<detail::MultiStageAlgorithm<SampleType>>();
658 next_.store(
nullptr);
680 for (
auto& algo :
pool_)
681 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();
803 const int numSamples = buffer.getNumSamples();
804 constexpr int kCoefRefresh = 16;
805 for (
int i = 0; i < numSamples; i += kCoefRefresh)
807 const int chunk = std::min(kCoefRefresh, numSamples - i);
809 for (
int k = 0; k < chunk; ++k)
818 auto subView = buffer.getSubView(i, chunk);
834 const int keepChannel =
843 if (keepChannel >= 0 && keepChannel < upView.getNumChannels() && upSamples > 0)
845 static_cast<std::size_t
>(upSamples) *
sizeof(SampleType));
849 if (keepChannel >= 0 && keepChannel < upView.getNumChannels() && upSamples > 0)
851 static_cast<std::size_t
>(upSamples) *
sizeof(SampleType));
858 if (keepChannel >= 0 && keepChannel < buffer.getNumChannels() && baseSamples > 0)
860 static_cast<std::size_t
>(baseSamples) *
sizeof(SampleType));
864 if (keepChannel >= 0 && keepChannel < buffer.getNumChannels() && baseSamples > 0)
866 static_cast<std::size_t
>(baseSamples) *
sizeof(SampleType));
878 const int numSamples = buffer.getNumSamples();
879 constexpr int kCoefRefresh = 16;
880 for (
int i = 0; i < numSamples; i += kCoefRefresh)
882 const int chunk = std::min(kCoefRefresh, numSamples - i);
885 for (
int k = 0; k < chunk; ++k)
901 auto subView = buffer.getSubView(i, chunk);
915 for (
int ch = 0; ch < nCh; ++ch)
917 SampleType* wet = buffer.getChannel(ch);
919 for (
int i = 0; i < nS; ++i) wet[i] -= dry[i];
1028 for (
auto& a :
pool_)
if (a) a->setAntialias(on);
1039 if (!std::isfinite(amount))
return;
1040 slewSensitivity_.store(std::clamp(amount, SampleType(0), SampleType(1)), std::memory_order_relaxed);
1056 pushParam([&](
auto& p){ p.postFilterTiltFreq = centerHz; p.postFilterTiltGain = amountDb; });
1069 if (factor < 1 || (factor & (factor - 1)) != 0)
return;
1073 oversampler_ = std::make_unique<Oversampling<SampleType>>(factor);
1146 w.
write(
"mix",
static_cast<float>(p.
mix));
1171 setDrive(
static_cast<SampleType
>(r.
read(
"drive", 0.0f)));
1172 setMix(
static_cast<SampleType
>(r.
read(
"mix", 1.0f)));
1177 static_cast<SampleType
>(r.
read(
"tiltGain", 0.0f)));
1194 SampleType
mix = SampleType(1);
1212 const auto keepFinite = [](SampleType& v, SampleType old)
noexcept
1214 if (!std::isfinite(v)) v = old;
1216 keepFinite(p.driveDb, prev.driveDb);
1217 keepFinite(p.mix, prev.mix);
1218 keepFinite(p.character, prev.character);
1219 keepFinite(p.analogDrift, prev.analogDrift);
1220 keepFinite(p.preFilterHpFreq, prev.preFilterHpFreq);
1221 keepFinite(p.postFilterTiltFreq, prev.postFilterTiltFreq);
1222 keepFinite(p.postFilterTiltGain, prev.postFilterTiltGain);
1223 keepFinite(p.outputGain, prev.outputGain);
1225 const auto clampEnum = [](
auto& e,
int hi)
noexcept
1227 using E = std::remove_reference_t<
decltype(e)>;
1228 e =
static_cast<E
>(std::clamp(
static_cast<int>(e), 0, hi));
1235 template <
typename Fn>
1293 auto* nxt =
next_.load();
1294 if (requested == act)
1310 next_.store(
nullptr);
1314 else if (requested == nxt)
1329 next_.store(requested);
1337 auto* primary =
active_.load();
1338 auto* secondary =
next_.load();
1348 const int nS = buffer.getNumSamples();
1357 if (primary) primary->update(driveGainTarget, characterTarget, updateSpec);
1358 if (secondary) secondary->update(driveGainTarget, characterTarget, updateSpec);
1360 SampleType peakInOriginal = SampleType(0);
1361 for (
int ch = 0; ch < nCh; ++ch) {
1362 const SampleType* d = buffer.getChannel(ch);
1363 for (
int i = 0; i < nS; ++i) peakInOriginal = std::max(peakInOriginal, std::abs(d[i]));
1368 if (slewAmt > SampleType(0))
1370 for (
int ch = 0; ch < nCh; ++ch) {
1371 SampleType* data = buffer.getChannel(ch);
1372 for (
int i = 0; i < nS; ++i) {
1373 SampleType dry = data[i];
1375 data[i] += std::tanh(std::abs(delta)) * slewAmt * dry;
1383 bool useDrift = driftIntensity > 0.01f;
1387 for (
int i = 0; i < nS; ++i) {
1396 for (
int ch = 0; ch < nCh; ++ch) {
1397 driftView.getChannel(ch)[i] =
1398 SampleType(1) +
static_cast<SampleType
>(driftS) * (ch == 0 ? noiseL : noiseR);
1407 for (
int ch = 0; ch < nCh; ++ch)
1408 std::memcpy(tempView.getChannel(ch), buffer.getChannel(ch),
static_cast<std::size_t
>(nS) *
sizeof(SampleType));
1413 for (
int i = 0; i < nS; ++i) {
1415 for (
int ch = 0; ch < nCh; ++ch) {
1416 SampleType* out = buffer.getChannel(ch);
1417 const SampleType* alt = tempView.getChannel(ch);
1418 out[i] = out[i] * fade + alt[i] * (SampleType(1) - fade);
1426 next_.store(
nullptr);
1427 if (primary) primary->reset();
1432 next_.store(
nullptr);
1446 SampleType peakOut = SampleType(0);
1447 for (
int ch = 0; ch < nCh; ++ch) {
1448 const SampleType* d = buffer.getChannel(ch);
1449 for (
int i = 0; i < nS; ++i) peakOut = std::max(peakOut, std::abs(d[i]));
1452 SampleType peakInDriven = peakInOriginal * driveGainTarget;
1453 if (peakInDriven > SampleType(1e-6)) {
1454 SampleType ratio = std::min(peakOut / peakInDriven, SampleType(1));
1466 const int nCh = std::min({ buffer.getNumChannels(),
1468 const int nS = std::min(buffer.getNumSamples(),
1471 for (
int ch = 0; ch < nCh; ++ch)
1473 SampleType* wetData = buffer.getChannel(ch);
1475 for (
int i = 0; i < nS; ++i)
1477 SampleType dry = dryData[i];
1478 SampleType wet = wetData[i];
1479 SampleType avg = (std::abs(
prevBlendSample_[ch]) + std::abs(dry)) * SampleType(0.5);
1480 SampleType apply = std::clamp(avg, SampleType(0), SampleType(1));
1481 wetData[i] = dry * (SampleType(1) - apply) + wet * apply;
1490 if (!baseAlgo)
return;
1491 switch (baseAlgo->getType())
1506 template <
typename ExactAlgo>
1512 const int nS = buffer.getNumSamples();
1515 for (
int i = 0; i < nS; ++i)
1520 for (
int ch = 0; ch < nCh; ++ch)
1522 SampleType drift = useDrift ? driftView.getChannel(ch)[i] : SampleType(1);
1523 SampleType* data = buffer.getChannel(ch);
1524 data[i] = algo->processSample(data[i], driveGainS * drift, charS * drift, ch);
1534 buffer.applyGain(
decibelsToGain(
static_cast<SampleType
>(targetGainDb)));
1538 const int nCh = buffer.getNumChannels();
1539 const int nS = buffer.getNumSamples();
1540 for (
int i = 0; i < nS; ++i)
1543 for (
int ch = 0; ch < nCh; ++ch) buffer.getChannel(ch)[i] *= gain;
1553 std::atomic<detail::SaturationAlgorithm<SampleType>*>
active_ {
nullptr };
1554 std::atomic<detail::SaturationAlgorithm<SampleType>*>
next_ {
nullptr };
Removes DC offset from audio signals with configurable filter order.
Mid/Side stereo encoding and decoding for real-time audio.
Main generator class for analog-style random modulation.
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.
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.
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
The core audio processing pipeline.
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
void processBlock(AudioBufferView< SampleType > buffer) noexcept
Processes an audio block in-place (AudioProcessor standard contract).
int getLatency() const noexcept
Alias of getLatencySamples() following the framework-wide latency reporting name, so ProcessorChain::...
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
Reports the processor's algorithmic latency in 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