Implementing an IIR Filter

What's an IIR Filter?

An Infinite Impulse Response (IIR) filter is a type of digital or analog filter used in signal processing to modify signals by selectively attenuating or amplifying certain frequencies. Unlike finite impulse response (FIR) filters, which have a finite duration response to an impulse input, IIR filters theoretically continue their response indefinitely, hence the term "infinite impulse response".

In JUCE it is generally used to create filters of various types, like a high-shelf filter, a low-cut filter and bandpass filter. Designing those filters can be very hard and sometimes even create an unstable output. Thankfully there is a JUCE helper class, that does this part for us.

Implementation

First off, you need to create the filter and add it to your ProcessorChain:

file: PluginProcessor.h

using ProcessorChain = juce::dsp::ProcessorChain
<
  juce::dsp::IIR::Filter<float>
>;

Don't forget to update your enum, if you're using one.

file: PluginProcessor.h

enum
{
  filterIndex
};

Next create a function, that reads the parameters and adjusts the filter. This function should be called during the playback, so we can adjust the filters settings in real time. Lets call this function updateFilter(). Since we just want to change the settings and don't actually process any audio, we don't need any return type. Also we won't use this function outside of the PluginProcessor-class so we can create it in the private section:

file: PluginProcessor.h

private:
  void updateFilter();

In the next step, you need to define, what filter to use. In this tutorial you can currently choose from Peak Filter, Low-/Highshelf Filter and an Low-/Highcut Filter.

Peak Filter

Now lets define the function. We first start of by reading any parameters from our APVTS and storing them in a temporary variable, so we can read them better. Please note, that you need to have parameter's named Peak Freq, Peak Quality and Peak Gain or else this wont work. The quality parameter should be between 0.5 and 5, wile 0.5 creates a very gentle slope and 5 creates a very steap slope. Next, we will calculate the coefficients. Thankfully JUCE has a helper class, that can help us out. This is the juce::dsp::IIR::Coefficients<float> part. Here we also define, what type of filter we want. Lastly, we will replace the old coefficients with the new ones. Here we assume, that you have a stereo audio plugin with a leftChain and a rightChain.

file: PluginProcessor.cpp

void AudioProcessor::updateFilter()
{
  // storing the parameters
  auto peakFreq = apvts.getRawParameterValue("Peak Freq")->load();
  auto peakQuality = apvts.getRawParameterValue("Peak Quality")->load();
  auto peakGain = apvts.getRawParameterValue("Peak Gain")->load();

  // calculating the coefficients
  auto coefficients = juce::dsp::IIR::Coefficients<float>::makePeakFilter(
    peakFreq,
    peakQuality,
    juce::Decibels::decibelsToGain(peakGain)
  );

  // updating the coefficients
  *leftChain.get<filterIndex>().coefficients = *coefficients;
  *rightChain.get<filterIndex>().coefficients = *coefficients;
}

High-/Lowcut Filter

Now lets define the function. We first start of by reading any parameters from our APVTS and storing them in a temporary variable, so we can read them better. Please note, that you need to have a parameter named Cut Freq or else this wont work. This is the juce::dsp::FilterDesign<float> part. The number 1 will define, what order, that we are going to use. 1 creates an -6db/octave slope, 2 a -18db/octave slope and so on. Here we also define, what type of filter we want. Please note, that we will calculate both low- and highcut filters in this tutorial, since they are almost the same. Lastly, we will replace the old coefficients with the new ones. Here we assume, that you have a stereo audio plugin with a leftChain and a rightChain. Also this will generate a lowcut filter, however you can change this, by just changing *lowCutCoefficients to *highCutCoefficients.

file: PluginProcessor.cpp

void AudioProcessor::updateFilter() 
{
  // storing the parameter
  auto cutFreq = apvts.getRawParameterValue("Cut Freq")->load();

  // calculating the coefficients
  auto lowCutCoefficients = juce::dsp::FilterDesign<float>::designIIRHighpassHighOrderButterworthMethod(
    cutFreq,
    getSampleRate(),
    1
  )
  auto highCutCoefficients = juce::dsp::FilterDesign<float>::designIIRLowpassHighOrderButterworthMethod(
    cutFreq,
    getSampleRate(),
    1
  ) 

  // updating the coefficients
  *leftChain.get<filterIndex>().coefficients = *lowCutCoefficients;
  *rightChain.get<filterIndex>().coefficients = *lowCutCoefficients;
}

High-/Lowshelf Filter

Now lets define the function. We first start of by reading any parameters from our APVTS and storing them in a temporary variable, so we can read them better. Please note, that you need to have parameter's named Shelf Freq, Shelf Quality and Shelf Gain or else this wont work. The quality parameter should be between 0.5 and 5, wile 0.5 creates a very gentle slope and 5 creates a very steap slope. Next, we will calculate the coefficients. Thankfully JUCE has a helper class, that can help us out. This is the juce::dsp::IIR::Coefficients<float> part. Here we also define, what type of filter we want. We will create both coefficients for a highshelf filter and a lowshelf filter. You should be able to simply swap between them by changing *lowShelfCoefficients with *highShelfCoefficients. Lastly, we will replace the old coefficients with the new ones. Here we assume, that you have a stereo audio plugin with a leftChain and a rightChain.

void AudioProcessor::updateFilter()
{
  // storing the parameters
  auto shelfFreq = apvts.getRawParameterValue("Shelf Freq")->load();
  auto shelfQuality = apvts.getRawParameterValue("Shelf Quality")->load();
  auto shelfGain = apvts.getRawParameterValue("Shelf Gain")->load();

  // calculating the coefficients
  auto lowShelfCoefficients = juce::dsp::IIR::Coefficients<float>::makeLowShelf(
    getSamplerate(),
    shelfFreq,
    shelfQuality,
    shelfGain
  );
  auto highShelfCoefficients = juce::dsp::IIR::Coefficients<float>::makeHighShelf(
    getSampleRate(),
    shelfFreq,
    shelfQuality,
    shelfGain
  );

  // updating the coefficients
  *leftChain.get<airFilterIndex>().coefficients = *lowShelfCoefficients;
  *rightChain.get<airFilterIndex>().coefficients = *lowShelfCoefficients;
}

Update the Filter

Now we just need call this function every time, audio is being processed or prepared. This is done in the prepareToPlay and in the processBlock. We also should update the filters after we loaded the plugin's state.

file: PluginProcessor.cpp

void AudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
  // existing code

  updateFilter();
}
void AudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
  // existing code

  updateFilter();
}
void SimpleEQAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    auto tree = juce::ValueTree::readFromData(data, sizeInBytes);
    if (tree.isValid())
    {
        apvts.replaceState(tree);
        updateFilters();
    }
}

Now your filter should work and usable.