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 Marc Poirier destroyfx@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;
}