The filters module provides a high-quality State Variable Filter implementation based on Andy Simper’s topology-preserving transform design.
SVF
Multi-mode state variable filter with 8 filter types. Supports multi-channel processing and smooth coefficient updates.
Template Parameters
Sample type (typically double or float)
Number of channels (max channels the filter can process)
Constructor
SVF(EMode mode = kLowPass, double freqCPS = 1000.)
Initial cutoff frequency in Hz
Filter Modes
EMode
Enumeration of available filter types:
kLowPass
Classic low-pass filter, attenuates frequencies above cutoff
kHighPass
High-pass filter, attenuates frequencies below cutoff
kBandPass
Band-pass filter, passes frequencies near cutoff
kNotch
Notch (band-reject) filter, attenuates frequencies near cutoff
kPeak
Peak filter with adjustable Q
kBell
Bell/parametric EQ with gain control
kLowPassShelf
Low-pass shelving filter with gain
kHighPassShelf
High-pass shelving filter with gain
Mode String List
For UI implementations, use the predefined string list:
#define SVFMODES_VALIST "LowPass", "HighPass", "BandPass", "Notch", "Peak", "Bell", "LowPassShelf", "HighPassShelf"
Methods
SetFreqCPS
void SetFreqCPS(double freqCPS)
Set the filter cutoff frequency.
Frequency in Hz (clamped to 10.0 - 20000.0 Hz)
SetQ
Set the filter resonance/Q factor.
Q factor (clamped to 0.1 - 100.0). Higher values = more resonance.
SetGain
void SetGain(double gainDB)
Set the gain for shelving and bell filters.
Gain in decibels (clamped to -36.0 to +36.0 dB)
Gain parameter only affects kBell, kLowPassShelf, and kHighPassShelf modes.
SetMode
Change the filter mode.
SetSampleRate
void SetSampleRate(double sampleRate)
Set the sample rate for processing.
Sample rate in samples per second
ProcessBlock
void ProcessBlock(T** inputs, T** outputs, int nChans, int nFrames)
Process a block of audio samples.
Two-dimensional array of input buffers (non-interleaved)
Two-dimensional array of output buffers (non-interleaved)
Number of channels to process (must be ≤ NC template parameter)
Number of samples per channel to process
Reset
Clear the filter’s internal state (delay elements).
PlotResponse (static)
static double PlotResponse(double freqCPS, double Q, EMode mode,
double x, double gain = 0.,
double minHz = 1., double maxHz = 20000)
Calculate the magnitude response at a given frequency for plotting filter curves.
Filter cutoff frequency in Hz
Normalized frequency position (0.0 to 1.0)
Gain in dB (for shelving/bell filters)
Minimum frequency for x=0.0
Maximum frequency for x=1.0
Returns: Magnitude response in dB
Usage Examples
Basic Low-Pass Filter
// Create stereo low-pass filter
SVF<double, 2> filter(SVF<double, 2>::kLowPass, 1000.0);
filter.SetSampleRate(44100.0);
filter.SetQ(0.707); // Butterworth response
// Process audio block
void ProcessBlock(double** inputs, double** outputs, int nFrames) {
filter.ProcessBlock(inputs, outputs, 2, nFrames);
}
Parameter Modulation
// Sweeping filter with LFO
SVF<float, 1> filter(SVF<float, 1>::kLowPass, 500.0);
LFO<float> lfo;
filter.SetSampleRate(44100.0);
filter.SetQ(2.0); // Resonant filter
lfo.SetSampleRate(44100.0);
lfo.SetFreqCPS(0.5); // 0.5 Hz LFO
void ProcessBlock(float** inputs, float** outputs, int nFrames) {
for (int i = 0; i < nFrames; i++) {
// Modulate filter frequency
float lfoValue = lfo.Process();
float freq = 500.0 + lfoValue * 2000.0; // 500-2500 Hz sweep
filter.SetFreqCPS(freq);
// Process per-sample for smooth modulation
float in[1] = { inputs[0][i] };
float out[1];
float* inPtr[1] = { in };
float* outPtr[1] = { out };
filter.ProcessBlock(inPtr, outPtr, 1, 1);
outputs[0][i] = out[0];
}
}
Setting parameters per-sample can be expensive. For performance-critical code, update parameters less frequently or use smoothing.
Multi-Mode Filter Plugin
class FilterPlugin : public Plugin {
private:
SVF<double, 2> mFilter;
public:
enum EParams {
kMode,
kFreq,
kQ,
kGain,
kNumParams
};
FilterPlugin(const InstanceInfo& info) : Plugin(info, MakeConfig(kNumParams, 0)) {
GetParam(kMode)->InitEnum("Mode", SVF<double>::kLowPass, SVF<double>::kNumModes, "",
IParam::kFlagsNone, "", SVFMODES_VALIST);
GetParam(kFreq)->InitDouble("Frequency", 1000.0, 10.0, 20000.0, 0.01, "Hz");
GetParam(kFreq)->SetShape(2.0); // Logarithmic
GetParam(kQ)->InitDouble("Q", 0.707, 0.1, 10.0, 0.01);
GetParam(kGain)->InitDouble("Gain", 0.0, -36.0, 36.0, 0.1, "dB");
mFilter.SetSampleRate(GetSampleRate());
}
void OnParamChange(int paramIdx) override {
switch (paramIdx) {
case kMode:
mFilter.SetMode((SVF<double>::EMode) GetParam(kMode)->Int());
break;
case kFreq:
mFilter.SetFreqCPS(GetParam(kFreq)->Value());
break;
case kQ:
mFilter.SetQ(GetParam(kQ)->Value());
break;
case kGain:
mFilter.SetGain(GetParam(kGain)->Value());
break;
}
}
void ProcessBlock(sample** inputs, sample** outputs, int nFrames) override {
mFilter.ProcessBlock(inputs, outputs, 2, nFrames);
}
void OnReset() override {
mFilter.SetSampleRate(GetSampleRate());
mFilter.Reset();
}
};
Plotting Filter Response
// Draw filter frequency response curve
void DrawFilterCurve(IGraphics* pGraphics, IRECT rect) {
const int numPoints = 200;
std::vector<float> points;
for (int i = 0; i < numPoints; i++) {
float x = (float)i / (numPoints - 1);
double magnitude = SVF<double>::PlotResponse(
1000.0, // 1kHz cutoff
2.0, // Q=2
SVF<double>::kLowPass,
x, // 0.0 to 1.0
0.0, // No gain
20.0, // 20Hz minimum
20000.0 // 20kHz maximum
);
// Convert dB to pixel height
float y = rect.B - ((magnitude + 60.0) / 60.0) * rect.H();
points.push_back(y);
}
// Draw curve...
}
Implementation Details
Algorithm
The SVF uses a topology-preserving transform (TPT) structure:
- Linear trapezoidal integration for stability
- Zero-delay feedback topology
- Optimized coefficient calculations for efficiency
Coefficient Updates
The filter uses a lazy coefficient update strategy:
- Parameters are stored in
mNewState
- On
ProcessBlock(), if mState != mNewState, coefficients are recalculated
- This allows parameter changes without recalculating every time a setter is called
State Variables
The filter maintains per-channel state:
mIc1eq[NC] - First integrator state
mIc2eq[NC] - Second integrator state
mV1[NC], mV2[NC], mV3[NC] - Intermediate values
Per-Sample Cost
~10-15 operations per sample per channel
Memory
5 doubles per channel + shared coefficients
Stability
Numerically stable at all frequencies
Aliasing
No anti-aliasing - use oversampling for high-resonance settings
Design Considerations
Q Factor Ranges
- Q < 0.707: Gentle slope, no resonance peak
- Q = 0.707: Butterworth response (maximally flat)
- Q > 0.707: Resonance peak at cutoff frequency
- Q > 10: Extreme resonance, can self-oscillate
Frequency Ranges
The filter clamps frequency to 10-20000 Hz:
- Below 10 Hz: Coefficient accuracy issues
- Above 20 kHz: Approaching Nyquist, aliasing artifacts
Oversampling Recommendation
For high-resonance or modulated filters, consider using with the OverSampler:
OverSampler<double> oversampler(k2x, true, 2, 2);
oversampler.ProcessBlock(inputs, outputs, nFrames, 2, 2,
[&](double** ins, double** outs, int nFrames) {
mFilter.ProcessBlock(ins, outs, 2, nFrames);
});
Code Location
Source: IPlug/Extras/SVF.h:1
See Also