198#include "../Core/DryWetMixer.h"
199#include "../Core/DspMath.h"
200#include "../Core/AudioSpec.h"
201#include "../Core/AudioBuffer.h"
202#include "../Core/DenormalGuard.h"
203#include "../Core/Biquad.h"
204#include "../Core/StateBlob.h"
205#include "../Core/SimdOps.h"
224template <FloatType T>
271 std::memory_order_relaxed);
273 std::memory_order_relaxed);
275 const auto samples = [sr](
double ms) {
return static_cast<int>(ms * sr / 1000.0) + 8; };
276 for (
int c = 0; c < 2; ++c)
303 const double fc = 0.9 * sr / (2.0 *
springK_);
304 const double qs[2] = { 0.5411961001461970, 1.3065629648763764 };
305 for (
int k = 0; k < 2; ++k)
308 springLP_[k] = {
static_cast<T
>(c.b0),
static_cast<T
>(c.b1),
static_cast<T
>(c.b2),
309 static_cast<T
>(c.a1),
static_cast<T
>(c.a2) };
315 subC_ = {
static_cast<T
>(c.b0),
static_cast<T
>(c.b1),
static_cast<T
>(c.b2),
316 static_cast<T
>(c.a1),
static_cast<T
>(c.a2) };
320 cohC_ = {
static_cast<T
>(c.b0),
static_cast<T
>(c.b1),
static_cast<T
>(c.b2),
321 static_cast<T
>(c.a1),
static_cast<T
>(c.a2) };
338 toneDirty_.store(
true, std::memory_order_relaxed);
355 const int nCh = std::min(buffer.getNumChannels(), 2);
356 const int nS = buffer.getNumSamples();
357 if (nCh == 0 || nS == 0 || !
prepared_)
return;
361 for (
int ch = 0; ch < nCh; ++ch)
363 T* d = buffer.getChannel(ch);
364 for (
int i = 0; i < nS; ++i)
365 if (!std::isfinite(d[i])) d[i] = T(0);
373 T* chL = buffer.getChannel(0);
374 T* chR = nCh >= 2 ? buffer.getChannel(1) :
nullptr;
375 alignas(64) T outL[
kChunk];
376 alignas(64) T outR[
kChunk];
377 for (
int start = 0; start < nS; start +=
kChunk)
379 const int n = std::min(
kChunk, nS - start);
380 processChunk(chL + start, (chR ? chR : chL) + start, outL, outR, n);
383 std::copy(outL, outL + n, chL + start);
384 std::copy(outR, outR + n, chR + start);
388 for (
int i = 0; i < n; ++i)
389 chL[start + i] = (outL[i] + outR[i]) * T(0.5);
393 mixer_.mixWet(buffer,
mix_.load(std::memory_order_relaxed));
413 if (!std::isfinite(input)) input = T(0);
414 T outL = T(0), outR = T(0);
416 return { outL, outR };
425 for (
int c = 0; c < 2; ++c)
429 for (
auto& ap :
inAP_[c]) ap.clear();
430 for (
auto& ap :
outAP_[c]) ap.clear();
450 const T rate =
modRate_.load(std::memory_order_relaxed);
487 const int t = std::clamp(
static_cast<int>(type), 0,
489 type_.store(
static_cast<Type>(t), std::memory_order_relaxed);
524 std::memory_order_relaxed);
539 if (!std::isfinite(seconds))
return;
540 decayTime_.store(std::clamp(seconds, T(0.1), T(30)),
541 std::memory_order_relaxed);
549 if (!std::isfinite(dryWet))
return;
550 mix_.store(std::clamp(dryWet, T(0), T(1)), std::memory_order_relaxed);
564 if (!std::isfinite(size))
return;
565 size_.store(std::clamp(size, T(0.01), T(1)), std::memory_order_relaxed);
578 if (!std::isfinite(amount))
return;
579 T clamped = std::clamp(amount, T(0), T(1));
580 damping_.store(clamped, std::memory_order_relaxed);
581 highDecayMult_.store(T(1) - clamped * T(0.9), std::memory_order_relaxed);
589 if (!std::isfinite(ms))
return;
591 preDelayMs_.store(clamped, std::memory_order_relaxed);
605 if (!std::isfinite(amount))
return;
606 diffusion_.store(std::clamp(amount, T(0), T(1)), std::memory_order_relaxed);
618 if (!std::isfinite(amount))
return;
619 modDepth_.store(std::clamp(amount, T(0), T(1)), std::memory_order_relaxed);
635 if (!std::isfinite(width))
return;
636 width_.store(std::clamp(width, T(0), T(2)), std::memory_order_relaxed);
642 if (!std::isfinite(ms))
return;
644 erToLateMs_.store(clamped, std::memory_order_relaxed);
665 if (!std::isfinite(mult))
return;
666 T clamped = std::clamp(mult, T(0.05), T(1));
668 damping_.store(std::clamp((T(1) - clamped) / T(0.9), T(0), T(1)),
669 std::memory_order_relaxed);
686 if (!std::isfinite(mult))
return;
688 std::memory_order_relaxed);
708 if (!std::isfinite(hz))
return;
710 std::memory_order_relaxed);
725 if (!std::isfinite(hz))
return;
727 std::memory_order_relaxed);
744 if (!std::isfinite(dB))
return;
752 if (!std::isfinite(dB))
return;
760 if (!std::isfinite(hz))
return;
761 modRate_.store(std::clamp(hz, T(0.1), T(5)), std::memory_order_relaxed);
772 if (!std::isfinite(hz))
return;
774 toneDirty_.store(
true, std::memory_order_release);
783 if (!std::isfinite(hz))
return;
785 toneDirty_.store(
true, std::memory_order_release);
792 [[nodiscard]]
Type getType() const noexcept {
return type_.load(std::memory_order_relaxed); }
795 [[nodiscard]] T
getMix() const noexcept {
return mix_.load(std::memory_order_relaxed); }
796 [[nodiscard]] T
getSize() const noexcept {
return size_.load(std::memory_order_relaxed); }
805 [[nodiscard]] T
getWidth() const noexcept {
return width_.load(std::memory_order_relaxed); }
814 [[nodiscard]] std::vector<uint8_t>
getState()
const
817 w.
write(
"type",
static_cast<int32_t
>(
type_.load(std::memory_order_relaxed)));
818 w.
write(
"quality",
static_cast<int32_t
>(
quality_.load(std::memory_order_relaxed)));
819 w.
write(
"decay",
static_cast<float>(
decayTime_.load(std::memory_order_relaxed)));
820 w.
write(
"size",
static_cast<float>(
size_.load(std::memory_order_relaxed)));
821 w.
write(
"damping",
static_cast<float>(
damping_.load(std::memory_order_relaxed)));
822 w.
write(
"diffusion",
static_cast<float>(
diffusion_.load(std::memory_order_relaxed)));
823 w.
write(
"modDepth",
static_cast<float>(
modDepth_.load(std::memory_order_relaxed)));
824 w.
write(
"modRate",
static_cast<float>(
modRate_.load(std::memory_order_relaxed)));
825 w.
write(
"preDelay",
static_cast<float>(
preDelayMs_.load(std::memory_order_relaxed)));
826 w.
write(
"erToLate",
static_cast<float>(
erToLateMs_.load(std::memory_order_relaxed)));
827 w.
write(
"mix",
static_cast<float>(
mix_.load(std::memory_order_relaxed)));
828 w.
write(
"width",
static_cast<float>(
width_.load(std::memory_order_relaxed)));
835 w.
write(
"toneLowCut",
static_cast<float>(
toneLowCutHz_.load(std::memory_order_relaxed)));
836 w.
write(
"toneHighCut",
static_cast<float>(
toneHighCutHz_.load(std::memory_order_relaxed)));
864 const float lo = r.
read(
"toneLowCut", -1.0f);
865 const float hi = r.
read(
"toneHighCut", -1.0f);
921 44.5, 46.5, 49.7, 52.3, 56.1, 58.9, 61.9, 65.2,
922 68.6, 73.7, 76.2, 79.8, 86.4, 89.8, 95.7, 101.2,
923 105.4, 112.5, 116.7, 126.4, 132.1, 140.6, 146.9, 154.1,
924 162.9, 175.8, 184.1, 192.8, 204.0, 218.5, 224.2, 240.1
930 3.55, 1.47, 3.80, 1.96, 1.84, 2.33, 4.41, 4.90,
931 1.10, 1.35, 4.29, 3.43, 4.16, 4.53, 2.08, 3.06,
932 1.22, 2.94, 3.92, 3.18, 2.57, 4.04, 4.65, 2.45,
933 2.69, 4.78, 3.67, 1.71, 2.20, 1.59, 2.82, 3.31
941 { 0.36, 0.69, 1.10, 1.65 },
942 { 0.43, 0.80, 1.20, 1.79 }
958 7.14, 3.31, 0.76, 0.25, 5.86, 2.04, 4.84, 5.35,
959 5.61, 6.88, 0.00, 7.65, 2.80, 1.02, 7.90, 1.53,
960 1.27, 4.33, 6.63, 2.55, 3.06, 6.12, 5.10, 6.37,
961 3.82, 1.78, 3.57, 7.39, 2.29, 0.51, 4.08, 4.59
964 1, 1, -1, 1, -1, 1, 1, -1,
965 -1, 1, 1, -1, 1, -1, 1, 1,
966 1, 1, 1, -1, 1, 1, 1, 1,
967 1, -1, 1, 1, -1, 1, -1, -1
973 -1, -1, 1, -1, -1, 1, 1, 1,
974 -1, -1, 1, 1, -1, -1, -1, 1,
975 -1, -1, 1, -1, -1, 1, 1, -1,
976 1, 1, -1, -1, -1, -1, -1, 1
979 -1, 1, 1, 1, -1, -1, 1, -1,
980 -1, 1, 1, -1, -1, 1, -1, -1,
981 -1, 1, 1, 1, -1, -1, 1, 1,
982 1, -1, -1, 1, -1, 1, -1, -1
996 return 33.0 + 27.0 * s / (
kSprings - 1) + 1.3 * (
hash01(s, 90) - 0.5);
1015 while (size < maxDelay + 2) size <<= 1;
1016 buf.assign(
static_cast<std::size_t
>(size), T(0));
1021 [[nodiscard]] T
at(
int k)
const noexcept {
return buf[
static_cast<std::size_t
>((
w - k) &
mask)]; }
1022 void push(T x)
noexcept {
buf[
static_cast<std::size_t
>(
w)] = x;
w = (
w + 1) &
mask; }
1041 while (
size < maxDelay + 2)
size <<= 1;
1046 stride =
size + 64 /
static_cast<int>(
sizeof(T));
1047 buf.assign(
static_cast<std::size_t
>(
stride) *
static_cast<std::size_t
>(numLines), T(0));
1051 [[nodiscard]] T
at(
int i,
int k)
const noexcept
1052 {
return buf[
static_cast<std::size_t
>(i *
stride + ((
w - k) &
mask))]; }
1071 void prepare(
double sr, T rate, uint32_t seed)
noexcept
1074 state_ = seed ? seed : 1;
1075 h0_ = nextRandom();
h1_ = nextRandom();
1076 h2_ = nextRandom();
h3_ = nextRandom();
1094 const T c1 = T(0.5) * (
h2_ -
h0_);
1095 const T c2 =
h0_ - T(2.5) *
h1_ + T(2) *
h2_ - T(0.5) *
h3_;
1096 const T c3 = T(0.5) * (
h3_ -
h0_) + T(1.5) * (
h1_ -
h2_);
1097 return ((c3 * d + c2) * d + c1) * d +
h1_;
1101 T nextRandom() noexcept
1106 return static_cast<T
>(
state_) /
static_cast<T
>(0xFFFFFFFFu) * T(2) - T(1);
1112 return T(0.71) + T(0.063) *
static_cast<T
>(i);
1116 return static_cast<uint32_t
>(i) * 7919u + 1u;
1121 return T(0.83) + T(0.051) *
static_cast<T
>(i);
1125 return static_cast<uint32_t
>(i) * 104729u + 7u;
1131 const double v = std::sin(
static_cast<double>(k) * 12.9898
1132 +
static_cast<double>(s) * 78.233) * 43758.5453;
1133 return v - std::floor(v);
1139 return static_cast<T
>(20.0 * std::log10(std::max(
static_cast<double>(gain), 1e-6)));
1144 return static_cast<int>(
static_cast<T
>(
spec_.
sampleRate) * ms / T(1000));
1157 std::array<std::array<Line, kInStages>, 2>
inAP_;
1161 std::array<std::array<Line, kOutStages>, 2>
outAP_;
1170 std::array<std::array<T, kMaxERTaps>, 2>
erGain_ {};
1177 std::array<std::array<T, kERGroups>, 2>
erLP_ {};
1192 std::array<SmoothRandomLFO, kMaxLines>
lfo_;
1196 alignas(64) std::array<T, kMaxLines / 2>
rotC_ {};
1197 alignas(64) std::array<T, kMaxLines / 2>
rotS_ {};
1232 std::array<std::array<T, 2>, 2>
subZ_ {};
1321 &&
qualityDirty_.exchange(
false, std::memory_order_acquire))
1334 &&
presetDirty_.exchange(
false, std::memory_order_acquire))
1341 &&
paramsDirty_.exchange(
false, std::memory_order_acquire))
1353 if (!(
toneDirty_.load(std::memory_order_acquire)
1354 &&
toneDirty_.exchange(
false, std::memory_order_acquire)))
1356 const T hpHz =
toneLowCutHz_.load(std::memory_order_relaxed);
1361 spec_.
sampleRate,
static_cast<double>(std::clamp(hpHz, T(20), T(500)))));
1365 spec_.
sampleRate,
static_cast<double>(std::clamp(lpHz, T(2000), T(16000)))));
1389 for (
int k = 0; k < n / 2; ++k)
1395 rotC_[k] =
static_cast<T
>(std::cos(
static_cast<double>(theta)));
1396 rotS_[k] =
static_cast<T
>(std::sin(
static_cast<double>(theta)));
1398 for (
int i = 0; i < n; ++i)
1409 bool moving =
false;
1410 for (
int i = 0; i < n; ++i)
1413 if (std::abs(diff) < T(0.01))
1430 if constexpr (M > 1)
1434 [x]<std::size_t... I>(std::index_sequence<I...>) {
1436 const T a = x[I], b = x[I + M / 2];
1438 x[I + M / 2] = a - b;
1440 }(std::make_index_sequence<M / 2> {});
1446 template <
int N,
int W,
typename O>
1449 using V =
typename O::V;
1450 for (
int b = 0; b < N; b += W)
1451 hadamardSmall<W>(x + b);
1452 for (
int h = W; h < N; h <<= 1)
1453 for (
int i = 0; i < N; i += h << 1)
1454 for (
int j = i; j < i + h; j += W)
1456 const V a = O::load(x + j), b = O::load(x + j + h);
1457 O::store(x + j, O::add(a, b));
1458 O::store(x + j + h, O::sub(a, b));
1469 void runFdn(T& lateL, T& lateR,
int injLag)
noexcept
1471 constexpr int W = std::min(simd::kVecWidth<T>, N);
1473 using V =
typename O::V;
1474 static_assert(N % W == 0,
"line count must be a multiple of the SIMD width");
1476 alignas(64) std::array<T, N + 1> r;
1477 alignas(64) std::array<T, N> v;
1478 alignas(64) std::array<T, N> tmp;
1479 alignas(64) std::array<int, N> idx;
1487 for (
int i = 0; i < N; ++i)
1490 const int M =
static_cast<int>(p - T(0.5));
1491 const T h = (p -
static_cast<T
>(M) - T(1)) * T(0.5);
1492 v[i] = -h * (T(1) - h * (T(1) - h * (T(1) - h)));
1495 for (
int i = 0; i < N; ++i)
1500 for (
int i = 0; i < N; i += W)
1502 const V y = O::madd(O::load(v.data() + i),
1503 O::sub(O::load(r.data() + i), O::load(
apY1_.data() + i)),
1504 O::load(tmp.data() + i));
1505 O::store(
apY1_.data() + i, y);
1506 O::store(r.data() + i, y);
1512 for (
int i = 0; i < N; i += W)
1514 const V x = O::load(r.data() + i);
1515 const V j = O::sub(O::madd(O::load(
jotB0_.data() + i), x,
1516 O::mul(O::load(
jotB1_.data() + i), O::load(
jotX1_.data() + i))),
1517 O::mul(O::load(
jotA1_.data() + i), O::load(
jotY1_.data() + i)));
1518 O::store(
jotX1_.data() + i, x);
1519 O::store(
jotY1_.data() + i, j);
1520 const V b = O::sub(O::madd(O::load(
bassB0_.data() + i), j,
1521 O::mul(O::load(
bassB1_.data() + i), O::load(
bassX1_.data() + i))),
1522 O::mul(O::load(
bassA1_.data() + i), O::load(
bassY1_.data() + i)));
1523 O::store(
bassX1_.data() + i, j);
1524 O::store(
bassY1_.data() + i, b);
1525 O::store(r.data() + i, b);
1533 V aL = O::set1(T(0)), aR = aL;
1534 for (
int i = 0; i < N; i += W)
1536 const V x = O::load(r.data() + i);
1537 aL = O::madd(O::load(
outSignL_.data() + i), x, aL);
1538 aR = O::madd(O::load(
outSignR_.data() + i), x, aR);
1540 alignas(64) std::array<T, W> sumL, sumR;
1541 O::store(sumL.data(), aL);
1542 O::store(sumR.data(), aR);
1543 for (
int k = 0; k < W; ++k) { lateL += sumL[k]; lateR += sumR[k]; }
1547 const V norm = O::set1(T(1) / std::sqrt(
static_cast<T
>(N)));
1548 fwht<N, W, O>(r.data());
1550 for (
int i = 0; i < N; i += W)
1551 O::store(v.data() + i, O::mul(norm, O::load(r.data() + i + 1)));
1558 static_assert(
kRotPairStride == 4 && N % 8 == 0,
"rotation pairs are (i, i + 4) in blocks of 8");
1559 for (
int blk = 0; blk < N; blk += 8)
1561 T* lo = v.data() + blk;
1563 const T* c =
rotC_.data() + blk / 2;
1564 const T* sn =
rotS_.data() + blk / 2;
1565 for (
int q = 0; q < 4; ++q)
1567 const T a = lo[q], b = hi[q];
1568 lo[q] = c[q] * a - sn[q] * b;
1569 hi[q] = sn[q] * a + c[q] * b;
1576 for (
int i = 0; i < N; ++i)
1578 const int k =
static_cast<int>(
loopAPCur_[i]);
1579 const T f =
loopAPCur_[i] -
static_cast<T
>(k);
1581 tmp[i] = a + f * (
loopAP_.
at(i, k + 1) - a);
1586 for (
int i = 0; i < N; ++i)
1590 for (
int i = 0; i < N; i += W)
1592 const V d = O::load(tmp.data() + i);
1593 const V w = O::madd(g, d, O::load(v.data() + i));
1594 O::store(v.data() + i, O::sub(d, O::mul(g, w)));
1595 O::store(tmp.data() + i, w);
1597 for (
int i = 0; i < N; ++i)
1602 for (
int i = 0; i < N; ++i)
1610 x = std::copysign(
kSoftLimit + over / (T(1) + over), x);
1634 template <
int Springs,
int Stages>
1640 constexpr int NW = simd::kVecNarrowWidth<T>;
1641 constexpr int W = (Springs % NW == 0) ? NW : 1;
1643 using V =
typename O::V;
1646 const int mask = P - 1;
1651 const T drive[2] = { T(0.75) * raw[0] + T(0.25) * raw[1],
1652 T(0.25) * raw[0] + T(0.75) * raw[1] };
1654 alignas(64) std::array<T, Springs> x;
1655 for (
int s = 0; s < Springs; ++s)
1658 const int M =
static_cast<int>(p - T(0.5));
1659 const T h = (p -
static_cast<T
>(M) - T(1)) * T(0.5);
1660 const T eta = -h * (T(1) - h * (T(1) - h * (T(1) - h)));
1669 const std::size_t stageStride =
static_cast<std::size_t
>(P) *
kSprings;
1670 const std::size_t wOff =
static_cast<std::size_t
>(iw) *
kSprings;
1671 const std::size_t kOff =
static_cast<std::size_t
>(ik) *
kSprings;
1672 for (
int g = 0; g < Springs; g += W)
1674 V xv = O::load(x.data() + g);
1676 for (
int m = 0; m < Stages; ++m, hin += stageStride)
1678 O::store(hin + wOff, xv);
1679 xv = O::madd(a, O::sub(xv, O::load(hin + stageStride + kOff)), O::load(hin + kOff));
1681 O::store(hin + wOff, xv);
1682 O::store(x.data() + g, xv);
1686 for (
int k = 0; k < 2; ++k)
1688 const auto& c =
springLP_[
static_cast<std::size_t
>(k)];
1689 const V b0 = O::set1(c[0]), b1 = O::set1(c[1]), b2 = O::set1(c[2]);
1690 const V a1 = O::set1(c[3]), a2 = O::set1(c[4]);
1692 T* s2 =
springLPState_[
static_cast<std::size_t
>(2 * k + 1)].data();
1693 for (
int g = 0; g < Springs; g += W)
1695 const V xv = O::load(x.data() + g);
1696 const V y = O::madd(b0, xv, O::load(s1 + g));
1697 O::store(s1 + g, O::sub(O::madd(b1, xv, O::load(s2 + g)), O::mul(a1, y)));
1698 O::store(s2 + g, O::sub(O::mul(b2, xv), O::mul(a2, y)));
1699 O::store(x.data() + g, y);
1703 T out[2] = { T(0), T(0) };
1704 for (
int s = 0; s < Springs; ++s) out[s & 1] += x[s];
1707 for (
int g = 0; g < Springs; g += W)
1709 const V xv = O::load(x.data() + g);
1710 const V j = O::sub(O::madd(O::load(
jotB0_.data() + g), xv,
1711 O::mul(O::load(
jotB1_.data() + g), O::load(
jotX1_.data() + g))),
1712 O::mul(O::load(
jotA1_.data() + g), O::load(
jotY1_.data() + g)));
1713 O::store(
jotX1_.data() + g, xv);
1714 O::store(
jotY1_.data() + g, j);
1715 const V b = O::sub(O::madd(O::load(
bassB0_.data() + g), j,
1716 O::mul(O::load(
bassB1_.data() + g), O::load(
bassX1_.data() + g))),
1717 O::mul(O::load(
bassA1_.data() + g), O::load(
bassY1_.data() + g)));
1718 O::store(
bassX1_.data() + g, j);
1719 O::store(
bassY1_.data() + g, b);
1720 O::store(x.data() + g, b);
1722 for (
int s = 0; s < Springs; ++s)
1728 v = std::copysign(
kSoftLimit + over / (T(1) + over), v);
1734 const T og =
kSpringOutGain / std::sqrt(
static_cast<T
>(Springs / 2));
1735 lateL += out[0] * og;
1736 lateR += out[1] * og;
1747 static void addTap(T* acc,
const Line& l,
int lag0, T gain,
int count)
noexcept
1751 const T* b = l.buf.data();
1752 const int start = (l.w - lag0) & l.mask;
1753 const int first = std::min(count, l.mask + 1 - start);
1766 void processChunk(
const T* inL,
const T* inR, T* outL, T* outR,
int count)
noexcept
1770 const int last = count - 1;
1773 alignas(64) T raw[2][
kChunk];
1774 for (
int c = 0; c < 2; ++c)
1777 const T* in = c == 0 ? inL : inR;
1778 for (
int n = 0; n < count; ++n) pre.
push(in[n]);
1779 std::fill(raw[c], raw[c] + count, T(0));
1780 addTap(raw[c], pre, pd + 1 + last, T(1), count);
1781 Line& ring =
ring_[
static_cast<std::size_t
>(c)];
1782 auto& aps =
inAP_[
static_cast<std::size_t
>(c)];
1783 for (
int n = 0; n < count; ++n)
1788 Line& ap = aps[
static_cast<std::size_t
>(st)];
1789 const T g =
inAPCoeff_[
static_cast<std::size_t
>(st)];
1790 const T d = ap.
at(
inAPLen_[
static_cast<std::size_t
>(c)][
static_cast<std::size_t
>(st)]);
1791 const T w = x + g * d;
1803 for (
int s = 0; s < 2; ++s)
1814 alignas(64) T early[2][
kChunk];
1815 for (
int s = 0; s < 2; ++s)
1817 const Line* src[4] = { &
ring_[
static_cast<std::size_t
>(s)], &
ring_[
static_cast<std::size_t
>(1 - s)],
1818 &
preDelay_[
static_cast<std::size_t
>(s)], &
preDelay_[
static_cast<std::size_t
>(1 - s)] };
1819 const int lagOff[4] = { last, last, pd + last, pd + last };
1820 std::fill(early[s], early[s] + count, T(0));
1821 auto& lp =
erLP_[
static_cast<std::size_t
>(s)];
1827 for (
auto& a : acc) std::fill(a, a + count, T(0));
1833 constexpr T hf =
static_cast<T
>(
kShadowHF) - T(1);
1834 T z =
erShadowLP_[
static_cast<std::size_t
>(s)][
static_cast<std::size_t
>(g)];
1835 for (
int n = 0; n < count; ++n)
1837 const T x = acc[1][n];
1839 acc[0][n] += x + hf * (x - z);
1841 erShadowLP_[
static_cast<std::size_t
>(s)][
static_cast<std::size_t
>(g)] = z;
1847 const T hg =
erShelfGain_[
static_cast<std::size_t
>(g)];
1849 for (
int n = 0; n < count; ++n)
1851 const T x = acc[0][n];
1853 early[s][n] += y + hg * (x - y);
1859 for (
int n = 0; n < count; ++n)
1865 T lateL = T(0), lateR = T(0);
1868 const T rawN[2] = { raw[0][n], raw[1][n] };
1869 if (
eco_) runSprings<kEcoSprings, kEcoSpringStages>(lateL, lateR, rawN);
1870 else runSprings<kSprings, kSpringStages>(lateL, lateR, rawN);
1872 else if (
eco_) runFdn<kEcoLines>(lateL, lateR, gap + last - n);
1873 else runFdn<kMaxLines>(lateL, lateR, gap + last - n);
1875 const auto [yl, yr] =
outputStage(lateL, lateR, early[0][n], early[1][n]);
1883 std::pair<T, T>
outputStage(T lateL, T lateR, T earlyL, T earlyR)
noexcept
1892 T* lr[2] = { &lateL, &lateR };
1894 for (
int c = 0; c < 2; ++c)
1899 Line& ap =
outAP_[
static_cast<std::size_t
>(c)][
static_cast<std::size_t
>(k)];
1900 const T d = ap.
at(
outAPLen_[
static_cast<std::size_t
>(c)][
static_cast<std::size_t
>(k)]);
1901 const T w = x + g * d;
1915 const T mid = (lateL + lateR) * T(0.5);
1916 T side = (lateL - lateR) * T(0.5);
1927 for (
int c = 0; c < 2; ++c)
1929 auto& z =
subZ_[
static_cast<std::size_t
>(c)];
1930 const T y =
subC_[0] * out[c] + z[0];
1931 z[0] =
subC_[1] * out[c] -
subC_[3] * y + z[1];
1945 return { out[0], out[1] };
1960 damping_.store(std::clamp((T(1) - hd) / T(0.9), T(0), T(1)), std::memory_order_relaxed);
1962 std::memory_order_relaxed);
1969 return T(0.35) + T(0.65) *
size_.load(std::memory_order_relaxed);
1975 const double sz =
static_cast<double>(
sizeFactor());
1976 const auto ms = [sr](
double v) {
return std::max(1,
static_cast<int>(v * sr / 1000.0)); };
1998 const double springScale = 0.6 + 2.0 *
static_cast<double>(
size_.load(std::memory_order_relaxed));
1999 for (
int s = 0; s <
nLines_; ++s)
2002 for (
int c = 0; c < 2; ++c)
2004 inAPLen_[
static_cast<std::size_t
>(c)][
static_cast<std::size_t
>(st)]
2010 const double diff =
static_cast<double>(
diffusion_.load(std::memory_order_relaxed));
2021 const double aRef = 0.3 + 0.5 * diff;
2023 const double f = 72.0 /
springStages_ * (1.0 + aRef) / (1.0 - aRef);
2024 springA_ =
static_cast<T
>((f - 1.0) / (f + 1.0));
2031 const T rate =
modRate_.load(std::memory_order_relaxed);
2065 const double t60H = std::max(0.05, decay *
static_cast<double>(
2067 const double t60B = std::max(0.05, decay *
static_cast<double>(
2069 const double kPi = 3.14159265358979323846;
2070 const double fh = std::clamp(
static_cast<double>(
2071 highCrossover_.load(std::memory_order_relaxed)), 100.0, 0.45 * sr);
2072 const double fb = std::clamp(
static_cast<double>(
2073 bassCrossover_.load(std::memory_order_relaxed)), 10.0, 0.45 * sr);
2074 const double Kh = std::tan(kPi * fh / sr);
2075 const double Kb = std::tan(kPi * fb / sr);
2076 double loopSum = 0.0;
2086 * (1.0 -
static_cast<double>(
springA_)) / (1.0 +
static_cast<double>(
springA_))
2088 const double gM = std::pow(0.001, M / (decay * sr));
2089 if (i <
nLines_) loopSum += M;
2090 const double gH = std::min(std::pow(0.001, M / (t60H * sr)), gM);
2093 const double gB = std::min(std::pow(0.001, M / (t60B * sr)), 0.9995);
2096 const double ratio = std::sqrt(gM / gH);
2097 const double a = Kh * ratio, b = Kh / ratio, inv = 1.0 / (1.0 + b);
2098 jotB0_[i] =
static_cast<T
>(gH * (1.0 + a) * inv);
2099 jotB1_[i] =
static_cast<T
>(gH * (a - 1.0) * inv);
2100 jotA1_[i] =
static_cast<T
>((b - 1.0) * inv);
2103 const double sq = std::sqrt(gB / gM);
2104 const double a = Kb * sq, b = Kb / sq, inv = 1.0 / (1.0 + b);
2105 bassB0_[i] =
static_cast<T
>((1.0 + a) * inv);
2106 bassB1_[i] =
static_cast<T
>((a - 1.0) * inv);
2107 bassA1_[i] =
static_cast<T
>((b - 1.0) * inv);
2132 const double target =
static_cast<double>(
decayTime_.load(std::memory_order_relaxed));
2133 if (!(sr > 0.0) ||
nLines_ < 1)
return target;
2134 const double hd =
static_cast<double>(
highDecayMult_.load(std::memory_order_relaxed));
2135 const double bd =
static_cast<double>(
bassDecayMult_.load(std::memory_order_relaxed));
2136 const double kPi = 3.14159265358979323846;
2137 const double Kh = std::tan(kPi * std::clamp(
static_cast<double>(
2138 highCrossover_.load(std::memory_order_relaxed)), 100.0, 0.45 * sr) / sr);
2139 const double Kb = std::tan(kPi * std::clamp(
static_cast<double>(
2140 bassCrossover_.load(std::memory_order_relaxed)), 10.0, 0.45 * sr) / sr);
2141 auto loopLen = [&](
int i) {
2144 * (1.0 -
static_cast<double>(
springA_)) / (1.0 +
static_cast<double>(
springA_))
2149 auto shelf = [](
double a,
double b,
double w) {
2150 const double c = std::cos(w), sn = std::sin(w);
2151 const double nr = (1.0 + a) + (a - 1.0) * c, ni = -(a - 1.0) * sn;
2152 const double dr = (1.0 + b) + (b - 1.0) * c, di = -(b - 1.0) * sn;
2153 return std::sqrt((nr * nr + ni * ni) / (dr * dr + di * di));
2158 auto t60At = [&](
double d,
double hz) {
2159 const double w = 2.0 * kPi * hz / sr;
2162 for (
int i = 0; i <
nLines_; ++i)
2164 const double M = loopLen(i);
2165 if (!(M > 0.0))
continue;
2166 const double gM = std::pow(0.001, M / (d * sr));
2167 const double gH = std::min(std::pow(0.001, M / (std::max(0.05, d * hd) * sr)), gM);
2168 const double gB = std::min(std::pow(0.001, M / (std::max(0.05, d * bd) * sr)), 0.9995);
2169 const double rh = std::sqrt(gM / gH), rb = std::sqrt(gB / gM);
2170 const double g = gH * shelf(Kh * rh, Kh / rh, w) * shelf(Kb * rb, Kb / rb, w);
2171 if (!(g > 0.0 && g < 1.0))
continue;
2172 rate += -sr * std::log10(g) / (3.0 * M);
2175 return (used > 0 && rate > 0.0) ? used / rate : d;
2178 for (
int it = 0; it < 4; ++it)
2180 const double tm = 0.5 * (t60At(d, 500.0) + t60At(d, 1000.0));
2181 if (!(tm > 0.0))
break;
2184 return std::clamp(d, 0.05, 120.0);
2226 constexpr double p = 1.6;
2229 const int discrete = 1 +
static_cast<int>(std::lround(
2232 const double decay =
static_cast<double>(
decayTime_.load(std::memory_order_relaxed));
2233 constexpr double kLn1000 = 6.907755278982137;
2234 struct Tap {
double ms;
int chan;
int bin;
double gain; };
2235 std::array<std::array<Tap, kMaxERTaps>, 2> taps {};
2236 std::array<int, 2> used { 0, 0 };
2237 for (
int c = 0; c < 2; ++c)
2239 std::array<double,
kMaxERTaps / 2> gv {}, msv {}, lat {};
2241 for (
int k = 0; k < R; ++k)
2243 const double u = (k + 0.15 + 0.7 *
hash01(k, c + 20)) / R;
2246 const double w = 0.3 * u + 0.7 * std::pow(u, 1.0 / p);
2247 msv[k] = minMs + (maxMs - minMs) * w;
2248 const double density = 1.0 / (0.3 + 0.7 / p * std::pow(std::max(u, 1e-3), 1.0 / p - 1.0));
2249 const double sign = k < 4 ||
hash01(k, c + 40) < 0.5 ? 1.0 : -1.0;
2254 gv[k] = sign * std::exp(-kLn1000 * msv[k] * 1e-3 / decay) / std::sqrt(density);
2255 if (k >= discrete) dcSum += gv[k];
2262 const double side =
hash01(k, c + 50) < 0.92 - 0.42 * w ? 1.0 : -1.0;
2263 lat[k] = k < 2 ? 0.5 : side * (0.25 + 0.75 *
hash01(k, c + 60));
2268 for (
int k = discrete; k < R; ++k) gv[k] -= dcSum / (R - discrete);
2269 double energy = 0.0;
2270 for (
int k = 0; k < R; ++k) energy += gv[k] * gv[k];
2277 const double tau = decay / (2.0 * kLn1000);
2278 const double tInj =
static_cast<double>(
erToLateSamples_.load(std::memory_order_relaxed)) / sr
2280 const double target =
static_cast<double>(
kOutGain) *
static_cast<double>(
kOutGain)
2282 * (std::exp(-(minMs * 1e-3 - tInj) / tau) - std::exp(-(maxMs * 1e-3 - tInj) / tau));
2283 const double norm = std::sqrt(0.5 * target / energy);
2289 for (
int e = 0; e < 2; ++e)
2291 for (
int k = 0; k < R; ++k)
2293 const double x = e == c ? lat[k] : -lat[k];
2294 const double phi = std::asin(std::min(1.0, std::abs(x)));
2295 const double itdMs = x < 0.0 ? 1000.0 * 0.0875 / 343.0 * (phi + std::sin(phi)) : 0.0;
2296 const int bin = x < -0.3 ? 1 : 0;
2297 const double ild = x > 0.0 ? 1.0 + 0.2 * x : 1.0 + 0.5 * x;
2298 taps[e][used[e]++] = { msv[k] + itdMs, (e == c ? 0 : 1) + (k < discrete ? 2 : 0),
2299 bin, gv[k] * norm * ild };
2303 for (
int e = 0; e < 2; ++e)
2306 std::sort(taps[e].begin(), taps[e].begin() + used[e],
2307 [](
const Tap& a,
const Tap& b) {
return a.ms < b.ms; });
2308 for (
int k = 0; k < used[e]; ++k)
2310 erTap_[e][k] = std::max(1,
static_cast<int>(taps[e][k].ms * sr / 1000.0));
2311 erChan_[e][k] = taps[e][k].chan;
2312 erBin_[e][k] = taps[e][k].bin;
2319 const double fh = std::clamp(
static_cast<double>(
highCrossover_.load(std::memory_order_relaxed)),
2321 erShelfCoeff_ =
static_cast<T
>(1.0 - std::exp(-6.283185307179586 * fh / sr));
2323 *
static_cast<double>(
highDecayMult_.load(std::memory_order_relaxed)));
2328 for (
int e = 0; e < 2; ++e)
2330 tSum +=
erTap_[e][k] / sr;
2331 const double t = cnt > 0 ? tSum / cnt : 0.0;
2332 erShelfGainTarget_[g] =
static_cast<T
>(std::pow(10.0, -3.0 * t * std::max(0.0, 1.0 / t60H - 1.0 / decay)));
2341 static const std::vector<uint8_t> sieve = []
2347 if (s[
static_cast<size_t>(i)])
2349 s[
static_cast<size_t>(j)] = 0;
2358 if (n <= 2)
return 2;
2366 if (n % 2 == 0) ++n;
2369 bool isPrime =
true;
2370 for (
int d = 3; d * d <= n; d += 2)
2371 if (n % d == 0) { isPrime =
false;
break; }
2372 if (isPrime)
return n;
2388 const uint32_t m =
userParamMask_.load(std::memory_order_acquire);
2389 if (!(m &
kUserSize))
size_.store(p.size, std::memory_order_relaxed);
2410 commitPreset({T(0.22), T(0.5), T(0.40), T(1.1), T(5000), T(250),
2411 T(0.72), T(0.07), T(0.5), T(1), T(0.8), T(0)});
2414 commitPreset({T(0.68), T(2.2), T(0.32), T(1.3), T(4500), T(200),
2415 T(0.84), T(0.10), T(0.55), T(1), T(1), T(15)});
2418 commitPreset({T(0.38), T(1.2), T(0.38), T(1.1), T(5000), T(250),
2419 T(0.78), T(0.08), T(0.6), T(1), T(0.9), T(8)});
2422 commitPreset({T(0.14), T(1.5), T(0.55), T(0.8), T(7000), T(150),
2423 T(0.94), T(0.16), T(1.4), T(0), T(1), T(0)});
2426 commitPreset({T(0.11), T(0.9), T(0.28), T(1.0), T(4000), T(200),
2427 T(0.6), T(0.08), T(0.35), T(0.5), T(1), T(0)});
2430 commitPreset({T(0.98), T(5.0), T(0.24), T(1.5), T(3500), T(150),
2431 T(0.91), T(0.12), T(0.35), T(1), T(1), T(25)});
True-stereo 32-line FDN reverb with exact per-band decay and 6 presets.
static constexpr T rotRateFactor(int i) noexcept
static constexpr double kOutDiffMs_[2][kOutStages]
Quality
Engine quality / CPU cost trade-off (see setQuality()).
@ Eco
Reduced 8-line engine, about 3x cheaper. For constrained targets.
@ Full
Complete 32-line engine. Default.
void runSprings(T &lateL, T &lateR, const T(&raw)[2]) noexcept
One sample of the spring tanks (Type::Spring): six springs per side (one in Eco), all of different le...
std::array< std::array< T, kERGroups >, 2 > erShadowLP_
T getHighDecayMultiplier() const noexcept
static constexpr double kLoopApMs_[kMaxLines]
std::atomic< T > modDepth_
static void hadamardSmall(T *x) noexcept
T sizeFactor() const noexcept
Size factor: size 0 -> 0.35, size 1 -> 1.
T getWidth() const noexcept
std::array< int, kMaxLines > loopAPLen_
in-loop allpass target lengths
double meanLoopSec_
mean FDN round trip (s), for the early/late energy match
std::atomic< int > erToLateSamples_
void processChunk(const T *inL, const T *inR, T *outL, T *outR, int count) noexcept
Core processing of up to kChunk samples: stereo in, wet stereo out.
T erGlidePerSample_
glide rate of the two (1 / samples)
std::array< T, kERGroups > erShelfGainTarget_
std::array< T, kMaxLines > posInc_
per-sample ramp
static constexpr double kInjectMs_[kMaxLines]
static constexpr double kMaxInjectMs
static constexpr int kShadowBins
head-shadow classes of the early taps (open, shadowed)
std::pair< T, T > outputStage(T lateL, T lateR, T earlyL, T earlyR) noexcept
static constexpr int kInStages
input diffusers per channel feeding the late field (Full)
static constexpr double kSpringCutHz
dispersion band edge (transition frequency)
std::atomic< T > toneHighCutHz_
static constexpr double kMaxErToLateMs
void drainTone() noexcept
std::array< T, kMaxLines/2 > rotC_
std::array< std::array< T, kERGroups >, 2 > erLP_
std::array< T, kMaxLines > bassY1_
std::array< T, 5 > cohC_
its coefficients b0 b1 b2 a1 a2
std::array< std::array< int, kMaxERTaps >, 2 > erBin_
static constexpr double kInDiffCoeffs_[kInStages]
static constexpr double kInDiffMs_[2][kInStages]
std::atomic< T > lateLevel_
static double hash01(int k, int s) noexcept
Deterministic hash in [0, 1) for the early-reflection layout.
void setQuality(Quality q) noexcept
Selects the engine quality / CPU cost trade-off.
Type getType() const noexcept
std::atomic< T > modRate_
static constexpr uint32_t rotSeed(int i) noexcept
std::array< int, kMaxLines > lenTarget_
prime line lengths (samples)
static void fwht(T *x) noexcept
void setToneHighCut(T hz) noexcept
Sets a post-reverb high-cut filter on the wet signal (12 dB/oct).
static constexpr double kMaxInDiffMs
static constexpr double kMaxGlideSpeed
max length change per sample (4% Doppler)
std::atomic< T > highDecayMult_
double calibratedMidDecay() const noexcept
The loop's DC decay that puts the ISO 3382 mid-frequency reverberation time on the decay setting.
void applyPreset(Type type) noexcept
static const std::vector< uint8_t > & getPrimeSieve() noexcept
std::array< T, kMaxLines > lenCur_
gliding length
std::pair< T, T > processSample(T input) noexcept
Processes a single mono sample and returns the wet stereo pair.
void generateERTapsForType(Type type) noexcept
Regenerates the early-reflection taps for a reverb type (Eco caps the count).
void refreshCachedParams() noexcept
Pulls the atomic parameters into the block-local cache (audio thread).
static constexpr int kPrimeTableMax
void setDiffusion(T amount) noexcept
Diffusion (0 - 1): the strength of the input, in-loop and output allpass diffusers,...
void setType(Type type) noexcept
Loads the selected preset baseline.
static constexpr int kEcoSprings
static constexpr double kMaxPreDelayMs
void setEarlyLevel(T dB) noexcept
Early reflections level in dB (-60 to +6). 0 dB is the physical balance: the early field then carries...
static constexpr int kMaxERTaps
early taps per output side: 64 reflections per input (Full)
Quality getQuality() const noexcept
std::array< T, kMaxLines > outSignR_
Biquad< T, 2 > toneLPBiquad_
static constexpr double kMeanInjectMs
mean of kInjectMs_
static constexpr int kOutSignR_[kMaxLines]
static constexpr int kSpringStages
dispersion allpasses per spring (Full)
T getErToLateDelay() const noexcept
std::array< T, kMaxLines > jotB1_
static constexpr T kOutGain
std::array< std::array< T, 2 >, 2 > subZ_
subsonic high-pass states per channel
static constexpr double kShadowHz
head-shadow corner of the early taps
void updateAll() noexcept
static constexpr int kOutStages
static constexpr int kChunk
sparse-FIR processing chunk (samples)
T getDecay() const noexcept
T getSize() const noexcept
std::array< T, kMaxLines > jotA1_
static constexpr int kMaxLines
std::atomic< bool > paramsDirty_
std::array< T, kInStages > inAPCoeff_
std::array< T, kMaxLines/2 > rotS_
std::array< std::array< T, 5 >, 2 > springLP_
b0 b1 b2 a1 a2, two sections
std::atomic< T > toneLowCutHz_
std::array< T, kMaxLines > apY1_
allpass interpolator state
void controlTick() noexcept
Control-rate update: glides the line lengths toward their targets, draws the next modulation values a...
static constexpr double kCoherenceQ
static constexpr T rotRate() noexcept
std::array< T, kMaxLines > pos_
current read position
std::array< T, kMaxLines > outSignL_
T erShelfCoeff_
one-pole coefficient at the high crossover
static int nearestPrime(int n) noexcept
Smallest prime >= n (lengths only grow by a few samples).
T getBassDecayMultiplier() const noexcept
void setModulation(T amount) noexcept
Modulation depth (0 - 1) of the delay-line wander. It smears the tail's resonances; 0 is fully static...
T getHighCrossover() const noexcept
T getModulation() const noexcept
static constexpr double kBaseDelaysMs_[kMaxLines]
static constexpr uint32_t lfoSeed(int i) noexcept
std::array< std::array< Line, kOutStages >, 2 > outAP_
late-field output diffusers
static T levelDb(T gain) noexcept
Linear gain to dB (a zero gain, a preset's muted early field, reads -120 dB).
std::atomic< T > erToLateMs_
void setBassCrossover(T hz) noexcept
Frequency where the bass decay transition is centered.
static constexpr int kInjectSign_[kMaxLines]
T getEarlyLevel() const noexcept
Early reflections level in dB (as set, or the preset's).
void setPreDelay(T ms) noexcept
Pre-delay before the early reflections, in ms (0 - 200).
T getPreDelay() const noexcept
static constexpr T kSpringOutGain
std::array< T, kERGroups > erShelfGain_
HF gain of each absorption group.
std::array< std::array< int, kInStages >, 2 > inAPLen_
void updateDelayLengths() noexcept
void setMix(T dryWet) noexcept
Dry/wet mix (0 = dry, 1 = wet).
T getModRate() const noexcept
void drainPendingChanges() noexcept
Drains deferred parameter changes on the audio thread.
int msToSamples(T ms) const noexcept
void updateDiffCoeffs() noexcept
static constexpr int kEcoSpringStages
void reset() noexcept
Clears all delay lines and filter states and restarts the modulation, snapping any length glide to it...
static constexpr int kCtrl
modulation control period (samples)
std::atomic< T > earlyLevel_
std::array< T, kMaxLines > bassX1_
static constexpr T lfoRateFactor(int i) noexcept
static constexpr double kSpringJitterMs
bool setState(const uint8_t *data, size_t size)
Restores parameters from a blob (tolerant; rejects foreign ids).
T getBassCrossover() const noexcept
void updateModulation() noexcept
std::array< std::array< T, kSprings >, 4 > springLPState_
[section * 2 + state][spring]
Biquad< T, 2 > toneHPBiquad_
static constexpr int kRotPairStride
std::array< T, kMaxLines > loopAPCur_
gliding lengths after a size change
std::array< T, kMaxLines > injGain_
sign / sqrt(lines)
std::array< std::array< Line, kInStages >, 2 > inAP_
void setLateLevel(T dB) noexcept
Late tail level in dB (-60 to +6).
T getMix() const noexcept
void setToneLowCut(T hz) noexcept
Sets a post-reverb low-cut filter on the wet signal (12 dB/oct).
std::vector< uint8_t > getState() const
Serializes the parameter state (setup/UI threads; allocates).
std::array< T, kMaxLines > bassA1_
static double springBaseMs(int s) noexcept
std::array< T, kMaxLines > bassB1_
T getLateLevel() const noexcept
Late tail level in dB (as set, or the preset's).
CachedParams cachedParams_
std::atomic< T > bassDecayMult_
std::array< std::array< T, kMaxERTaps >, 2 > erGainTarget_
gains for the current size/decay
std::atomic< bool > toneDirty_
std::vector< T > springHist_
std::array< T, kMaxLines > bassB0_
static constexpr double kMaxErMs
std::atomic< T > diffusion_
static constexpr int kERGroups
absorption groups (early -> late)
void setDamping(T amount) noexcept
Sets high-frequency damping (0 = bright, 1 = dark).
void setErToLateDelay(T ms) noexcept
Extra gap between the early reflections and the late tail, in ms (0 - 200).
void processBlock(AudioBufferView< T > buffer) noexcept
Processes an audio block in place with zero allocations.
void markUserParam(uint32_t bit) noexcept
std::array< std::array< int, kOutStages >, 2 > outAPLen_
static constexpr T kMinReadPos
static constexpr double kRotRateHz
std::atomic< uint32_t > userParamMask_
void setSize(T size) noexcept
Room size (0.01 - 1). Scales the delay lines from 36% to 100% of their base lengths....
std::atomic< bool > presetDirty_
void commitPreset(const PresetValues &p) noexcept
std::array< SmoothRandomLFO, kMaxLines/2 > rotLfo_
static constexpr double kSubsonicHz
wet-output high-pass (2nd order): no room rings below it
std::atomic< T > decayTime_
void setModRate(T hz) noexcept
Modulation rate in Hz (0.1 - 5); each line wanders at its own multiple.
static constexpr int kSprings
std::array< SmoothRandomLFO, kMaxLines > lfo_
void updateDecayParams() noexcept
Per-line absorption: Jot mid/high shelf and bass shelf.
void setWidth(T width) noexcept
Sets stereo width of the late reverb tail.
@ Chamber
Recording studio chamber, warm, balanced.
@ Spring
Spring reverb, bouncy vintage character.
@ Cathedral
Large cathedral, immense decay, vast space.
@ Hall
Concert hall, spacious, long smooth tail.
@ Plate
Metal plate, dense shimmer, no early reflections.
@ Room
Small room, short decay, dense close reflections.
std::array< std::array< int, kMaxERTaps >, 2 > erTap_
std::atomic< T > bassCrossover_
T getDiffusion() const noexcept
~AlgorithmicReverb()=default
std::array< int, kERGroups+1 > erGroupStart_
std::atomic< T > damping_
void setHighDecayMultiplier(T mult) noexcept
Sets HF decay as a multiplier of mid decay time.
std::array< Line, 2 > preDelay_
void generateERTaps(double minMs, double maxMs, int numTaps) noexcept
Velvet-noise early field (Valimaki et al.): numTaps sparse +-1 impulses per side between minMs and ma...
std::array< Line, 2 > ring_
allpass-diffused input, feeds the late field
static constexpr double kCoherenceHz
side high-pass of the late field (2nd order, Q 0.74)
std::atomic< Quality > quality_
static void addTap(T *acc, const Line &l, int lag0, T gain, int count) noexcept
std::array< std::array< T, kMaxERTaps >, 2 > erGain_
current (gliding) tap gains
void refreshTopology() noexcept
Line count of the active engine: the springs, or the FDN size.
static constexpr int kMaxDiscreteER
raw (discrete) early taps at diffusion 0
std::array< std::array< int, kMaxERTaps >, 2 > erChan_
std::array< T, kMaxLines > jotX1_
void setHighCrossover(T hz) noexcept
Frequency where the HF decay transition is centered.
static constexpr double kModMaxMs
void prepare(const AudioSpec &spec)
Prepares the reverberation engine and allocates required memory.
std::array< T, kMaxLines > jotY1_
std::array< T, kMaxLines > jotB0_
void runFdn(T &lateL, T &lateR, int injLag) noexcept
One sample of the N-line FDN (compile-time N so the per-line stages vectorize): allpass-interpolated ...
void setDecay(T seconds) noexcept
Mid-frequency reverberation time in seconds (0.1 - 30).
static constexpr double kShadowHF
high-frequency gain of a shadowed (far-ear) tap
static constexpr int kEcoLines
std::atomic< T > highCrossover_
T getDamping() const noexcept
std::atomic< Type > type_
static constexpr int kOutSignL_[kMaxLines]
void setBassDecayMultiplier(T mult) noexcept
Sets bass decay as a multiplier of mid decay time.
std::atomic< bool > qualityDirty_
std::array< int, kMaxLines > injTap_
static constexpr T kSoftLimit
in-loop safety limiter threshold
std::atomic< int > preDelaySamples_
std::array< T, 2 > cohZ_
side high-pass state (coherent low band)
static constexpr double kGlideMs
size-change glide time constant
std::atomic< T > preDelayMs_
static constexpr double kRotMaxRad
peak rotation angle at modulation 1 (rad)
static constexpr double kMaxLoopApMs
static constexpr int kEcoERTaps
Non-owning view over audio channel data.
Biquad filter using Transposed Direct Form II (TDF-II) with thread-safe updates.
void setCoeffsNow(const BiquadCoeffs &c) noexcept
Stream-owner direct set: makes c the active set immediately.
void reset() noexcept
Resets all per-channel filter states to zero to avoid ringing/clicks.
T processSample(T input, int channel) noexcept
Processes a single sample for a specific channel.
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
Pre-allocated, SIMD-friendly dry/wet blender for real-time audio.
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 addWithGain(float *DSPARK_RESTRICT dst, const float *DSPARK_RESTRICT src, float gain, int count) noexcept
Adds source samples scaled by a gain factor into a destination buffer.
Main namespace for the DSPark framework.
T decibelsToGain(T dB, T minusInfinityDb=T(-100)) noexcept
Converts a value in decibels to linear gain.
constexpr uint32_t stateId(const char(&tag)[5]) noexcept
Builds a FOURCC processor id, e.g. dspark::stateId("COMP").
Block-cached copies of the atomics read in the sample loop.
Equal-size delay lines in one contiguous buffer sharing a write index (every line is written once per...
void prepare(int numLines, int maxDelay)
void write(int i, T x) noexcept
T at(int i, int k) const noexcept
Power-of-two circular delay line: at(k) is the sample pushed k samples ago.
void prepare(int maxDelay)
T at(int k) const noexcept
Hermite-interpolated random noise generator for organic modulation.
void setRate(T rate, double sr) noexcept
T nextStride(int stride) noexcept
void prepare(double sr, T rate, uint32_t seed) noexcept
Describes the audio environment for a DSP processor.
constexpr bool isValid() const noexcept
Checks if the specification contains valid, processable parameters.
double sampleRate
Sample rate in Hz.
static BiquadCoeffs makeHighPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
High-pass filter.
static BiquadCoeffs makeLowPass(double sampleRate, double freq, double Q=0.7071067811865476) noexcept
Low-pass filter.