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.
82 lines
3.0 KiB
C++
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
|