IBufferSender Implementation

Hi,

I’m trying to implement a message system from my process block to my custom control class. So far working from the example IPlug Controls project etc I have the following code:

MyPlugin.h

class MyPlugin final : public Plugin
{
public:
MyPlugin(const InstanceInfo& info);

#if IPLUG_DSP // Distributed Plugins · iPlug2/iPlug2 Wiki · GitHub
void ProcessBlock(sample** inputs, sample** outputs, int nFrames) override;
void OnIdle() override;

private:
IBufferSender<2> mSender;
#endif
};

MyPlugin.cpp

#if IPLUG_DSP
void MyPlugin::ProcessBlock(sample** inputs, sample** outputs, int nFrames)
{
const double gain = GetParam(pGainIn)->Value() / 100.;
const int nChans = NOutChansConnected();

mSender.ProcessBlock(outputs, nFrames, pScope, 2);

for (int s = 0; s < nFrames; s++) {
for (int c = 0; c < nChans; c++) {
outputs[c][s] = inputs[c][s] * gain;
}
}
}

void Saturator::OnIdle()
{
mSender.TransmitData(*this);
}
#endif

My Control Class

class MyControlClass: public IControl, public IVectorBase
{

public:
MyControlClass(const IRECT& bounds, int paramIdx)
: IControl(bounds, paramIdx)
, IVectorBase(false, false)
{
}

void OnMsgFromDelegate(int msgTag, int dataSize, const void* pData) override
{

}

private:
ISenderData<2, std::array<float, 128>> mBuf;

};

Even though I’ve not got as far as doing anything with the sent data, my plugin is already crashing, and giving the following error:

Assertion Error
Program: MyPlugin.vst3\Contents\x86… Line 90
Expression: pControl

I can’t for the life of me work out where I might have gone wrong?

Thanks,
James

you have to check if the UI actually exists. if the UI is closed for example (but the plugin is still processing), your UI controls don’t exist.

so before you transmit the message, check:

if (GetUI())

also it looks like you’re constantly sending the message on every OnIdle() call (which by default on Windows seems to be about ~30Hz) - is that intended? if not, you can use use eg. a std::atomic boolean to signal during the processing that you want to send the message once, and then test it in OnIdle() (and set the bool back to false when you’re done).

actually @olilarkin, that raises a good point - my UI is quite slow to open each time. many commercial plugins are also super slow, some are really bad (Softube, some NI stuff, …).

do the plug format SDKs force you to destroy and re-create the UI each time? because I find that lag a pain, and would rather incur some memory overhead by keeping the UI alive, just hidden. I don’t know if that’s possible though?

Thank you for your help @_gl.

I haven’t had chance to try your suggestion, so I can’t say whether it’s solved my error. But wondered if I could check something more generally…

You mention not updating on every OnIdle call, would you have an example of how to do this? I’m thinking if it’s to update UI which is at say 60fps, would you maybe just take a single value every other OnIdle call?

Thanks again,
James

Sorry I should add for something like a meter or scope

I tried the if (GetUI()) but still get the same fatal error

are you definitely creating your UI control in mLayoutFunc = [&](IGraphics* graphics)) { … }?

re. updating the value, if you want constant ‘live’ updates like a meter, then sending it every OnIdle() call is fine. Note you can also change the OnIdle call frequency by defining IDLE_TIMER_RATE and setting it to a timeout value in milliseconds. On MSVC, I have to do that in the project’s preprocessor settings, else it gets missed by the Iplug2 code (ie. you can’t just place it into your config.h, at least with VST3).

Note the number you set is supposed to be the gap between calls in milliseconds, but isn’t reliable as the timing seems to be hampered by Windows’ internal low-priority timer resolution (a better option would be to roll your own timer based on QueryPerformanceFrequency(), which I plan to do).

I was originally talking about on-demand updates - say if something only changes occasionally based on your processing code, I use a class member like this:

std::atomic<bool> SendValueToDSP = false;

then during processing, if you need to send an update, just set this to true. In OnIdle(), test whether it’s true - if so, send the update, and set it back to false. (OnIdle() and the audio processing typically happen on different threads, std::atomic ensures that the boolean value is threadsafe).

Thanks again for the reply.

My controls are added within the base Plugin constructor like so…

Saturator::Saturator(const InstanceInfo& info)
: Plugin(info, MakeConfig(kNumParams, kNumPresets))
{
  GetParam(pGainIn)->InitDouble("Gain In", 100., 0., 300.0, 0.01, "%");
  GetParam(pGainOut)->InitDouble("Gain Out", 100., 0., 300.0, 0.01, "%");
  GetParam(pMix)->InitDouble("Mix", 100., 0., 100.0, 0.01, "%");

#if IPLUG_EDITOR
  mMakeGraphicsFunc = [&]() {
	return MakeGraphics(*this, PLUG_WIDTH, PLUG_HEIGHT, PLUG_FPS, GetScaleForScreen(PLUG_WIDTH, PLUG_HEIGHT));
  };
  
  mLayoutFunc = [&](IGraphics* pGraphics) {
	pGraphics->AttachPanelBackground( IColor(255,51,55,69) );
	pGraphics->LoadFont("Roboto-Regular", ROBOTO_FN);
	const IRECT b = pGraphics->GetBounds();

	pGraphics->AttachControl(new ITextControl(b.GetFromTLHC(60, 15).GetHShifted(14).GetVShifted(12), "saturator", IText(16, EAlign::Near, IColor(255, 170, 171, 175))));
	pGraphics->AttachControl(new AARect(b.GetFromBottom(15), IColor(255, 33, 35, 44)));
	pGraphics->AttachControl(new AAKnobControl(b.GetCentredInside(100).GetHShifted(-80).GetVShifted(68), pGainIn, "gain in"));
	pGraphics->AttachControl(new AAKnobControl(b.GetCentredInside(100).GetHShifted(0).GetVShifted(68), pGainOut, "gain out"));
	pGraphics->AttachControl(new AAKnobControl(b.GetCentredInside(100).GetHShifted(80).GetVShifted(68), pMix, "mix"));
	pGraphics->AttachControl(new AAGraphUnit(b.GetCentredInside(132).GetHShifted(46).GetVShifted(-43), pScope));
	;
  };
#endif
}

Please excuse the scrappiness, It’s just a test project!

cool, and which control are you sending the update to? I don’t see ‘MyControlClass’ in there (unless that was just an example?).

‘pScope’. Which I’ve declared like this…

enum EParams
{
  pGainIn = 0,
  pGainOut = 1,
  pMix = 2,
  pMode = 3,
  pScope = 4,
  kNumParams
};

Sorry - it was just an example! It’s going to ‘AAGraphUnit’

I don’t see you creating a scope control?

@GreyhoundProject as far as I can see you haven’t said where you are getting a crash where pControl is a dangling pointer. This context info is needed to work out why it’s crashing, but like @_gl says you probably need to check if the UI exists with GetUI() before trying to access it.

BTW

mSender.ProcessBlock(outputs, nFrames, pScope, 2);

< this line is called before outputs are updated, so they may be full of zeros/invalid data

The OnIdle() call fires every 20 ms by default. You can change that time like this. The UI redraw at e.g. 60 FPS is triggered by VSync on Windows and is therefore very accurate (on macOS it is an NSTimer by default but can be set to VSync via IGRAPHICS_CVDISPLAYLINK in the xcconfig - both are accurate). Not sure why you’d need a higher precision timer

Sorry I don’t know how to find this? I’m getting the error when I load my plugin (in FL Studio) with no sound. If I have sound playing the error triggers again, and again, and again and causes a BSOD.

No and iPlug1 didn’t do this since it used software rendering. Since we’re using the GPU it’s a bit complicated. Would need to implement something to share GPU resources between instances of the plug-in (which is hard and maybe pointless, or even harder if plug-ins are hosted out of process). The memory usage is very significant. It might be possible not to re-create the IGraphics each time with SKIA_CPU mode, but I can’t remember exactly.

I don’t find iPlug2 UIs slow to load TBH - can you profile it?

I’ve done a lot of graphics work, so I checked the frequency of OnIdle() to see what I was actually getting in practice. On Windows you’re running off the SetTimer() API, which is a very low priority timer that’s easily interrupted. As a result I wasn’t getting the values I was expecting from various IDLE_TIMER_RATE values - the results are all over the place, and seem related to UI timer ‘quanta’ (whatever that is, something to do with Windows UI scheduling no doubt).

from my code notes:

// the IDLE_TIMER_RATE is arkward (due to Windows' internal slop?):
//			15ms works out as a little over 60Hz
//			16ms ~40Hz
//  default 20ms ~30Hz
// ALSO IT HAS TO BE SPECIFIED IN THE PROJECT SETTINGS OR IT DOESN'T WORK

I cut my teeth on writing game engine code and low-level 2D graphics stuff, so I’ve been using my own timer class based on QueryPerformanceCounter() for everything to get rock solid animations and predictable timings. It’s the best timer on Windows. Happy to contribute the code.

Sorry @olilarkin (I’m being stupid, I blame the heat)

The error is line 90 of IGraphics\IGraphicsEditorDelegate.cpp

void IGEditorDelegate::SendControlMsgFromDelegate(int ctrlTag, int msgTag, int dataSize, const void* pData)
{
  if(!mGraphics)
    return;
  
  IControl* pControl = mGraphics->GetControlWithTag(ctrlTag);
  
  assert(pControl);     // <<<<<<< HERE
  
  if(pControl)
  {
    pControl->OnMsgFromDelegate(msgTag, dataSize, pData);
  }
}

Very happy to receive contributions in this area :slight_smile: !

You can also avoid the ISender stuff and have your IControl take a pointer to e.g. float mVisData[] in your plugin class, then just draw those values in IControl::Draw() . This will not work with the “distributed” mode of iPlug2 for instance the WAM/webassembly output. It would bypass the Win32 timer though

The assertion is firing because there is no control with the tag ctrlTag. When you call pGraphics->AttachControl() you need to specify an integer for the ctrlTag argument which is then used with the ISender stuff to direct the visualization data to the correct control. Search for e.g. kCtrlTagMeter in the IPlugInstrument.cpp and you should see what is missing.