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;
}