(DEPRECATED) The ultimate JUCE 4.1 MultiBus Guide

Hi Fabian,

i am creating a multi input and multi output plugin and have in the introjucer the following {12,12}. When testing the plugin an assertion occurs in the function findAllCompatibleLayoutsForBus  in the file juce_PluginBusUtilities.h. In this function the first line says:

const int maxNumChannels = 9;

Is the number of channels limited to 8 ? That would be too bad. My plugin needs just one bus with lots of channels, input and output wise. 32 should be the min. Maximum and 64 would be better.

In earlier Juce version i had at least 32 ins and 32 outs... (64 not tested) ...

Best,

Thomas

Hi Thomas (and others),

I've been working on fixing recently. The fix will have no maximum limit of channels. It will also give you more control on how your AudioProcessor will behave as a VST-2 plug-in. Stay tuned!

Fabian

i have a synth plugin crashing when setup with no inputs busses.

    bool getSpeakerArrangement (VstSpeakerArrangement** pluginInput, VstSpeakerArrangement** pluginOutput) override
    {
        *pluginInput = 0;
        *pluginOutput = 0;

        if (! AudioEffectX::allocateArrangement (pluginInput, busUtils.findTotalNumChannels (true)))
            return false;

        if (! AudioEffectX::allocateArrangement (pluginOutput, busUtils.findTotalNumChannels (false)))
        {
            AudioEffectX::deallocateArrangement (pluginInput);
            *pluginInput = 0;
            return false;
        }

        if (busUtils.getBusCount (true) > 1)
        {
            AudioChannelSet layout = AudioChannelSet::canonicalChannelSet (busUtils.findTotalNumChannels(true));
            SpeakerMappings::channelSetToVstArrangement (layout,  **pluginInput);
        }
        else
        {
>           SpeakerMappings::channelSetToVstArrangement (busUtils.getChannelSet (true, 0),  **pluginInput);
        }

        if (busUtils.getBusCount (false) > 1)
        {
            AudioChannelSet layout = AudioChannelSet::canonicalChannelSet (busUtils.findTotalNumChannels(false));

            SpeakerMappings::channelSetToVstArrangement (layout,  **pluginOutput);
        }
        else
        {
            SpeakerMappings::channelSetToVstArrangement (busUtils.getChannelSet (false, 0), **pluginOutput);
        }

        return true;
    }

when ioChanged is triggered i get this called for setting the inputs/outputs busses... but getChannelSet(true, 0) try to obtain a reference of an item in a juce array with 0 size (i have no inputs in this configuration {0, 2}).

Several times in the guide at the beginning of this posts, the method setPreferredBusArrangement() in the base AudioProcessor class is referred to as the method to call to accept the layout change from our, overridden, version of setPreferredBusArrangement().

Why isn't that "accepting" method named accordingly (perhaps acceptBusArrangementIfPossible()) and made non-virtual and protected in the base AudioProcessor class?

The base class implementation of setPreferredBusArrangement() would then result in a simple call forward to this acceptBusArrangementIfPossible() method (to retain the default behaviour), and all the derived versions of setPreferredBusArrangement() would call this accepting method instead of the base class' setPreferredBusArrangement(), which would make the code a little clearer and less prone to mistakenly calling the derived version instead, IMHO.

Speaking in code:

class AudioProcessor   // this is the JUCE AudioProcessor class
{
public:

    virtual bool setPreferredBusArrangement (whatever)
    {
        /* the base class tries to accept whatever change,
           to retain its current default behavior and also
           in order not to break existing code */

        return acceptBusArrangementIfPossible (whatever);
    }

protected:

    // This is protected so that only this and derived classes can call this.
    // Notice this is non-virtual so no mistakes of calling on base/dervied classes.

    bool acceptBusArrangementIfPossible (whatever)
    {
        /* this does whatever the current
           AudioProcessor::setPreferredBusArrangement() 
           does in order to accept changes */
    }

};


class DerivedAudioProcessor    // this is the AudioProcessor class that implements our plug-ins
{
    bool setPreferredBusArrangement (whatever) override
    {
        if (reason)
            return false;     // deny change in bus arrangement because of 'reason'

        /* below is the case of a change requiring a change on another bus, 
           thus the outcome is subordinate to the other bus change being accepted 
         */
        if (whatever == requires_another_change) 
            return acceptBusArrangementIfPossible (another bus change);

        // otherwise, try to accept the change
        return acceptBusArrangementIfPossible (whatever);
    }
};

Please also note that this change would not break existing code, because any call made explicitly to the base AudioProcessor::setPreferredBusArragement() would retain its current meaning of "accept that", because of its default implementation that forwards the call to this proposed acceptBusArrangementIfPossible()

Or maybe I am missing something?

1 Like

Same here. 

> i have no inputs in this configuration {0, 2}

even if you had, input buses get added only if  ! JucePlugin_IsSynth (see AudioProcessor::AudioProcessor())

Sounds like a great suggestion. I'll ammend the code accordingly.

Yes I have a fix for this which will be coming soon with a whole number of other multi-bus fixes.

Will that include fixing the AudioProcessorGraph? Please… please… please…

Thanks,

Rail

FYI, I think I have found and fixed  a bug in the implementation of the sidechain for AAX.

I have submitted is as a pull request for the JUCE GitHub repo (detailed description included there):

https://github.com/julianstorer/JUCE/pull/67

 

any ETA on this ? it has currently broken everything regarding vst synth outputs

We are working on it. We want to give it proper testing before releasing. 

+1 - I'm also waiting for the Graph to support Bus layout... that would be great!

Hi Fabian, is your experimental JUCE fork available somewhere in case we want to help testing your new multibus fixes?

1 Like

HomeopaticChannelSet class. providing inconsistent bus channels in every daw since 1999

Are the new multibus fixes available in JUCE 4.2?

Hi @myhrman,

all the fixes should be in this release apart from things involving the AudioProcessorGraph or hosting multi-bus plug-ins. Also, we haven’t touched the API in this release - but we will rename a few things going forward to avoid confusion. This will also get rid of the macros being using in AudioProcessor.cpp.

In addition, this release offers more control for VST-2 plug-ins if you use the setBusArrangement API. See examples/PluginSamples/GainPlugin for this. For example, if JUCE detects that you are using the setBusArrangement API, then the layout set in the constructor of your plug-in will be the default VST-2 plug-in layout. You can also indirectly specify the maximum number of supported channels in the setPreferredBusArrangement callback. See the example for more details.

Kraken, have you tested 4.2 regarding that “vst with no inputs busses” issue?

yes, i will do a detailed report tomorrow

I believe I hit the exact same problem (“VST with no input buses”) with 4.2. The crash happens after setLatencySamples() has been called, or more specifically after the ioChanged() function has been called asynchronously.

bool getSpeakerArrangement (VstSpeakerArrangement** pluginInput, VstSpeakerArrangement** pluginOutput) override
{
    ...

    if (pluginHasSidechainsOrAuxs())
    {
        int numIns  = busUtils.findTotalNumChannels (true);
        int numOuts = busUtils.findTotalNumChannels (false);

        AudioChannelSet layout = AudioChannelSet::canonicalChannelSet (numIns);
        SpeakerMappings::channelSetToVstArrangement (layout,  **pluginInput);

        layout = AudioChannelSet::canonicalChannelSet (numOuts);
        SpeakerMappings::channelSetToVstArrangement (layout,  **pluginOutput);
    }
    else
    {
        // CRASHES ON THIS LINE:
        SpeakerMappings::channelSetToVstArrangement (busUtils.getChannelSet (true,  0), **pluginInput);
        SpeakerMappings::channelSetToVstArrangement (busUtils.getChannelSet (false, 0), **pluginOutput);
    }

    return true;
}

The crash above only happens if the synth has:

  • No individual output buses (i.e. only a main output bus).
  • No sidechain inputs

So, as soon as there is only a main output bus, I have to add also a sidechain input bus, or else the plugin crashes as soon as anything triggers the asyncUpdate in VST wrapper and ends up in ioChanged().

This is reproducible in the MultiOutSynth example, if you simply set the maxMidiChannel = 1 instead of 16 (in which case it will only get a main output bus). And then call setLatencySamples(1000) from prepareToPlay(). Crash!