Multibus API

I was using

getTotalNumInputChannels ();

to get the Number of InputChannels in my AudioProcessor Constructor in order to create the right number of Parameters for the plugin. Now, after the update, this always returns 0 in the constructor. Only in the prepareToPlay function it returns correctly. I’m not using any of the Multibus features, I just use 3 different possible channel configurations in the Projucer ( {8,2}, {2,2,}, {1,2} )


1 Like

Have you had a look at the examples in JUCE/examples/PluginSamples? There are a few use cases there?[quote=“fbecker, post:78, topic:18491”]
I’ve got a plug-in with 10 inputs and 4 outputs. i’ve declared it using the following constructor:

AudioProcessor(BusesProperties().withInput(“In”, AudioChannelSet::discreteChannels(10), true)
.withOutput(“Out”, AudioChannelSet::ambisonic(), true))

The first issue I encounter with REAPER is the additional channels visible in the plug-in I/O pane: I’ve got 8in/8out whereas I previously had 10in/4out:

The formats in the constructor are just the default formats of your plug-in. The host can change the format of the plug-in to anything else. You should be overriding the isBusesLayoutSupported callback and only return true if you are supporting the layout. This way you can restrict the layouts.

I don’t quite understand this. The buffers you receive will always have the correct channel size. It just that reaper has initialized you plug-in to have too many ins and outs. I think overriding isBusesLayoutSupported will help you here as well.

If you run into problems please send me a pm and when schedule a skype meeting to get you started.

@Logan_Thomas @yfede A quick note on callbacks when the layout changes: As @yfede mentions, the callbacks numChannelsChanged etc. can be used to get a callback when the number of channels changes. However, you should very rarely need to use them. This is because the API is passive: you only know the number of channels and layout when your prepareToPlay method is called - usually just before playback, i.e. several layout changes may be requested behind the scenes while your plug-in is suspended. This was always the way JUCE worked even before multibus (i.e. setPlayConfigDetails does not give you a callback either).

This should not be a problem though: as your plug-in is suspended you will not get any processBlock calls. If you need to do some work when the layout/channels change, then you should be doing this in the prepareToPlay method anyway. If you need to reject layout changes, then you should be doing this in the isBusesLayoutSupported callback.

But if you need to the callbacks are there for you to use.

1 Like

Sorry about this. The fact that you were getting the correct number of channels in the constructor is actually a “bug” with the first multi-bus implementation that was on master recently. The current implementation on master is now in line with how JUCE worked before multi-bus was introduced, i.e. the number of channels was always zero when the constructor is called. This behaviour is needed for backwards compatibility.

However, fixing this is easy: you can get the correct number of channels if you simply use the non-default AudioProcessor base constructors and specify a default layout for your plug-in.

I’m not sure this will work very well. Many DAWs will request a layout change after the plug-in was constructed (this has nothing to do with multi-bus). However, hosts don’t really like you changing the number of parameters after you plug-in is constructed.

I think the only feasible way to do this is to construct as many parameters as there can maximumly be channels (i.e. 8) and let your editor respond to the layout changes by hiding unused parameters.

1 Like

@fabian, it looks like the AudioProcessor documentation on the website is not up to date with this new multibus API.
Can you please update it?

@fabian No, the examples in the juce repository were of no help.

I’ve finally achieved to make it run (my 10in/4out plug-in) doing the following:

-> Implement isBusesLayoutSupported:

    bool isBusesLayoutSupported (const BusesLayout& layouts) const override
        return (layouts.getMainInputChannels() == 10
             && layouts.getMainOutputChannels() == 4);

-> Hack Juce (because otherwise I would end up with a crash in REAPER) for the channel count limit:

diff --git a/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h b/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h
index c4c9274..7eb56a7 100644
--- a/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h
+++ b/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h
@@ -305,7 +305,7 @@ public:
-        maxChannelsOfNamedLayout = 8
+        maxChannelsOfNamedLayout = 16
     /** Adds a channel to the set. */
diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp
index 37f4f3f..4cf0f73 100644
--- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp
+++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp
@@ -1943,7 +1943,7 @@ private:
         AudioChannelSet inputLayout  = filter->getChannelLayoutOfBus (true,  0);
         AudioChannelSet outputLayout = filter->getChannelLayoutOfBus (false,  0);
-        const std::size_t speakerBaseSize = sizeof (VstSpeakerConfiguration) - (sizeof (VstIndividualSpeakerInfo) * 8);
+        const std::size_t speakerBaseSize = sizeof (VstSpeakerConfiguration) - (sizeof (VstIndividualSpeakerInfo) * 16);
         cachedInArrangement .malloc (speakerBaseSize + (static_cast<std::size_t> (inputLayout. size()) * sizeof (VstSpeakerConfiguration)), 1);
         cachedOutArrangement.malloc (speakerBaseSize + (static_cast<std::size_t> (outputLayout.size()) * sizeof (VstSpeakerConfiguration)), 1);
diff --git a/modules/juce_audio_processors/format_types/juce_VSTInterface.h b/modules/juce_audio_processors/format_types/juce_VSTInterface.h
index 502f367..e077a48 100644
--- a/modules/juce_audio_processors/format_types/juce_VSTInterface.h
+++ b/modules/juce_audio_processors/format_types/juce_VSTInterface.h
@@ -409,7 +409,7 @@ struct VstSpeakerConfiguration
     int32 type;
     int32 numberOfChannels;
-    VstIndividualSpeakerInfo speakers[8];
+    VstIndividualSpeakerInfo speakers[16];
 enum VstSpeakerConfigurationType

Not sure about everything in this patch but “it works”, “allows 10 channels”, “prevents an unavoidable crash”.

I hope it helps (ROLI & juce users),

seems reasonable, thanks for the detailed answer.


I am not sure if this is more a VST2 specific problem or something to do with the Juce Multibus API or maybe something that concerns both…

I have some VST2 plugins, from the GRM Tools bundle, that don’t work properly with the Juce plugin hosting. For example, the GRM Tools Shuffling plugin does support up to 8 output channels, but it looks like the Juce plugin hosting can’t handle that. This can be confirmed with the Juce Plugin Host application. There is no way to make the plugin to use its maximum of 8 output channels, the host only shows 1 mono or stereo bus. Also in my own Juce application’s code, I didn’t find a way to activate the 8 output channels. (I know the 8 output channels of the plugin can actually be made to work since that works in Cockos Reaper and if I recall right, in my HourGlass application that uses some self written VST2 hosting code.)

My understanding is that dealing with the multibus/channel stuff in VST2 isn’t exactly fun, but maybe something still could be done in the Juce plugin hosting to deal with this? It wouldn’t need to be anything fancy, just make it possible to set the desired number of output channels in a VST2 plugin, even if the plugin reports a lower maximum count. (I know this potentially could lead to things crashing/malfunctioning in some cases, but even that would better than not being able to some plugins to their full capabilities.)

edit : Looks like this might be a problem specific to the GRM plugins. Right after loading and opening the plugin, they report 2 ins and 2 outs in the AEffect struct. However, if the effSetSpeakerArrangement opcode is called right after loading/opening the plugin, the IO counts in the AEffect struct update to 2 ins and 8 outs. I will probably have to hack something into the Juce code to make this work for myself…

This is a limitation with the “JUCE plugin host”. Currently, it only supports a subset of layouts (mono, stereo, LCR, quadrophonic, 5.0, 5.1, 6.0, 6.1, …) but no discrete layouts yet.

I want to stress that JUCE does support discrete channels in general. It’s just the host that doesn’t support it.

That doesn’t sound good. The IO count in the AEffect struct should be the maximum number of channels the plug-in can handle.

Right, I would expect that too, but some of the GRM Tools plugins don’t work that way. For the time being, I added a pretty ugly hack into the JUCE VST2 hosting code that checks for the GRM plugin names and does some of the channel count and bus initialization stuff differently if the name matches. I should probably drop the GRM developer an email about this…(AFAIK they also use JUCE for developing the plugins, so it’s a pity their plugins don’t work out of the box with the JUCE hosting code. :confused: )

By the way, at least one of the GRM plugins (the Spaces surround panner) does report 32 ins and outs initially and JUCE interprets that as 16 x 2 channel busses per signal direction, is that the intended behavior? Is it because the JUCE hosting code does not support the discrete channel layouts? (For that plugin the channel layout should probably be one bus of 32 channels per signal direction, the plugin internally handles stereo, quad, 5.1 etc formats.)

It does! It’s just that the “hosting demo code” doesn’t.

would be nice if it did - since thats something i’ll be using a lot in my host code and plugin code ( plugins like kontakt in 32 or 64 discrete output mode - or more accurately 16 or 32 stereo outputs )

Hmmm … I’ve got a few synths that are crashing the scanner as well.

Not sure if it’s related, but it seems that any synth with 0 inputs, 2 outputs is causing some craziness.
Verified in plugin host demo, but also in several other host apps that I put together.

Maybe these are just discrete layouts? Not completely sure how to check that?

Yes, we found a similar problem with some plugins calling plugInOpcodeGetSpeakerArrangement: Latest JUCE updates in develop crashes OBXD plugin

I don’t think this used to be called pre-multi-bus. It sounds like a bug with those plugins but perhaps there’s some work around?

On the topic of the new MultiBus API I still haven’t received a single reply from the JUCE team about this problem i posted about some weeks back.

Fabian is the multi-bus guru and he’s been on holiday… I know he’s been answering a few of the simpler forum posts in his time off, but probably saving the more complex ones until he’s back!

I’m still seeing a lot of crashes in plugins due to the introduction of the plugInOpcodeGetSpeakerArrangement call in queryBusIO.

All these plugins worked before the multi-bus re-write (presumably because this was never called?).

I’ve looked all over the VST spec (which is incredibly difficult now that all the JUCE implementation is named differently) and the only thing I can find that’s related is this:

Which has two points of interest:

  1. getSpeakerArrangement() are always called in suspended state
    Which I don’t think applies here because the plugin isn’t even fully initialised yet
  2. The check for > getFormatVersion () >= 2300
    However, if I find this using plugInOpcodeGetVstInterfaceVersion, it returns 2400 for the below plugin so is unlikely to be much help.

A lightweight plugin that exhibits this behaviour is: Wiggle
If you build the plugin host and scan the VST you’ll see it crash after the call to queryBusIO.
Commenting out the call to dispatchFunction (effect, plugInOpcodeGetSpeakerArrangement enabled the plugin to scan correctly so I’m really hoping there’s some way of deciding at run time if this opcode is safe to call?

I’m not ruling out that this isn’t a bug in the plugins but they must have worked in other hosts that queried the speaker arrangement as there are so many that exhibit this behaviour.

Any chance this could get a look at?

1 Like

Thanks dave96,
I’ve been looking into this but needed a lightweight plugin to further test changes - so thanks for the reference to Wiggle.

One issue is that an old public JUCE version (I think the initial JUCE 4 release) had a hard to spot nullptr dereference. Any plug-in made with that version (this includes an old version of equator) can crash if queryBusIO is called. However, I could not find a workaround to detect if a plug-in was built with that broken version of JUCE.

I’ll look into this tomorrow.


1 Like

Ah. That could well be it. I’ve spotted crashes there in plugins that don’t have their symbols hidden.

Strange that they wouldn’t crash in other hosts though (I’ve tried Reaper at the least)? I wonder how this manages to bypass calling that method?

Hmm this is going to be really tough to fix. I can see that Wiggle was built with the infamous JUCE 4.1 release and will therefore randomly crash in BigInteger. The offending line in JUCE 4.1 is this one: it tries to get the plug-in input layout which will crash if the plug-in is a synth - and therefore doesn’t have any input buses.

I workaround is to not call getSpeakerArrangement if the plug-in is a synth. Although not ideal, I’ve pushed this workaround to develop for now.