Deriving the transfer function of virtual analog first order filters.

HTML output built with: jupyter nbconvert --to html one_pole_z_domain_tf.ipynb


We will derive the algorithm from the block diagram found on page 5, but we will follow the style of Andrew Simper's SVF paper.

Sympy can't (very easily) be bent to display transfer functions in terms of $z^{-1}, z^{-2}, ...$ which is the convention. Plain $z$ will be used here instead - keep in mind it actually means $z^{-1}$.

Start with the parameters.

g = Tan[π * cutoff / samplerate];
a1 = g / (1.0 + g);

The other coefficients defining the shape of the filter (m0, m1) will be ignored for now, as they are only used to "mix" the output.

Then the computation.

The variable v0 represents the input signal - we will consider it to represent the z-transform of the input over time. v1 and v2 represent two other nodes in the block diagram.

The state variable ic1eq will be defined as unknown first, and then we will solve it using its equations.

The relevant lines of the algorithm are:

v1 = a1 * (v0 - ic1eq);
v2 = v1 + ic1eq;

Notice that ic1eq actually refers to the previous value of these samples. This corresponds to multiplying by $z$ (contrary to convention!) in the z-domain.

The "new" value for ic1eq is computed as follows:

ic1eq = v2 + v1;

depending on the current values of v1, v2, and the previous value of ic1eq.

Consider this equation, and solve it:

We may now subsitute the solution into v2 to obtain the transfer function

$$ \begin{aligned} H_0(z) &= \frac {v_0(z)} {v_0(z)} = 1 \\ H_1(z) &= \frac {v_2(z)} {v_0(z)} \\ \end{aligned} $$

We can now assemble the complete transfer function, taking into account the mix coefficients m0, m1.

$$ H(z) = m_0 H_0(z) + m_1 H_1(z) $$

Sanity check: High pass filter