Using IIR Filter after a Delay creates a lot of artefacts

I am doing an Echo delay where I’m copying my input Buffer to a circular Delay Buffer, reading that in that buffer from another position and copying that to the (input) Buffer again.
After that, I’m trying to use a StateVariableTPTFilter with a fixed Cutoff frequency to filter that signal per block but I get tons of artifacts. I tried to filter it sample per sampler but in that case the filter didn’t work at all.
My audioProcessorBlock is:

void CircularAudioBufferAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{

    auto delayBufferSize = 2.0 * (sampleRate + samplesPerBlock); 
    mSampleRate = sampleRate;
    delayBuffer.setSize(getTotalNumInputChannels(), (int)delayBufferSize);

    dsp::ProcessSpec spec; //DSP algorithm needs this info to work
    spec.sampleRate = sampleRate;
    spec.maximumBlockSize = samplesPerBlock;
    spec.numChannels = getTotalNumInputChannels();

    filter.prepare(spec);
    reset();
    filter.setType(dsp::StateVariableTPTFilterType::lowpass);
}


void CircularAudioBufferAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    juce::ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();
 
    filter.setCutoffFrequency(500.f);

 for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        buffer.clear (i, 0, buffer.getNumSamples());

    auto bufferSize = buffer.getNumSamples();
    auto delayBufferSize = delayBuffer.getNumSamples();

    for (int channel = 0; channel < totalNumInputChannels; ++channel) 
    {
        const float* bufferData = buffer.getReadPointer(channel);
        const float* delayBufferData = delayBuffer.getReadPointer(channel);
       
        fillDelayBuffer(channel, bufferSize, delayBufferSize, bufferData, delayBufferData);
        getFromDelayBuffer(buffer, channel, bufferSize, delayBufferSize, bufferData, delayBufferData);
      
    }
  
    auto audioBlock = juce::dsp::AudioBlock<float>(delayBuffer); //already do both channels for the dsp process
    auto context = juce::dsp::ProcessContextReplacing<float>(audioBlock);
    filter.process(context);

    writePos += bufferSize; 
    writePos %= delayBufferSize;
    
}

This function “fillDelayBuffer” copies the buffer data to the circular Delay Buffer.
This function “getFromDelayBuffer” copies again the data from the Delay Buffer into the ‘normal’ Buffer.

Following what I have read, the AudioBlock class to create a Context already iterates for both channels (L and R) that is why I set the filter process outside of the for channel loop. Anyway, it creates those artifacts anywhere I place it.

Could someone point me how could I filter that signal from the Delay Buffer or from the ‘normal’ Buffer without all of those artifacts?
Thank you so much.

If I understand your code correctly:

  • filter is prepared with buffer(block) size samplesPerBlock
  • the size of delayBuffer is 2.0 * (sampleRate + samplesPerBlock)

and you process delayBuffer with the filter, which looks quite suspicious.


I have just read the source code. I am afraid that the artefact may not come from the prepare (since buffer size seems to be irrelevant.

Yes, the size of the buffer is quite big to avoid problems with the circular delay buffer.
As you said, Is it problematic to process the filter with the delayBuffer? Is it problematic to prepare the delayBuffer size with the filter?

Should I create a different function to process the filtering? I don’t know much about Juce dsp process yet.

Thanks

No, the prepare() is fine (cause filter does not care about the buffer size). However, in each processBlock, you ask the filter to process ALL samples in the delayBuffer (i.e., 2.0 * (sampleRate + samplesPerBlock) samples in total). It is not a normal usage.

If what you want is a normal delay effect, using juce::dsp::DelayLine is much easier that writing your own circular delay buffer.

I have minimised the delay Buffer size to:

auto delayBufferSize = 0.5 * sampleRate; 

But the artifacts are still there.
I have also ignored the last action that I do after copying again from my delayBuffer which is to add the output to the ‘normal’ Buffer again to achieve a feedback delay. Maybe this worsens the situation.

fillDelayBuffer(channel, bufferSize, delayBufferSize, bufferData, delayBufferData);      
getFromDelayBuffer(buffer, channel, bufferSize, delayBufferSize, bufferData, delayBufferData);
feedbackDelay(channel, bufferSize, delayBufferSize, ouputDryBuffer, delayGain);

I think the problem must to be another thing more than the delayBuffer size. Maybe something related to the copying of the channel data between buffers?

The echo delay works well though, but I cannot filter it.

An IIR filter is stateful, there is no need to set its cutoff frequency in each process callback

Hi PeterRoos, you are right, I have moved the filter.setCutoffFrequency() to the prepareToPlay. The artifacts are still there although I think that it sounds a little bit better.

1 Like

filter should process each audio sample once and only once. However, since you ask filter to process the whole delayBuffer each processBlock, I am afraid that some samples get processed much more than once (if I understand the delayBuffer correctly).

Yes, I have realised that If I filter buffer instead delayBuffer I don’t get those artifacts.
Filtering buffer I’m filtering the buffer before is copied to the delayBuffer so I suppose that I’m avoiding some type of duplicity on the filtering.