From the Cytomic paper (last page) and kerfuffle’s filter source code, something like:
// Resonant low-pass filter based on Cytomic SVF.
class Filter {
public:
float sampleRate;
void updateCoefficientsLP(float cutoff, float Q) {
g = std::tan(PI * cutoff / sampleRate);
k = 1.0f / Q;
a1 = 1.0f / (1.0f + g * (g + k));
a2 = g * a1;
a3 = g * a2;
m0 = 0;
m1 = 0;
m2 = 1;
}
void updateCoefficientsBP(float cutoff, float Q) {
g = std::tan(PI * cutoff / sampleRate);
k = 1.0f / Q;
a1 = 1.0f / (1.0f + g * (g + k));
a2 = g * a1;
a3 = g * a2;
m0 = 0;
m1 = 1;
m2 = 0;
}
void updateCoefficientsHP(float cutoff, float Q) {
g = std::tan(PI * cutoff / sampleRate);
k = 1.0f / Q;
a1 = 1.0f / (1.0f + g * (g + k));
a2 = g * a1;
a3 = g * a2;
m0 = 1;
m1 = -k;
m2 = -1;
}
void reset() {
g = 0.0f;
k = 0.0f;
a1 = 0.0f;
a2 = 0.0f;
a3 = 0.0f;
m0 = 0.f;
m1 = 0.f;
m2 = 0.f;
ic1eq = 0.0f;
ic2eq = 0.0f;
}
float render(float x) {
float v3 = x - ic2eq;
float v1 = a1 * ic1eq + a2 * v3;
float v2 = ic2eq + a2 * ic1eq + a3 * v3;
ic1eq = 2.0f * v1 - ic1eq;
ic2eq = 2.0f * v2 - ic2eq;
return m0 * x + m1 * v1 + m2 * v2;
}
private:
const float PI = 3.1415926535897932f;
float g, k, a1, a2, a3; // filter coefficients
float m0, m1, m2; // filter mixers
float ic1eq, ic2eq; // internal state
};