Weird path behavior for filter magnitudes

Hi folks,
I’m heaving a weird problem with my filter response curve.
When I slide any value affecting the curve, it sometimes produces weird spikes:

image

image


  • It’s especially weird because it seems to be entirely random. Whatever value I change, these spikes may appear - as long as the value changed causes the path to be redrawn.
  • It also does not seem to be consistent for specific values - when I see a spike, change the value past that point and then back, the spike does not necessarily reappear.
  • There seems to be some correlation between the frequencies affected by the change and the location of artifacts: changing the low cut does not seem to produce spikes in the far high frequencies. Peak filters have a tiny effect on the entire spectrum so they can produce artifacts anywhere. I’m not entirely sure whether this observation is true though.

This is how I generate the path:

    // if filter params have changed, regenerate new path
    if (parametersChanged.compareAndSetBool(false, true)) {
        std::vector<double> magnitudes;
        // analysisWidth is the width in px of the graphing area
        magnitudes.resize(analysisWidth);
        const double sampleRate = processor->getSampleRate();
        // DSP is a reference to a class handling all processing
        // Processing::CutFilter is a type defined as a ProcessorChain of 4 filters
        Processing::CutFilter* loCut = &DSP->preBandChainL.get<0>();
        Processing::CutFilter* hiCut = &DSP->preBandChainL.get<1>();

        for (int i = 0; i < analysisWidth; i++) {
            double freq = mapToLog10((double)i / (double)analysisWidth, 20.0, 20000.0); // X axis
            double magnitude = 1.0;                                                     // Y axis

            if (!loCut->isBypassed<0>()) magnitude *= loCut->get<0>().coefficients->getMagnitudeForFrequency(freq, sampleRate);
            if (!loCut->isBypassed<1>()) magnitude *= loCut->get<1>().coefficients->getMagnitudeForFrequency(freq, sampleRate);
            if (!loCut->isBypassed<2>()) magnitude *= loCut->get<2>().coefficients->getMagnitudeForFrequency(freq, sampleRate);
            if (!loCut->isBypassed<3>()) magnitude *= loCut->get<3>().coefficients->getMagnitudeForFrequency(freq, sampleRate);
            if (!hiCut->isBypassed<0>()) magnitude *= hiCut->get<0>().coefficients->getMagnitudeForFrequency(freq, sampleRate);
            if (!hiCut->isBypassed<1>()) magnitude *= hiCut->get<1>().coefficients->getMagnitudeForFrequency(freq, sampleRate);
            if (!hiCut->isBypassed<2>()) magnitude *= hiCut->get<2>().coefficients->getMagnitudeForFrequency(freq, sampleRate);
            if (!hiCut->isBypassed<3>()) magnitude *= hiCut->get<3>().coefficients->getMagnitudeForFrequency(freq, sampleRate);

            // the plugin handles processing in separate bands
            // filters should not be applied to curve when the band (and thus the filter) has no effect
            if (!DSP->isBypassedLo) magnitude *= DSP->BandLoL.chain.get<Processing::Band::PosPeakFilter>().coefficients->getMagnitudeForFrequency(freq, sampleRate);
            if (!DSP->isBypassedMid) magnitude *= DSP->BandMidL.chain.get<Processing::Band::PosPeakFilter>().coefficients->getMagnitudeForFrequency(freq, sampleRate);
            if (!DSP->isBypassedHi) magnitude *= DSP->BandHiL.chain.get<Processing::Band::PosPeakFilter>().coefficients->getMagnitudeForFrequency(freq, sampleRate);

            magnitudes[i] = Decibels::gainToDecibels(magnitude);
        }

        filterCurve.clear();
        auto map = [outputMin, outputMax](double input) { return jmap(input, -36.0, 36.0, outputMin, outputMax); };
        filterCurve.startNewSubPath(analysisArea.getX(), map(magnitudes.front()));
        for (int i = 1; i < magnitudes.size(); i++) {   // set path for every pixel
            filterCurve.lineTo(analysisArea.getX() + i, map(magnitudes[i]));
        }
    }

    // filter curve
    g.setColour(Colours::white);
    g.strokePath(filterCurve, PathStrokeType(2));

To be entirely clear:

  • most frames do not have these weird artifacts
  • the generation and drawing of the response curve work, it’s just that they sometimes do … whatever this is

My guess is that some values crash to neg infinity / NaN or 0 but I have no idea if that’s true and even so where that may come from.

It looks like you’re sharing the coefficient values between the DSP process and the GUI, without any measures to make sure the two threads don’t collide. What likely happens is that sometimes the coefficients are change while you’re drawing them. When that happens, it could be that the magnitudes are computed from a half-computed set of coefficients.

It’s probably better to - for example - mirror the filter calculations from your parameters in the GUI code, instead of reading the stuff from the DSP part. Otherwise you’d need to do some lock-free gymnastics to make sure that the UI always sees complete coefficient sets. Normally you’d use locks for such things, but the realtime thread is allergic to that. So the easiest solution is probably to simply re-compute coefficients in your GUI.

You were absolutely correct, thank you.

Seems like I was so happy to have done that step in a CPU-saving way by using merely a pointer that I didn’t realize how stupid that was :sweat_smile: