Multi-channel AUs
Developing Multi Channel Audio Units - with N inputs, M outputs
If you are not doing NxN input, output, then you need to construct with a superclass constructor call as follows - ie with the second argument set to false to indicate not to use the Process call (this argument defaults to true):
test_NxM_AU::test_NxM_AU(AudioUnit component) : AUEffectBase(component,false)
Or auval testing will throw an error like this:
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Output Buffer Size does not match requested num frames * * This will fail using -strict option. Will fail in future auval version * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
You also need code of the following format (changing the arguments as necessary) in the constructor after the CreateElements() call:
// suggested for NxM operation by Sophia Poirier of smartelectronix.com // on the Core Audio mailing list CAStreamBasicDescription streamDescIn; streamDescIn.SetCanonical(4, false); // number of input channels streamDescIn.mSampleRate = GetSampleRate(); CAStreamBasicDescription streamDescOut; streamDescOut.SetCanonical(2, false); // number of output channels streamDescOut.mSampleRate = GetSampleRate(); Inputs().GetIOElement(0)->SetStreamFormat(streamDescIn); Outputs().GetIOElement(0)->SetStreamFormat(streamDescOut);
You also need to add a function to the main class with code similar to below (and of course, don’t forget the declaration in the header):
UInt32 test_NxM_AU::SupportedNumChannels (const AUChannelInfo** outInfo) { // set an array of arrays of different combinations of supported numbers // of ins and outs static const AUChannelInfo sChannels[1] = {{ 4, 2}}; if (outInfo) *outInfo = sChannels; return sizeof (sChannels) / sizeof (AUChannelInfo); }
And we also need to override the Initialize() method to return errors for unsupported i/o combinations, and return “noErr” for supported i/o combinations - in order to avoid auval errors such as this:
"WARNING: Can Initialize Unit to un-supported num channels:InputChan:1, OutputChan:2", etc.
The Initialize() method will look something like this (although things seem to work fine in auval when the initialize function is not used):
ComponentResult test_NxM_AU::Initialize() { // get the current numChannels for input and output. // a host may test various combinations of these // regardless of the outInfo returned by our SupportedNumChannels method SInt16 auNumInputs = (SInt16) GetInput(0)->GetStreamFormat().mChannelsPerFrame; SInt16 auNumOutputs = (SInt16) GetOutput(0)->GetStreamFormat().mChannelsPerFrame; if ((auNumInputs == 4) && (auNumOutputs == 2)) { MaintainKernels(); return noErr; } else return kAudioUnitErr_FormatNotSupported; }
Or if there are various possible i/o configurations, you could interrogate the unit’s own configuration programmatically (as a host would) and return errors when necessary as follows:
// does the unit publish specific information about channel configurations? const AUChannelInfo *auChannelConfigs = NULL; UInt32 numIOconfigs = SupportedNumChannels(&auChannelConfigs); if ((numIOconfigs > 0) && (auChannelConfigs != NULL)) { bool foundMatch = false; for (UInt32 i = 0; (i < numIOconfigs) && !foundMatch; ++i) { SInt16 configNumInputs = auChannelConfigs[i].inChannels; SInt16 configNumOutputs = auChannelConfigs[i].outChannels; if ((configNumInputs < 0) && (configNumOutputs < 0)) { // unit accepts any number of channels on input and output if (((configNumInputs == -1) && (configNumOutputs == -2)) || ((configNumInputs == -2) && (configNumOutputs == -1))) { foundMatch = true; } // unit accepts any number of channels on input and output IFF // they are the same number on both scopes else if (((configNumInputs == -1) && (configNumOutputs == -1)) && (auNumInputs == auNumOutputs)) { foundMatch = true; } // unit has specified a particular number of channels on both scopes else continue; } else { // the -1 case on either scope is saying that the unit // doesn't care about the number of channels on that scope bool inputMatch = (auNumInputs == configNumInputs) || (configNumInputs == -1); bool outputMatch = (auNumOutputs == configNumOutputs) || (configNumOutputs == -1); if (inputMatch && outputMatch) foundMatch = true; } } if (!foundMatch) return kAudioUnitErr_FormatNotSupported; }
An important factor though is to remove all references to the AUKernelBase - which is an abstraction provided by the template for processing NxN format plugins - where there is one output for every input, and the channels are completely independent - each input gets processed using the same path to a single output. A simple example of an MxN process is a simple stereo panner - with a mono input that gets fed partly to each of two outputs. The panner will not be supported by the AUKernelBase functions. So you need to remove the following parts from the AU Effect Template code: From the .cpp file, remove the following:
#pragma mark ____test_NxM_AUEffectKernel //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // test_NxM_AU::test_NxM_AUKernel::Reset() //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void test_NxM_AU::test_NxM_AUKernel::Reset() { printf(">>> test_NxM_AU::test_NxM_AUKernel::Reset\n"); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // test_NxM_AU::test_NxM_AUKernel::Process //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void test_NxM_AU::test_NxM_AUKernel::Process( const Float32 *inSourceP, Float32 *inDestP, UInt32 inFramesToProcess, UInt32 inNumChannels, bool &ioSilence ) // Note: for version 2 AudioUnits inNumChannels is always 1 { printf(">>> test_NxM_AU::test_NxM_AUKernel::Process\n"); // This code will pass-thru the audio data. // This is where you want to process data to produce an effect. UInt32 nSampleFrames = inFramesToProcess; const Float32 *sourceP = inSourceP; Float32 *destP = inDestP; Float32 gain = GetParameter( kParam_One ); while (nSampleFrames-- > 0) { Float32 inputSample = *sourceP; // The current (version 2) AudioUnit specification *requires* // non-interleaved format for all inputs and outputs. // Therefore inNumChannels is always 1 // advance to next frame (e.g. if stereo, we're advancing 2 samples); // we're only processing one of an arbitrary number of interleaved channels sourceP += inNumChannels; // here's where you do your DSP work Float32 outputSample = inputSample * gain; *destP = outputSample; destP += inNumChannels; } }
and from the main .h file, remove this:
// most of the real work happens here class test_NxM_AUKernel : public AUKernelBase { public: test_NxM_AUKernel(AUEffectBase *inAudioUnit ) : AUKernelBase(inAudioUnit) { } // *Required* overides for the process method for this effect // processes one channel of interleaved samples virtual void Process(const Float32 *inSourceP, Float32 *inDestP, UInt32 inFramesToProcess, UInt32 inNumChannels, bool &ioSilence); virtual void Reset(); //private: //state variables... };
and this:
virtual AUKernelBase * NewKernel() { return new test_NxM_AUKernel(this); }
Now, we need to add a different processing function to support uneven numbers of inputs and outputs as follows:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // four2twoAU::ProcessBufferLists //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OSStatus four2twoAU::ProcessBufferLists (AudioUnitRenderActionFlags& iFlags, const AudioBufferList& inBufferList, AudioBufferList& outBufferList, UInt32 iFrames) { // from Digenis BProc plugin (http://www.digenis.co.uk/plugins.html) // Array of pointers, as many as the number of input signals. float* audioData[NUM_INS]; // Pointers point to incomming audio buffers. for(int i = 0; i < NUM_INS; i++) { audioData[i] = (float*) inBufferList.mBuffers[i].mData; } for(int i = 0; i < NUM_OUTS; i++) { // as a test, copy audio from the first channel straight to all outputs outBufferList.mBuffers[i].mData = audioData[0]; } return noErr; }