Question about VST3 multi-bus and cakewalk

Hi,

Cakewalk seems to behave differently from other hosts and I wonder if it is an “acceptable” behaviour or not.

My plugin has a main stereo bus, and a number of output aux buses. In other hosts (reaper, studio one,…), when the aux buses are enabled, the bus enablements are performed by the host before prepareToPlay is called. So, when I’m in prepareToPlay, getTotalNumOutputChannels returns the correct value, the number of channels that I will actually receive in processBlock.

In the case of Cakewalk, the bus enablement is changed after the call to prepareToPlay, before the first call to processBlock. So, when I’m in prepareToPlay, getTotalNumOutputChannels return 2, while the buffer passed in processBlock has a different number of channels.

Shall I make a bug report to cakewalk or does it behave within the specs of vst3 (or is it JUCE that calls prepareToPlay a bit too early) ?

Which version of Cakewalk are you using?

Cakewalk activates VST3 bus configuration by calling component->activateBus and we do this BEFORE calling IComponent->setActive. In fact before activating buses the component is deactivated first.

We also handle explicit requests from a plugin by calling Steinberg::Vst::IComponentHandlerBusActivation::requestBusActivation or via IComponentHandler::restartComponent

I see JUCE calls prepareToPlay from VST3HostContext::restartComponentOnMessageThread which appears to be called from InitializePlugin and via restartComponent.

So offhand, I’m not sure why you arent getting buses setup before restart. You can e-mail me at noel at bandlab.com and I can help troubleshoot if necessary.

Hi Noel,

I was using version 2021.11 when I wrote my post – I have now updated to 2021.12 and the behaviour seems a bit different, but I don’t know if it is the version change, or if it just that the issue is a bit random. Here is a log (from the JUCE point of view) of the current behaviour, you can see that there are two calls to processBlock() that have five channels before the actual call to prepareToPlay:

this=00000000143DCDA0 prepareToPlay: getNumOutputChannels: 2, Real-time:1 getBusCount=4 bus0=Output, enabled=1 getNumberOfChannels=2
 bus1=Aux1, enabled=0 getNumberOfChannels=0
 bus2=Aux2, enabled=0 getNumberOfChannels=0
 bus3=Aux3, enabled=0 getNumberOfChannels=0

this=00000000143DCDA0 processBlock : getTotalNumOutputChannels=2 
this=00000000143DCDA0, numChannelsChanged: 3 isThisTheMessageThread=1
this=00000000143DCDA0 processorLayoutsChanged, buses=0/4, channels=0/3
in:X out:2+1+0+0
this=00000000143DCDA0, numChannelsChanged: 4 isThisTheMessageThread=1
this=00000000143DCDA0 processorLayoutsChanged, buses=0/4, channels=0/4
in:X out:2+1+1+0
this=00000000143DCDA0, numChannelsChanged: 5 isThisTheMessageThread=1
this=00000000143DCDA0 processorLayoutsChanged, buses=0/4, channels=0/5
in:X out:2+1+1+1

-----------UNEXPECTED processBlock HERE -------

this=00000000143DCDA0 processBlock : getTotalNumOutputChannels=5
this=00000000143DCDA0 processBlock : getTotalNumOutputChannels=5
this=00000000143DCDA0, releaseResources

this=00000000143DCDA0 prepareToPlay: getNumOutputChannels: 5, Real-time:1 getBusCount=4
 bus0=Output, enabled=1 getNumberOfChannels=2
 bus1=Aux1, enabled=1 getNumberOfChannels=1
 bus2=Aux2, enabled=1 getNumberOfChannels=1
 bus3=Aux3, enabled=1 getNumberOfChannels=1

this=00000000143DCDA0 prepareToPlay: getNumOutputChannels: 5, Real-time:1 getBusCount=4
 bus0=Output, enabled=1 getNumberOfChannels=2
 bus1=Aux1, enabled=1 getNumberOfChannels=1
 bus2=Aux2, enabled=1 getNumberOfChannels=1
 bus3=Aux3, enabled=1 getNumberOfChannels=1

this=00000000143DCDA0 processBlock : getTotalNumOutputChannels=5  buffer: 5
this=00000000143DCDA0 processBlock : getTotalNumOutputChannels=5  buffer: 5
this=00000000143DCDA0 processBlock : getTotalNumOutputChannels=5  buffer: 5
etc..

So, there is still an issue, but not exactly the one I was reporting.

I’ll send you an email with further details. Thank you !

1 Like

So, to sum up the exchange with Noel, Cakewalk is not doing anything wrong. Maybe the JUCE VST3 wrapper could be changed so that a last call to prepareToPlay() is made when the final number of channel is known.

But for now, in its current state, the value returned by getTotalNumOutputChannels() inside prepareToPlay() cannot be trusted as is it not final.

1 Like

A few notes To add to the explanation.

prepareToPlay is called in response to a few scenarios like this:

JuceVST3Component::initialize
JuceVST3Component::setActive
audioProcessor->setupProcessing.

However, it is not called in response to component->activateBus (JuceVST3Component::activateBus)
Since activateBus can be called after setupProcessing the plugin can’t rely on prepareToPlay to get the number of active channels. Perhaps activateBus could also call that function. However that would mean that there will be multiple calls to setupProcessing.

Also, I found another issue with JUCE’s VST3 implementation:
VST3 has a documented mode where a “null” process call can be made with a zero buffer size to pass parameters state to the plugin. This call can be made even BEFORE the plugin has been activated. In this call the plugin is supposed to only update parameters and no processing is done. However, JUCE makes an unnecessary call to processBlock which can cause some plugins to misbehave.
If the VST component->process call has a zero buffer size JUCE should not call processBlock to solve this issue.

1 Like

I’ve debugged this a bit now. I’m testing with 2021.12 build 102 on Windows 11. My testing plugin is a modified version of the AudioPluginExample demo project with an optional sidechain input.

When I instantiate the plugin on a track, and then direct another track’s output to the plugin’s sidechain input, I see that prepareToPlay is not called again (as you reported) - however, the call to activateBus on the VST3 wrapper ends up calling the virtual function processorLayoutsChanged on the AudioProcessor, alerting it that its bus layout has changed.

One thing I did find strange (I think this is likely a bug in Cakewalk) is that the plugin remains in the ‘activated’ state when calling activateBus. According to the workflow diagrams and this forum thread, it is only valid to call activateBus while the plugin is deactivated (i.e. after calling setupProcessing, before calling setActive (true)). I would expect the plugin to receive a setActive (false) call, followed by one or more activateBus calls, and finally a setActive (true) call once all buses have been configured. I think if Cakewalk were to implement this behaviour, the wrapper would end up calling prepareToPlay on the AudioProcessor after the buses were reconfigured.

Thanks for the heads-up about the parameter processing change, I’ll get that fixed. The only reference in the docs I can find for this mode is on this page (search for “flush”). If you’re aware of a more detailed description of this processing mode, I’d find that useful so that I can be sure I’m implementing the behaviour correctly.

2 Likes

@nborthwick Thanks again, I’ve updated the VST3 process callback here:

Can you comment on the setActive calls before and after activateBus? Do you think this will be fixed in a future version of Cakewalk?

1 Like

Hi @reuk Indeed the activateBus call should be done only when the plugin is not in an active state.
I was unaware of this until last week when Yvan from Steinberg clarified it. I am actually working on changing that today. I was going to post when I finished but you beat me to it :slight_smile:

Regarding the null process call that link you posted is all there is in the VST3 SDK.
These are the notes I had copied into our code from an earlier SDK:
*The host could call Steinberg::Vst::IAudioProcessor::process without buffers *
*(numInputs and numOutputs of Steinberg::Vst::AudioBusBuffers are zeroed, numSamples too), in order to flush parameters (from host to Plug-in). *
Parameters flush could happen only when the host needs to send parameter changes and no processing is called.

Many years ago, I asked Yvan to add a specific test for that into the VST3 plugin validator utility. I’m not sure if that was done. This is such a common problem area that I wish it was highlit more clearly and prominently.

1 Like

The fix will be in the next update we do which is likely next month. I can send you a build if you would like to validate the fix however.

1 Like

That’s great to hear, thanks for the prompt response. I’m not too worried about validating the fix before release, but I’m happy to do so if you’d find that helpful.

1 Like

@reuk You can try the fix here. Let me know if you have any questions.

I’ve tested this a bit and it’s a definite improvement. When I enable/disable a sidechain input, the plugin is deactivated and reactivated, and prepareToPlay is called.

However, if I add a bool isActive = false data member to the VST3, update it in setActive, and add a jassert (! isActive) inside activateBus, I see that this function is called on the plugin in the active state, whenever playback is started. I think this is against the VST3 spec, even if the states of the buses aren’t changed by the call.

@reuk can you please try again with this build and let me know if you still see activate being called while its already active.

Just tried build 026 from the EAP and it’s still not quite conforming to the VST3 spec. I’m using the same setup as above - setActive sets a bool data member to the current active state, and we check that this member is false during activateBus.

The assertion is hit in a few scenarios:

  • When opening a project containing the plugin, setActive is called three times, setting the plugin active, then inactive, then active again. Then, activateBus is called a few times.
  • When hitting play, activateBus is called a few more times, then setActive is called twice to toggle the plugin off and on again.
  • When closing the project, activateBus is called a few more times, then setActive is called once to make the plugin inactive.

Hey @reuk

It should be completely harmless to call setActive redundantly and I don’t see this as being called out anywhere in the spec. I have never seen any well behaved VST3 plugins have issues with this.
However, there could be some plugins that do inefficient stuff when setActive is called because they don’t check their current state. So, I’ve made some changes to maintain active bus states internally and prevent calling setActive when it’s not changing.

The 2022.02 update has a fix for this now.
@jpo you may want to also try this release and see if your original issues are also resolved. Thanks for your feedback…

This issue seems to affect at least one other major DAW. I’ve now added an assertion to JUCE’s unit test wrapper that should make it a bit easier to tell whether or not the DAW is calling activateBus at a valid point:

1 Like