4.3.0 Some AU plugins have wrong I/O Count

Hi Nikolai,

You are right this has gotten a bit more complicated, especially if you are using an AudioProcessor to wrap another PluginInstance. The correct way to wrap an AudioProcessor is the following way:

class WrapperProcessor
     : public AudioProcessor
{
public:
    WrapperProcessor (AudioPluginInstance* processorToUse)
        : AudioProcessor (getBusesPropertiesFromProcessor (processorToUse)),
          plugin (processorToUse)
    {}
    
    //==============================================================================
    const String getName() const override                                          { return plugin->getName(); }
    bool canAddBus (bool inputBus) const override                                  { return plugin->canAddBus (inputBus); }
    bool canRemoveBus (bool inputBus) const override                               { return plugin->canRemoveBus (inputBus); }
    bool supportsDoublePrecisionProcessing() const override                        { return plugin->supportsDoublePrecisionProcessing(); }
    double getTailLengthSeconds() const override                                   { return plugin->getTailLengthSeconds(); }
    bool acceptsMidi() const override                                              { return plugin->acceptsMidi(); }
    bool producesMidi() const override                                             { return plugin->producesMidi(); }
    bool supportsMPE() const override                                              { return plugin->supportsMPE(); }
    bool isMidiEffect() const override                                             { return plugin->isMidiEffect(); }
    void reset() override                                                          { plugin->reset(); }
    AudioProcessorEditor* createEditor() override                                  { return plugin->createEditor(); }
    bool hasEditor() const override                                                { return plugin->hasEditor(); }
    int getNumParameters() override                                                { return plugin->getNumParameters(); }
    const String getParameterName (int parameterIndex) override                    { return plugin->getParameterName (parameterIndex); }
    String getParameterID (int index) override                                     { return plugin->getParameterID (index); }
    float getParameter (int parameterIndex) override                               { return plugin->getParameter (parameterIndex); }
    String getParameterName (int parameterIndex, int maximumStringLength) override { return plugin->getParameterName (parameterIndex, maximumStringLength); }
    const String getParameterText (int parameterIndex) override                    { return plugin->getParameterText (parameterIndex); }
    String getParameterText (int parameterIndex, int maximumStringLength) override { return plugin->getParameterText (parameterIndex, maximumStringLength); }
    int getParameterNumSteps (int parameterIndex) override                         { return plugin->getParameterNumSteps (parameterIndex); }
    float getParameterDefaultValue (int parameterIndex) override                   { return plugin->getParameterDefaultValue (parameterIndex); }
    String getParameterLabel (int index) const override                            { return plugin->getParameterLabel (index); }
    bool isParameterOrientationInverted (int index) const override                 { return plugin->isParameterOrientationInverted (index); }
    void setParameter (int parameterIndex, float newValue) override                { plugin->setParameter (parameterIndex, newValue); }
    bool isParameterAutomatable (int parameterIndex) const override                { return plugin->isParameterAutomatable (parameterIndex); }
    bool isMetaParameter (int parameterIndex) const override                       { return plugin->isMetaParameter (parameterIndex); }
    int getNumPrograms() override                                                  { return plugin->getNumPrograms(); }
    int getCurrentProgram() override                                               { return plugin->getCurrentProgram(); }
    void setCurrentProgram (int index) override                                    { plugin->setCurrentProgram (index); }
    const String getProgramName (int index) override                               { return plugin->getProgramName (index); }
    void changeProgramName (int index, const String& newName) override             { plugin->changeProgramName (index, newName); }
    void getStateInformation (juce::MemoryBlock& destData) override                { plugin->getStateInformation (destData); }
    void getCurrentProgramStateInformation (juce::MemoryBlock& destData) override  { plugin->getCurrentProgramStateInformation (destData); }
    void setStateInformation (const void* data, int sizeInBytes) override          { plugin->setStateInformation (data, sizeInBytes); }
    void setCurrentProgramStateInformation (const void* data, int bytes) override  { plugin->setCurrentProgramStateInformation (data, bytes); }
    
    //==============================================================================
    void prepareToPlay (double sampleRate, int maximumExpectedSamplesPerBlock) override
    {
        plugin->releaseResources();
        
        plugin->setRateAndBufferSizeDetails (sampleRate, maximumExpectedSamplesPerBlock);
        
        // sync number of buses
        for (int dir = 0; dir < 2; ++dir)
        {
            const bool isInput = (dir == 0);
            int expectedNumBuses =         getBusCount (isInput);
            int requiredNumBuses = plugin->getBusCount (isInput);
            
            for (; expectedNumBuses < requiredNumBuses; expectedNumBuses++)
                plugin->addBus (isInput);
                
            for (; requiredNumBuses < expectedNumBuses; requiredNumBuses++)
                plugin->removeBus (isInput);
        }
        
        plugin->setBusesLayout (getBusesLayout());
        plugin->prepareToPlay (sampleRate, maximumExpectedSamplesPerBlock);
    }
    
    void releaseResources() override         { return plugin->releaseResources(); }
    
    //==============================================================================
    void processBlock (AudioBuffer<float>& buffer,  MidiBuffer& midiMessages) override         { plugin->processBlock (buffer, midiMessages); }
    void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override         { plugin->processBlock (buffer, midiMessages); }
    void processBlockBypassed (AudioBuffer<float>& buffer,  MidiBuffer& midiMessages) override { plugin->processBlockBypassed (buffer, midiMessages); }
    void processBlockBypassed (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override { plugin->processBlockBypassed (buffer, midiMessages); }
    
protected:
    
    //==============================================================================
    bool isBusesLayoutSupported (const BusesLayout& layouts) const override        { return plugin->checkBusesLayoutSupported (layouts); }
    bool canApplyBusesLayout (const BusesLayout& layouts) const override           { return plugin->setBusesLayout (layouts); }
    bool canApplyBusCountChange (bool isInput, bool isAddingBuses,
                                 BusProperties& outNewBusProperties) override
    {
        if (isAddingBuses)
        {
            int busIdx = plugin->getBusCount (isInput);
            
            if (! plugin->addBus (isInput))
                return false;
            
            if (Bus* bus = plugin->getBus (isInput, busIdx))
            {
                outNewBusProperties.busName              = bus->getName();
                outNewBusProperties.defaultLayout        = bus->getDefaultLayout();
                outNewBusProperties.isActivatedByDefault = bus->isEnabledByDefault();
                
                return true;
            }
            else
            {
                jassertfalse;
                return false;
            }
        }
        else
            return plugin->removeBus (isInput);
    }
private:
    //==============================================================================
    static BusesProperties getBusesPropertiesFromProcessor (AudioProcessor* processor)
    {
        BusesProperties retval;
        
        for (int dir = 0; dir < 2; ++dir)
        {
            const bool isInput = (dir == 0);
            const int n = processor->getBusCount (isInput);
            
            for (int i = 0; i < n; ++i)
                if (AudioProcessor::Bus* bus = processor->getBus (isInput, i))
                    retval.addBus (isInput, bus->getName(), bus->getDefaultLayout(), bus->isEnabledByDefault());
        }
        
        return retval;
    }
    
    ScopedPointer<AudioProcessor> plugin;
};

The reason why the extra code is needed in prepareToPlay is that the AudioProcessor class has always been passive: even before JUCE 4, the owner of an AudioProcessor could call setPlayConfigDetails and change the number of channels without the plug-in getting any callbacks. This is the same for the multi-bus API: the owner of an AudioProcessor can muck about with the number of buses and formats without the AudioProcessor ever getting a callback. The first time the plug-in can actually “know” which format, buses, sampleRate, bufferSize it has been assigned is in the prepareToPlay method. Therefore, the above code forwards all those properties in the prepareToPlay method. Does this make sense?

Let me know how you get along with the above code.

2 Likes