ProcessorDuplicator - how to?

dsp_module

#1

Hi everybody,

over the weekend I did my first steps with the dsp module, and I quite like it, especially getMagnitudeForFrequency is very handy.

However, I failed to translate the demos into my usecases, especially how to use ProcessorDuplicator.

So far I created a chain of IIR filters, but I had to go old school, creating one instance per channel:

using Equalizer = dsp::ProcessorChain<dsp::IIR::Filter<float>, dsp::IIR::Filter<float>>;
Equalizer iirLeft, iirRight;

How can I use the ProcessDuplicator instead?

using Equalizer = dsp::ProcessorChain<dsp::IIR::Filter<float>, dsp::IIR::Filter<float>>;
dsp::ProcessorDuplicator<Equalizer, dsp::ProcessorState> filter;

Or do I have to use one ProcessorDuplicator per filter? What is this state? Do I have to use a certain state variable?

Apologies, this kind of syntax is quite different from what I did the last 20 years…

Thanks!


#2

Hey, don’t apologise! Template meta-programming is a bit like quantum mechanics - if you claim to understand it, you clearly don’t.

That’s exactly it. If you look at the OverdriveDemo.cpp and more specifically the DCFilter inside that demo, you can see how the IIR::Filter is duplicated. This duplicated filter is then used in the ProcessorChain.

Does this answer your question?


#3

Thanks, I appreciate that!

Yes, I was staring at it, but it confuses me. Can the ProcessorChain actually chain mono and Duplicators after each other?
Or is a Processor already able to process multiple channels, and the Duplicator is only needed, if you need different states for each channel? - It annoys me, that I can only guess instead of understanding it.

I tired this setup now, wrapping several ProcessorDuplicators into one ProcessorChain, but fun fact, I can’t hear anything:

using Stereo    = dsp::ProcessorDuplicator<dsp::IIR::Filter<float>, dsp::IIR::Coefficients<float>>;
using Equalizer = dsp::ProcessorChain<dsp::IIR::Filter<float>, dsp::IIR::Filter<float>>;
dsp::ProcessorChain<Stereo, Stereo> filter;

void FilterProcessor::prepareToPlay (double sr, int samplesPerBlock)
{
    sampleRate = sr;
    dsp::ProcessSpec spec;
    spec.sampleRate = sr;
//    spec.maximumBlockSize = samplesPerBlock;
//    spec.numChannels = getTotalNumInputChannels();

    updateCoefficients();
    filter.prepare (spec);
}

void FilterProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
    dsp::AudioBlock<float>              ioBuffer (buffer);
    dsp::ProcessContextReplacing<float> context  (ioBuffer);
    filter.process (context);
}

void FilterProcessor::releaseResources()
{
    filter.reset();
}

void FilterProcessor::updateCoefficients ()
{
    const float loFreq = *(state.getRawParameterValue (paramLoFreq));
    const float loQ    = *(state.getRawParameterValue (paramLoQ));
    const float hiFreq = *(state.getRawParameterValue (paramHiFreq));
    const float hiQ    = *(state.getRawParameterValue (paramHiQ));

    auto loCoeff = dsp::IIR::Coefficients<float>::makeLowShelf (sampleRate, loFreq, loQ, 0.25);
    filter.get<0>  ().state = loCoeff;
    auto hiCoeff = dsp::IIR::Coefficients<float>::makeHighShelf (sampleRate, hiFreq, hiQ, 0.25);
    filter.get<1>  ().state = hiCoeff;

    loCoeff->getMagnitudeForFrequencyArray (frequencies.data(), magnitudes.data(), frequencies.size(), sampleRate);
    std::vector<double> mags (frequencies.size());
    hiCoeff->getMagnitudeForFrequencyArray (frequencies.data(), mags.data(), frequencies.size(), sampleRate);
    FloatVectorOperations::multiply (magnitudes.data(), mags.data(), static_cast<int> (frequencies.size()));
}

Is that the right approach at all?

Thanks,
Daniel


#4

Yes your approach looks good except for a single line. You need to dereference the filter state, as each mono filter has a pointer to the state of the ProcessorDuplicator:

auto loCoeff = dsp::IIR::Coefficients<float>::makeLowShelf (sampleRate, loFreq, loQ, 0.25);
*filter.get<0>  ().state = *loCoeff;
auto hiCoeff = dsp::IIR::Coefficients<float>::makeHighShelf (sampleRate, hiFreq, hiQ, 0.25);
*filter.get<1>  ().state = *hiCoeff;

In general, what may be confusing is that some filters in the dsp module are multi-channel and some are only mono and need to be duplicated (like IIRFilter and FIRFilter).


DSP Module IIR Filtering sample by sample
#5

Thank you, that did the trick!
I don’t understand the concept though, because out of curiosity I tried:

filter.get<0>  ()->state = *loCoeff;

but that wouldn’t compile, so it is not the same dereferencing. I think I need more input on that…

Ah, that makes sense… would you or someone of your team mind to flesh out the documentation, so it could be looked up, which need a separate state, and which don’t?
(well, some are obvious, but would be good to have it written somewhere)

Thanks a lot for the help!


#6

Is there no surefire way to update the states of the current processors that are held in ProcessDuplicator?

It seems like prepare() only updates the states if they happen to be instantiated (depending on the ProcessSpec channel count).

Right now in my prepareToPlay() I have to fully re-instantiate my ProcessDuplicator object to make sure the filter coefficients update (eg because of a possible new sample rate), or call prepare() with channel count = 0, then with the real channel count.

    void ProcessorDuplicator::prepare (const ProcessSpec& spec)
    {
        processors.removeRange ((int) spec.numChannels, processors.size());

        while (static_cast<size_t> (processors.size()) < spec.numChannels)
            processors.add (new MonoProcessorType (state));

        auto monoSpec = spec;
        monoSpec.numChannels = 1;

        for (auto* p : processors)
            p->prepare (monoSpec);
    }

Ideally I would like my code to read (like in some of the demos):

filter.state = makeCoefficientsForMyFilter (sampleRate);
filter.prepare (spec);

#7

Hmmm what’s wrong with *processors.state = myNewState?


#8

Whoops :hushed: I see now. That does the trick. Thanks @fabian!