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
I patched prepareToPlay as it is harcoded for 1 output bus:
[code] juceFilter->setPlayConfigDetails (0,
#endif
-
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.