- 36 files move into the module whose AGENTS.md already claimed them: Engine/, Synth/, Modulation/, Params/, Plugin/, GUI/, EffectUnits/, Resources/ - Source/engine, modulation, params, plugin and synth become Engine, Modulation, Params, Plugin and Synth, matching the directories that already hold code - ExactCompare.h stays at the Source/ root as the one shared header - SERUMALT_INCLUDE_DIRS adds one include directory per module, so the 31 parent-relative includes become plain names with no path prefix - the module AGENTS.md files list owned files instead of describing a move that had not happened, and README.md and AGENTS.md describe the real tree Verified: the harness passes CTest, the Linux VST3 and Standalone link, the Windows cross-build passes its module checks, each configure exposes only its own targets, no first-party warning line appears, and the generated Windows manifest is unchanged.
84 lines
3.0 KiB
C++
84 lines
3.0 KiB
C++
#include "FilterBank.h"
|
|
#include "ExactCompare.h"
|
|
|
|
namespace serum
|
|
{
|
|
|
|
void FilterBank::prepare (double sampleRate)
|
|
{
|
|
f1L.prepare (sampleRate);
|
|
f1R.prepare (sampleRate);
|
|
f2L.prepare (sampleRate);
|
|
f2R.prepare (sampleRate);
|
|
}
|
|
|
|
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. Skip the multiply only at exact unity so the
|
|
// bypass never changes the rendered samples.
|
|
if (! exactly (p.out, 1.0f))
|
|
{
|
|
for (int i = 0; i < numSamples; ++i)
|
|
{
|
|
l[i] *= p.out;
|
|
r[i] *= p.out;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace serum
|