"IVDisplayControl" best starting point for spectrum display or?

Sure

Class:

class Plotter : protected virtual GraphicsTypes
{
public:
    
    struct Style
    {
        Style() : mFill(false), mThickness(1.f) {}
        
        Style(const IColor color, bool fill, float thickness = 1.f)
        : mColor(color), mFill(fill), mThickness(thickness) {}
        
        IColor mColor;
        bool mFill = false;
        float mThickness = 1.f;
    };
    
    Plotter(const IRECT& bounds) : mBounds(bounds) {}
    
    void SetBounds(const IRECT& bounds) { mBounds = bounds; }
    
    void Plot(IGraphics& g, const float* normY, unsigned long N, const Style& style);
    void Plot(IGraphics& g, const float* normX, const float* normY, unsigned long N, const Style& style);
    
protected:
    
    float XCalc(float xNorm) { return mBounds.L + (mBounds.W() * xNorm); }
    float YCalc(float yNorm) { return mBounds.B - (mBounds.H() * yNorm); }
    
    void Complete(IGraphics& g, const Style& s);
    
    IRECT mBounds;
}

Implementation::


void Plotter::Plot(IGraphics& g, const float* normY, unsigned long N, const Style& style)
{
    g.PathClear();
    g.PathClipRegion(mBounds);
    
    g.PathMoveTo(mBounds.L, YCalc(normY[0]));
    
    for (unsigned long i = 1; i < N; i++)
        g.PathLineTo(XCalc((1.f / (N - 1)) * i), YCalc(normY[i]));
    
    Complete(g, style);
}
    
void Plotter::Plot(IGraphics& g, const float* normX, const float* normY, unsigned long N, const Style& style)
{
    // The x values must be in order, so we can trim the ends
    
    for (; N > 1 && normX[0] < 0.f && normX[1] < 0.f; N--)
    {
        normX++;
        normY++;
    }
    
    for (; N > 1 && normX[N - 2] > 1.f && normX[N - 1] > 1.f; N--);
    
    if (!N)
        return;
    
    g.PathClear();
    g.PathClipRegion(mBounds);
    
    // Draw
    
    if (normX[0] >= 0.f)
    {
        g.PathMoveTo(mBounds.L, YCalc(normY[0]));
    }
    else
    {
        g.PathMoveTo(normX[0], YCalc(normY[0]));
        normX++;
        normY++;
        N--;
    }
   
    for (unsigned long i = 0 ; i < N; i++)
        g.PathLineTo(XCalc(normX[i]), YCalc(normY[i]));
    
    if (N && normX[N - 1] < 1.f)
        g.PathLineTo(mBounds.R,  YCalc(normY[N - 1]));
    
    Complete(g, style);
}

void Plotter::Complete(IGraphics& g, const Style& s)
{
    if (s.mFill)
    {
        g.PathLineTo(mBounds.R, mBounds.B);
        g.PathLineTo(mBounds.L, mBounds.B);
        g.PathClose();
        g.PathFill(s.mColor);
    }
    else
    {
        IStrokeOptions options;
        options.mJoinOption = ELineJoin::Round;
        g.PathStroke(s.mColor, s.mThickness, options);
    }
    
    g.PathClipRegion();
}

GraphicsType is a convenience class that pulls a bunch of iplug/igraphics types into a local scope, so they don’t require explicit namespace access.

Thank you! Greatly appreciated :slightly_smiling_face:

If you get a chance I would greatly appreciate some insight on how you did this. Do you pass the computed FFT array(s) back and forth between UI thread and Audio thread or some other means?

I use a blocking DSP algorithm on the DSP side of things to collect samples - this allows me to collect blocks of size N at a hop (interval in samples) of K. Each time I have gathered a full N samples I copy them to elements of an IPlugQueue<> and then that queue is checked in OnIdle() (this is the sender pattern, as used in other igraphics controls). Mine is hand rolled, but I’d suggest checking out ISenderData (which I think didn’t exist when I was doing this, or I hadn’t noticed it).

In my case everything else happens from OnIdle() - FFT and smoothing etc. This ensures that I minimise processing when the UI is closed.

1 Like

WIP:

1 Like

Awesome! Will be a great addition to iPlug2. I will try to contribute to development and test here the best I can. Hope some of my posts above offered something to the process.

Thank you!

Integrated @AlexHarker 's Spectrum Plotter code. Currently looking a bit like this:

4 Likes

I was just looking at the code for ISpectrumSender in ISender.h. I was wondering if someone could outline how the “overlap” is supposed to be working there? To my eyes the code looks as though it just does the exact same FFT twice (in PrepareDataForUI) because it sources the input from the same buffer dboth times in the loop.

I would have expected to see something more like this: set the buffer size equal to fftSize - overlapSamples (which would need to be computed). Then inside the PrepareDataForUI copy the acquired samples into a circular buffer. The most recent fftSize samples from the circular buffer would then be used to compute the FFT.

Thanks for spotting this, i think you are correct. I am looking into it

1 Like