92 initDefaultFrequencies();
116 numChannels_ = std::min(
static_cast<int>(spec.
numChannels), 16);
138 lpFftSize_ = fftPow2;
143 lpLatency_ = firLength_ / 2;
145 const int numBins = lpFftSize_ / 2 + 1;
147 lpMagnitudesFlat_.assign(
static_cast<size_t>(MaxBands) *
static_cast<size_t>(numBins) * 2, T(0));
148 lpPrevBlockFlat_.assign(
static_cast<size_t>(spec.
numChannels) *
static_cast<size_t>(firLength_), T(0));
150 lpFftIn_.assign(
static_cast<size_t>(lpFftSize_), T(0));
151 lpFftOut_.assign(
static_cast<size_t>(lpFftSize_ + 2), T(0));
152 lpBandFft_.assign(
static_cast<size_t>(lpFftSize_ + 2), T(0));
153 lpFftResult_.assign(
static_cast<size_t>(lpFftSize_), T(0));
157 lpIdealMagsFlat_.assign(
static_cast<size_t>(MaxBands) *
static_cast<size_t>(numBins), T(0));
158 lpTimeResponse_.assign(
static_cast<size_t>(lpFftSize_ + 2), T(0));
159 lpFirKernel_.assign(
static_cast<size_t>(lpFftSize_), T(0));
161 lpFft_ = std::make_unique<FFTReal<T>>(lpFftSize_);
166 allpassFlat_.resize(
static_cast<size_t>(MaxBands * kMaxSplits));
171 for (
int i = 0; i < kMaxSplits; ++i)
173 freqSmoothers_[i].prepare(spec.
sampleRate, 5.0);
175 T target = targetFrequencies_[i].load(std::memory_order_relaxed);
176 freqSmoothers_[i].reset(target);
177 frequencies_[i] = target;
181 lastMode_ = filterMode_.load(std::memory_order_relaxed);
182 dirty_.store(
true, std::memory_order_relaxed);
183 lpMagDirty_.store(
true, std::memory_order_relaxed);
212 if (!prepared_ || bandOutputs ==
nullptr)
return 0;
214 const FilterMode mode = filterMode_.load(std::memory_order_relaxed);
216 if (mode != lastMode_)
223 if (lpActive) syncFrequenciesToTargets();
227 if (freqUpdatePending_.exchange(
false, std::memory_order_acquire))
233 syncFrequenciesToTargets();
237 for (
int i = 0; i < kMaxSplits; ++i)
238 freqSmoothers_[i].setTargetValue(targetFrequencies_[i].load(std::memory_order_relaxed));
242 if (dirty_.load(std::memory_order_relaxed) &&
243 dirty_.exchange(
false, std::memory_order_acquire))
245 updateCoefficients();
248 const int n = std::min(numOutputBands, numBands_.load(std::memory_order_relaxed));
253 int nS = std::min(input.getNumSamples(), spec_.
maxBlockSize);
254 for (
int b = 0; b < n; ++b)
255 nS = std::min(nS, bandOutputs[b].getNumSamples());
256 if (nS <= 0)
return 0;
259 processLinearPhase(input, bandOutputs, n, nS);
261 processIIR(input, bandOutputs, n, nS);
263 passExtraChannels(input, bandOutputs, n, nS);
278 numBands_.store(std::clamp(n, 2, MaxBands), std::memory_order_relaxed);
279 initDefaultFrequencies();
280 dirty_.store(
true, std::memory_order_release);
281 lpMagDirty_.store(
true, std::memory_order_release);
292 if (!std::isfinite(freqHz))
return;
293 if (index >= 0 && index < numBands_.load(std::memory_order_relaxed) - 1)
295 freqHz = std::max(freqHz, T(1));
298 std::array<T, kMaxSplits> localTargets;
299 for (
int i = 0; i < kMaxSplits; ++i)
300 localTargets[i] = targetFrequencies_[i].load(std::memory_order_relaxed);
302 localTargets[
static_cast<size_t>(index)] = freqHz;
310 const int activeSplits = std::clamp(numBands_.load(std::memory_order_relaxed) - 1, 0, kMaxSplits);
311 for (
int i = 1; i < activeSplits; ++i)
313 const T key = localTargets[
static_cast<size_t>(i)];
315 while (j >= 0 && localTargets[
static_cast<size_t>(j)] > key)
317 localTargets[
static_cast<size_t>(j + 1)] = localTargets[
static_cast<size_t>(j)];
320 localTargets[
static_cast<size_t>(j + 1)] = key;
323 for (
int i = 0; i < kMaxSplits; ++i)
324 targetFrequencies_[i].store(localTargets[
static_cast<size_t>(i)], std::memory_order_relaxed);
326 freqUpdatePending_.store(
true, std::memory_order_release);
327 dirty_.store(
true, std::memory_order_release);
328 lpMagDirty_.store(
true, std::memory_order_release);
335 if (order == 12 || order == 24 || order == 48)
337 order_.store(order, std::memory_order_relaxed);
338 dirty_.store(
true, std::memory_order_release);
339 lpMagDirty_.store(
true, std::memory_order_release);
346 const int m = std::clamp(
static_cast<int>(mode), 0, 1);
347 filterMode_.store(
static_cast<FilterMode>(m), std::memory_order_relaxed);
348 dirty_.store(
true, std::memory_order_release);
349 lpMagDirty_.store(
true, std::memory_order_release);
354 [[nodiscard]]
int getNumBands() const noexcept {
return numBands_.load(std::memory_order_relaxed); }
355 [[nodiscard]]
int getOrder() const noexcept {
return order_.load(std::memory_order_relaxed); }
361 if (index < 0 || index >= kMaxSplits)
return T(0);
362 return targetFrequencies_[
static_cast<size_t>(index)].load(std::memory_order_relaxed);
379 for (
auto& sp : splits_)
381 for (
auto& b : sp.lp) b.reset();
382 for (
auto& b : sp.hp) b.reset();
384 for (
auto& apChain : allpassFlat_)
385 for (
auto& b : apChain.stages) b.reset();
387 std::fill(lpPrevBlockFlat_.begin(), lpPrevBlockFlat_.end(), T(0));
392 [[nodiscard]] std::vector<uint8_t>
getState()
const
396 w.
write(
"numBands", n);
400 for (
int i = 0; i < n - 1; ++i)
402 std::snprintf(key,
sizeof(key),
"x%d", i);
418 for (
int i = 0; i < n - 1; ++i)
420 std::snprintf(key,
sizeof(key),
"x%d", i);
421 const float f = r.
read(key, -1.0f);
428 static constexpr int kMaxSplits = MaxBands - 1;
429 static constexpr int kMaxStagesPerFilter = 4;
430 static constexpr int kLpMaxBlockSize = 1 << 18;
434 std::array<Biquad<T, 16>, kMaxStagesPerFilter> lp;
435 std::array<Biquad<T, 16>, kMaxStagesPerFilter> hp;
440 std::array<Biquad<T, 16>, kMaxStagesPerFilter> stages;
452 [[nodiscard]]
static BiquadCoeffs makeFirstOrderAllPass(
double sampleRate,
double freq)
noexcept
454 freq = std::clamp(freq, 1.0, std::max(1.0, sampleRate * 0.499));
455 const double w = std::tan(std::numbers::pi * freq / sampleRate);
456 const double a = (w - 1.0) / (w + 1.0);
466 void initDefaultFrequencies() noexcept
468 int numSplits = numBands_.load(std::memory_order_relaxed) - 1;
469 const T logMin = std::log(T(100));
470 const T logMax = std::log(T(10000));
472 for (
int s = 0; s < numSplits; ++s)
474 T t =
static_cast<T
>(s + 1) /
static_cast<T
>(numSplits + 1);
475 targetFrequencies_[s].store(std::exp(logMin + t * (logMax - logMin)), std::memory_order_relaxed);
477 freqUpdatePending_.store(
true, std::memory_order_release);
483 void syncFrequenciesToTargets() noexcept
485 for (
int i = 0; i < kMaxSplits; ++i)
487 const T t = targetFrequencies_[i].load(std::memory_order_relaxed);
489 freqSmoothers_[i].reset(t);
493 [[nodiscard]]
static double clampSplitFreq(
double f,
double sr)
noexcept
495 return std::clamp(f, 20.0, sr * 0.499);
498 void updateCoefficients() noexcept
502 int numSplits = numBands_.load(std::memory_order_relaxed) - 1;
507 if (numSplits > lastNumSplits_)
509 for (
int s = lastNumSplits_; s < numSplits; ++s)
511 for (
auto& b : splits_[s].lp) b.
reset();
512 for (
auto& b : splits_[s].hp) b.
reset();
513 for (
int b = 0; b < s; ++b)
514 for (
auto& st : allpassFlat_[static_cast<size_t>(b * kMaxSplits + s)].stages) st.
reset();
517 lastNumSplits_ = numSplits;
527 switch (order_.load(std::memory_order_relaxed))
530 numStagesPerFilter_ = 2;
531 numStagesAllpass_ = 1;
532 for (
int s = 0; s < numSplits; ++s)
534 double f = clampSplitFreq(
static_cast<double>(frequencies_[s]), sr);
537 auto apC = makeFirstOrderAllPass(sr, f);
548 splits_[s].lp[0].setCoeffs(lpC);
549 splits_[s].lp[1].setCoeffs(lpC);
550 splits_[s].hp[0].setCoeffs(hpC0);
551 splits_[s].hp[1].setCoeffs(hpC);
552 for (
int b = 0; b < s; ++b)
553 allpassFlat_[
static_cast<size_t>(b * kMaxSplits + s)].stages[0].setCoeffs(apC);
558 numStagesPerFilter_ = 2;
559 numStagesAllpass_ = 1;
560 for (
int s = 0; s < numSplits; ++s)
562 double f = clampSplitFreq(
static_cast<double>(frequencies_[s]), sr);
567 for (
int st = 0; st < 2; ++st)
569 splits_[s].lp[st].setCoeffs(lpC);
570 splits_[s].hp[st].setCoeffs(hpC);
572 for (
int b = 0; b < s; ++b)
573 allpassFlat_[
static_cast<size_t>(b * kMaxSplits + s)].stages[0].setCoeffs(apC);
579 numStagesPerFilter_ = 4;
580 numStagesAllpass_ = 2;
581 constexpr double q1 = 0.5412;
582 constexpr double q2 = 1.3066;
583 const double qArr[4] = { q1, q2, q1, q2 };
585 for (
int s = 0; s < numSplits; ++s)
587 double f = clampSplitFreq(
static_cast<double>(frequencies_[s]), sr);
589 for (
int st = 0; st < 4; ++st)
594 for (
int b = 0; b < s; ++b)
596 auto& chain = allpassFlat_[
static_cast<size_t>(b * kMaxSplits + s)];
608 void processIIR(AudioBufferView<T> input, AudioBufferView<T>* outputs,
int numBands,
int nS)
noexcept
611 const int nCh = std::min(input.getNumChannels(), numChannels_);
612 const int numSplits = numBands - 1;
614 bool anySmoothing =
false;
615 for (
int i = 0; i < numSplits; ++i)
616 anySmoothing = anySmoothing || freqSmoothers_[i].isSmoothing();
620 constexpr int kSubBlockSize = 32;
624 int blockLen = std::min(kSubBlockSize, nS - offset);
625 for (
int i = 0; i < numSplits; ++i)
627 for (
int s = 0; s < blockLen; ++s)
628 (
void)freqSmoothers_[i].getNextValue();
629 frequencies_[i] = freqSmoothers_[i].getCurrentValue();
631 updateCoefficients();
632 processIIRRange(input, outputs, numBands, nCh, offset, blockLen);
638 processIIRRange(input, outputs, numBands, nCh, 0, nS);
642 inline T* getWorkBufChannel(
int ch)
noexcept
644 return workBuf_.data() +
static_cast<size_t>(ch) *
static_cast<size_t>(spec_.
maxBlockSize);
647 void processIIRRange(AudioBufferView<T> input, AudioBufferView<T>* outputs,
int numBands,
int nCh,
int offset,
int blockLen)
noexcept
649 const int numSplits = numBands - 1;
651 for (
int ch = 0; ch < nCh; ++ch)
653 const T* src = input.getChannel(ch) + offset;
654 T* dst = getWorkBufChannel(ch);
655 std::copy(src, src + blockLen, dst);
658 for (
int s = 0; s < numSplits; ++s)
660 for (
int i = 0; i < blockLen; ++i)
662 for (
int ch = 0; ch < nCh; ++ch)
664 T* workCh = getWorkBufChannel(ch);
665 const double sample =
static_cast<double>(workCh[i]);
670 double lpSample = sample;
671 for (
int st = 0; st < numStagesPerFilter_; ++st)
672 lpSample = splits_[s].lp[st].processSampleCore(lpSample, ch);
673 outputs[s].getChannel(ch)[offset + i] =
static_cast<T
>(lpSample);
675 double hpSample = sample;
676 for (
int st = 0; st < numStagesPerFilter_; ++st)
677 hpSample = splits_[s].hp[st].processSampleCore(hpSample, ch);
678 workCh[i] =
static_cast<T
>(hpSample);
683 for (
int ch = 0; ch < nCh; ++ch)
685 T* dst = outputs[numBands - 1].getChannel(ch) + offset;
686 const T* src = getWorkBufChannel(ch);
687 std::copy(src, src + blockLen, dst);
690 for (
int s = 1; s < numSplits; ++s)
692 for (
int b = 0; b < s; ++b)
694 for (
int i = 0; i < blockLen; ++i)
696 for (
int ch = 0; ch < nCh; ++ch)
698 double sample =
static_cast<double>(outputs[b].getChannel(ch)[offset + i]);
699 for (
int st = 0; st < numStagesAllpass_; ++st)
700 sample = allpassFlat_[
static_cast<size_t>(b * kMaxSplits + s)].stages[st].processSampleCore(sample, ch);
701 outputs[b].getChannel(ch)[offset + i] =
static_cast<T
>(sample);
711 void passExtraChannels(AudioBufferView<T> input, AudioBufferView<T>* outputs,
int numBands,
int nS)
noexcept
713 const int inCh = input.getNumChannels();
714 for (
int ch = numChannels_; ch < inCh; ++ch)
716 for (
int b = 0; b < numBands; ++b)
718 if (ch >= outputs[b].getNumChannels())
continue;
719 T* dst = outputs[b].getChannel(ch);
722 const T* src = input.getChannel(ch);
723 std::copy(src, src + nS, dst);
727 std::fill(dst, dst + nS, T(0));
733 void recomputeLinearPhaseMagnitudes() noexcept
735 if (lpFftSize_ == 0)
return;
736 if (!lpMagDirty_.load(std::memory_order_relaxed) ||
737 !lpMagDirty_.exchange(
false, std::memory_order_acquire))
740 const int numBins = lpFftSize_ / 2 + 1;
741 const int numSplits = numBands_.load(std::memory_order_relaxed) - 1;
745 switch (order_.load(std::memory_order_relaxed))
747 case 12: expo = 2;
break;
748 case 24: expo = 4;
break;
749 case 48: expo = 8;
break;
754 const int nBands = numBands_.load(std::memory_order_relaxed);
756 for (
int k = 0; k < numBins; ++k)
758 double freq = sr *
static_cast<double>(k) /
static_cast<double>(lpFftSize_);
759 T lpMag[kMaxSplits], hpMag[kMaxSplits];
761 for (
int s = 0; s < numSplits; ++s)
763 double fc = clampSplitFreq(
static_cast<double>(frequencies_[s]), sr);
764 double ratio = freq / fc;
769 double rPow = std::pow(ratio,
static_cast<double>(expo));
771 double denom = 1.0 + rPow;
772 lpMag[s] =
static_cast<T
>(1.0 / denom);
773 hpMag[s] =
static_cast<T
>(rPow / denom);
776 lpIdealMagsFlat_[k] = lpMag[0];
777 for (
int b = 1; b < nBands - 1; ++b)
778 lpIdealMagsFlat_[
static_cast<size_t>(b * numBins + k)] = hpMag[b - 1] * lpMag[b];
779 lpIdealMagsFlat_[
static_cast<size_t>((nBands - 1) * numBins + k)] = hpMag[numSplits - 1];
783 for (
int b = 0; b < nBands; ++b)
786 for (
int k = 0; k < numBins; ++k)
788 lpTimeResponse_[
static_cast<size_t>(2 * k)] = lpIdealMagsFlat_[
static_cast<size_t>(b * numBins + k)];
789 lpTimeResponse_[
static_cast<size_t>(2 * k + 1)] = T(0);
792 lpFft_->inverse(lpTimeResponse_.data(), lpFirKernel_.data());
795 std::fill(lpFftIn_.begin(), lpFftIn_.end(), T(0));
796 int halfLen = firLength_ / 2;
797 const double N =
static_cast<double>(firLength_ - 1);
799 for (
int i = 0; i < firLength_; ++i)
802 int srcIdx = (i - halfLen + lpFftSize_) % lpFftSize_;
808 double n =
static_cast<double>(i);
809 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);
812 lpFftIn_[
static_cast<size_t>(i)] = lpFirKernel_[
static_cast<size_t>(srcIdx)] *
static_cast<T
>(window);
816 lpFft_->forward(lpFftIn_.data(), lpFftOut_.data());
819 T* bandMagData = lpMagnitudesFlat_.data() +
static_cast<size_t>(b * numBins * 2);
820 std::copy(lpFftOut_.begin(), lpFftOut_.begin() + (numBins * 2), bandMagData);
824 inline T* getPrevBlockChannel(
int ch)
noexcept
826 return lpPrevBlockFlat_.data() +
static_cast<size_t>(ch) *
static_cast<size_t>(firLength_);
829 void processLinearPhase(AudioBufferView<T> input, AudioBufferView<T>* outputs,
int numBands,
int nS)
noexcept
831 recomputeLinearPhaseMagnitudes();
833 const int nCh = std::min(input.getNumChannels(), numChannels_);
834 const int numBins = lpFftSize_ / 2 + 1;
835 const int overlapSize = firLength_ - 1;
837 for (
int ch = 0; ch < nCh; ++ch)
839 T* channelData = input.getChannel(ch);
840 T* prev = getPrevBlockChannel(ch);
843 for (
int i = 0; i < overlapSize; ++i)
844 lpFftIn_[
static_cast<size_t>(i)] = prev[i];
846 for (
int i = 0; i < nS; ++i)
847 lpFftIn_[
static_cast<size_t>(overlapSize + i)] = channelData[i];
849 for (
int i = overlapSize + nS; i < lpFftSize_; ++i)
850 lpFftIn_[
static_cast<size_t>(i)] = T(0);
853 for (
int i = 0; i < overlapSize; ++i)
855 if (nS - overlapSize + i >= 0)
856 prev[i] = channelData[nS - overlapSize + i];
858 prev[i] = lpFftIn_[
static_cast<size_t>(nS + i)];
861 lpFft_->forward(lpFftIn_.data(), lpFftOut_.data());
863 for (
int b = 0; b < numBands; ++b)
865 const T* kernelSpectrum = lpMagnitudesFlat_.data() +
static_cast<size_t>(b * numBins * 2);
868 for (
int k = 0; k < numBins; ++k)
870 T r1 = lpFftOut_[
static_cast<size_t>(2 * k)];
871 T i1 = lpFftOut_[
static_cast<size_t>(2 * k + 1)];
872 T r2 = kernelSpectrum[2 * k];
873 T i2 = kernelSpectrum[2 * k + 1];
875 lpBandFft_[
static_cast<size_t>(2 * k)] = r1 * r2 - i1 * i2;
876 lpBandFft_[
static_cast<size_t>(2 * k + 1)] = r1 * i2 + i1 * r2;
879 lpFft_->inverse(lpBandFft_.data(), lpFftResult_.data());
882 T* outCh = outputs[b].getChannel(ch);
883 for (
int i = 0; i < nS; ++i)
884 outCh[i] = lpFftResult_[
static_cast<size_t>(overlapSize + i)];
892 bool prepared_ =
false;
893 std::atomic<int> numBands_ { 2 };
894 std::atomic<int> order_ { 24 };
895 int numChannels_ = 2;
897 std::atomic<bool> dirty_ {
true };
899 std::array<std::atomic<T>, kMaxSplits> targetFrequencies_ {};
900 std::atomic<bool> freqUpdatePending_ {
false };
902 std::array<T, kMaxSplits> frequencies_ {};
903 std::array<SmoothedValue<T>, kMaxSplits> freqSmoothers_;
905 int numStagesPerFilter_ = 2;
906 int numStagesAllpass_ = 1;
907 int lastNumSplits_ = 0;
909 std::array<SplitPoint, kMaxSplits> splits_ {};
912 std::vector<AllPassChain> allpassFlat_;
913 std::vector<T> workBuf_;
916 std::unique_ptr<FFTReal<T>> lpFft_;
920 std::atomic<bool> lpMagDirty_ {
true };
922 std::vector<T> lpMagnitudesFlat_;
923 std::vector<T> lpPrevBlockFlat_;
924 std::vector<T> lpFftIn_, lpFftOut_, lpBandFft_, lpFftResult_;
925 std::vector<T> lpIdealMagsFlat_;
926 std::vector<T> lpTimeResponse_;
927 std::vector<T> lpFirKernel_;