A Simple Low-Cut Filter
Plug'n Script includes a built-in biquad filter class that can be used to create various types of filters.
- // include filter class and PI constant
- #include "../library/BiquadFilter.hxx"
- #include "../library/Constants.hxx"
- // script name
- string name="Biquad Low-Cut filter";
- // make an input parameter to control cutoff frequency
- array<double> inputParameters(1); // how many input controls we need
- array<string> inputParametersNames={"Cutoff freq"}; // name of input param
- array<string> inputParametersUnits={"Hz"}; // which units to use
- array<double> inputParametersMin={20}; // min value
- array<double> inputParametersMax={5000}; // max value
- array<double> inputParametersDefault={250}; // default value
- // index of input param just for convenience
- const int CUTOFF = 0;
- // create a filter for all channels
- KittyDSP::Biquad::Filter myFilter(audioInputsCount);
- // audio per-sample processing function
- // process all channels
- }
- // is called when input parameters change
- // update filter cutoff frequency
- myFilter.setHighPass(2 * PI * inputParameters[CUTOFF] / sampleRate);
- }
Short Info about Filters
When designing an EQ or similar audio processor, filters—mathematical functions that modify the signal—are used. They often rely on previous samples or pre-calculated buffer values to compute the current sample.
There are IIR and FIR filters. FIR is doing a convolution, it multiplies and sums each sample with so called "kernel" (an array of numbers). It's like an "impulse" that we load for convolution reverb or convolution-based amp sim. Using such pre-calculated "kernels" (they can be recalculated "on-the-fly") allows creating very precise filters, and making linear-phase one is easy with FIR. Though usually FIR filter require more CPU, depending on kernel size. Because of that they are not often used in Plug'n Script AngelScript API (because AngelScript is comparatively slow with array access and some other stuff). But in C++ they are ok.
IIR filters do not use convolution; instead, they rely on previously calculated values to compute new samples and are generally more CPU-efficient. However, achieving a linear-phase response with IIR filters is more challenging. Among various IIR filter types, the biquad filter is popular for its sound quality.
Explanation
First, a myFilter object is created for the required number of channels:
- KittyDSP::Biquad::Filter myFilter(audioInputsCount);
Don't be scared of these "::" in class name. If you're not familiar with Namespaces, they are just a way to make code better organized. We could use value 2 instead of audioInputsCount (for stereo track), but this way we make it work for all channel configurations.
Before the first call to processSample, the updateInputParameters is called (as well as after every change of input parameter), where we set the parameters of a filter. In our case we set it to be lowcut and use a formula to set the desired frequency in Hertz.
- // inputParameters[CUTOFF] holds our cutoff frequency in Hz
- myFilter.setHighPass(2 * PI * inputParameters[CUTOFF] / sampleRate);
We could write inputParameters[0] instead, but using constant for parameter index is convenient, especially in longer scripts.
And finally we apply a filter to all channels inside processSample function.
This myFilter.processSample takes our array of current sample for all channels, calculates new sample values and updates them in the same array. The myFilter.processSample also can process channels individually, so we could write it like that:
- for(uint channel=0;channel<audioOutputsCount;channel++) {
- // process each channel individually
- }
- }
The full code of out low-cut filter script can be found at the top of this page.
Different Types of Filters
If you examine the file library/BiquadFilter.hxx, you will find a variety of filter types available.
- // different methods from biquad filter class
- setLowPass(double theta);
- setHighPass(double theta);
- setResonantLowPass(double theta,double q /*peak value*/);
- setResonantHighPass(double theta,double q /*peak value*/);
- setLowShelf(double theta,double doubleSquareRootGain);
- setHighShelf(double theta,double doubleSquareRootGain);
- setPeak(double theta,double halfBwInOctava,double sqrtGain);
- setBandPass(double theta,double halfBwInOctava);
- setNotch(double theta, double halfBwInOctava);
- setAllPass(double theta, double Q);
- // there's also a function to reset filter buffer
- resetState();
As you see, the parameters for these methods are not in Hertz and Decibels, so to calculate them you can use formulas.
- double theta = 2 * PI * frequencyInHertz / sampleRate;
- double doubleSquareRootGain = sqrt(pow(10, gainInDecibels / 40.0)); // for shelf filter
- double sqrtGain = sqrt(pow(10, gainInDecibels / 20.0)); // for peak filter
For resonant LowPass and HiPass you can use q values > 0. Value ~0.707 gives a flat cutoff (with minimum resonance). Alternatively you can use values ranging from 0 (flat) to 1 (maximum) and convert them like this:
- // for setResonantLowPass and setResonantHighPass
- // map values 0..1 to range from flat (-3dB boost) to 20 dB resonance boost
- double q = pow(10, (-3 + Q_FROM_0_TO_1 * 23) / 20);
You can experiment with Q for AllPass as well as halfBwInOctava for Peak, Notch and BandPass. These values should also be > 0.
Additional examples can be found in the factory scripts located in the Filter folder.
Comments
Please, authorize to view and post comments.