Multibus API


#1

We are currently working hard at revising the multibus API and a sneak peek can be found under the experimental/multibus branch.

This branch is unstable and for testing only, most notably the branch does not currently support RTAS, AAX or AU v3 (in fact compiling these will fail).

This branch now also supports hosting multibus plug-ins. For a quick overview I’ve recorded two tutorial videos:


 

It would be great if anybody interested in multibus plug-ins could watch the above video and comment on the new API.

This new API does introduce breaking changes for any JUCE plug-in using multibus. However, any plug-in not using multibus (in particular any plugin built before JUCE 4.1) will work without any modifications.

The new API has been greatly simplified to the previous multibus API. In essence, only an additional callback (isBusLayoutSupported) and a new base class constructor are necessary to create a multi-bus plug-in. For example, the NoiseGate plug-in has the following relevant additions to support multi-bus:

class NoiseGate  : public AudioProcessor
{
public:
    //==============================================================================
    //==============================================================================
    NoiseGate()
        : AudioProcessor (AudioIOProperties().withInput  ("Input",     AudioChannelSet::stereo())
                                             .withOutput ("Output",    AudioChannelSet::stereo())
                                             .withInput  ("Sidechain", AudioChannelSet::mono()))
    {
        addParameter (threshold = new AudioParameterFloat ("threshold", "Threshold", 0.0f, 1.0f, 0.5f));
        addParameter (alpha  = new AudioParameterFloat ("alpha",  "Alpha",   0.0f, 1.0f, 0.8f));
    }

    .
    .
    .

    //==============================================================================
    bool isBusLayoutSupported (const AudioBusLayouts& layouts) const override
    {
        // the sidechain can take any layout, the main bus needs to be the same on the input and output
        return (layouts.getMainInputChannelSet() == layouts.getMainOutputChannelSet() &&
                (! layouts.getMainInputChannelSet().isDisabled()));
    }

There are many more callbacks which you can override for more fine-grain control, however, for most plug-ins only the above callback is necessary. Have a look at AudioProcessor.h for documentation on these new callbacks.

Please let me know your thoughts on the new API.


setPreferredBusArrangement removed in JUCE 4.3.0?
Add audio file player node for plugin host example
(DEPRECATED) The ultimate JUCE 4.1 MultiBus Guide
#2

#3

If I understand correctly, an AudioBusLayouts instance represents a “snapshot” of a set of busses, each represented by the channel arrangement given to it (represented by a AudioChannelSet value), right?

If that is correct, then (as a non native english speaker, thus the name may sound ugly), I would have more conveyed that meaning by naming it “AudioBussesLayout”.
This gives me more the idea of “this represents the exact channel arrangement for each of a given set of busses”.

On the other hand, AudioBusLayouts gives me more the idea that it stores a set of possible arrangements for a single given bus (main input, main output, or sidechain, for example).

If AudioBussesLayout sounds ugly, perhaps AudioBussesArrangement is better? Or LayoutOfAudioBusses, ArrangementOfAudioBusses?

Aside from that, I haven’t tested the code yet but the API looks a lot nicer than before! Well done!

edit: And accordingly, also I think that isBusLayoutSupported() could be renamed to isAudioBussesLayoutSupported(), because it checks for the arrangement of all the busses at the same time, not only one of them


#4

Thanks yfede, that does sound very reasonable. AudioBusLayouts is indeed a snapshot so AudioBussesLayout does sound better. Just not sure on the plural of Bus(s)es :smile:. Jules and I were arguing about this far too long and then went with Buses. But apparently both are ok. :smile:. I’ll change the code accordingly.


#5

I am in the middle of JUCE3 -> JUCE4 upgrade, applying my code changes to JUCE 4.2.1. The main reason for upgrade is actually need for side-chain input with AAX, AU and VST3.

Do you recommend to wait for the new API to be finished (i’am afraid i don’t have time for it) or should i finish porting to 4.2.1 as planned and use old JUCE4.1 multi-bus API ? Any drawbacks for the latter approach ?


#6

This looks great!

The isBusLayoutSupported(…) callback looks much more cleaner than the current confusing setPreferredBusArrangement(…) callback.

Now some criticism, sorry,

Base class initialisation Parameters are not cool because of inflexibly at runtime.

For example ProTools wants Mono sidechain, where other hosts allow stereo sidechain.

Better use setDefaultBussArangement(…) or a callback.

Before serious testing i need AAX support. I don’t like the fact that I support now 3 parallel API-levels of JUCE, thats makes things very convoluted here.

TBH nobody will spend time testing this, without getting paid for it or until its not fully tested from someone from the juce-team.

It just feels like loosing time. Its not the fun part. But thats the why i would spend money for a framework. (No need for projucer etc, just a reliable framework WITHOUT breaking changes.)

One thing which really scares me, now if there is an issue with JUCE_4.2, you may say this will be fixed with the “new-new” multibus-API, which forces me to update again to something new “untested”.

After a long decision i just decided i use the current tip again, now i’m not sure if its was the right decision and go back to my patched 4.0 juce.

I think a lot of commercial plugin-makers think the same.


#7

Hey,

After watching the videos I think the new API style is cleaner. I don’t have any issues with the base class initialization as long as there is a cleaner guideline to setting the “isBusLayoutSupported”

Next week I can compile some apps with the new test and let you know how it goes in the supported formats.

There are still some issues with the multi bus in some areas, but overall it is extremely useful, powerful, and exciting!

Thanks for all your work Fabian!


#8

I Zabukowski, I recommend to stick with the old API for now. The new API will still have a ton of bugs. The old one also has a ton of bugs which couldn’t be fixed without breaking the API (hence the new API), but simple side-chain plug-in should work with the old API.


#9

Hey chkn, I completely understand where you are coming from. We all know that the first version of the multibus API was quite a disaster - and that’s really unacceptable for paying customers.

We are not expecting any of our customers to test this. In fact, we now have the capacity at ROLI to do testing ourselves and this is currently happening. This post is really about nailing the API and getting feedback on it. I’d much rather have JUCE customers - who have been programming plug-ins for years - look at the API structure and see if it makes sense. We are doing this to avoid there being 4 parallel APIs if it turns out that some API design choices are wrong with this API.

One thing we had in mind when designing this API was that we wanted the API itself to make it impossible to make mistakes. One problem with the old API was that there were a lot of rules you needed to know to work with it. Those rules should really be implicit in the API. One of those rules is that you can only create buses yourself in the constructor. To enforce this rule, I chose to have the default buses as constructor parameters. This way it is impossible to try to create the default buses anywhere else in the code.

I understand this can be a bit ugly if you require non-trivial code to figure out your default layout. Obviously, you could always use a static method in the following way:

class MyAudioProcessor : public AudioProcessor
{
public:
    MyAudioProcessor()
        : AudioProcessor (getDefaultIOProperties())
    {
        .
        .
        .
    }
private:
    static AudioProcessor::AudioIOProperties getDefaultIOProperties()
    {
          // my complex code here
         if (PluginHost::getPluginLoadedAs() == AudioProcessor::wrapperType_VST)
         {
              .
              .
              .
         }
    }
};

But even that won’t always fit the bill if your getDefaultIOProperties requires access to your member variables. I’ve hit the above problems myself in the hosting code for VST3, VST and AU, as I first need to initialize the whole underlying native plug-in to probe for the default layout. I needed to re-arrange a lot of code to make it fit the above pattern - so I definitely see where you are coming from.

As often, this is a choice between a more elegant, cleaner, safer API vs. flexibility. I chose to use the constructor parameter as I believe that requiring complex code to figure out the default bus layout - which can’t be written as a static member function - is more of an edge case.


#10

Now is the time to let us know what these issues are.


#11

Thanks, i’ll stick with old API then.

Btw. will JUCE documentation (classes etc.) of the 4.1 multi-bus API still be available, after the new API is released ?


#12

No but for a simple side chain plugin the changes necessary for your code shouldn’t be hard.


#13

Speaking of documentation, as the API-doc needs a makeover anyway (still no search and inheritance diagrams, at least in firefox on mac), couldn’t you add a dropdown menu to adapt the documentation to the available branches? At least master/develop/multibusExperimental…
The generation is fully automatable, so I believe your web team is capable of such things… Who knows, how long samuel want’s to run the doxygen :wink:


#14

Hey Fabian, very excited about the new API, is looking good.

But while waiting for that to stabilise, I’ve got to get an AU synth going right now with a single, static 16 stereo output bus layout. So I’ve set the PlugIn Channel Configuration field to “{0, 32}” in Projucer - which should work with the last stable Juce, if I understand correctly?

The resulting AU passes auval and I can use the 16 stereo channels in Ableton Live just fine.
However, Logic does NOT play ball with the same AU: it refuses to list it in the instrument picker (Logic preferences/plugin manager does see it though and says it’s validated)

So is Juce 4.2.3 multibus AU currently broken in Logic? Or does Logic require something special besides the Projucer setting? Perhaps I’m overlooking something?


#15

I hate to bring this up again, but I think that there is still some inconsistency in naming due to the fact that you have renamed the class to AudioBusesLayouts (with plural layouts) instead of AudioBusesLayout as was suggested (plural buses, singular layout).

The fact is that the class represents a layout (singular, in the sense of an “arrangement”) of multiple (hence plural) buses. Naming it with plural “layouts” still sounds wrong to me, and the tendency to treat it as an object that represents a (singluar) layout is already present in JUCE code, so why not take the full step forward?

For example, consider the methods getNextBestLayout(), containsLayout(), getNextBestLayoutInLayoutList() and the important isAudioBusesLayoutSupported(): they all imply that the object they deal with is a layout (singluar), but what is either returned from them or passed to them as an argument is a (plural) AudioBusesLayouts (named layouts when it is an argument).

Doesn’t it sound confusing?

If I had to decide upon this, I’d rename AudioBusesLayouts to AudioBusesLayout, and all the method and argument that deal with it should follow accordingly

e.g. applyBusLayouts() -> applyBusesLayout(),
checkAudioBusesLayoutsSupported (layouts) -> checkAudioBusesLayoutSupported (layout)

and so on…


#16

Sorry, not the API, bug issues in the plugin/host bus setups. Although, the issues appear to be more DAW related than JUCE as it works in 95% of environments.


#17

These suggestions are great. Please don’t say that “you hate to bring it up again”. We really want as much feedback as we can get. I’ve renamed the APIs accordingly on experimental/multibus.


#18

Another proposal for a cleaner API: I have noticed that many of the multibus methods use this pair of parameters: bool isInput, int busIndex.

If we were to embed them in a struct/class (let’s name it BusHandle for the sake of discussion), that could be passed by value as a single argument much like the Point and Rectangle classes are juggled around in place of their explicit coordinates.

Its simplest implementation could be like this, but there is a more advanced implementation farther down in this post.

struct BusHandle
{
     bool isInput;
     int busIndex;
};

That would have the following advantages:

  • One parameter instead of two to pass that information to methods;
  • BusHandle could be used as return type for methods;
  • It would be possible to make arrays of BusHandles, in case the need arises
  • Wrapping them in a class would also add the possibility to add self-explaining methods to make the code more readable, for example BusHandle::isSameSide (BusHandle other)

Last but not least, I have devised a way to implement such class with only one int member variable to encode both the ‘side’ and index of the bus (see below), which makes it even better than passing around the (bool, int) pair that is being used now.

The core is the int busHandle member variable, please first read its doc for easier understanding:

class BusHandle
{
public:
    BusHandle () : busHandle (0) { }

    BusHandle (bool isInput, int busIndex)
    {
        const int busNumber = (busIndex + 1); // 1-based bus number

        if (isInput) busHandle = -busNumber;
        else         busHandle = busNumber;
    }

    bool isValid  () const { busHandle != 0; }
    bool isInput  () const { busHandle < 0; }   // returns false if invalid
    bool isOutput () const { busHandle > 0; }   // returns false if invalid
    int getBusIndex () const { abs (busHandle) - 1; } // 0-based bus index, or -1 if invalid

private:
    /** Positive numbers starting from 1 are for output buses,
        Negative numbers starting from -1 are for input buses,
        0 is a special value for an uninitialized/invalid instance (useful for return values) */
    int busHandle;
}

#19

It [0] is fully automated, I don’t intend to take it down anytime soon. Nevertheless it would be nice if ROLI would improve their hosted doc a bit.

[0] http://klangfreund.github.io/jucedoc/


#20

I’ve just updated the experimental/multibus branch with many of your suggestions and AUv3 support. I’ve also rebased develop onto it (force push) so you’ll need to delete the branch and pull it again.

I also really like yfede suggestion and we will also add this to the API. AAX is also in the works and will be ready soon.