88 initDefaultFrequencies();
112 numChannels_ = std::min(
static_cast<int>(spec.
numChannels), 16);
134 lpFftSize_ = fftPow2;
139 lpLatency_ = firLength_ / 2;
141 const int numBins = lpFftSize_ / 2 + 1;
143 lpMagnitudesFlat_.assign(
static_cast<size_t>(MaxBands) *
static_cast<size_t>(numBins) * 2, T(0));
144 lpPrevBlockFlat_.assign(
static_cast<size_t>(spec.
numChannels) *
static_cast<size_t>(firLength_), T(0));
146 lpFftIn_.assign(
static_cast<size_t>(lpFftSize_), T(0));
147 lpFftOut_.assign(
static_cast<size_t>(lpFftSize_ + 2), T(0));
148 lpBandFft_.assign(
static_cast<size_t>(lpFftSize_ + 2), T(0));
149 lpFftResult_.assign(
static_cast<size_t>(lpFftSize_), T(0));
153 lpIdealMagsFlat_.assign(
static_cast<size_t>(MaxBands) *
static_cast<size_t>(numBins), T(0));
154 lpTimeResponse_.assign(
static_cast<size_t>(lpFftSize_ + 2), T(0));
155 lpFirKernel_.assign(
static_cast<size_t>(lpFftSize_), T(0));
157 lpFft_ = std::make_unique<FFTReal<T>>(lpFftSize_);
162 allpassFlat_.resize(
static_cast<size_t>(MaxBands * kMaxSplits));
167 for (
int i = 0; i < kMaxSplits; ++i)
169 freqSmoothers_[i].prepare(spec.
sampleRate, 5.0);
171 T target = targetFrequencies_[i].load(std::memory_order_relaxed);
172 freqSmoothers_[i].reset(target);
173 frequencies_[i] = target;
177 lastMode_ = filterMode_.load(std::memory_order_relaxed);
178 dirty_.store(
true, std::memory_order_relaxed);
179 lpMagDirty_.store(
true, std::memory_order_relaxed);
208 if (!prepared_ || bandOutputs ==
nullptr)
return 0;
210 const FilterMode mode = filterMode_.load(std::memory_order_relaxed);
212 if (mode != lastMode_)
219 if (lpActive) syncFrequenciesToTargets();
223 if (freqUpdatePending_.exchange(
false, std::memory_order_acquire))
229 syncFrequenciesToTargets();
233 for (
int i = 0; i < kMaxSplits; ++i)
234 freqSmoothers_[i].setTargetValue(targetFrequencies_[i].load(std::memory_order_relaxed));
238 if (dirty_.load(std::memory_order_relaxed) &&
239 dirty_.exchange(
false, std::memory_order_acquire))
241 updateCoefficients();
244 const int n = std::min(numOutputBands, numBands_.load(std::memory_order_relaxed));
249 int nS = std::min(input.getNumSamples(), spec_.
maxBlockSize);
250 for (
int b = 0; b < n; ++b)
251 nS = std::min(nS, bandOutputs[b].getNumSamples());
252 if (nS <= 0)
return 0;
255 processLinearPhase(input, bandOutputs, n, nS);
257 processIIR(input, bandOutputs, n, nS);
259 passExtraChannels(input, bandOutputs, n, nS);
274 numBands_.store(std::clamp(n, 2, MaxBands), std::memory_order_relaxed);
275 initDefaultFrequencies();
276 dirty_.store(
true, std::memory_order_release);
277 lpMagDirty_.store(
true, std::memory_order_release);
288 if (!std::isfinite(freqHz))
return;
289 if (index >= 0 && index < numBands_.load(std::memory_order_relaxed) - 1)
291 freqHz = std::max(freqHz, T(1));
294 std::array<T, kMaxSplits> localTargets;
295 for (
int i = 0; i < kMaxSplits; ++i)
296 localTargets[i] = targetFrequencies_[i].load(std::memory_order_relaxed);
298 localTargets[
static_cast<size_t>(index)] = freqHz;
306 const int activeSplits = std::clamp(numBands_.load(std::memory_order_relaxed) - 1, 0, kMaxSplits);
307 for (
int i = 1; i < activeSplits; ++i)
309 const T key = localTargets[
static_cast<size_t>(i)];
311 while (j >= 0 && localTargets[
static_cast<size_t>(j)] > key)
313 localTargets[
static_cast<size_t>(j + 1)] = localTargets[
static_cast<size_t>(j)];
316 localTargets[
static_cast<size_t>(j + 1)] = key;
319 for (
int i = 0; i < kMaxSplits; ++i)
320 targetFrequencies_[i].store(localTargets[
static_cast<size_t>(i)], std::memory_order_relaxed);
322 freqUpdatePending_.store(
true, std::memory_order_release);
323 dirty_.store(
true, std::memory_order_release);
324 lpMagDirty_.store(
true, std::memory_order_release);
331 if (order == 12 || order == 24 || order == 48)
333 order_.store(order, std::memory_order_relaxed);
334 dirty_.store(
true, std::memory_order_release);
335 lpMagDirty_.store(
true, std::memory_order_release);
342 const int m = std::clamp(
static_cast<int>(mode), 0, 1);
343 filterMode_.store(
static_cast<FilterMode>(m), std::memory_order_relaxed);
344 dirty_.store(
true, std::memory_order_release);
345 lpMagDirty_.store(
true, std::memory_order_release);
350 [[nodiscard]]
int getNumBands() const noexcept {
return numBands_.load(std::memory_order_relaxed); }
351 [[nodiscard]]
int getOrder() const noexcept {
return order_.load(std::memory_order_relaxed); }
357 if (index < 0 || index >= kMaxSplits)
return T(0);
358 return targetFrequencies_[
static_cast<size_t>(index)].load(std::memory_order_relaxed);
375 for (
auto& sp : splits_)
377 for (
auto& b : sp.lp) b.reset();
378 for (
auto& b : sp.hp) b.reset();
380 for (
auto& apChain : allpassFlat_)
381 for (
auto& b : apChain.stages) b.reset();
383 std::fill(lpPrevBlockFlat_.begin(), lpPrevBlockFlat_.end(), T(0));
388 [[nodiscard]] std::vector<uint8_t>
getState()
const
392 w.
write(
"numBands", n);
396 for (
int i = 0; i < n - 1; ++i)
398 std::snprintf(key,
sizeof(key),
"x%d", i);
414 for (
int i = 0; i < n - 1; ++i)
416 std::snprintf(key,
sizeof(key),
"x%d", i);
417 const float f = r.
read(key, -1.0f);
424 static constexpr int kMaxSplits = MaxBands - 1;
425 static constexpr int kMaxStagesPerFilter = 4;
426 static constexpr int kLpMaxBlockSize = 1 << 18;
430 std::array<Biquad<T, 16>, kMaxStagesPerFilter> lp;
431 std::array<Biquad<T, 16>, kMaxStagesPerFilter> hp;
436 std::array<Biquad<T, 16>, kMaxStagesPerFilter> stages;
448 [[nodiscard]]
static BiquadCoeffs makeFirstOrderAllPass(
double sampleRate,
double freq)
noexcept
450 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
451 const double w = std::tan(std::numbers::pi * freq / sampleRate);
452 const double a = (w - 1.0) / (w + 1.0);
462 void initDefaultFrequencies() noexcept
464 int numSplits = numBands_.load(std::memory_order_relaxed) - 1;
465 const T logMin = std::log(T(100));
466 const T logMax = std::log(T(10000));
468 for (
int s = 0; s < numSplits; ++s)
470 T t =
static_cast<T
>(s + 1) /
static_cast<T
>(numSplits + 1);
471 targetFrequencies_[s].store(std::exp(logMin + t * (logMax - logMin)), std::memory_order_relaxed);
473 freqUpdatePending_.store(
true, std::memory_order_release);
479 void syncFrequenciesToTargets() noexcept
481 for (
int i = 0; i < kMaxSplits; ++i)
483 const T t = targetFrequencies_[i].load(std::memory_order_relaxed);
485 freqSmoothers_[i].reset(t);
489 [[nodiscard]]
static double clampSplitFreq(
double f,
double sr)
noexcept
491 return std::clamp(f, 20.0, sr * 0.499);
494 void updateCoefficients() noexcept
498 int numSplits = numBands_.load(std::memory_order_relaxed) - 1;
503 if (numSplits > lastNumSplits_)
505 for (
int s = lastNumSplits_; s < numSplits; ++s)
507 for (
auto& b : splits_[s].lp) b.
reset();
508 for (
auto& b : splits_[s].hp) b.
reset();
509 for (
int b = 0; b < s; ++b)
510 for (
auto& st : allpassFlat_[static_cast<size_t>(b * kMaxSplits + s)].stages) st.
reset();
513 lastNumSplits_ = numSplits;
523 switch (order_.load(std::memory_order_relaxed))
526 numStagesPerFilter_ = 2;
527 numStagesAllpass_ = 1;
528 for (
int s = 0; s < numSplits; ++s)
530 double f = clampSplitFreq(
static_cast<double>(frequencies_[s]), sr);
533 auto apC = makeFirstOrderAllPass(sr, f);
544 splits_[s].lp[0].setCoeffs(lpC);
545 splits_[s].lp[1].setCoeffs(lpC);
546 splits_[s].hp[0].setCoeffs(hpC0);
547 splits_[s].hp[1].setCoeffs(hpC);
548 for (
int b = 0; b < s; ++b)
549 allpassFlat_[
static_cast<size_t>(b * kMaxSplits + s)].stages[0].setCoeffs(apC);
554 numStagesPerFilter_ = 2;
555 numStagesAllpass_ = 1;
556 for (
int s = 0; s < numSplits; ++s)
558 double f = clampSplitFreq(
static_cast<double>(frequencies_[s]), sr);
563 for (
int st = 0; st < 2; ++st)
565 splits_[s].lp[st].setCoeffs(lpC);
566 splits_[s].hp[st].setCoeffs(hpC);
568 for (
int b = 0; b < s; ++b)
569 allpassFlat_[
static_cast<size_t>(b * kMaxSplits + s)].stages[0].setCoeffs(apC);
575 numStagesPerFilter_ = 4;
576 numStagesAllpass_ = 2;
577 constexpr double q1 = 0.5412;
578 constexpr double q2 = 1.3066;
579 const double qArr[4] = { q1, q2, q1, q2 };
581 for (
int s = 0; s < numSplits; ++s)
583 double f = clampSplitFreq(
static_cast<double>(frequencies_[s]), sr);
585 for (
int st = 0; st < 4; ++st)
590 for (
int b = 0; b < s; ++b)
592 auto& chain = allpassFlat_[
static_cast<size_t>(b * kMaxSplits + s)];
604 void processIIR(AudioBufferView<T> input, AudioBufferView<T>* outputs,
int numBands,
int nS)
noexcept
607 const int nCh = std::min(input.getNumChannels(), numChannels_);
608 const int numSplits = numBands - 1;
610 bool anySmoothing =
false;
611 for (
int i = 0; i < numSplits; ++i)
612 anySmoothing = anySmoothing || freqSmoothers_[i].isSmoothing();
616 constexpr int kSubBlockSize = 32;
620 int blockLen = std::min(kSubBlockSize, nS - offset);
621 for (
int i = 0; i < numSplits; ++i)
623 for (
int s = 0; s < blockLen; ++s)
624 (
void)freqSmoothers_[i].getNextValue();
625 frequencies_[i] = freqSmoothers_[i].getCurrentValue();
627 updateCoefficients();
628 processIIRRange(input, outputs, numBands, nCh, offset, blockLen);
634 processIIRRange(input, outputs, numBands, nCh, 0, nS);
638 inline T* getWorkBufChannel(
int ch)
noexcept
640 return workBuf_.data() +
static_cast<size_t>(ch) *
static_cast<size_t>(spec_.
maxBlockSize);
643 void processIIRRange(AudioBufferView<T> input, AudioBufferView<T>* outputs,
int numBands,
int nCh,
int offset,
int blockLen)
noexcept
645 const int numSplits = numBands - 1;
647 for (
int ch = 0; ch < nCh; ++ch)
649 const T* src = input.getChannel(ch) + offset;
650 T* dst = getWorkBufChannel(ch);
651 std::copy(src, src + blockLen, dst);
654 for (
int s = 0; s < numSplits; ++s)
656 for (
int i = 0; i < blockLen; ++i)
658 for (
int ch = 0; ch < nCh; ++ch)
660 T* workCh = getWorkBufChannel(ch);
661 const double sample =
static_cast<double>(workCh[i]);
666 double lpSample = sample;
667 for (
int st = 0; st < numStagesPerFilter_; ++st)
668 lpSample = splits_[s].lp[st].processSampleCore(lpSample, ch);
669 outputs[s].getChannel(ch)[offset + i] =
static_cast<T
>(lpSample);
671 double hpSample = sample;
672 for (
int st = 0; st < numStagesPerFilter_; ++st)
673 hpSample = splits_[s].hp[st].processSampleCore(hpSample, ch);
674 workCh[i] =
static_cast<T
>(hpSample);
679 for (
int ch = 0; ch < nCh; ++ch)
681 T* dst = outputs[numBands - 1].getChannel(ch) + offset;
682 const T* src = getWorkBufChannel(ch);
683 std::copy(src, src + blockLen, dst);
686 for (
int s = 1; s < numSplits; ++s)
688 for (
int b = 0; b < s; ++b)
690 for (
int i = 0; i < blockLen; ++i)
692 for (
int ch = 0; ch < nCh; ++ch)
694 double sample =
static_cast<double>(outputs[b].getChannel(ch)[offset + i]);
695 for (
int st = 0; st < numStagesAllpass_; ++st)
696 sample = allpassFlat_[
static_cast<size_t>(b * kMaxSplits + s)].stages[st].processSampleCore(sample, ch);
697 outputs[b].getChannel(ch)[offset + i] =
static_cast<T
>(sample);
707 void passExtraChannels(AudioBufferView<T> input, AudioBufferView<T>* outputs,
int numBands,
int nS)
noexcept
709 const int inCh = input.getNumChannels();
710 for (
int ch = numChannels_; ch < inCh; ++ch)
712 for (
int b = 0; b < numBands; ++b)
714 if (ch >= outputs[b].getNumChannels())
continue;
715 T* dst = outputs[b].getChannel(ch);
718 const T* src = input.getChannel(ch);
719 std::copy(src, src + nS, dst);
723 std::fill(dst, dst + nS, T(0));
729 void recomputeLinearPhaseMagnitudes() noexcept
731 if (lpFftSize_ == 0)
return;
732 if (!lpMagDirty_.load(std::memory_order_relaxed) ||
733 !lpMagDirty_.exchange(
false, std::memory_order_acquire))
736 const int numBins = lpFftSize_ / 2 + 1;
737 const int numSplits = numBands_.load(std::memory_order_relaxed) - 1;
741 switch (order_.load(std::memory_order_relaxed))
743 case 12: expo = 2;
break;
744 case 24: expo = 4;
break;
745 case 48: expo = 8;
break;
750 const int nBands = numBands_.load(std::memory_order_relaxed);
752 for (
int k = 0; k < numBins; ++k)
754 double freq = sr *
static_cast<double>(k) /
static_cast<double>(lpFftSize_);
755 T lpMag[kMaxSplits], hpMag[kMaxSplits];
757 for (
int s = 0; s < numSplits; ++s)
759 double fc = clampSplitFreq(
static_cast<double>(frequencies_[s]), sr);
760 double ratio = freq / fc;
765 double rPow = std::pow(ratio,
static_cast<double>(expo));
767 double denom = 1.0 + rPow;
768 lpMag[s] =
static_cast<T
>(1.0 / denom);
769 hpMag[s] =
static_cast<T
>(rPow / denom);
772 lpIdealMagsFlat_[k] = lpMag[0];
773 for (
int b = 1; b < nBands - 1; ++b)
774 lpIdealMagsFlat_[
static_cast<size_t>(b * numBins + k)] = hpMag[b - 1] * lpMag[b];
775 lpIdealMagsFlat_[
static_cast<size_t>((nBands - 1) * numBins + k)] = hpMag[numSplits - 1];
779 for (
int b = 0; b < nBands; ++b)
782 for (
int k = 0; k < numBins; ++k)
784 lpTimeResponse_[
static_cast<size_t>(2 * k)] = lpIdealMagsFlat_[
static_cast<size_t>(b * numBins + k)];
785 lpTimeResponse_[
static_cast<size_t>(2 * k + 1)] = T(0);
788 lpFft_->inverse(lpTimeResponse_.data(), lpFirKernel_.data());
791 std::fill(lpFftIn_.begin(), lpFftIn_.end(), T(0));
792 int halfLen = firLength_ / 2;
793 const double N =
static_cast<double>(firLength_ - 1);
795 for (
int i = 0; i < firLength_; ++i)
798 int srcIdx = (i - halfLen + lpFftSize_) % lpFftSize_;
804 double n =
static_cast<double>(i);
805 window = 0.42 - 0.5 * std::cos(2.0 * std::numbers::pi * n / N) + 0.08 * std::cos(4.0 * std::numbers::pi * n / N);
808 lpFftIn_[
static_cast<size_t>(i)] = lpFirKernel_[
static_cast<size_t>(srcIdx)] *
static_cast<T
>(window);
812 lpFft_->forward(lpFftIn_.data(), lpFftOut_.data());
815 T* bandMagData = lpMagnitudesFlat_.data() +
static_cast<size_t>(b * numBins * 2);
816 std::copy(lpFftOut_.begin(), lpFftOut_.begin() + (numBins * 2), bandMagData);
820 inline T* getPrevBlockChannel(
int ch)
noexcept
822 return lpPrevBlockFlat_.data() +
static_cast<size_t>(ch) *
static_cast<size_t>(firLength_);
825 void processLinearPhase(AudioBufferView<T> input, AudioBufferView<T>* outputs,
int numBands,
int nS)
noexcept
827 recomputeLinearPhaseMagnitudes();
829 const int nCh = std::min(input.getNumChannels(), numChannels_);
830 const int numBins = lpFftSize_ / 2 + 1;
831 const int overlapSize = firLength_ - 1;
833 for (
int ch = 0; ch < nCh; ++ch)
835 T* channelData = input.getChannel(ch);
836 T* prev = getPrevBlockChannel(ch);
839 for (
int i = 0; i < overlapSize; ++i)
840 lpFftIn_[
static_cast<size_t>(i)] = prev[i];
842 for (
int i = 0; i < nS; ++i)
843 lpFftIn_[
static_cast<size_t>(overlapSize + i)] = channelData[i];
845 for (
int i = overlapSize + nS; i < lpFftSize_; ++i)
846 lpFftIn_[
static_cast<size_t>(i)] = T(0);
849 for (
int i = 0; i < overlapSize; ++i)
851 if (nS - overlapSize + i >= 0)
852 prev[i] = channelData[nS - overlapSize + i];
854 prev[i] = lpFftIn_[
static_cast<size_t>(nS + i)];
857 lpFft_->forward(lpFftIn_.data(), lpFftOut_.data());
859 for (
int b = 0; b < numBands; ++b)
861 const T* kernelSpectrum = lpMagnitudesFlat_.data() +
static_cast<size_t>(b * numBins * 2);
864 for (
int k = 0; k < numBins; ++k)
866 T r1 = lpFftOut_[
static_cast<size_t>(2 * k)];
867 T i1 = lpFftOut_[
static_cast<size_t>(2 * k + 1)];
868 T r2 = kernelSpectrum[2 * k];
869 T i2 = kernelSpectrum[2 * k + 1];
871 lpBandFft_[
static_cast<size_t>(2 * k)] = r1 * r2 - i1 * i2;
872 lpBandFft_[
static_cast<size_t>(2 * k + 1)] = r1 * i2 + i1 * r2;
875 lpFft_->inverse(lpBandFft_.data(), lpFftResult_.data());
878 T* outCh = outputs[b].getChannel(ch);
879 for (
int i = 0; i < nS; ++i)
880 outCh[i] = lpFftResult_[
static_cast<size_t>(overlapSize + i)];
888 bool prepared_ =
false;
889 std::atomic<int> numBands_ { 2 };
890 std::atomic<int> order_ { 24 };
891 int numChannels_ = 2;
893 std::atomic<bool> dirty_ {
true };
895 std::array<std::atomic<T>, kMaxSplits> targetFrequencies_ {};
896 std::atomic<bool> freqUpdatePending_ {
false };
898 std::array<T, kMaxSplits> frequencies_ {};
899 std::array<SmoothedValue<T>, kMaxSplits> freqSmoothers_;
901 int numStagesPerFilter_ = 2;
902 int numStagesAllpass_ = 1;
903 int lastNumSplits_ = 0;
905 std::array<SplitPoint, kMaxSplits> splits_ {};
908 std::vector<AllPassChain> allpassFlat_;
909 std::vector<T> workBuf_;
912 std::unique_ptr<FFTReal<T>> lpFft_;
916 std::atomic<bool> lpMagDirty_ {
true };
918 std::vector<T> lpMagnitudesFlat_;
919 std::vector<T> lpPrevBlockFlat_;
920 std::vector<T> lpFftIn_, lpFftOut_, lpBandFft_, lpFftResult_;
921 std::vector<T> lpIdealMagsFlat_;
922 std::vector<T> lpTimeResponse_;
923 std::vector<T> lpFirKernel_;