DSP Filters causing noise in FL Studio

Hi all,

I’m encountering what seems to be a fairly common issue, though I’m at a loss as to how to solve it. I’ve already followed threads such as this to try to fix the problem, but no dice.

Here’s the high level flow of what I’m doing:

  • I have a wetBuffer that I initialize in prepareToPlay to the size of samplesPerBlock. That means it’s possible for this to eclipse the size of the incoming buffer in processBlock.

  • I have some DSP objects (namely some filters and a gain) in a ProcessorChain, which processes the wetBuffer exclusively. In prepareToPlay and every processBlock, I prepare that DSP ProcessorChain with the incoming number of samples.

  • I fill the wetBuffer with the current number of incoming samples, create an AudioBlock of the proper size from it (using getSubBlock), and attempt to process the samples. In highly variable hosts like FL Studio, I’m getting a ton of noise here.

Here are some condensed code snippets:

void MyProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
        bufferSampleNum = buffer.getNumSamples();
        juce::dsp::ProcessSpec processSpec;
        processSpec.maximumBlockSize = bufferSampleNum;
        processSpec.sampleRate = sampleRate;
        processSpec.numChannels = wetBuffer.getNumChannels();
        wetProcessorChain.prepare(processSpec);

        wetBuffer.clear();

        ... (fill wetBuffer with bufferSampleNum samples)

        auto wetBlock = juce::dsp::AudioBlock<float> (wetBuffer).getSubBlock (0, bufferSampleNum);
        juce::dsp::ProcessContextReplacing<float> wetContext (wetBlock);
        wetProcessorChain.process(wetContext);
    
        // Sum dry and wet buffers.
        for (auto channel = 0; channel < wetBuffer.getNumChannels(); channel++)
            buffer.addFrom(channel, 0, wetBuffer, channel, 0, bufferSampleNum);
}

Am I missing anything super obvious here? I’ve added some logs throughout processBlock and verified that the number of incoming samples == the maximumBlockSize of the DSP chain == the size of the wetBlock before processing == the size of the wetBlock after processing. I’ve also verified that the number of samples in wetBuffer is always >= the number of incoming samples.

If it’s relevant at all, my ProcessorChain is defined as this:

juce::dsp::ProcessorChain<juce::dsp::StateVariableTPTFilter<float>, juce::dsp::StateVariableTPTFilter<float>, juce::dsp::Gain<float>> wetProcessorChain;

The first thing I noticed is that you are calling wetProcessorChain.prepare(processSpec) in the processBlock() as well as in the prepareToPlay() function, where it should really only be called. You don’t need to prepare the DSP objects with every block of audio. In the code you shared you are effectively resetting the filters with every block of audio, which would lead to clicks or noise.

As an aside, calling prepare() on some of the JUCE DSP classes will allocate memory, which you shouldn’t do on a real-time audio thread. This could also cause some nasty click, pops, and dropouts.

A-ha! That did indeed fix it, thank you! I wasn’t aware that calling prepare had any adverse effects; I was under the impression that one had to explicitly call reset on the DSP processor for the behavior that you described.

I’m glad that was it. It’s not very clear in the online documentation, but it is something that comes up in the tutorials and examples.
It’s also good to look at the dsp classes code and you’ll see that pretty much all of them call reset() after prepare(). The assumption is that you only call prepare when the sample rate or the number of channels have changed, which would require a reallocation of memory for the internal vectors and buffers, and a recalculation of any coefficients.
The easiest way to think of it is:

  • prepareToPlay() means call prepare()
  • processBlock() means call process()

That’s an oversimplification, but for most plugin situations it works.

That’s a great rule of thumb to be cognizant of, and feels a little obvious in retrospect :slight_smile: Oops. Thanks again!