Easy DSP with Vult

Functions with memory simplify writing Digital Signal Processing (DSP) code since a mem variable can be used to as a single sample delay z^1. I order to implement the Digital Biquad Filter (direct form 2) we can take the equations directly from Wikipedia: https://en.wikipedia.org/wiki/Digital_biquad_filter.

We can see the architecture and the equations here:

We can implement these equations in Vult as follows.

fun biquad(x0, b0, b1, b2 ,a1 ,a2) : real { mem w1, w2; val w0 = x0 - a1*w1 - a2*w2; val y0 = b0*w0 + b1*w1 + b2*w2; w2, w1 = w1, w0; return y0; }

The variables w1 and w2 represent the terms w[n-1] and w[n-2]. The variables y0 and x0 represent y[n] and x[n]. The coefficients of the filter are b0, b1, b2, a0 and a2 which are calculated depending on the filter. We can see that w1 and w2 are declared as mem variables since we want them to keep their value every time the function is called.

Now that we have the architecture of the biquad, we can design filters. In order to design a filter we need to calculate the coefficients. Here we are gonna follow the formulae show in the Audio-EQ-Cookbook (http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt). The code for the filter is the following:

fun lowpass(x,w0,q) { val cos_w = cos(w0); val alpha = sin(w0)/(2.0*q); val den = 1.0 + alpha; val a1 = (-2.0*cos_w)/den; val a2 = (1.0 - alpha)/den; val b0 = ((1.0 - cos_w)/(2.0*den)); val b1 = (1.0 - cos_w)/den; val b2 = ((1.0 - cos_w)/(2.0*den)); return biquad(x,b0,b1,b2,a1,a2); }

In the code, x is the input signal w0 is the cut frequency (in radians) and q controls the resonance. This filter is variable and we can adjust the cut frequency and the resonance. In order to achieve that we need to calculate the coefficients every time the cut or resonance changes. We can see in the above code that the implementation is not very efficient since it calculates the coefficients every sample. To improve it can use the function change that we defined in the Functions with Memory tutorial.

// returns true when the input changes fun change(current:real) : bool { mem previous; val result = current <> previous; previous = current; return result; } fun lowpass(x,w0,q) { mem b0,b1,b2,a1,a2; if(change(w0) || change(q)) { val cos_w = cos(w0); val alpha = sin(w0)/(2.0*q); val den = 1.0 + alpha; a1 = (-2.0*cos_w)/den; a2 = (1.0 - alpha)/den; b0 = ((1.0 - cos_w)/(2.0*den)); b1 = (1.0 - cos_w)/den; b2 = ((1.0 - cos_w)/(2.0*den)); } return biquad(x,b0,b1,b2,a1,a2); }

In the example above, we have changed the declaration of the coefficients to mem variables because we want to remember the previous values. By wrapping the calculation inside the if-statement we recalculate the coefficients every time the values of fc and q change.

Now that we have the filter, we can use it as part of a bigger program. For example:

fun process(x) : real { ... // some other here val s_1 = ...; // generate a signal val s_2 = ...; // generate a signal // the first filter is controlled by control_1 and control_2 val s_1_filtered = lowpass(s_1, control_1, control_2); // the second filter is controlled by control_3 and control_4 val s_2_filtered = lowpass(s_2, control_3, control_4); return s_1_filtered + s_2_filtered; }

Just for you to remember, every time you call the function lowpass and independent memory space is generated. Therefore, the filter for signal s_1 and s_2 do not interfere each other.

In the next tutorial we are gonna create a state-variable filter and we are gonna show how easy is to do oversampling in Vult.

comments powered by Disqus