Published on

How to implement anti-alias filters in JUCE?

Authors

Introduction

After discussing the theory behind aliasing and oversampling, let’s look at how to practically implement an anti-alias (or anti-imaging) filter in JUCE.

  • These filters are typically low-pass filters that remove frequencies above the Nyquist frequency - either before downsampling (anti-alias) or after upsampling (anti-imaging).

JUCE’s dsp module provides convenient tools for this. Let's see how we can implement it.

How to implement anti-alias filters?

juce::dsp::Oversampling class

JUCE's oversampling class has it's own filter implementation that uses as anti-alias during the oversampling process.

They can be found in:


enum juce::dsp::Oversampling::FilterType

The filters that are available for the oversampling processing are:

filterHalfBandFIREquiripple 	
filterHalfBandPolyphaseIIR 

These filters are polyphase allpass IIR and FIR, explanation about which is a topic of another article.

Choosing between FIR (finite impulse response) and IIR (infinite impulse response) depends on your needs in terms of latency and phase:

  • FIR filters have more latency but provide linear phase.
  • IIR filter have minimum latency but compromise phase around the Nyquist frequency.

By creating an object from the JUCE's oversampling class you are already signing up for an anti-alias filter.

juce::dsp::Oversampling<float> oversampling {
    2,
    4, 
    juce::dsp::Oversampling<float>::filterHalfBandFIREquiripple, // the anti-alias filter
    true, 
    false 
};

Most of the time this is enough but sometimes we want or need a custom one.

Custom anti-alias filter implementation

Sometimes if we have heavy non-linear processing or just want a custom filter we can create one easily with JUCE's dsp module.

  1. Create filter instance: To create an instance from the desired filter type we will need two things:
  • The processor part - the filter type with which we will process our signal buffer

juce::dsp::IIR::Filter<T>
juce::dsp::FIR::Filter<T>

  • The coefficient part - the filter coefficients that will define the filter's behaviour at any current time

    juce::dsp::IIR::Coefficients<float>
    juce::dsp::FIR::Coefficients<float>

JUCE provides an easy way to group them with Processor Dublicator, which will create one filter per channel, all sharing the same coefficient state:

PluginProcessor.h

juce::dsp::ProcessorDuplicator<
        juce::dsp::IIR::Filter<float>,
        juce::dsp::IIR::Coefficients<float>
    > filter;
  1. Initialize the filter instance We can do that in the constructor or in prepareToPlay function. We will use a type alias for the filter type to shorten its lenght and then we will initialize it:

using Coeffs = juce::dsp::IIR::Coefficients<float>;
filter.state = Coeffs::makeLowPass(44100.0, 18000.0); // sample rate, cutoff frequency
  1. Prepare the filter in PrepareToPlay We use our ProcessSpec instance that we use to push our project general specifications as sample rate, samples per block and the output channel number.

Here's a ProcessorSpec example:

pluginProcessor.cpp/prepareToPlay

    juce::dsp::ProcessSpec spec { sampleRate,
                                  static_cast<juce::uint32>(samplesPerBlock),
                                  static_cast<juce::uint32>(getTotalNumOutputChannels()) };

Then we prepare our filter:

pluginProcessor.cpp/prepareToPlay

filter.prepare(spec);
  1. Process the audio in ProcessBlock Now we need to process our audio with the newly created and prepared anti-alias (low-pass) filter.

We use the dsp module to warp our signal buffer into blocks and create a processing context for it. First, we need the block:

pluginProcessor.cpp/processBlock

juce::dsp::AudioBlock<float> block(buffer);

The context we will use will be the replacing one, since we will be taking the buffer block, process it and then replace it with the new filtered audio and then send the same block to the way of the output, so this data is important before processing.

pluginProcessor.cpp/processBlock

juce::dsp::ProcessContextReplacing<float> context(block);

Finally we process the context through the filter:

pluginProcessor.cpp/processBlock

filter.process(context);

For a simple anti-alias filter, we don't need to update the coefficients dynamically, so we will skip that part here.

Summary

An anti-alias filter can be easily implemented using JUCE’s dsp module. It’s already part of the dsp::Oversampling class constructor, so it’s automatically applied whenever we use oversampling in our plugin.

If we want to create a custom anti-alias filter, we simply implement a low-pass filter and treat it as a standard filter with a cutoff around 18–20 kHz.