77 feedbackMode_.store(mode, std::memory_order_relaxed);
83 return feedbackMode_.load(std::memory_order_relaxed);
107 if (!spec.
isValid() || !(maxDelaySeconds >= 0.0))
return;
108 sampleRate_ =
static_cast<SampleType
>(spec.
sampleRate);
118 const double capacityD =
119 std::min(std::ceil(maxDelaySeconds * spec.
sampleRate) + blockSize_,
121 maxDelaySamples_ = nextPow2(
static_cast<int>(capacityD));
122 bufferMask_ = maxDelaySamples_ - 1;
124 delayBuffer_.
resize(numChannels_, maxDelaySamples_);
125 wetBuffer_.
resize(numChannels_, blockSize_);
126 lastWetSamples_ = blockSize_;
128 float timeMs = smoothingTimeMs_.load(std::memory_order_relaxed);
129 for (
int ch = 0; ch < std::min(numChannels_,
kMaxChannels); ++ch)
131 resetChannelSmoothers(states_[ch], timeMs, 0.0f);
132 writeIndices_[ch] = 0;
136 insertMixMaxStep_ =
static_cast<SampleType
>(1.0 / std::max(1.0, spec.
sampleRate * 0.02));
139 prepared_.store(
true, std::memory_order_release);
149 prepare(spec, maxDelayMs / 1000.0);
157 delayBuffer_.
clear();
161 writeIndices_[ch] = 0;
162 resetChannelState(states_[ch]);
165 insertMix_ = mix_.load(std::memory_order_relaxed);
176 smootherType_.store(type, std::memory_order_relaxed);
177 smootherDirty_.store(
true, std::memory_order_release);
186 if (!std::isfinite(ms))
return;
187 smoothingTimeMs_.store(std::max(0.0f, ms), std::memory_order_relaxed);
188 smootherDirty_.store(
true, std::memory_order_release);
201 if (!std::isfinite(samples))
return;
202 samples = std::clamp(samples, SampleType(0),
203 static_cast<SampleType
>(std::max(0, maxDelaySamples_ - 1)));
204 globalDelay_.store(samples, std::memory_order_relaxed);
209 delayTargetDirty_.store(
true, std::memory_order_release);
219 [[nodiscard]] SampleType
getCurrentDelaySamples() const noexcept {
return globalDelay_.load(std::memory_order_relaxed); }
229 if (!std::isfinite(gain))
return;
230 feedbackGain_.store(gain, std::memory_order_relaxed);
237 if (!std::isfinite(freq))
return;
238 fbLpCoef_.store((freq > 0) ? calcLpCoef(freq) : SampleType(0), std::memory_order_relaxed);
239 fbLpHzShadow_.store(freq, std::memory_order_relaxed);
246 if (!std::isfinite(freq))
return;
247 fbHpCoef_.store((freq > 0) ? calcHpCoef(freq) : SampleType(0), std::memory_order_relaxed);
248 fbHpHzShadow_.store(freq, std::memory_order_relaxed);
261 if (ch < 0 || ch >= numChannels_ || !prepared_.load(std::memory_order_acquire))
return input;
262 maybeUpdateSmoothers();
264 auto& s = states_[ch];
265 auto st = smootherType_.load(std::memory_order_relaxed);
267 ? globalDelay_.load(std::memory_order_relaxed) : advanceSmoother(s, st);
269 SampleType out = processSampleInternal(ch, input, delay, s,
270 feedbackGain_.load(std::memory_order_relaxed),
271 fbLpCoef_.load(std::memory_order_relaxed),
272 fbHpCoef_.load(std::memory_order_relaxed));
273 advanceWriteIndexUnchecked(ch);
290 if (!prepared_.load(std::memory_order_acquire))
return;
291 maybeUpdateSmoothers();
293 const int nS = buffer.getNumSamples();
294 const int nCh = std::min(buffer.getNumChannels(), numChannels_);
295 const auto smoothType = smootherType_.load(std::memory_order_relaxed);
296 const SampleType targetDelay = globalDelay_.load(std::memory_order_relaxed);
297 const SampleType fb = feedbackGain_.load(std::memory_order_relaxed);
298 const SampleType lpC = fbLpCoef_.load(std::memory_order_relaxed);
299 const SampleType hpC = fbHpCoef_.load(std::memory_order_relaxed);
300 const SampleType mixTarget = mix_.load(std::memory_order_relaxed);
301 const SampleType mixStart = insertMix_;
302 const bool pingPong = pingPong_.load(std::memory_order_relaxed) && nCh >= 2;
309 SampleType* L = buffer.getChannel(0);
310 SampleType* R = buffer.getChannel(1);
311 auto& sL = states_[0];
312 auto& sR = states_[1];
313 for (
int i = 0; i < nS; ++i)
316 ? targetDelay : advanceSmoother(sL, smoothType);
317 advanceSmoother(sR, smoothType);
318 const SampleType dryL = L[i], dryR = R[i];
319 const SampleType outL = processSampleInternal(0, dryL + sL.pingPongFb, currentDelay, sL, SampleType(0), lpC, hpC);
320 const SampleType outR = processSampleInternal(1, dryR + sR.pingPongFb, currentDelay, sR, SampleType(0), lpC, hpC);
321 sR.pingPongFb = saturateFeedback(processFbFilters(1, outL * fb, lpC, hpC), analogFb);
322 sL.pingPongFb = saturateFeedback(processFbFilters(0, outR * fb, lpC, hpC), analogFb);
323 advanceWriteIndexUnchecked(0);
324 advanceWriteIndexUnchecked(1);
325 const SampleType m =
moveTowards(mixStart, mixTarget,
326 insertMixMaxStep_ *
static_cast<SampleType
>(i + 1));
327 L[i] = dryL + (outL - dryL) * m;
328 R[i] = dryR + (outR - dryR) * m;
332 for (
int ch = pingPong ? 2 : 0; ch < nCh; ++ch)
334 SampleType* data = buffer.getChannel(ch);
335 auto& s = states_[ch];
336 for (
int i = 0; i < nS; ++i)
339 ? targetDelay : advanceSmoother(s, smoothType);
340 const SampleType dry = data[i];
341 const SampleType wet = processSampleInternal(ch, dry, currentDelay, s, fb, lpC, hpC);
342 advanceWriteIndexUnchecked(ch);
343 const SampleType m =
moveTowards(mixStart, mixTarget,
344 insertMixMaxStep_ *
static_cast<SampleType
>(i + 1));
345 data[i] = dry + (wet - dry) * m;
348 insertMix_ =
moveTowards(mixStart, mixTarget, insertMixMaxStep_ *
static_cast<SampleType
>(nS));
358 if (!std::isfinite(mix))
return;
359 mix_.store(std::clamp(mix, SampleType(0), SampleType(1)), std::memory_order_relaxed);
363 [[nodiscard]] SampleType
getMix() const noexcept {
return mix_.load(std::memory_order_relaxed); }
373 void setPingPong(
bool on)
noexcept { pingPong_.store(on, std::memory_order_relaxed); }
376 [[nodiscard]]
bool getPingPong() const noexcept {
return pingPong_.load(std::memory_order_relaxed); }
395 SampleType feedback = 0, SampleType lpHz = 0, SampleType hpHz = 0) noexcept
397 if (!prepared_.load(std::memory_order_acquire))
return;
403 maybeUpdateSmoothers();
409 const auto smoothType = smootherType_.load(std::memory_order_relaxed);
410 const SampleType targetDelay = globalDelay_.load(std::memory_order_relaxed);
411 const SampleType fb = feedbackGain_.load(std::memory_order_relaxed);
412 const SampleType lpC = fbLpCoef_.load(std::memory_order_relaxed);
413 const SampleType hpC = fbHpCoef_.load(std::memory_order_relaxed);
415 for (
int ch = 0; ch < nCh; ++ch)
418 auto& s = states_[ch];
420 for (
int i = 0; i < nS; ++i)
422 SampleType currentDelay = (smoothType ==
SmootherType::None) ? targetDelay : advanceSmoother(s, smoothType);
423 data[i] = processSampleInternal(ch, data[i], currentDelay, s, fb, lpC, hpC);
424 advanceWriteIndexUnchecked(ch);
433 SampleType feedback = 0, SampleType lpHz = 0, SampleType hpHz = 0) noexcept
435 if (ch < 0 || ch >= numChannels_ || !prepared_.load(std::memory_order_acquire))
return;
441 maybeUpdateSmoothers();
446 const auto smoothType = smootherType_.load(std::memory_order_relaxed);
447 const SampleType targetDelay = globalDelay_.load(std::memory_order_relaxed);
448 const SampleType fb = feedbackGain_.load(std::memory_order_relaxed);
449 const SampleType lpC = fbLpCoef_.load(std::memory_order_relaxed);
450 const SampleType hpC = fbHpCoef_.load(std::memory_order_relaxed);
452 auto& s = states_[ch];
454 for (
int i = 0; i < nS; ++i)
456 SampleType currentDelay = (smoothType ==
SmootherType::None) ? targetDelay : advanceSmoother(s, smoothType);
457 data[i] = processSampleInternal(ch, data[i], currentDelay, s, fb, lpC, hpC);
458 advanceWriteIndexUnchecked(ch);
467 pushDryToWetImpl([&](
int ch) {
return dry.getChannel(ch); }, dry.getNumChannels(), dry.getNumSamples());
473 pushDryToWetImpl([&](
int ch) {
return dry.getChannel(ch); }, dry.getNumChannels(), dry.getNumSamples());
481 void processWet(SampleType delayMs, SampleType feedback = 0, SampleType lpHz = 0, SampleType hpHz = 0) noexcept
483 processBlock(wetBuffer_.
toView().getSubView(0, lastWetSamples_), delayMs, feedback, lpHz, hpHz);
494 void processPingPong(SampleType delayMs, SampleType feedback = 0, SampleType lpHz = 0, SampleType hpHz = 0) noexcept
496 if (wetBuffer_.
getNumChannels() < 2 || !prepared_.load(std::memory_order_acquire))
return;
502 maybeUpdateSmoothers();
506 const int nS = std::min(lastWetSamples_, wetBuffer_.
getNumSamples());
508 const auto smoothType = smootherType_.load(std::memory_order_relaxed);
509 const SampleType targetDelay = globalDelay_.load(std::memory_order_relaxed);
510 const SampleType fb = feedbackGain_.load(std::memory_order_relaxed);
511 const SampleType lpC = fbLpCoef_.load(std::memory_order_relaxed);
512 const SampleType hpC = fbHpCoef_.load(std::memory_order_relaxed);
515 for (
int i = 0; i < nS; ++i)
517 auto& sL = states_[0];
518 auto& sR = states_[1];
520 SampleType currentDelay = (smoothType ==
SmootherType::None) ? targetDelay : advanceSmoother(sL, smoothType);
521 advanceSmoother(sR, smoothType);
528 SampleType inL = L[i] + sL.pingPongFb;
529 SampleType inR = R[i] + sR.pingPongFb;
531 SampleType outL = processSampleInternal(0, inL, currentDelay, sL, SampleType(0), lpC, hpC);
532 SampleType outR = processSampleInternal(1, inR, currentDelay, sR, SampleType(0), lpC, hpC);
534 sR.pingPongFb = saturateFeedback(processFbFilters(1, outL * fb, lpC, hpC), analogFb);
535 sL.pingPongFb = saturateFeedback(processFbFilters(0, outR * fb, lpC, hpC), analogFb);
539 advanceWriteIndexUnchecked(0);
540 advanceWriteIndexUnchecked(1);
553 mix = std::min(SampleType(1), std::max(SampleType(0), mix));
554 auto st = smootherType_.load(std::memory_order_relaxed);
558 const int nS = std::min(dry.getNumSamples(), wetBuffer_.
getNumSamples());
559 const int nCh = std::min(dry.getNumChannels(), wetBuffer_.
getNumChannels());
566 std::array<SampleType*, kMaxChannels> d {};
567 std::array<const SampleType*, kMaxChannels> w {};
568 for (
int ch = 0; ch < nCh; ++ch)
570 d[ch] = dry.getChannel(ch);
573 for (
int i = 0; i < nS; ++i)
576 ?
static_cast<SampleType
>(mixSmoother_.
getNextValue()) : mix;
577 for (
int ch = 0; ch < nCh; ++ch)
578 d[ch][i] = d[ch][i] * (SampleType(1) - m) + w[ch][i] * m;
590 [[nodiscard]] std::vector<uint8_t>
getState()
const
593 w.
write(
"delaySamples", globalDelay_.load(std::memory_order_relaxed));
594 w.
write(
"feedback", feedbackGain_.load(std::memory_order_relaxed));
595 w.
write(
"fbLpHz", fbLpHzShadow_.load(std::memory_order_relaxed));
596 w.
write(
"fbHpHz", fbHpHzShadow_.load(std::memory_order_relaxed));
597 w.
write(
"smoother",
static_cast<int32_t
>(smootherType_.load(std::memory_order_relaxed)));
598 w.
write(
"smoothingMs", smoothingTimeMs_.load(std::memory_order_relaxed));
599 w.
write(
"fbMode",
static_cast<int32_t
>(feedbackMode_.load(std::memory_order_relaxed)));
600 w.
write(
"mix",
static_cast<float>(mix_.load(std::memory_order_relaxed)));
601 w.
write(
"pingPong",
static_cast<int32_t
>(pingPong_.load(std::memory_order_relaxed) ? 1 : 0));
621 setMix(
static_cast<SampleType
>(r.
read(
"mix", 0.3f)));
657 advanceWriteIndexUnchecked(ch);
662 void advanceWriteIndexUnchecked(
int ch)
noexcept { writeIndices_[ch] = (writeIndices_[ch] + 1) & bufferMask_; }
667 [[nodiscard]] SampleType saturateFeedback(SampleType x,
bool analog)
const noexcept
669 return analog ? std::tanh(x) : std::clamp(x, SampleType(-2), SampleType(2));
672 template <
typename ChannelFn>
673 void pushDryToWetImpl(ChannelFn&& getCh,
int dryCh,
int drySamples)
noexcept
676 const int nS = std::min(drySamples, wetBuffer_.
getNumSamples());
677 for (
int ch = 0; ch < nCh; ++ch)
678 std::memcpy(wetBuffer_.
getChannel(ch), getCh(ch),
static_cast<std::size_t
>(nS) *
sizeof(SampleType));
679 lastWetSamples_ = nS;
682 void resetChannelState(ChannelState& s)
noexcept
684 s.fbLpZ1 = s.fbHpZ1 = s.lastFeedback = s.pingPongFb = 0;
685 s.currentDelay = s.targetDelay = 0;
688 void resetChannelSmoothers(ChannelState& s,
float timeMs,
float init)
noexcept
690 double sr =
static_cast<double>(sampleRate_);
691 s.lin.reset(sr, timeMs, init);
692 s.exp.reset(sr, timeMs, std::max(init, 1e-6f));
693 s.onePole.reset(sr, timeMs, init);
694 s.multi2.reset(sr, timeMs, init);
695 s.asym.reset(sr, timeMs / 5.0f, timeMs, init);
696 float timeSec = timeMs / 1000.0f;
697 float maxRate =
static_cast<float>(maxDelaySamples_) / std::max(timeSec, 1e-6f);
698 s.slew.reset(sr, maxRate, init);
699 s.svf.reset(sr, timeMs, 0.707f, init);
700 s.butter.reset(sr, timeMs, init);
701 s.crit.reset(sr, timeMs, init);
704 void maybeUpdateSmoothers() noexcept
707 if (delayTargetDirty_.exchange(
false, std::memory_order_acquire))
708 updateSmootherTargets(globalDelay_.load(std::memory_order_relaxed));
713 if (!smootherDirty_.exchange(
false, std::memory_order_acquire))
return;
714 float timeMs = smoothingTimeMs_.load(std::memory_order_relaxed);
715 for (
int ch = 0; ch < numChannels_; ++ch)
717 auto& s = states_[ch];
718 float cur =
static_cast<float>(s.currentDelay);
719 float tgt =
static_cast<float>(s.targetDelay);
720 resetChannelSmoothers(s, timeMs, cur);
721 setSmootherTarget(s,
static_cast<SampleType
>(tgt));
726 void updateSmootherTargets(SampleType target)
noexcept
729 for (
int ch = 0; ch < numChannels_; ++ch)
730 setSmootherTarget(states_[ch], target);
733 void setSmootherTarget(ChannelState& s, SampleType target)
noexcept
735 s.targetDelay = target;
736 float t =
static_cast<float>(target);
737 switch (smootherType_.load(std::memory_order_relaxed))
752 inline SampleType advanceSmoother(ChannelState& s,
SmootherType st)
noexcept
766 default: val =
static_cast<float>(s.targetDelay);
768 s.currentDelay =
static_cast<SampleType
>(val);
769 return s.currentDelay;
772 inline SampleType processSampleInternal(
int ch, SampleType input, SampleType delaySamples,
773 ChannelState& s, SampleType fbMult,
774 SampleType lpC, SampleType hpC)
noexcept
782 if (!std::isfinite(input)) input = SampleType(0);
794 constexpr SampleType kMinHermiteDelay = SampleType(3);
795 delaySamples = std::clamp(delaySamples, kMinHermiteDelay,
796 static_cast<SampleType
>(maxDelaySamples_ - 1));
798 SampleType* data = delayBuffer_.
getChannel(ch);
799 int writeIdx = writeIndices_[ch];
807 const int dInt =
static_cast<int>(delaySamples);
808 const SampleType dFrac = delaySamples -
static_cast<SampleType
>(dInt);
811 if (dFrac > SampleType(0))
813 idx0 = (writeIdx - dInt - 1) & bufferMask_;
814 frac = SampleType(1) - dFrac;
818 idx0 = (writeIdx - dInt) & bufferMask_;
819 frac = SampleType(0);
824 int idxM1 = (idx0 - 1 + maxDelaySamples_) & bufferMask_;
825 int idx1 = (idx0 + 1) & bufferMask_;
826 int idx2 = (idx0 + 2) & bufferMask_;
828 SampleType ym1 = data[idxM1];
829 SampleType y0 = data[idx0];
830 SampleType y1 = data[idx1];
831 SampleType y2 = data[idx2];
834 SampleType c = (y1 - ym1) * SampleType(0.5);
835 SampleType v = y0 - y1;
836 SampleType w = c + v;
837 SampleType a = w + v + (y2 - y0) * SampleType(0.5);
838 SampleType b = w + a;
839 SampleType delayed = ((((a * frac) - b) * frac + c) * frac + y0);
842 SampleType fbInput = delayed * fbMult;
843 if (fbInput != SampleType(0))
845 fbInput = processFbFilters(ch, fbInput, lpC, hpC);
846 fbInput = saturateFeedback(fbInput,
849 s.lastFeedback = fbInput;
851 data[writeIdx] = input + s.lastFeedback;
855 inline SampleType processFbFilters(
int ch, SampleType sample, SampleType lpC, SampleType hpC)
noexcept
857 auto& s = states_[ch];
858 SampleType out = sample;
861 out += SampleType(1e-18);
863 if (hpC > SampleType(0))
865 s.fbHpZ1 = out * (SampleType(1) - hpC) + s.fbHpZ1 * hpC;
868 if (lpC > SampleType(0))
870 s.fbLpZ1 = out * (SampleType(1) - lpC) + s.fbLpZ1 * lpC;
874 out -= SampleType(1e-18);
879 SampleType calcOnePoleCoef(SampleType freq)
const noexcept {
return std::exp(-twoPi<SampleType> * freq / sampleRate_); }
880 SampleType calcLpCoef(SampleType freq)
const noexcept {
return calcOnePoleCoef(freq); }
881 SampleType calcHpCoef(SampleType freq)
const noexcept {
return calcOnePoleCoef(freq); }
883 static int nextPow2(
int v)
noexcept
886 while (r < v) r <<= 1;
891 std::atomic<bool> prepared_{
false };
892 AudioBuffer<SampleType> delayBuffer_, wetBuffer_;
893 std::array<ChannelState, kMaxChannels> states_ {};
894 std::array<int, kMaxChannels> writeIndices_ {};
896 int maxDelaySamples_ = 0, bufferMask_ = 0;
897 int numChannels_ = 0, blockSize_ = 0;
898 int lastWetSamples_ = 0;
899 SampleType sampleRate_ = SampleType(48000);
901 std::atomic<SampleType> globalDelay_ { SampleType(0) };
902 std::atomic<SampleType> feedbackGain_ { SampleType(0) };
903 std::atomic<SampleType> fbLpCoef_ { SampleType(0) };
904 std::atomic<SampleType> fbLpHzShadow_ { SampleType(0) };
905 std::atomic<SampleType> fbHpHzShadow_ { SampleType(0) };
906 std::atomic<SampleType> fbHpCoef_ { SampleType(0) };
909 std::atomic<float> smoothingTimeMs_ { 20.0f };
910 std::atomic<bool> smootherDirty_ {
false };
911 std::atomic<bool> delayTargetDirty_ {
false };
914 Smoothers::LinearSmoother mixSmoother_;
915 std::atomic<SampleType> mix_ { SampleType(0.3) };
916 std::atomic<bool> pingPong_ {
false };
917 SampleType insertMix_ = SampleType(0.3);
918 SampleType insertMixMaxStep_ = SampleType(1.0 / 960.0);