69 feedbackMode_.store(mode, std::memory_order_relaxed);
75 return feedbackMode_.load(std::memory_order_relaxed);
90 if (!spec.
isValid() || !(maxDelaySeconds >= 0.0))
return;
91 sampleRate_ =
static_cast<SampleType
>(spec.
sampleRate);
101 const double capacityD =
102 std::min(std::ceil(maxDelaySeconds * spec.
sampleRate) + blockSize_,
104 maxDelaySamples_ = nextPow2(
static_cast<int>(capacityD));
105 bufferMask_ = maxDelaySamples_ - 1;
107 delayBuffer_.
resize(numChannels_, maxDelaySamples_);
108 wetBuffer_.
resize(numChannels_, blockSize_);
109 lastWetSamples_ = blockSize_;
111 float timeMs = smoothingTimeMs_.load(std::memory_order_relaxed);
112 for (
int ch = 0; ch < std::min(numChannels_,
kMaxChannels); ++ch)
114 resetChannelSmoothers(states_[ch], timeMs, 0.0f);
115 writeIndices_[ch] = 0;
121 prepared_.store(
true, std::memory_order_release);
131 prepare(spec, maxDelayMs / 1000.0);
139 delayBuffer_.
clear();
143 writeIndices_[ch] = 0;
144 resetChannelState(states_[ch]);
157 smootherType_.store(type, std::memory_order_relaxed);
158 smootherDirty_.store(
true, std::memory_order_release);
167 if (!std::isfinite(ms))
return;
168 smoothingTimeMs_.store(std::max(0.0f, ms), std::memory_order_relaxed);
169 smootherDirty_.store(
true, std::memory_order_release);
182 if (!std::isfinite(samples))
return;
183 samples = std::clamp(samples, SampleType(0),
184 static_cast<SampleType
>(std::max(0, maxDelaySamples_ - 1)));
185 globalDelay_.store(samples, std::memory_order_relaxed);
190 delayTargetDirty_.store(
true, std::memory_order_release);
200 [[nodiscard]] SampleType
getCurrentDelaySamples() const noexcept {
return globalDelay_.load(std::memory_order_relaxed); }
210 if (!std::isfinite(gain))
return;
211 feedbackGain_.store(gain, std::memory_order_relaxed);
218 if (!std::isfinite(freq))
return;
219 fbLpCoef_.store((freq > 0) ? calcLpCoef(freq) : SampleType(0), std::memory_order_relaxed);
220 fbLpHzShadow_.store(freq, std::memory_order_relaxed);
227 if (!std::isfinite(freq))
return;
228 fbHpCoef_.store((freq > 0) ? calcHpCoef(freq) : SampleType(0), std::memory_order_relaxed);
229 fbHpHzShadow_.store(freq, std::memory_order_relaxed);
242 if (ch < 0 || ch >= numChannels_ || !prepared_.load(std::memory_order_acquire))
return input;
243 maybeUpdateSmoothers();
245 auto& s = states_[ch];
246 auto st = smootherType_.load(std::memory_order_relaxed);
248 ? globalDelay_.load(std::memory_order_relaxed) : advanceSmoother(s, st);
250 SampleType out = processSampleInternal(ch, input, delay, s,
251 feedbackGain_.load(std::memory_order_relaxed),
252 fbLpCoef_.load(std::memory_order_relaxed),
253 fbHpCoef_.load(std::memory_order_relaxed));
254 advanceWriteIndexUnchecked(ch);
274 SampleType feedback = 0, SampleType lpHz = 0, SampleType hpHz = 0) noexcept
276 if (!prepared_.load(std::memory_order_acquire))
return;
282 maybeUpdateSmoothers();
288 const auto smoothType = smootherType_.load(std::memory_order_relaxed);
289 const SampleType targetDelay = globalDelay_.load(std::memory_order_relaxed);
290 const SampleType fb = feedbackGain_.load(std::memory_order_relaxed);
291 const SampleType lpC = fbLpCoef_.load(std::memory_order_relaxed);
292 const SampleType hpC = fbHpCoef_.load(std::memory_order_relaxed);
294 for (
int ch = 0; ch < nCh; ++ch)
297 auto& s = states_[ch];
299 for (
int i = 0; i < nS; ++i)
301 SampleType currentDelay = (smoothType ==
SmootherType::None) ? targetDelay : advanceSmoother(s, smoothType);
302 data[i] = processSampleInternal(ch, data[i], currentDelay, s, fb, lpC, hpC);
303 advanceWriteIndexUnchecked(ch);
312 SampleType feedback = 0, SampleType lpHz = 0, SampleType hpHz = 0) noexcept
314 if (ch < 0 || ch >= numChannels_ || !prepared_.load(std::memory_order_acquire))
return;
320 maybeUpdateSmoothers();
325 const auto smoothType = smootherType_.load(std::memory_order_relaxed);
326 const SampleType targetDelay = globalDelay_.load(std::memory_order_relaxed);
327 const SampleType fb = feedbackGain_.load(std::memory_order_relaxed);
328 const SampleType lpC = fbLpCoef_.load(std::memory_order_relaxed);
329 const SampleType hpC = fbHpCoef_.load(std::memory_order_relaxed);
331 auto& s = states_[ch];
333 for (
int i = 0; i < nS; ++i)
335 SampleType currentDelay = (smoothType ==
SmootherType::None) ? targetDelay : advanceSmoother(s, smoothType);
336 data[i] = processSampleInternal(ch, data[i], currentDelay, s, fb, lpC, hpC);
337 advanceWriteIndexUnchecked(ch);
346 pushDryToWetImpl([&](
int ch) {
return dry.getChannel(ch); }, dry.getNumChannels(), dry.getNumSamples());
352 pushDryToWetImpl([&](
int ch) {
return dry.getChannel(ch); }, dry.getNumChannels(), dry.getNumSamples());
360 void processWet(SampleType delayMs, SampleType feedback = 0, SampleType lpHz = 0, SampleType hpHz = 0) noexcept
362 processBlock(wetBuffer_.
toView().getSubView(0, lastWetSamples_), delayMs, feedback, lpHz, hpHz);
373 void processPingPong(SampleType delayMs, SampleType feedback = 0, SampleType lpHz = 0, SampleType hpHz = 0) noexcept
375 if (wetBuffer_.
getNumChannels() < 2 || !prepared_.load(std::memory_order_acquire))
return;
381 maybeUpdateSmoothers();
385 const int nS = std::min(lastWetSamples_, wetBuffer_.
getNumSamples());
387 const auto smoothType = smootherType_.load(std::memory_order_relaxed);
388 const SampleType targetDelay = globalDelay_.load(std::memory_order_relaxed);
389 const SampleType fb = feedbackGain_.load(std::memory_order_relaxed);
390 const SampleType lpC = fbLpCoef_.load(std::memory_order_relaxed);
391 const SampleType hpC = fbHpCoef_.load(std::memory_order_relaxed);
394 for (
int i = 0; i < nS; ++i)
396 auto& sL = states_[0];
397 auto& sR = states_[1];
399 SampleType currentDelay = (smoothType ==
SmootherType::None) ? targetDelay : advanceSmoother(sL, smoothType);
400 advanceSmoother(sR, smoothType);
407 SampleType inL = L[i] + sL.pingPongFb;
408 SampleType inR = R[i] + sR.pingPongFb;
410 SampleType outL = processSampleInternal(0, inL, currentDelay, sL, SampleType(0), lpC, hpC);
411 SampleType outR = processSampleInternal(1, inR, currentDelay, sR, SampleType(0), lpC, hpC);
413 sR.pingPongFb = saturateFeedback(processFbFilters(1, outL * fb, lpC, hpC), analogFb);
414 sL.pingPongFb = saturateFeedback(processFbFilters(0, outR * fb, lpC, hpC), analogFb);
418 advanceWriteIndexUnchecked(0);
419 advanceWriteIndexUnchecked(1);
432 mix = std::min(SampleType(1), std::max(SampleType(0), mix));
433 auto st = smootherType_.load(std::memory_order_relaxed);
437 const int nS = std::min(dry.getNumSamples(), wetBuffer_.
getNumSamples());
438 const int nCh = std::min(dry.getNumChannels(), wetBuffer_.
getNumChannels());
445 std::array<SampleType*, kMaxChannels> d {};
446 std::array<const SampleType*, kMaxChannels> w {};
447 for (
int ch = 0; ch < nCh; ++ch)
449 d[ch] = dry.getChannel(ch);
452 for (
int i = 0; i < nS; ++i)
455 ?
static_cast<SampleType
>(mixSmoother_.
getNextValue()) : mix;
456 for (
int ch = 0; ch < nCh; ++ch)
457 d[ch][i] = d[ch][i] * (SampleType(1) - m) + w[ch][i] * m;
469 [[nodiscard]] std::vector<uint8_t>
getState()
const
472 w.
write(
"delaySamples", globalDelay_.load(std::memory_order_relaxed));
473 w.
write(
"feedback", feedbackGain_.load(std::memory_order_relaxed));
474 w.
write(
"fbLpHz", fbLpHzShadow_.load(std::memory_order_relaxed));
475 w.
write(
"fbHpHz", fbHpHzShadow_.load(std::memory_order_relaxed));
476 w.
write(
"smoother",
static_cast<int32_t
>(smootherType_.load(std::memory_order_relaxed)));
477 w.
write(
"smoothingMs", smoothingTimeMs_.load(std::memory_order_relaxed));
478 w.
write(
"fbMode",
static_cast<int32_t
>(feedbackMode_.load(std::memory_order_relaxed)));
531 advanceWriteIndexUnchecked(ch);
536 void advanceWriteIndexUnchecked(
int ch)
noexcept { writeIndices_[ch] = (writeIndices_[ch] + 1) & bufferMask_; }
541 [[nodiscard]] SampleType saturateFeedback(SampleType x,
bool analog)
const noexcept
543 return analog ? std::tanh(x) : std::clamp(x, SampleType(-2), SampleType(2));
546 template <
typename ChannelFn>
547 void pushDryToWetImpl(ChannelFn&& getCh,
int dryCh,
int drySamples)
noexcept
550 const int nS = std::min(drySamples, wetBuffer_.
getNumSamples());
551 for (
int ch = 0; ch < nCh; ++ch)
552 std::memcpy(wetBuffer_.
getChannel(ch), getCh(ch),
static_cast<std::size_t
>(nS) *
sizeof(SampleType));
553 lastWetSamples_ = nS;
556 void resetChannelState(ChannelState& s)
noexcept
558 s.fbLpZ1 = s.fbHpZ1 = s.lastFeedback = s.pingPongFb = 0;
559 s.currentDelay = s.targetDelay = 0;
562 void resetChannelSmoothers(ChannelState& s,
float timeMs,
float init)
noexcept
564 double sr =
static_cast<double>(sampleRate_);
565 s.lin.reset(sr, timeMs, init);
566 s.exp.reset(sr, timeMs, std::max(init, 1e-6f));
567 s.onePole.reset(sr, timeMs, init);
568 s.multi2.reset(sr, timeMs, init);
569 s.asym.reset(sr, timeMs / 5.0f, timeMs, init);
570 float timeSec = timeMs / 1000.0f;
571 float maxRate =
static_cast<float>(maxDelaySamples_) / std::max(timeSec, 1e-6f);
572 s.slew.reset(sr, maxRate, init);
573 s.svf.reset(sr, timeMs, 0.707f, init);
574 s.butter.reset(sr, timeMs, init);
575 s.crit.reset(sr, timeMs, init);
578 void maybeUpdateSmoothers() noexcept
581 if (delayTargetDirty_.exchange(
false, std::memory_order_acquire))
582 updateSmootherTargets(globalDelay_.load(std::memory_order_relaxed));
587 if (!smootherDirty_.exchange(
false, std::memory_order_acquire))
return;
588 float timeMs = smoothingTimeMs_.load(std::memory_order_relaxed);
589 for (
int ch = 0; ch < numChannels_; ++ch)
591 auto& s = states_[ch];
592 float cur =
static_cast<float>(s.currentDelay);
593 float tgt =
static_cast<float>(s.targetDelay);
594 resetChannelSmoothers(s, timeMs, cur);
595 setSmootherTarget(s,
static_cast<SampleType
>(tgt));
600 void updateSmootherTargets(SampleType target)
noexcept
603 for (
int ch = 0; ch < numChannels_; ++ch)
604 setSmootherTarget(states_[ch], target);
607 void setSmootherTarget(ChannelState& s, SampleType target)
noexcept
609 s.targetDelay = target;
610 float t =
static_cast<float>(target);
611 switch (smootherType_.load(std::memory_order_relaxed))
626 inline SampleType advanceSmoother(ChannelState& s,
SmootherType st)
noexcept
640 default: val =
static_cast<float>(s.targetDelay);
642 s.currentDelay =
static_cast<SampleType
>(val);
643 return s.currentDelay;
646 inline SampleType processSampleInternal(
int ch, SampleType input, SampleType delaySamples,
647 ChannelState& s, SampleType fbMult,
648 SampleType lpC, SampleType hpC)
noexcept
656 if (!std::isfinite(input)) input = SampleType(0);
668 constexpr SampleType kMinHermiteDelay = SampleType(3);
669 delaySamples = std::clamp(delaySamples, kMinHermiteDelay,
670 static_cast<SampleType
>(maxDelaySamples_ - 1));
672 SampleType* data = delayBuffer_.
getChannel(ch);
673 int writeIdx = writeIndices_[ch];
675 SampleType readPos =
static_cast<SampleType
>(writeIdx) - delaySamples;
676 if (readPos < SampleType(0)) readPos +=
static_cast<SampleType
>(maxDelaySamples_);
679 int idx0 =
static_cast<int>(readPos) & bufferMask_;
680 SampleType frac = readPos -
static_cast<SampleType
>(idx0);
682 int idxM1 = (idx0 - 1 + maxDelaySamples_) & bufferMask_;
683 int idx1 = (idx0 + 1) & bufferMask_;
684 int idx2 = (idx0 + 2) & bufferMask_;
686 SampleType ym1 = data[idxM1];
687 SampleType y0 = data[idx0];
688 SampleType y1 = data[idx1];
689 SampleType y2 = data[idx2];
692 SampleType c = (y1 - ym1) * SampleType(0.5);
693 SampleType v = y0 - y1;
694 SampleType w = c + v;
695 SampleType a = w + v + (y2 - y0) * SampleType(0.5);
696 SampleType b = w + a;
697 SampleType delayed = ((((a * frac) - b) * frac + c) * frac + y0);
700 SampleType fbInput = delayed * fbMult;
701 if (fbInput != SampleType(0))
703 fbInput = processFbFilters(ch, fbInput, lpC, hpC);
704 fbInput = saturateFeedback(fbInput,
707 s.lastFeedback = fbInput;
709 data[writeIdx] = input + s.lastFeedback;
713 inline SampleType processFbFilters(
int ch, SampleType sample, SampleType lpC, SampleType hpC)
noexcept
715 auto& s = states_[ch];
716 SampleType out = sample;
719 out += SampleType(1e-18);
721 if (hpC > SampleType(0))
723 s.fbHpZ1 = out * (SampleType(1) - hpC) + s.fbHpZ1 * hpC;
726 if (lpC > SampleType(0))
728 s.fbLpZ1 = out * (SampleType(1) - lpC) + s.fbLpZ1 * lpC;
732 out -= SampleType(1e-18);
737 SampleType calcOnePoleCoef(SampleType freq)
const noexcept {
return std::exp(-twoPi<SampleType> * freq / sampleRate_); }
738 SampleType calcLpCoef(SampleType freq)
const noexcept {
return calcOnePoleCoef(freq); }
739 SampleType calcHpCoef(SampleType freq)
const noexcept {
return calcOnePoleCoef(freq); }
741 static int nextPow2(
int v)
noexcept
744 while (r < v) r <<= 1;
749 std::atomic<bool> prepared_{
false };
750 AudioBuffer<SampleType> delayBuffer_, wetBuffer_;
751 std::array<ChannelState, kMaxChannels> states_ {};
752 std::array<int, kMaxChannels> writeIndices_ {};
754 int maxDelaySamples_ = 0, bufferMask_ = 0;
755 int numChannels_ = 0, blockSize_ = 0;
756 int lastWetSamples_ = 0;
757 SampleType sampleRate_ = SampleType(48000);
759 std::atomic<SampleType> globalDelay_ { SampleType(0) };
760 std::atomic<SampleType> feedbackGain_ { SampleType(0) };
761 std::atomic<SampleType> fbLpCoef_ { SampleType(0) };
762 std::atomic<SampleType> fbLpHzShadow_ { SampleType(0) };
763 std::atomic<SampleType> fbHpHzShadow_ { SampleType(0) };
764 std::atomic<SampleType> fbHpCoef_ { SampleType(0) };
767 std::atomic<float> smoothingTimeMs_ { 20.0f };
768 std::atomic<bool> smootherDirty_ {
false };
769 std::atomic<bool> delayTargetDirty_ {
false };
772 Smoothers::LinearSmoother mixSmoother_;