Sidechain fixed to default channel set in Cubase

I’m working on a VST3 plugin with two ins (main and sidechain), one out. It only supports all mono or all stereo, and the sidechain can be disabled. So I set the buses in the processor’s constructor with

: AudioProcessor (BusesProperties()
    .withInput("main in", AudioChannelSet::mono(), true)
    .withInput("sidechain", AudioChannelSet::mono(), false)
    .withOutput("main out", AudioChannelSet::mono(), true))

Then I reject wrong combinations in isBusesLayoutSupported with

return layouts.getChannelSet(true, 0) == layouts.getChannelSet(false, 0) &&
      (layouts.getChannelSet(true, 0) == AudioChannelSet::mono() ||
       layouts.getChannelSet(true, 0) == AudioChannelSet::stereo()) &&
      (layouts.getChannelSet(true, 1) == AudioChannelSet::disabled() ||
       layouts.getChannelSet(true, 1) == layouts.getChannelSet(true, 0));

Then I go test it on Cubase 10, and the plugin doesn’t react to mono/stereo changes: if I set mono() in the constructor, it’s mono for stereo and mono tracks; if I set stereo(), it’s always stereo. It does react to sidechain changes, also fixed to mono/stereo when active. Am I missing something?

Ok, so I set isBusesLayoutSupported to accept everything and checked the channel sets in Editor::paint with

g.drawFittedText (
    processor.getChannelLayoutOfBus(true, 0).getDescription() + " - " +
    processor.getChannelLayoutOfBus(true, 1).getDescription() + " - " +
    processor.getChannelLayoutOfBus(false, 0).getDescription(),
    getLocalBounds(), Justification::topLeft, 1);

and I got this

Then I changed the default sidechain channel set to stereo, with

: AudioProcessor (BusesProperties()
    .withInput("main in", AudioChannelSet::mono(), true)
    .withInput("sidechain", AudioChannelSet::stereo(), false)
    .withOutput("main out", AudioChannelSet::mono(), true))

and I got

So it would seem that sidechains don’t change their channel set dynamically, they’re fixed to the default setting. Can anyone confirm this?

(edit) Well, I guess not. It rather makes sense to me now -if a sidechain can be sent from many tracks with different channel sets, it’s simpler to let the plugin set its own channel set and adjust whatever is sent to it. So I’ll default to stereo, reject nothing, check in processBlock and finish this monologue.

I think our post here: Issues with channel configuration in VST3 / Cubase is essentially referencing this same behavior.

Yap, it’s the same. I didn’t dive into the wrapper to see if it’s a Cubase or a JUCE issue. I just report 2, and ignore the second one for mono. In my current project:

MyAudioProcessor::MyAudioProcessor()
    : AudioProcessor(BusesProperties().withInput("main in", AudioChannelSet::stereo(), true)
                                      .withInput("sidechain", AudioChannelSet::stereo(), false)
                                      .withOutput("main out", AudioChannelSet::stereo(), true)),
{ /* etc */ }

bool MyAudioProcessor::isBusesLayoutSupported(const BusesLayout& layouts) const
{
    return layouts.getChannelSet(true, 0) == layouts.getChannelSet(false, 0) &&
          (layouts.getChannelSet(true, 0) == AudioChannelSet::mono() ||
           layouts.getChannelSet(true, 0) == AudioChannelSet::stereo());
}

bool MyAudioProcessor::isMono() const
{
    return getChannelCountOfBus(true, 0) == 1;
}

void MyAudioProcessor::processBlock(AudioBuffer<float>& buffer, MidiBuffer&)
{
    ScopedNoDenormals noDenormals;
    auto scChannels{ getChannelCountOfBus(true, 1) };
    auto in{ getBusBuffer(buffer, true, 0) }, sc{ scChannels == 0 ? in : getBusBuffer(buffer, true, 1) };
    if (isMono()) main->processBlock(in.getWritePointer(0), sc.getReadPointer(0), buffer.getNumSamples());
    else main->processBlock(in.getWritePointer(0), in.getWritePointer(1),
        sc.getReadPointer(0), sc.getReadPointer(scChannels == 1 ? 0 : 1), buffer.getNumSamples());
}

If there’s no sidechain, sidechain is main in. If main is stereo and sidechain is mono, I use it for both channels.