110 prepared_.store(
false, std::memory_order_relaxed);
114 sampleRate_ =
static_cast<double>(osFactor_) * spec.
sampleRate;
115 mixMaxStep_ =
static_cast<T
>(1.0 / std::max(1.0, spec.
sampleRate * 0.02));
121 oversampler_ = std::make_unique<Oversampling<T>>(
123 oversampler_->prepare(spec);
127 oversampler_.reset();
129 latency_ = oversampler_ ? oversampler_->getLatency() : 0;
131 while (drySize_ < latency_ + maxBlock_ + 1) drySize_ <<= 1;
132 dryRing_.assign(
static_cast<size_t>(numChannels_),
133 std::vector<T>(
static_cast<size_t>(drySize_), T(0)));
136 channels_.assign(
static_cast<size_t>(numChannels_), {});
137 for (
auto& ch : channels_)
139 ch.hyst.prepare(sampleRate_);
142 ch.hyst.setParameters(3.5e5, 2.2e4, 1.6e-3, 3.2e4, 0.25);
145 prepared_.store(
true, std::memory_order_relaxed);
146 dirty_.store(
true, std::memory_order_release);
153 if (!prepared_.load(std::memory_order_relaxed))
return;
154 for (
auto& ch : channels_)
161 ch.hpX = ch.hpY = 0.0;
164 for (
auto& d : dryRing_)
165 std::fill(d.begin(), d.end(), T(0));
167 if (oversampler_) oversampler_->reset();
171 currentMix_ = mix_.load(std::memory_order_relaxed);
180 if (!std::isfinite(db))
return;
181 driveDb_.store(std::clamp(db, T(-12), T(24)), std::memory_order_relaxed);
182 dirty_.store(
true, std::memory_order_release);
190 if (!std::isfinite(size))
return;
191 coreSize_.store(std::clamp(size, T(0), T(1)), std::memory_order_relaxed);
192 dirty_.store(
true, std::memory_order_release);
199 if (!std::isfinite(amount))
return;
200 resonance_.store(std::clamp(amount, T(0), T(1)), std::memory_order_relaxed);
201 dirty_.store(
true, std::memory_order_release);
209 if (!std::isfinite(mix))
return;
210 mix_.store(std::clamp(mix, T(0), T(1)), std::memory_order_relaxed);
213 [[nodiscard]] T
getDrive() const noexcept {
return driveDb_.load(std::memory_order_relaxed); }
214 [[nodiscard]] T
getCoreSize() const noexcept {
return coreSize_.load(std::memory_order_relaxed); }
215 [[nodiscard]] T
getResonance() const noexcept {
return resonance_.load(std::memory_order_relaxed); }
216 [[nodiscard]] T
getMix() const noexcept {
return mix_.load(std::memory_order_relaxed); }
230 if (factor < 1 || factor > 16 || (factor & (factor - 1)) != 0)
return;
231 if (factor == osFactor_)
return;
233 if (prepared_.load(std::memory_order_relaxed))
242 [[nodiscard]]
int getLatency() const noexcept {
return latency_; }
248 [[nodiscard]] std::vector<uint8_t>
getState()
const
253 w.
write(
"drive",
static_cast<float>(driveDb_.load(std::memory_order_relaxed)));
254 w.
write(
"coreSize",
static_cast<float>(coreSize_.load(std::memory_order_relaxed)));
255 w.
write(
"resonance",
static_cast<float>(resonance_.load(std::memory_order_relaxed)));
256 w.
write(
"mix",
static_cast<float>(mix_.load(std::memory_order_relaxed)));
257 w.
write(
"oversampling", osFactor_);
283 if (!prepared_.load(std::memory_order_relaxed))
return;
286 const int nCh = std::min(buffer.getNumChannels(), numChannels_);
287 const int nS = buffer.getNumSamples();
288 if (nCh == 0 || nS == 0)
return;
292 if (dirty_.load(std::memory_order_relaxed)
293 && dirty_.exchange(
false, std::memory_order_acquire))
296 for (
int off = 0; off < nS; off += maxBlock_)
297 processChunk(buffer.getSubView(off, std::min(maxBlock_, nS - off)), nCh);
303 const int nS = buffer.getNumSamples();
310 for (
int ch = 0; ch < nCh; ++ch)
312 T* d = buffer.getChannel(ch);
313 for (
int i = 0; i < nS; ++i)
314 if (!std::isfinite(d[i])) d[i] = T(0);
322 const T mixTarget = mix_.load(std::memory_order_relaxed);
323 const T mixStart = currentMix_;
326 for (
int ch = 0; ch < nCh; ++ch)
328 const T* in = buffer.getChannel(ch);
329 auto& dry = dryRing_[
static_cast<size_t>(ch)];
331 for (
int i = 0; i < nS; ++i)
333 dry[
static_cast<size_t>(dp)] = in[i];
334 dp = (dp + 1) & (drySize_ - 1);
338 const bool osOn = (oversampler_ !=
nullptr);
339 auto osView = osOn ? oversampler_->upsample(buffer) : buffer;
340 const int osN = osView.getNumSamples();
350 if (hScaleSm_ <= 0.0) { hScaleSm_ = hScale_; mScaleSm_ = mScale_; }
351 const double kSm = 1.0 - std::exp(-
static_cast<double>(osN) / (0.050 * sampleRate_));
352 const double hEnd = hScaleSm_ * std::pow(hScale_ / hScaleSm_, kSm);
353 const double mEnd = mScaleSm_ * std::pow(mScale_ / mScaleSm_, kSm);
354 const double hRat = std::pow(hEnd / hScaleSm_, 1.0 /
static_cast<double>(osN));
355 const double mRat = std::pow(mEnd / mScaleSm_, 1.0 /
static_cast<double>(osN));
357 for (
int ch = 0; ch < nCh; ++ch)
359 T* d = osView.getChannel(ch);
360 auto& st = channels_[
static_cast<size_t>(ch)];
361 double hSm = hScaleSm_, mSm = mScaleSm_;
363 for (
int i = 0; i < osN; ++i)
367 const double x =
static_cast<double>(d[i]);
370 const double fluxNew = leak_ * st.flux + halfT_ * (x + st.vPrev);
374 const double m = mSm *
static_cast<double>(
375 st.hyst.processSample(
static_cast<T
>(hSm * fluxNew)));
383 double v = (m - leak_ * st.mPrev) * invHalfT_ - kDiffRho * st.vPrev2;
389 const double hp = hpA_ * (st.hpY + v - st.hpX);
394 d[i] =
static_cast<T
>(st.bell.process(hp));
399 if (osOn) oversampler_->downsample(buffer);
401 for (
int ch = 0; ch < nCh; ++ch)
403 T* d = buffer.getChannel(ch);
404 const auto& dry = dryRing_[
static_cast<size_t>(ch)];
405 for (
int i = 0; i < nS; ++i)
407 const int idx = (dryPos_ + i - latency_) & (drySize_ - 1);
408 const T drySample = dry[
static_cast<size_t>(idx)];
409 const T mixVal =
moveTowards(mixStart, mixTarget, mixMaxStep_ *
static_cast<T
>(i + 1));
410 d[i] = drySample + (d[i] - drySample) * mixVal;
413 currentMix_ =
moveTowards(mixStart, mixTarget, mixMaxStep_ *
static_cast<T
>(nS));
414 dryPos_ = (dryPos_ + nS) & (drySize_ - 1);
417 static constexpr double kDiffRho = 0.974;
421 double b0 = 1.0, b1 = 0.0, b2 = 0.0, a1 = 0.0, a2 = 0.0;
422 double z1 = 0.0, z2 = 0.0;
424 [[nodiscard]]
double process(
double x)
noexcept
426 const double y = b0 * x + z1;
427 z1 = b1 * x - a1 * y + z2;
428 z2 = b2 * x - a2 * y;
440 double hpX = 0.0, hpY = 0.0;
444 void recompute() noexcept
446 const double drive = std::pow(10.0,
static_cast<double>(
447 driveDb_.load(std::memory_order_relaxed)) / 20.0);
448 const double size =
static_cast<double>(coreSize_.load(std::memory_order_relaxed));
449 const double res =
static_cast<double>(resonance_.load(std::memory_order_relaxed));
454 const double cornerHz = 40.0 * std::pow(0.125, size);
455 leak_ = std::exp(-2.0 * std::numbers::pi * cornerHz / sampleRate_);
456 hpA_ = 1.0 - 2.0 * std::numbers::pi * cornerHz / sampleRate_;
457 halfT_ = 0.5 / sampleRate_;
458 invHalfT_ = 2.0 * sampleRate_;
462 const double fluxRef = 1.0 / (2.0 * std::numbers::pi * 30.0);
463 const double headroom = 0.6 + 0.8 * size;
464 hScale_ = drive * 1.1 * 2.2e4 / (fluxRef * headroom);
475 cal.prepare(sampleRate_);
476 cal.setParameters(3.5e5, 2.2e4, 1.6e-3, 3.2e4, 0.25);
477 double flux = 0.0, vPrev = 0.0, mPrev = 0.0, vPrev2 = 0.0;
478 double inSq = 0.0, outSq = 0.0;
479 const int n =
static_cast<int>(0.04 * sampleRate_);
480 for (
int i = 0; i < n; ++i)
482 const double x = 0.25 * std::sin(2.0 * std::numbers::pi * 100.0 * i / sampleRate_);
483 const double fluxNew = leak_ * flux + halfT_ * (x + vPrev);
485 const double m =
static_cast<double>(
486 cal.processSample(
static_cast<T
>(hScale_ * fluxNew)));
487 double v = (m - leak_ * mPrev) * invHalfT_ - kDiffRho * vPrev2;
497 mScale_ = (outSq > 0.0) ? std::sqrt(inSq / outSq) : 1.0;
501 const double bellHz = std::min(12000.0 + 6000.0 * res, 0.42 * sampleRate_);
502 const double bellDb = 2.5 * res;
504 for (
auto& ch : channels_)
506 const double pz1 = ch.bell.z1, pz2 = ch.bell.z2;
507 ch.bell = BellSection { bc.b0, bc.b1, bc.b2, bc.a1, bc.a2, 0.0, 0.0 };
515 double sampleRate_ = 48000.0;
516 int numChannels_ = 0;
520 std::unique_ptr<Oversampling<T>> oversampler_;
521 std::vector<std::vector<T>> dryRing_;
524 std::atomic<bool> prepared_ {
false };
526 std::vector<ChannelState> channels_;
528 double leak_ = 0.999;
530 double halfT_ = 0.5 / 48000.0;
531 double invHalfT_ = 96000.0;
532 double hScale_ = 1.0;
533 double mScale_ = 1.0;
534 double hScaleSm_ = -1.0;
535 double mScaleSm_ = -1.0;
536 T currentMix_ = T(1);
537 T mixMaxStep_ = T(1.0 / 960.0);
539 std::atomic<T> driveDb_ { T(0) };
540 std::atomic<T> coreSize_ { T(0.5) };
541 std::atomic<T> resonance_ { T(0.3) };
542 std::atomic<T> mix_ { T(1) };
543 std::atomic<bool> dirty_ {
true };