Multiple busses for AU?


#1

Are their currently plans to support AU buses properly in Juce? In the v1.46 the AU plugin versions just support a single input and output bus, so for a 2xstereo input plugin defining 4 ins and 2 outs only gives you one “bus” in logic 8 and hence no way to route audio to the second stereo input.

Andrew Simper


#2

this is something I have made, but in a hackish way (and only for outputs). I found it quite a PITA to get that to work on AU … Documentation is very sparse.

It was even easier on RTAS !


#3

[quote=“jpo”]this is something I have made, but in a hackish way (and only for outputs). I found it quite a PITA to get that to work on AU … Documentation is very sparse.

It was even easier on RTAS ![/quote]
I am currently using a library by Magnus Lidstrom called “Symbiosis” which handles multiple buses for both inputs and outputs. It compiles into the binary the au entry points and wraps a vst plugin to make it an au. For this initial release I think I’ll just undef the juce au and use his wrapper, but in the long run I would like to get his permission and crib the multiple bus sections of his code and put them into Juce.

Andrew Simper


#4

jpo could you please contact me so I can talk to you about multichannel au and rtas stuff?

andy atsign cytomic.com

Thanks!

Andrew Simper


#5

Here we go. As I said it is a bit of a hack for my particular situation: 0 input and 5 outputs, that the host may show as 5 mono buses, 1 stereo + 3 mono, etc.

In AU-speak, an input / output bus is an “element” , so they are indexed by the “inElement” argument that is present in many AU functions. Input buses have “kAudioUnitInput_Scope”, and ouput buses have “kAudioUnitOutput_Scope”

The number of input / output busses in set in the AUBase constructor. In my case , I let it to 0 input, and 1 output as I want the host to decide how many buses it wants.

In order to allow the host to change the number of buses, these functions must be overiden from AUBase:

[code]+ /* we allow the host to change the number of output busses */

  • bool BusCountWritable(AudioUnitScope inScope)
  • {
  •    return (inScope == kAudioUnitScope_Output);
    
  • }
  • /* called when the host changes the number of output busses */
  • ComponentResult SetBusCount(AudioUnitScope inScope,
  •                            UInt32 inCount) {
    
  •    //cerr << "SetBusCount(scope=" << inScope << ", count=" << inCount << ")\n";
    
  •    return JuceAUBaseClass::SetBusCount(inScope, inCount);
    
  • }
    [/code]

I have also set the JucePlugin_PreferredChannelConfigurations to {0, -5} , that is interpreted by AU hosts as “I want 0 input and can provide up to 5 output channels”

I removed the original juce checking code in JuceAU::Initialise, and replaced it by some hard-wired stuff - the original code was checking against the JucePlugin_PreferredChannelConfigurations , but it does not work here, as Live only accepts to instanciate my plugin with 3 stereo busses while my maxNumOutputChannels is set to 5 …

[code] ComponentResult Initialize()
{
// take all busses into account
const int numOuts = getTotalNumOutChannels();
/* the ((x+1)/2)*2 is very ugly, but Live wants to
instanciate us with 3 stereo busses when the max output
channels is set to 5… */
if (numOuts <= 0 || numOuts > ((JucePlugin_MaxNumOutputChannels+1)/2)*2)
return kAudioUnitErr_FormatNotSupported;

  JuceAUBaseClass::Initialize();
  prepareToPlay();
  return noErr;
}

[/code]

I also added a fuunction to retrieve the current total number of output channels:

[code]+ // sum the number of channels on all output busses

  • int getTotalNumOutChannels() {
  • int total_num_channels = 0;
  • for (int i=0; i < Outputs().GetNumberOfElements(); ++i)
  •  total_num_channels += GetOutput(i)->GetStreamFormat().mChannelsPerFrame;
    
  • return total_num_channels;
  • }
    [/code]

I patched prepareToPlay as it is harcoded for 1 output bus:

[code] juceFilter->setPlayConfigDetails (0,
#endif

  •                                          GetOutput(0)->GetStreamFormat().mChannelsPerFrame,
    
  •                                          jmin(getTotalNumOutChannels(), 
    
  •                                               JucePlugin_MaxNumOutputChannels),
                                             GetSampleRate(),
                                             GetMaxFramesPerSlice());
    

[/code]

(basically , all places where GetOutput(0) or GetInput(0) is used are hardcoded for 1 input or output bus).

And then finally there is the Render callback. You cannot use it for multi-bus plugins. For those, you have to override the “AUBase::RenderBus” function (which by defaults calls Render on the first bus…)
What is a bit painful here is that the hosts call RenderBus for each output bus, separately. Here is my code, but please note that I have stripped (for clarity) much of the original juce code that handled the input channels (there was also some code for interleaving/deinterleaving which is not useful for AU plugins as they will never receive interleaved AudioBuffer)

[code]
/* we need to override RenderBus instead if Render because the
default RenderBus handles only single-bus situations… */
ComponentResult RenderBus(AudioUnitRenderActionFlags &ioActionFlags,
const AudioTimeStamp &inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames) {
if (NeedsToRender(inTimeStamp.mSampleTime)) {
// render all channels at once into our internal buffer
renderAllBusses(ioActionFlags, inTimeStamp, inNumberFrames);
}
AudioBufferList &outBuffer = GetOutput(inBusNumber)->GetBufferList();
assert(!GetOutput(inBusNumber)->IsInterleaved());

  UInt32 bus_ch0 = 0;
  for (UInt32 bus=0; bus < inBusNumber; ++bus) {
    bus_ch0 += GetOutput(bus)->NumberChannels();
  }

  for (UInt32 ch = 0; ch < outBuffer.mNumberBuffers; ++ch) {
    AudioBuffer& buf = outBuffer.mBuffers[ch];
    assert(buf.mNumberChannels == 1);
    float* dest = (float*)buf.mData;
    if (bus_ch0 + ch < juceFilter->getNumOutputChannels()) {
      assert(bus_ch0 + ch < bufferSpace.getNumChannels() && inNumberFrames < bufferSpace.getNumSamples());
      for (UInt32 k=0; k < inNumberFrames; ++k) 
        dest[k] = bufferSpace.getSampleData(bus_ch0 + ch)[k]; // + */0.1*sin(k/10.f);
    } else {
      for (UInt32 k=0; k < inNumberFrames; ++k) 
        dest[k] = 0;
    }
  }
  return noErr;
}


ComponentResult renderAllBusses(AudioUnitRenderActionFlags &ioActionFlags,
                                const AudioTimeStamp& inTimeStamp,
                                UInt32 nFrames) {
  lastSMPTETime = inTimeStamp.mSMPTETime;
  if (juceFilter == 0 || nFrames == 0 || juceFilter->getNumOutputChannels() == 0) { bufferSpace.clear(); return noErr; }
  
  jassert (prepared);
  const int numIn = juceFilter->getNumInputChannels();
  const int numOut = juceFilter->getNumOutputChannels();
  jassert(numIn == 0);
  assert(numOut <= JucePlugin_MaxNumOutputChannels);
  for (int i=0; i < numOut; ++i) channels[i] = bufferSpace.getSampleData(i);
  AudioSampleBuffer buffer(channels, numOut, nFrames);
  {
    const ScopedLock sl (juceFilter->getCallbackLock());
    if (juceFilter->isSuspended()) {
      buffer.clear();
    } else {
      juceFilter->processBlock(buffer, midiEvents);
    }
  }
  midiEvents.clear();
  return noErr;
}[/code]

The code has been tested in logic 7/8, aulab, live 7. Please note that I did not check with digital performer.


#6

And now the RTAS version, much simpler:

[code]@@ -85,6 +85,7 @@
#include “CPluginControl.h”
#include “CPluginControl_OnOff.h”
#include “FicProcessTokens.h”
+#include “CAOSManager.h”

//==============================================================================
#ifdef _MSC_VER
@@ -603,13 +611,30 @@
return noErr;
}

  • int getTotalNumOutputs() {
  •  return (fNumOutputs // == GetNumOutputs()
    
  •          + GetNumAuxOutputPorts());
    
  • }
  • //==============================================================================
    void EffectInit()
    {
    SFicPlugInStemFormats stems;
    GetProcessType()->GetStemFormats (&stems);
  •    juceFilter->setPlayConfigDetails (fNumInputs, fNumOutputs,
    
  •    //std::cerr << "GetNumOutputs=" << GetNumOutputs() << "\n";
    
  •    int nb_main_output = GetNumOutputs();
    
  •    for (int i = nb_main_output; i < JucePlugin_MaxNumOutputChannels; i++) {
    
  •      int output_num = i+1;	// "absolute" output number -- includes main outputs;
    
  •      int base_port_num = output_num;
    
  •      char name[cAOSPortStrSize] = "Mono"; //need to have a big enough array to prevent buffer overrun in AppendOutputPortNumToName 
    
  •      AppendOutputPortNumToName(name, output_num);
    
  •      AddAuxOutputStem(new CAOSInfo(ePlugIn_StemFormat_Mono, base_port_num, name));
    
  •    }
    
  •    juceFilter->setPlayConfigDetails (fNumInputs, getTotalNumOutputs(), //fNumOutputs,
                                         juceFilter->getSampleRate(), juceFilter->getBlockSize());
    
       AddControl (new CPluginControl_OnOff ('bypa', "Master Bypass\nMastrByp\nMByp\nByp", false, true));
    

@@ -646,6 +671,7 @@

     juceFilter->setPlayHead (this);
     juceFilter->addListener (this);
  • }

    void handleAsyncUpdate()
    @@ -659,7 +685,7 @@
    channels = (float**) juce_calloc (sizeof (float*) * jmax (juceFilter->getNumInputChannels(),
    juceFilter->getNumOutputChannels()));

  •        juceFilter->setPlayConfigDetails (fNumInputs, fNumOutputs,
    
  •        juceFilter->setPlayConfigDetails (fNumInputs, getTotalNumOutputs(), //fNumOutputs,
                                             sampleRate, mRTGlobals->mHWBufferSizeInSamples);
    
           juceFilter->prepareToPlay (sampleRate,
    

@@ -946,9 +972,9 @@
bool prepared;
double sampleRate;

  • void bypassBuffers (float** const inputs, float** const outputs, const long numSamples) const
  • void bypassBuffers (float** const inputs, float** const outputs, const long numSamples)
    {
  •    for (int i = fNumOutputs; --i >= 0;)
    
  •  for (int i = getTotalNumOutputs(); --i >= 0;)
       {
           if (i < fNumInputs)
               memcpy (outputs[i], inputs[i], numSamples * sizeof (float));
    

@@ -1068,6 +1094,9 @@
type->AddGestalt (pluginGestalt_CanBypass);
type->AddGestalt (pluginGestalt_SupportsVariableQuanta);
type->AddGestalt (pluginGestalt_DoesntSupportMultiMono);

  •        type->AddGestalt (pluginGestalt_SupportsAuxOutputStems);
           type->AttachEffectProcessCreator (createNewProcess);
    
           AddEffectType (type);
    

[/code]


#7

jpo, that is awesome, thanks loads for sharing this :slight_smile: I’ll go through the code and post back any updates to make things more generic here.

Andrew Simper


#8

Are you sure it is not possible to simply override Render to process all busses? In terms of code it seems possible by calling ProcessBufferLists on an AudioBufferList you fill yourself. Any thoughts would be appreciated.


#9

my two cents:

i wanted to use a flexible channel count :

#define JucePlugin_PreferredChannelConfigurations   { -1, -2 }

for that i had to overwrite initialize() to accept this, so i just deleted some Juce checks.

also, it is possible to just overwrite render() to do everything for you, and i also had to rewrite prepareToPlay().

i also edited the juce_AU_Resources.r file to have my plugin presented as both instrument and effect.

ill post a conversion “kit” soon.


#10

Oki i have composed a help file, an .h file to include, and an example of a modified AU.

this offers the possibility of making a quick upgrade for the wrapper.
enabling multi buss, and multi channel action.

i hope this helps anyone here, and if you have any fixes, suggestions or improvements, id love to hear them!

edited:
http://www.gogma.com/MultiJuceAU.zip


#11

Wondering if Jules would consider adding this to JUCE for RTAS?

In AppConfig.h

#ifndef JucePlugin_RTASMonoAuxOutputs #define JucePlugin_RTASMonoAuxOutputs 0 #endif #ifndef JucePlugin_RTASStereoAuxOutputs #define JucePlugin_RTASStereoAuxOutputs 0 #endif

Usage: Set the Aux Outputs values as desired (set to 0 will create no Aux Stems), for example:

#ifndef JucePlugin_RTASMonoAuxOutputs #define JucePlugin_RTASMonoAuxOutputs 6 #endif #ifndef JucePlugin_RTASStereoAuxOutputs #define JucePlugin_RTASStereoAuxOutputs 3 #endif

In juce_RTAS_Wrapper.cpp

add:

for class JucePlugInProcess add the method:

[code]
int getTotalNumOutputs()
{
int outs = GetNumOutputs() + GetNumAuxOutputPorts();

    return outs;
}[/code]

for class JucePlugInProcess modify EffectInit():

        SFicPlugInStemFormats stems;
        GetProcessType()->GetStemFormats (&stems);

        juceFilter->setPlayConfigDetails (fNumInputs, fNumOutputs,
                                          juceFilter->getSampleRate(), juceFilter->getBlockSize());
    
        // Addition & changes for Multi-Out:
    
        int output_num;
        int base_port_num;
    
        for (int k=0; k < JucePlugin_RTASMonoAuxOutputs; k++)
            {
            output_num = k+1;
            base_port_num = output_num;
            
            char name[cAOSPortStrSize] = "Mono";
            
            AppendOutputPortNumToName(name, output_num);
            
            AddAuxOutputStem(new CAOSInfo(ePlugIn_StemFormat_Mono, base_port_num, name));
            }
        
        for (int k=0; k < JucePlugin_RTASStereoAuxOutputs; k++)
            {
            // This names the stereo output starting at "Stereo 1" -- You may wish to change
            // this to start after int nb_main_outputs = GetNumOutputs();
            // as in the previous post in this thread

            output_num = k+1;
            base_port_num = output_num;
            
            char name[cAOSPortStrSize] = "Stereo";
            
            AppendOutputPortNumToName(name, output_num);
            
            AddAuxOutputStem(new CAOSInfo(ePlugIn_StemFormat_Stereo, base_port_num, name));
            }
    
        juceFilter->setPlayConfigDetails(fNumInputs, getTotalNumOutputs(), juceFilter->getSampleRate(), juceFilter->getBlockSize());
     
        :

in handleAsyncUpdate() change the line:

[code]
// Change for Multi-Out:

        // juceFilter->setPlayConfigDetails (fNumInputs, fNumOutputs, sampleRate, mRTGlobals->mHWBufferSizeInSamples);
    
        juceFilter->setPlayConfigDetails (fNumInputs, getTotalNumOutputs(),
                                      sampleRate, mRTGlobals->mHWBufferSizeInSamples);[/code]

in bypassBuffers() change the method declaration to:

and in the method change the line:

[code]
// Change for Multi-Out:

    // for (int i = fNumOutputs; --i >= 0;)

    for (int i = getTotalNumOutputs(); --i >= 0;)[/code]

in class JucePlugInGroup in the method CreateEffectTypes() add:

[code]
type->AddGestalt (pluginGestalt_CanBypass);
type->AddGestalt (pluginGestalt_SupportsVariableQuanta);

        // Addition for Multi-Out:
    
        if (JucePlugin_RTASMonoAuxOutputs || JucePlugin_RTASStereoAuxOutputs)
            {
            type->AddGestalt (pluginGestalt_DoesntSupportMultiMono);
            type->AddGestalt (pluginGestalt_SupportsAuxOutputStems);
            }
    
        ////////////////////////////////////////////////////////
    
        type->AttachEffectProcessCreator (createNewProcess);[/code]

Thanks,

Rail