Skip to main content
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.
Based on Andy Simper’s “SVF Linear Trapezoidal Optimised” design. See Cytomic Technical Papers

Template Parameters

T
typename
default:"double"
Sample type (typically double or float)
NC
int
default:"1"
Number of channels (max channels the filter can process)

Constructor

SVF(EMode mode = kLowPass, double freqCPS = 1000.)
mode
EMode
default:"kLowPass"
Filter mode (see Filter Modes)
freqCPS
double
default:"1000.0"
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.
freqCPS
double
Frequency in Hz (clamped to 10.0 - 20000.0 Hz)

SetQ

void SetQ(double Q)
Set the filter resonance/Q factor.
Q
double
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.
gainDB
double
Gain in decibels (clamped to -36.0 to +36.0 dB)
Gain parameter only affects kBell, kLowPassShelf, and kHighPassShelf modes.

SetMode

void SetMode(EMode mode)
Change the filter mode.
mode
EMode
New filter mode

SetSampleRate

void SetSampleRate(double sampleRate)
Set the sample rate for processing.
sampleRate
double
Sample rate in samples per second

ProcessBlock

void ProcessBlock(T** inputs, T** outputs, int nChans, int nFrames)
Process a block of audio samples.
inputs
T**
Two-dimensional array of input buffers (non-interleaved)
outputs
T**
Two-dimensional array of output buffers (non-interleaved)
nChans
int
Number of channels to process (must be ≤ NC template parameter)
nFrames
int
Number of samples per channel to process

Reset

void 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.
freqCPS
double
Filter cutoff frequency in Hz
Q
double
Filter Q factor
mode
EMode
Filter mode
x
double
Normalized frequency position (0.0 to 1.0)
gain
double
default:"0.0"
Gain in dB (for shelving/bell filters)
minHz
double
default:"1.0"
Minimum frequency for x=0.0
maxHz
double
default:"20000.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:
  1. Parameters are stored in mNewState
  2. On ProcessBlock(), if mState != mNewState, coefficients are recalculated
  3. 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

Performance Characteristics

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