Mono signal processing

I am writing a simple 4-band equalizer. However, I keep getting this error. Am I correct in understanding that I need to process each channel as mono, and then it will work correctly? If not, I apologize for being persistent, but could you please point out what might be the issue? I’ll finish the rest myself. Thank you.

void EQAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    juce::ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();

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

    juce::dsp::AudioBlock<float> block (buffer);
    filterChain.process(juce::dsp::ProcessContextReplacing<float>(block));
}
void EQAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    juce::dsp::ProcessSpec spec;
    
    spec.maximumBlockSize = samplesPerBlock;
    spec.sampleRate = sampleRate;
    spec.numChannels = getMainBusNumOutputChannels();

    auto lfCoefficients = juce::dsp::IIR::Coefficients<float>::makeLowShelf(sampleRate, LF_Frequency, 0.75, LF_Volume);
    auto lmfCoefficients = juce::dsp::IIR::Coefficients<float>::makePeakFilter(sampleRate, LMF_Frequency, LMF_Q_Value, LMF_Volume);
    auto hmfCoefficients = juce::dsp::IIR::Coefficients<float>::makePeakFilter(sampleRate, HMF_Frequency, HMF_Q_Value, HMF_Volume);
    auto hfCoefficients = juce::dsp::IIR::Coefficients<float>::makeHighShelf(sampleRate, HF_Frequency, 0.75, HF_Volume);


    filterChain.template get<0>().coefficients = *lfCoefficients;
    filterChain.template get<1>().coefficients = *lmfCoefficients;
    filterChain.template get<2>().coefficients = *hmfCoefficients;
    filterChain.template get<3>().coefficients = *hfCoefficients;

    filterChain.prepare(spec);
}
    // This class can only process mono signals. Use the ProcessorDuplicator class
    // to apply this filter on a multi-channel audio stream.
    jassert (inputBlock.getNumChannels()  == 1); <===============
com.apple.audio.IOThread.client (9): EXC_BREAKPOINT (code=1, subcode=0x1003e3e08)
    jassert (outputBlock.getNumChannels() == 1);

Hi, yes you should create a MonoChain for each Chanel so for ex: CustomChain leftChain, rightChain;. You need to update the filters for both of them since they both are their own objects.
It’s kinda hard to see what the problem is since you didn’t post the output of the issue but rather the code where the issue occured. Please also post the error output.

So, as I understand it, to support mono and stereo, I need to process the left mono and right mono channels separately. Unfortunately, I’m still in the process of learning the framework, so I’ll share the working code once I manage to get something done.

head.
private:
    //==============================================================================
    float HF_Volume = 0.0f; // vars
    ...

    void updateFilters();
    
    using monoChain = juce::dsp::ProcessorChain<juce::dsp::IIR::Filter<float>, juce::dsp::IIR::Filter<float>, juce::dsp::IIR::Filter<float>, juce::dsp::IIR::Filter<float>>;
         
     monoChain leftChain, rightChain;
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (_243EQAudioProcessor)
};
void EQAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    juce::dsp::ProcessSpec spec;
    spec.maximumBlockSize = samplesPerBlock;
    spec.sampleRate = sampleRate;
    spec.numChannels = 1; // processing

    leftChain.prepare(spec);
    rightChain.prepare(spec);
    
    updateFilters();
}

void EQAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    juce::ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();

    updateFilters();

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

    juce::dsp::AudioBlock<float> block (buffer);
    
    auto leftBlock = block.getSingleChannelBlock(0);
    auto rightBlock = block.getSingleChannelBlock(1);

    juce::dsp::ProcessContextReplacing<float> leftContext (leftBlock);
    juce::dsp::ProcessContextReplacing<float> rightContext (rightBlock);

    leftChain.process(leftContext);
    rightChain.process(rightContext);
}

void EQAudioProcessor::updateFilters()
{
    auto sampleRate = getSampleRate();
    
    auto lfCoefficients = juce::dsp::IIR::Coefficients<float>::makeLowShelf(sampleRate, LF_Frequency, 0.75, LF_Volume);
    auto lmfCoefficients = juce::dsp::IIR::Coefficients<float>::makePeakFilter(sampleRate, LMF_Frequency, LMF_Q_Value, LMF_Volume);
    auto hmfCoefficients = juce::dsp::IIR::Coefficients<float>::makePeakFilter(sampleRate, HMF_Frequency, HMF_Q_Value, HMF_Volume);
    auto hfCoefficients = juce::dsp::IIR::Coefficients<float>::makeHighShelf(sampleRate, HF_Frequency, 0.75, HF_Volume);

    *leftChain.get<0>().coefficients = *lfCoefficients;
    *leftChain.get<1>().coefficients = *lmfCoefficients;
    *leftChain.get<2>().coefficients = *hmfCoefficients;
    *leftChain.get<3>().coefficients = *hfCoefficients;

    *rightChain.get<0>().coefficients = *lfCoefficients;
    *rightChain.get<1>().coefficients = *lmfCoefficients;
    *rightChain.get<2>().coefficients = *hmfCoefficients;
    *rightChain.get<3>().coefficients = *hfCoefficients;
}

Something like this, but I haven’t tested it yet. I hope I’m on the right way…

I would suggest taking a look at the JUCE IIRFilterDemo, especially how to use juce::dsp::ProcessorDuplicator to handle multiple channels:

Besides that:

  • In the audio thread, you should use juce::dsp::IIR::ArrayCoefficients<float> instead of juce::dsp::IIR::Coefficients<float> to avoid allocation
  • Filter coefficient calculation is time-consuming. Therefore, you should only update the coefficients if the freq/gain/q change.
1 Like