Number of input / output channels is incorrect in prepareToPlay() for certain VST3 plugins

I think we’ve encountered a bug with Juce 4.3.0: in certain situations, prepareToPlay is called with {0, 0} channels but followed by calls to processBlock with {2, 2} channels.

We actually saw so behaviors also.

  1. As a workaround try moving to newer bus reporting (from Development branch and up-to-date projucer create an audio plugin project you’ll see the new layout). it’s a few calls and should resolve your issue (if you’re on a tight schedule).

  2. Here is a similar thing we had:
    https://github.com/julianstorer/JUCE/commit/27be047e8e54a2a032824b98acfe77125e40d577

In addition those days I’ve also add some “workaround” on our code (since we sometimes use internal buffers) so any prepareToPlay called with 0 I/O (for our plug-ins that we know are never to expect such value) we ignore them…

Calling prepareToPlay with {0,0} is a valid call. It’s disabling both the input and output bus (in fact this is the default layout if you use the default constructor of AudioProcessor). Don’t use the default constructor of AudioProcessor if your plug-in does not support that layout.

@fabian what we saw here on VST3 wasn’t related to default constructor…
All callouts was on prepareToPlay() something as follows:
{2,2}
{2,2}
{2,2}
{0,0}
{0,0}

It was a few months ago so I need to recreate it or do more tests in-order to reproduce it.
But what we had was our buffers was adjusting to zeros in prepareToPlay just to be called afterward in process with different layout making the plug-in crash.

I’m confused. Shouldn’t prepareToPlay and processBlock still call with the same number of channels regardless? This note seems to imply that (with the whichever-is-greater stipulation):

This method will return the total number of input channels by accumulating the number of channels on each input bus. The number of channels of the buffer passed to your processBlock callback will be equivalent to either getTotalNumInputChannels or getTotalNumOutputChannels - which ever is greater.

What constructor should be used if a plugin supports several equally desirable layouts, such as: {1, 1,} {1, 2}, {2, 2}? The appropriateness of any layout depends on the host configuration, so how should we choose what to call the constructor with?

Yes your right. Sorry I misread part of the post. I was referring to {0,0} being a valid call to prepareToPlay. The number of channels in processBlock should always match whatever the number of channels were at the last call to prepareToPlay. I’ll investigate…

The layout in the constructor is just the default layout! Choose any supported layout you like (or the layout your user would most likely use). However, make sure it doesn’t contradict with what isBusesLayoutSupported reports. For example, if your isBusesLayoutSupported rejects any zero channel counts then your default layout cannot include a zero channel count (like the default constructor of AudioProcessor). This is a contradiction and you may get some strange results. I’ll add an assertion to stop people from doing this.

Any more hints on how I can reproduce this? I took the GainPlugin example and modified it so that it uses the default constructor. Then tested it in Cubase as a VST3. I get this:

prepareToPlay: {0, 0}
prepareToPlay: {0, 0}
prepareToPlay: {2, 2}
processBlock: {2, 2}

Which looks ok to me.

Using the GainPlugin, here are my mods:

bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
    const AudioChannelSet& mainInput  = layouts.getMainInputChannelSet();
    const AudioChannelSet& mainOutput = layouts.getMainOutputChannelSet();
    
    if (mainInput.size() == 2 &&
        mainOutput.size() == 2) {
        return true;
    } else {
        return false;
    }
}

And changing to the default constructor.

In REAPER using VST3, I get:

prepareToPlay: 0, 0
prepareToPlay: 0, 0
prepareToPlay: 0, 0
prepareToPlay: 0, 0
prepareToPlay: 0, 0
processBlock: 2, 2
processBlock: 2, 2
…

Adding an assert to prevent a non-supported {0,0} constructor would catch this, correct?

At the moment we’re just doing something like this to make the largest channel configuration our default, which I guess seems reasonable:

int defaultNumOutputChannels() {
    std::vector<std::pair<int, int>> channelConfigs = {PBJucePlugin_PreferredChannelConfigurations};
    
    return channelConfigs.back().second;
}

Yes exactly. I’ll add an assertion to develop…

I’m getting the same problem here, JUCE 4.3.0 version. Do we have a solution?

The solution seems to be to declare some reasonable channel defaults for construction, even though you don’t know what the host is going to ask for later. In our case we just use the maximum.

Not sure if the assert was ever added to catch it.

hmmm as I understood, I’ll always use maximum channels size. I’m not 100% sure about processBlock function. But I think it gives the same channel sizes.

If it’s I need to separate the plugin for channel configurations. Stereo one and mono one that I don’t want to do it (It was only logical for VST2 version).

Maybe new configuration system will be the solution.