Handling non-critical animation

I’m trying to add some animations to my controls. I’m planning to use the IVDisplay control to show a sine wave with varying frequency and amplitude. Pretty straight forward and it shouldn’t be that CPU intensive. In my case, the sine wave is purely cosmetic. It’s not part of the audio chain in any way.

So, I’m not sure what the best place to “perform” the animation would be. If I understand correctly, the IVDisplay control is only responsible for displaying some values in a buffer. It doesn’t actually generate the animated data. This means the buffer has to be filled elsewhere (in my case, samples need to be generated to represent a sine wave). In the IPlugControls example, the samples are updated in ProcessBlock. This works, but suppose the required computation was more complex, could it cause slowdown or incomplete handling of more critical tasks also performed in ProcessBlock, namely audio processing?

What’s the best way to ensure animation never compromises audio quality?

I’ve been using IVPlotControl for basic graphics. It takes a lambda function as a parameter, which takes care of calculating the Y coordinate to plot a fixed number of points (in your case this function would be a sine). It only gets redrawn when you set it to “dirty”, so you can control how many times you want these calculations to take place.

Having said that, I’m looking right now at the IPlugControls example and sure enough the IVDisplayControl seems much better suited for continuous plotting.

In any case, as l mentioned above, these controls will only get drawn when isDirty() returns true, so you can control the refresh rate with that.

If you just want to do some drawing and have a sine wave in an animated control, I would just make a custom control. You don’t need to use the audio thread at all…

Here is an example with ILambdaControl, which can be set to animate continuously.

1 Like

edit: this was intended as a reply for Ric

Can I ask how and where you are passing data to the control in your application? What data type are you using to store the waveform data?

The example in IPlugControls uses ProcessBlock to generate the data and puts it in “sample** outputs”, but this didn’t seem like the right way to handle cosmetic data to me.

Also, the example uses IBufferSender, but as far as I can tell this only works with elements of type “sample**”. It’s possible to send the raw data directly to the control without using an IBufferSender by using “this->SendControlValueFromDelegate(ctrlTag, mData)” where mData is of type float. This requires overriding the “SetValueFromDelegate” method because “Update” needs to be called.

I wasn’t able to get it working with “OnMsgFromDelegate”, but I think that would be the correct way to do it without having to modify IVDisplayControl.

Oh right, so that would run completely independent without requiring any “updates” or other method calls? That does seem like a more clean way to implement animation.

I’ll show you how I do it with IVPlotControl to pass data and set dirty. It must be something analogous with IVDisplayControl.

class IRPlot final : public IVPlotControl
{
public:
    bool* mDirty;
    int* mDisplay;
    IRPlot(const IRECT& bounds, const std::initializer_list<Plot>& funcs, int numPoints, const char* label, bool* Dirty, int* Points) :
    IVPlotControl(bounds,funcs,numPoints,label)
    {
        mDirty = Dirty;
        mDisplay = Points;
    }
    bool IsDirty() override {return *mDirty;}
    void Draw(IGraphics& g) override
    {
        mPoints.resize(*mDisplay);
        IVPlotControl::Draw(g);
        *mDirty = false;
    }
};

Basically, I create a derivate class (in this case IRPlot inherits from IVPlotControl), and I add two pointer variables: mDirty and mDisplay, which are passed on as parameters on creation. These pointers point to member variables in my plugin: called mIRDirty and mIRPoints respectively. And finally, I override isDirty() and Draw(), where I pass the data to the control. This way, whenever I want to update the plot, all I need to do is update mIRPoints and then set mIRDirty to true.

However, all this is just a workaround which may not be necessary if I were to dig deeper in sending values from delegate. But it works for now, so I haven’t tried it just yet.

1 Like