Drawing a filter response


#1

Hi guys,

I’m relatively new to JUCE and DSP. I’ve been playing with this State Variable Filter class: https://github.com/JordanTHarris/VAStateVariableFilter
It sounds pretty good, but I’d like to make a GUI component to show the filter response. I have no idea how to do it. Can anyone point me to the right direction?
Something like this:

I’ve found this project: https://github.com/JoshMarler/filter-gui-demo which works really well but it’s only for a one pole. I have no idea how to swap the transfer function to include a 2-pole lowpass from the State Variable Filter class.

This is the piece of code that needs to be changed, any help?

float getMagnitudeResponse(float frequency) const
{
    float magnitude = 0.0;
    
    float wd = 2 * M_PI * filterCutoff;
    float T = 1/this->sampleRate;

    //Calculating pre-warped/analogue cutoff frequency to use in virtual analogue frequeny response calculations
    float cutOff = (2/T) * tan(wd*T/2);

    //Digital frequency to evaluate
    float wdEval =  2 * M_PI * frequency;
    float sValue =  (2/T) * tan(wdEval*T/2);

    /* This is the digital transfer function which is equal to the analogue transfer function
     evaluated at H(s) where s = (2/T) * tan(wd*T/2) hence why the cutoff used is the pre warped analogue equivalent.
     See Art Of VA Filter Design 3.8 Bilinear Transform Section */
    switch (filterType) {
        case SVFLowpass:
            //VA Lowpass Frequency response wc/s+wc
            magnitude = cutOff/(sValue + cutOff);
            break;
        case SVFHighpass:
            //VA Highpass Frequency response s/s+wc
            magnitude = sValue/(sValue + cutOff);
            break;
        default:
            break;
    }

    //Convert to db for log db response display
    magnitude = Decibels::gainToDecibels(magnitude);
    return  magnitude;
}

#2

Basically, you will want to evaluate the transfer function of the filter directly for some frequency. If you calculate the coefficients for the equivalent biquad, there are loads of equations on the internet, most of them probably provided by RBJ:
https://groups.google.com/forum/#!topic/comp.dsp/jA-o05autEQ

Here’s some code for calculating the magnitude response given normalized a0, a1, a2, b1, b2 coefficients from a biquad:

T magnitudeResponseAt(T w0)
{
	auto const piw0 = w0 * cpl::simd::consts<T>::pi;
	auto const cosw = std::cos(piw0);
	auto const sinw = std::sin(piw0);

	auto square = [](auto z) { return z * z; };

	auto const numerator = sqrt(square(a0*square(cosw) - a0*square(sinw) + a1*cosw + a2) + square(2 * a0*cosw*sinw + a1*(sinw)));
	auto const denominator = sqrt(square(square(cosw) - square(sinw) + b1*cosw + b2) + square(2 * cosw*sinw + b1*(sinw)));

	/*auto const w = std::sin(cpl::simd::consts<T>::pi * w / 2);
	auto const phi = w * w;
	auto const phi2 = phi * phi;

	// black magic. supposedly gives better precision than direct evaluation of H(z)
	auto const numerator = 10 * std::log10((b0 + b1 + b2) * (b0 + b1 + b2) - 4 * (b0 * b1 + 4 * b0 * b2 + b1 * b2) * phi + 16 * b0 * b2 * phi2);
	auto const denominator = -10 * std::log10((a0 + a1 + a2) * (a0 + a1 + a2) - 4 * (a0 * a1 + 4 * a0 * a2 + a1 * a2) * phi + 16 * a0 * a2 * phi2);
	return numerator + denominator; */

	return numerator / denominator;
}

For calculating such coefficients you can either use JUCE or perhaps @earlevel’s (?) coefficient calculator:
http://www.earlevel.com/main/2011/01/02/biquad-formulas/
Note that Q is not taken into account for shelving filters in above those formulas.


#3

Thanks for the reply. I managed to swap that with what I had in getMagnitudeResponse, and it works really well. However, it’s not the response of the filter that I’m using. I have no idea how to adapt that to the state variable filter I’m using. Any help?


#4

How are you calculating the biquad? What response is your SVF?


#5

I am using this:

void VAStateVariableFilter::calcFilter()
{
    if (active) {

        // prewarp the cutoff (for bilinear-transform filters)
        float wd = static_cast<float>(cutoffFreq * 2.0f * M_PI);
        float T = 1.0f / (float)sampleRate;
        float wa = (2.0f / T) * tan(wd * T / 2.0f);

        // Calculate g (gain element of integrator)
        gCoeff = wa * T / 2.0f;         // Calculate g (gain element of integrator)

        // Calculate Zavalishin's R from Q (referred to as damping parameter)
        RCoeff = 1.0f / (2.0f * Q);     
        
        // Gain for BandShelving filter
        KCoeff = shelfGain;             
    }
}

From: https://github.com/JordanTHarris/VAStateVariableFilter/blob/master/Source/Effects/VAStateVariableFilter.cpp

For the biquad I’ve used earlevel’s calculator.


#6

So when you say it’s not the response of the filter you’re using, what do you mean exactly? Are you using the lowpass/highpass/bandpass (etc…) component of the SVF? You should be able to calculate the same type of biquad, and their transfer function will be the same.


#7

Maybe I’m wrong, but if I’m using a biquad to calculate the graphic response, and then I use the SVF to process the audio, it won’t be accurate, right? It will be similar, but not the same thing as they are two different transfer functions.

As far as I know, when you increase the resonance of the SVF, the overall volume should also drop a little bit, and it’s not showing that using the biquad of course.


#8

If you’re designing the same classical type (e.g. lowpass or anything else based on RBJ’s cookbook, like these) of filter, they will have the same transfer function.

Their actual performance under different, potentially time-variying parameters is usually the difference.


#9

does JUCE’s dsp module have a State Variable Filter in it? take a look at @daniel’s Frequalizer on github for a pretty rad way to draw filter response and FFT in the background.