Files
biggy dc6fd88ed1 refactor(filter): cache coefficients and extract cutoff calculation
Add updateCoefficients() that short-circuits when cutoff, resonance,
and type are unchanged. Cache ladderG and formantCoefficients so
processSample() avoids redundant exp()/tan() calls per sample.

Extract static getCutoffHz() from Filter::process() so FilterBank
can compute the keytracked cutoff once per parallel branch instead
of per sample. Simplify formant() and screamer() signatures to use
cached state.
2026-09-09 14:32:49 +02:00

82 lines
3.0 KiB
C++

#include "FilterBank.h"
namespace serum
{
void FilterBank::prepare (double sampleRate, int maxBlockSize)
{
f1L.prepare (sampleRate, maxBlockSize);
f1R.prepare (sampleRate, maxBlockSize);
f2L.prepare (sampleRate, maxBlockSize);
f2R.prepare (sampleRate, maxBlockSize);
}
void FilterBank::reset()
{
f1L.reset(); f1R.reset(); f2L.reset(); f2R.reset();
}
void FilterBank::applySlot (Filter& f, float* buf, int n, bool on, int type, float cutoff,
float res, float drive, float key, int slope, float noteHz) noexcept
{
if (on)
f.process (buf, n, cutoff, res, drive, key, noteHz, type, slope);
}
void FilterBank::process (float* l, float* r, int numSamples, const FilterBankParams& p, float noteHz) noexcept
{
const bool anyFilter = p.f1On || p.f2On;
if (! anyFilter)
return;
if (p.route == (int) FilterRoute::Serial)
{
applySlot (f1L, l, numSamples, p.f1On, p.f1Type, p.f1Cutoff, p.f1Res, p.f1Drive, p.f1Key, p.f1Slope, noteHz);
applySlot (f1R, r, numSamples, p.f1On, p.f1Type, p.f1Cutoff, p.f1Res, p.f1Drive, p.f1Key, p.f1Slope, noteHz);
applySlot (f2L, l, numSamples, p.f2On, p.f2Type, p.f2Cutoff, p.f2Res, p.f2Drive, p.f2Key, p.f2Slope, noteHz);
applySlot (f2R, r, numSamples, p.f2On, p.f2Type, p.f2Cutoff, p.f2Res, p.f2Drive, p.f2Key, p.f2Slope, noteHz);
}
else if (p.route == (int) FilterRoute::Parallel)
{
// Run both filters on copies and crossfade.
const float cutoff1 = p.f1On ? Filter::getCutoffHz (p.f1Cutoff, p.f1Key, noteHz) : 0.0f;
const float cutoff2 = p.f2On ? Filter::getCutoffHz (p.f2Cutoff, p.f2Key, noteHz) : 0.0f;
float f1l = 0.0f, f1r = 0.0f, f2l = 0.0f, f2r = 0.0f;
for (int i = 0; i < numSamples; ++i)
{
f1l = l[i]; f1r = r[i];
f2l = l[i]; f2r = r[i];
if (p.f1On)
{
f1l = f1L.processSample (f1l, cutoff1, p.f1Res, p.f1Drive, p.f1Type, p.f1Slope);
f1r = f1R.processSample (f1r, cutoff1, p.f1Res, p.f1Drive, p.f1Type, p.f1Slope);
}
if (p.f2On)
{
f2l = f2L.processSample (f2l, cutoff2, p.f2Res, p.f2Drive, p.f2Type, p.f2Slope);
f2r = f2R.processSample (f2r, cutoff2, p.f2Res, p.f2Drive, p.f2Type, p.f2Slope);
}
const float m = p.mix;
l[i] = f1l * (1.0f - m) + f2l * m;
r[i] = f1r * (1.0f - m) + f2r * m;
}
}
else // Split: filter 1 -> left, filter 2 -> right
{
applySlot (f1L, l, numSamples, p.f1On, p.f1Type, p.f1Cutoff, p.f1Res, p.f1Drive, p.f1Key, p.f1Slope, noteHz);
applySlot (f2R, r, numSamples, p.f2On, p.f2Type, p.f2Cutoff, p.f2Res, p.f2Drive, p.f2Key, p.f2Slope, noteHz);
}
// Post-filter output level.
if (p.out != 1.0f)
{
for (int i = 0; i < numSamples; ++i)
{
l[i] *= p.out;
r[i] *= p.out;
}
}
}
} // namespace serum