/*============================================================================= CAAudioUnit.cpp $Log: CAAudioUnit.cpp,v $ Revision 1.64 2005/01/12 20:30:13 luke add newlines to make gcc 4.0 happy Revision 1.63 2004/12/11 00:05:53 jcm10 ad missing include and skip new MusicDevice entry on Windows Revision 1.62 2004/12/05 02:34:21 bills some tweaks to the multi can do Revision 1.61 2004/12/03 23:06:11 bills optimise CanDo checks Revision 1.60 2004/12/02 03:03:13 bills some cleanup Revision 1.59 2004/12/02 02:48:36 bills add full multi-bus CanDo support (inc. definition of CAAUChanHelper class) Revision 1.58 2004/11/29 22:58:27 bills add fast dispatch for MusicDeviceMIDIEvent Revision 1.57 2004/11/22 23:10:49 bills on second thought Revision 1.56 2004/11/22 23:03:47 bills tweak to HasDynamic test Revision 1.55 2004/11/10 19:04:36 bills add global sample rate method Revision 1.54 2004/08/24 00:01:47 bills fix warnings Revision 1.53 2004/07/26 19:28:23 bills define equality operator Revision 1.52 2004/07/09 02:50:53 bills fix handling of AUOutputBL Revision 1.51 2004/05/22 00:41:17 bills fix HasDynamicScope Revision 1.50 2004/04/28 18:41:58 bills add HasCustomView Revision 1.49 2004/04/17 23:25:10 bills add method to get an AU's latency Revision 1.48 2004/04/15 20:05:15 bills don't be particular about whether its an AU or not... Revision 1.47 2004/03/16 20:23:20 bills move over to AUOutputBL Revision 1.46 2003/12/13 23:32:27 bills add handlers for dynamic/configurable I/O Revision 1.45 2003/12/04 22:54:54 luke CABufferList prepare buffer takes frames not bytes Revision 1.44 2003/12/03 23:14:41 bills changes to CABufferList (and no dependencies on AUGrah) Revision 1.43 2003/12/02 20:50:44 luke depend on CABufferList instead of AUBuffer Revision 1.42 2003/12/02 00:15:46 bills some fixes for ref counting thread-safe-ness Revision 1.41 2003/11/20 22:56:53 dwyatt __COREAUDIO_USE_FLAT_INCLUDES__ Revision 1.40 2003/10/29 01:54:00 bills add element count API Revision 1.39 2003/10/25 00:17:46 bills fast dispatch support Revision 1.38 2003/10/19 20:46:40 bills add a preroll call Revision 1.37 2003/10/18 18:48:26 bills add supports num channels query Revision 1.36 2003/10/09 05:30:25 bills change GetNumChannels to NumberChannels for consistency Revision 1.35 2003/08/24 02:27:12 bills fix handling of numChannels Revision 1.34 2003/08/06 22:20:23 bills handle present preset property correctly Revision 1.33 2003/08/01 20:25:19 mhopkins Moved PrintMatrixMixerVolumes() into MatrixMixerVolumes.cpp Revision 1.32 2003/07/16 00:59:37 bills get/set AUPreset Revision 1.31 2003/07/08 14:53:42 bills reinstate alternate SetChanLayout Revision 1.30 2003/07/08 06:23:36 bills SetChannelLayout Revision 1.29 2003/07/08 00:03:57 bills remove unused ACL methods Revision 1.28 2003/07/07 19:38:20 dwyatt use CAReferenceCounted Revision 1.27 2003/07/07 18:36:05 bills fix some of the channel layout methods Revision 1.26 2003/07/06 18:26:35 bills cleanup canDo loop Revision 1.25 2003/06/15 23:58:48 bills make the opening of an AU a static method that returns a bad result code if it fails (nothing worse than failing silently) Revision 1.24 2003/06/03 22:06:48 bills remove speaker config fallback Revision 1.23 2003/06/02 19:25:15 bills tweaks Revision 1.22 2003/05/25 02:45:13 bills don't use the busInUse stuff Revision 1.21 2003/05/11 22:07:45 bills some tweaks Revision 1.20 2003/05/10 19:44:44 bills tweak print of MM Revision 1.19 2003/05/09 06:42:22 bills print bus state for MM Revision 1.18 2003/04/30 06:49:31 bills better use of const to define those things in an AU that should and shouldn't be changed if the AU is part of a graph Revision 1.17 2003/04/29 17:57:58 bills add print for matrix mixer Revision 1.16 2003/04/20 09:15:51 bills fix operator= Revision 1.15 2003/04/02 18:20:02 bills changes to make persistence work Revision 1.14 2003/03/30 23:10:45 bills ChannelMap to ChannelLayout Revision 1.13 2003/03/23 02:53:12 bills some rewrite and add methods Revision 1.12 2003/03/21 06:53:36 bills prelim impl of CA-AU persistence Revision 1.11 2003/03/19 21:16:34 bills changes to how CAAudioUnit manages its internal state Revision 1.10 2003/03/17 02:56:12 bills AU can now keep track of its active busses Revision 1.9 2003/03/15 19:14:58 bills update handling of n->m channel handling Revision 1.8 2003/03/15 18:57:34 bills update handling of n->m channel handling Revision 1.7 2003/03/14 22:47:32 bills ACM handling Revision 1.6 2003/03/13 18:19:13 bills add handling of AudioChannelLayouts Revision 1.5 2003/03/12 23:38:20 bills CAAudioUnit::CanDo Revision 1.4 2003/03/12 06:49:45 bills redo CAAudioUnit to dynamically manage its state Revision 1.3 2003/03/12 05:05:05 bills refactor persistence Revision 1.2 2003/03/11 20:57:29 bills first pass at save/restore state Revision 1.1 2003/03/11 00:38:21 bills initial checkin Created by William Stewart on Sat Mar 08 2003. Copyright (c) 2003 Apple Computer. All rights reserved. =============================================================================*/ #include "CAAudioUnit.h" #if !defined(__COREAUDIO_USE_FLAT_INCLUDES__) #include #else #include #endif #include "CAReferenceCounted.h" #include "AUOutputBL.h" //this is for the Preroll only struct StackAUChannelInfo { StackAUChannelInfo (UInt32 inSize) : mChanInfo ((AUChannelInfo*)malloc (inSize)) {} ~StackAUChannelInfo() { free (mChanInfo); } AUChannelInfo* mChanInfo; }; class CAAudioUnit::AUState : public CAReferenceCounted { public: AUState (Component inComp) : mUnit(0), mNode (0) { OSStatus result = ::OpenAComponent (inComp, &mUnit); if (result) throw result; Init(); } AUState (const AUNode &inNode, const AudioUnit& inUnit) : mUnit (inUnit), mNode (inNode) { Init(); } ~AUState(); AudioUnit mUnit; AUNode mNode; OSStatus GetParameter(AudioUnitParameterID inID, AudioUnitScope scope, AudioUnitElement element, Float32 &outValue) const { if (mGetParamProc != NULL) { return reinterpret_cast(mGetParamProc) (mConnInstanceStorage, inID, scope, element, &outValue); } return AudioUnitGetParameter(mUnit, inID, scope, element, &outValue); } OSStatus SetParameter(AudioUnitParameterID inID, AudioUnitScope scope, AudioUnitElement element, Float32 value, UInt32 bufferOffsetFrames) { if (mSetParamProc != NULL) { return reinterpret_cast(mSetParamProc) (mConnInstanceStorage, inID, scope, element, value, bufferOffsetFrames); } return AudioUnitSetParameter(mUnit, inID, scope, element, value, bufferOffsetFrames); } OSStatus Render (AudioUnitRenderActionFlags * ioActionFlags, const AudioTimeStamp * inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames, AudioBufferList * ioData) { if (mRenderProc != NULL) { return reinterpret_cast(mRenderProc) (mConnInstanceStorage, ioActionFlags, inTimeStamp, inOutputBusNumber, inNumberFrames, ioData); } return AudioUnitRender(mUnit, ioActionFlags, inTimeStamp, inOutputBusNumber, inNumberFrames, ioData); } OSStatus MIDIEvent (UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame) { if (mMIDIEventProc != NULL) { return reinterpret_cast(mMIDIEventProc) (mConnInstanceStorage, inStatus, inData1, inData2, inOffsetSampleFrame); } #if !TARGET_OS_WIN32 return MusicDeviceMIDIEvent (mUnit, inStatus, inData1, inData2, inOffsetSampleFrame); #else return 0; #endif } private: // get the fast dispatch pointers void Init() { UInt32 size = sizeof(AudioUnitRenderProc); if (AudioUnitGetProperty(mUnit, kAudioUnitProperty_FastDispatch, kAudioUnitScope_Global, kAudioUnitRenderSelect, &mRenderProc, &size) != noErr) mRenderProc = NULL; if (AudioUnitGetProperty(mUnit, kAudioUnitProperty_FastDispatch, kAudioUnitScope_Global, kAudioUnitGetParameterSelect, &mGetParamProc, &size) != noErr) mGetParamProc = NULL; if (AudioUnitGetProperty(mUnit, kAudioUnitProperty_FastDispatch, kAudioUnitScope_Global, kAudioUnitSetParameterSelect, &mSetParamProc, &size) != noErr) mSetParamProc = NULL; if (AudioUnitGetProperty(mUnit, kAudioUnitProperty_FastDispatch, kAudioUnitScope_Global, kMusicDeviceMIDIEventSelect, &mMIDIEventProc, &size) != noErr) mMIDIEventProc = NULL; if (mRenderProc || mGetParamProc || mSetParamProc || mMIDIEventProc) mConnInstanceStorage = GetComponentInstanceStorage(mUnit); else mConnInstanceStorage = NULL; } ProcPtr mRenderProc, mGetParamProc, mSetParamProc, mMIDIEventProc; void * mConnInstanceStorage; private: // get the compiler to tell us when we do a bad thing!!! AUState () {} AUState (const AUState&) {} AUState& operator= (const AUState&) { return *this; } }; CAAudioUnit::AUState::~AUState () { if (mUnit && (mNode == 0)) { ::CloseComponent (mUnit); } mNode = 0; mUnit = 0; } OSStatus CAAudioUnit::Open (const CAComponent& inComp, CAAudioUnit &outUnit) { try { outUnit = inComp; return noErr; } catch (OSStatus res) { return res; } catch (...) { return -1; } } CAAudioUnit::CAAudioUnit (const AudioUnit& inUnit) : mComp (inUnit), mDataPtr (new AUState (-1, inUnit)) { } CAAudioUnit::CAAudioUnit (const CAComponent& inComp) : mComp (inComp), mDataPtr (0) { mDataPtr = new AUState (mComp.Comp()); } CAAudioUnit::CAAudioUnit (const AUNode &inNode, const AudioUnit& inUnit) : mComp (inUnit), mDataPtr(new AUState (inNode, inUnit)) { } CAAudioUnit::~CAAudioUnit () { if (mDataPtr) { mDataPtr->release(); mDataPtr = NULL; } } CAAudioUnit& CAAudioUnit::operator= (const CAAudioUnit &a) { if (mDataPtr != a.mDataPtr) { if (mDataPtr) mDataPtr->release(); if ((mDataPtr = a.mDataPtr) != NULL) mDataPtr->retain(); mComp = a.mComp; } return *this; } bool CAAudioUnit::operator== (const CAAudioUnit& y) const { if (mDataPtr == y.mDataPtr) return true; AudioUnit au1 = mDataPtr ? mDataPtr->mUnit : 0; AudioUnit au2 = y.mDataPtr ? y.mDataPtr->mUnit : 0; return au1 == au2; } bool CAAudioUnit::operator== (const AudioUnit& y) const { if (!mDataPtr) return false; return mDataPtr->mUnit == y; } #pragma mark __State Management bool CAAudioUnit::IsValid () const { return mDataPtr ? mDataPtr->mUnit != 0 : false; } AudioUnit CAAudioUnit::AU() const { return mDataPtr ? mDataPtr->mUnit : 0; } AUNode CAAudioUnit::GetAUNode () const { return mDataPtr ? mDataPtr->mNode : 0; } #pragma mark __Format Handling bool CAAudioUnit::CanDo ( int inChannelsIn, int inChannelsOut) const { // this is the default assumption of an audio effect unit Boolean* isWritable = 0; UInt32 dataSize = 0; // lets see if the unit has any channel restrictions OSStatus result = AudioUnitGetPropertyInfo (AU(), kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, &dataSize, isWritable); //don't care if this is writable // if this property is NOT implemented an FX unit // is expected to deal with same channel valance in and out if (result) { if (Comp().Desc().IsEffect() && (inChannelsIn == inChannelsOut) || Comp().Desc().IsOffline() && (inChannelsIn == inChannelsOut)) { return true; } else { // the au should either really tell us about this // or we will assume the worst return false; } } StackAUChannelInfo info (dataSize); result = GetProperty (kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, info.mChanInfo, &dataSize); if (result) { return false; } return ValidateChannelPair (inChannelsIn, inChannelsOut, info.mChanInfo, (dataSize / sizeof (AUChannelInfo))); } bool CAAudioUnit::ValidateChannelPair (int inChannelsIn, int inChannelsOut, const AUChannelInfo * info, UInt32 numChanInfo) const { // we've the following cases (some combinations) to test here: /* >0 An explicit number of channels on either side 0 that side (generally input!) has no elements -1 wild card: -1,-1 any num channels as long as same channels on in and out -1,-2 any num channels channels on in and out - special meaning -2+ indicates total num channs AU can handle - elements configurable to any num channels, - element count in scope must be writable */ //now chan layout can contain -1 for either scope (ie. doesn't care) for (unsigned int i = 0; i < numChanInfo; ++i) { //less than zero on both sides - check for special attributes if ((info[i].inChannels < 0) && (info[i].outChannels < 0)) { // these are our wild card matches if (info[i].inChannels == -1 && info[i].outChannels == -1) { if (inChannelsOut == inChannelsIn) { return true; } } else if ((info[i].inChannels == -1 && info[i].outChannels == -2) || (info[i].inChannels == -2 && info[i].outChannels == -1)) { return true; } // these are our total num channels matches // element count MUST be writable else { bool outWrite = false; bool inWrite = false; IsElementCountWritable (kAudioUnitScope_Output, outWrite); IsElementCountWritable (kAudioUnitScope_Input, inWrite); if (inWrite && outWrite) { if ((inChannelsOut <= abs(info[i].outChannels)) && (inChannelsIn <= abs(info[i].inChannels))) { return true; } } } } // special meaning on input, specific num on output else if (info[i].inChannels < 0) { if (info[i].outChannels == inChannelsOut) { // can do any in channels if (info[i].inChannels == -1) { return true; } // total chans on input else { bool inWrite = false; IsElementCountWritable (kAudioUnitScope_Input, inWrite); if (inWrite && (inChannelsIn <= abs(info[i].inChannels))) { return true; } } } } // special meaning on output, specific num on input else if (info[i].outChannels < 0) { if (info[i].inChannels == inChannelsIn) { // can do any out channels if (info[i].outChannels == -1) { return true; } // total chans on output else { bool outWrite = false; IsElementCountWritable (kAudioUnitScope_Output, outWrite); if (outWrite && (inChannelsOut <= abs(info[i].outChannels))) { return true; } } } } // both chans in struct >= 0 - thus has to explicitly match else if ((info[i].inChannels == inChannelsIn) && (info[i].outChannels == inChannelsOut)) { return true; } // now check to see if a wild card on the args (inChannelsIn or inChannelsOut chans is zero) is found // tells us to match just one side of the scopes else if (inChannelsIn == 0) { if (info[i].outChannels == inChannelsOut) { return true; } } else if (inChannelsOut == 0) { if (info[i].inChannels == inChannelsIn) { return true; } } } return false; } bool CheckDynCount (SInt32 inTotalChans, const CAAUChanHelper &inHelper) { int totalChans = 0; for (unsigned int i = 0; i < inHelper.mNumEls; ++i) totalChans += inHelper.mChans[i]; return (totalChans <= inTotalChans); } bool CAAudioUnit::CheckOneSide (const CAAUChanHelper &inHelper, bool checkOutput, const AUChannelInfo *info, UInt32 numInfo) const { // now we can use the wildcard option (see above impl) to see if this matches for (unsigned int el = 0; el < inHelper.mNumEls; ++el) { bool testAlready = false; for (unsigned int i = 0; i < el; ++i) { if (inHelper.mChans[i] == inHelper.mChans[el]) { testAlready = true; break; } } if (!testAlready) { if (checkOutput) { if (!ValidateChannelPair (0, inHelper.mChans[el], info, numInfo)) return false; } else { if (!ValidateChannelPair (inHelper.mChans[el], 0, info, numInfo)) return false; } } } return true; } bool CAAudioUnit::CanDo (const CAAUChanHelper &inputs, const CAAUChanHelper &outputs) const { // first check our state // huh! if (inputs.mNumEls == 0 && outputs.mNumEls == 0) return false; UInt32 elCount; if (GetElementCount (kAudioUnitScope_Input, elCount)) { return false; } if (elCount != inputs.mNumEls) return false; if (GetElementCount (kAudioUnitScope_Output, elCount)) { return false; } if (elCount != outputs.mNumEls) return false; // (1) special cases (effects and sources (generators and instruments) only) UInt32 dataSize = 0; if (GetPropertyInfo (kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, &dataSize, NULL) != noErr) { if (Comp().Desc().IsEffect() || Comp().Desc().IsOffline()) { UInt32 numChan = outputs.mNumEls > 0 ? outputs.mChans[0] : inputs.mChans[0]; for (unsigned int in = 0; in < inputs.mNumEls; ++in) if (numChan != inputs.mChans[in]) return false; for (unsigned int out = 0; out < outputs.mNumEls; ++out) if (numChan != outputs.mChans[out]) return false; return true; } // in this case, all the channels have to match the current config if (Comp().Desc().IsGenerator() || Comp().Desc().IsMusicDevice()) { for (unsigned int in = 0; in < inputs.mNumEls; ++in) { UInt32 chan; if (!NumberChannels (kAudioUnitScope_Input, in, chan)) return false; if (chan != UInt32(inputs.mChans[in])) return false; } for (unsigned int out = 0; out < outputs.mNumEls; ++out) { UInt32 chan; if (!NumberChannels (kAudioUnitScope_Output, out, chan)) return false; if (chan != UInt32(outputs.mChans[out])) return false; } return true; } // if we get here we can't determine anything about channel capabilities return false; } StackAUChannelInfo info (dataSize); if (GetProperty (kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, info.mChanInfo, &dataSize) != noErr) { return false; } int numInfo = dataSize / sizeof(AUChannelInfo); // (2) Test for dynamic capability (or no elements on that scope) SInt32 dynInChans = 0; if (ValidateDynamicScope (kAudioUnitScope_Input, dynInChans, info.mChanInfo, numInfo)) { if (CheckDynCount (dynInChans, inputs) == false) return false; } SInt32 dynOutChans = 0; if (ValidateDynamicScope (kAudioUnitScope_Output, dynOutChans, info.mChanInfo, numInfo)) { if (CheckDynCount (dynOutChans, outputs) == false) return false; } if (dynOutChans && dynInChans) { return true; } // (3) Just need to test one side if (dynInChans || (inputs.mNumEls == 0)) { return CheckOneSide (outputs, true, info.mChanInfo, numInfo); } if (dynOutChans || (outputs.mNumEls == 0)) { return CheckOneSide (inputs, false, info.mChanInfo, numInfo); } // (4) - not a dynamic AU, has ins and outs, and has channel constraints so we test every possible pairing for (unsigned int in = 0; in < inputs.mNumEls; ++in) { bool testInAlready = false; for (unsigned int i = 0; i < in; ++i) { if (inputs.mChans[i] == inputs.mChans[in]) { testInAlready = true; break; } } if (!testInAlready) { for (unsigned int out = 0; out < outputs.mNumEls; ++out) { // try to save a little bit and not test the same pairing multiple times... bool testOutAlready = false; for (unsigned int i = 0; i < out; ++i) { if (outputs.mChans[i] == outputs.mChans[out]) { testOutAlready = true; break; } } if (!testOutAlready) { if (!ValidateChannelPair (inputs.mChans[in], outputs.mChans[out],info.mChanInfo, numInfo)) { return false; } } } } } return true; } bool CAAudioUnit::SupportsNumChannels () const { // this is the default assumption of an audio effect unit Boolean* isWritable = 0; UInt32 dataSize = 0; // lets see if the unit has any channel restrictions OSStatus result = AudioUnitGetPropertyInfo (AU(), kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, &dataSize, isWritable); //don't care if this is writable // if this property is NOT implemented an FX unit // is expected to deal with same channel valance in and out if (result) { if (Comp().Desc().IsEffect() || Comp().Desc().IsOffline()) return true; } return result == noErr; } bool CAAudioUnit::GetChannelLayouts (AudioUnitScope inScope, AudioUnitElement inEl, ChannelTagVector &outChannelVector) const { if (HasChannelLayouts (inScope, inEl) == false) return false; UInt32 dataSize; OSStatus result = AudioUnitGetPropertyInfo (AU(), kAudioUnitProperty_SupportedChannelLayoutTags, inScope, inEl, &dataSize, NULL); if (result == kAudioUnitErr_InvalidProperty) { // if we get here we can do layouts but we've got the speaker config property outChannelVector.erase (outChannelVector.begin(), outChannelVector.end()); outChannelVector.push_back (kAudioChannelLayoutTag_Stereo); outChannelVector.push_back (kAudioChannelLayoutTag_StereoHeadphones); outChannelVector.push_back (kAudioChannelLayoutTag_Quadraphonic); outChannelVector.push_back (kAudioChannelLayoutTag_AudioUnit_5_0); return true; } if (result) return false; bool canDo = false; // OK lets get our channel layouts and see if the one we want is present AudioChannelLayoutTag* info = (AudioChannelLayoutTag*)malloc (dataSize); result = AudioUnitGetProperty (AU(), kAudioUnitProperty_SupportedChannelLayoutTags, inScope, inEl, info, &dataSize); if (result) goto home; outChannelVector.erase (outChannelVector.begin(), outChannelVector.end()); for (unsigned int i = 0; i < (dataSize / sizeof (AudioChannelLayoutTag)); ++i) outChannelVector.push_back (info[i]); home: free (info); return canDo; } bool CAAudioUnit::HasChannelLayouts (AudioUnitScope inScope, AudioUnitElement inEl) const { OSStatus result = AudioUnitGetPropertyInfo (AU(), kAudioUnitProperty_SupportedChannelLayoutTags, inScope, inEl, NULL, NULL); return !result; } OSStatus CAAudioUnit::GetChannelLayout (AudioUnitScope inScope, AudioUnitElement inEl, CAAudioChannelLayout &outLayout) const { UInt32 size; OSStatus result = AudioUnitGetPropertyInfo (AU(), kAudioUnitProperty_AudioChannelLayout, inScope, inEl, &size, NULL); if (result) return result; AudioChannelLayout *layout = (AudioChannelLayout*)malloc (size); require_noerr (result = AudioUnitGetProperty (AU(), kAudioUnitProperty_AudioChannelLayout, inScope, inEl, layout, &size), home); outLayout = CAAudioChannelLayout (layout); home: free (layout); return result; } OSStatus CAAudioUnit::SetChannelLayout (AudioUnitScope inScope, AudioUnitElement inEl, CAAudioChannelLayout &inLayout) { OSStatus result = AudioUnitSetProperty (AU(), kAudioUnitProperty_AudioChannelLayout, inScope, inEl, inLayout, inLayout.Size()); return result; } OSStatus CAAudioUnit::SetChannelLayout (AudioUnitScope inScope, AudioUnitElement inEl, AudioChannelLayout &inLayout, UInt32 inSize) { OSStatus result = AudioUnitSetProperty (AU(), kAudioUnitProperty_AudioChannelLayout, inScope, inEl, &inLayout, inSize); return result; } OSStatus CAAudioUnit::ClearChannelLayout (AudioUnitScope inScope, AudioUnitElement inEl) { return AudioUnitSetProperty (AU(), kAudioUnitProperty_AudioChannelLayout, inScope, inEl, NULL, 0); } OSStatus CAAudioUnit::GetFormat (AudioUnitScope inScope, AudioUnitElement inEl, AudioStreamBasicDescription &outFormat) const { UInt32 dataSize = sizeof (AudioStreamBasicDescription); return AudioUnitGetProperty (AU(), kAudioUnitProperty_StreamFormat, inScope, inEl, &outFormat, &dataSize); } OSStatus CAAudioUnit::SetFormat (AudioUnitScope inScope, AudioUnitElement inEl, const AudioStreamBasicDescription &inFormat) { return AudioUnitSetProperty (AU(), kAudioUnitProperty_StreamFormat, inScope, inEl, const_cast(&inFormat), sizeof (AudioStreamBasicDescription)); } OSStatus CAAudioUnit::GetSampleRate (AudioUnitScope inScope, AudioUnitElement inEl, Float64 &outRate) const { UInt32 dataSize = sizeof (Float64); return AudioUnitGetProperty (AU(), kAudioUnitProperty_SampleRate, inScope, inEl, &outRate, &dataSize); } OSStatus CAAudioUnit::SetSampleRate (AudioUnitScope inScope, AudioUnitElement inEl, Float64 inRate) { return AudioUnitSetProperty (AU(), kAudioUnitProperty_SampleRate, inScope, inEl, &inRate, sizeof (Float64)); } OSStatus CAAudioUnit::SetSampleRate (Float64 inSampleRate) { OSStatus result; UInt32 elCount; require_noerr (result = GetElementCount(kAudioUnitScope_Input, elCount), home); if (elCount) { for (unsigned int i = 0; i < elCount; ++i) { require_noerr (result = SetSampleRate (kAudioUnitScope_Input, i, inSampleRate), home); } } require_noerr (result = GetElementCount(kAudioUnitScope_Output, elCount), home); if (elCount) { for (unsigned int i = 0; i < elCount; ++i) { require_noerr (result = SetSampleRate (kAudioUnitScope_Output, i, inSampleRate), home); } } home: return result; } OSStatus CAAudioUnit::NumberChannels (AudioUnitScope inScope, AudioUnitElement inEl, UInt32 &outChans) const { AudioStreamBasicDescription desc; OSStatus result = GetFormat (inScope, inEl, desc); if (!result) outChans = desc.mChannelsPerFrame; return result; } OSStatus CAAudioUnit::IsElementCountWritable (AudioUnitScope inScope, bool &outWritable) const { Boolean isWritable; UInt32 outDataSize; OSStatus result = GetPropertyInfo (kAudioUnitProperty_ElementCount, inScope, 0, &outDataSize, &isWritable); if (result) return result; outWritable = isWritable ? true : false; return noErr; } OSStatus CAAudioUnit::GetElementCount (AudioUnitScope inScope, UInt32 &outCount) const { UInt32 propSize = sizeof(outCount); return GetProperty (kAudioUnitProperty_ElementCount, inScope, 0, &outCount, &propSize); } OSStatus CAAudioUnit::SetElementCount (AudioUnitScope inScope, UInt32 inCount) { return SetProperty (kAudioUnitProperty_ElementCount, inScope, 0, &inCount, sizeof(inCount)); } bool CAAudioUnit::HasDynamicScope (AudioUnitScope inScope, SInt32 &outTotalNumChannels) const { // ok - now we need to check the AU's capability here. // this is the default assumption of an audio effect unit Boolean* isWritable = 0; UInt32 dataSize = 0; OSStatus result = GetPropertyInfo (kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, &dataSize, isWritable); //don't care if this is writable // AU has to explicitly tell us about this. if (result) return false; StackAUChannelInfo info (dataSize); result = GetProperty (kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, info.mChanInfo, &dataSize); if (result) return false; return ValidateDynamicScope (inScope, outTotalNumChannels, info.mChanInfo, (dataSize / sizeof(AUChannelInfo))); } // as we've already checked that the element count is writable // the following conditions will match this.. /* -1, -2 -> signifies no restrictions -2, -1 -> signifies no restrictions -> in this case outTotalNumChannels == -1 (any num channels) -N (where N is less than -2), signifies the total channel count on the scope side (in or out) */ bool CAAudioUnit::ValidateDynamicScope (AudioUnitScope inScope, SInt32 &outTotalNumChannels, const AUChannelInfo *info, UInt32 numInfo) const { bool writable = false; OSStatus result = IsElementCountWritable (inScope, writable); if (result || (writable == false)) return false; //now chan layout can contain -1 for either scope (ie. doesn't care) for (unsigned int i = 0; i < numInfo; ++i) { // lets test the special wild card case first... // this says the AU can do any num channels on input or output - for eg. Matrix Mixer if (((info[i].inChannels == -1) && (info[i].outChannels == -2)) || ((info[i].inChannels == -2) && (info[i].outChannels == -1))) { outTotalNumChannels = -1; return true; } // ok lets now test our special case.... if (inScope == kAudioUnitScope_Input) { // isn't dynamic on this side at least if (info[i].inChannels >= 0) continue; if (info[i].inChannels < -2) { outTotalNumChannels = abs (info[i].inChannels); return true; } } else if (inScope == kAudioUnitScope_Output) { // isn't dynamic on this side at least if (info[i].outChannels >= 0) continue; if (info[i].outChannels < -2) { outTotalNumChannels = abs (info[i].outChannels); return true; } } else { break; // wrong scope was specified } } return false; } OSStatus CAAudioUnit::ConfigureDynamicScope (AudioUnitScope inScope, UInt32 inNumElements, UInt32 *inChannelsPerElement, Float64 inSampleRate) { SInt32 numChannels = 0; bool isDyamic = HasDynamicScope (inScope, numChannels); if (isDyamic == false) return kAudioUnitErr_InvalidProperty; //lets to a sanity check... // if numChannels == -1, then it can do "any"... if (numChannels > 0) { SInt32 count = 0; for (unsigned int i = 0; i < inNumElements; ++i) count += inChannelsPerElement[i]; if (count > numChannels) return kAudioUnitErr_InvalidPropertyValue; } OSStatus result = SetElementCount (inScope, inNumElements); if (result) return result; CAStreamBasicDescription desc; desc.mSampleRate = inSampleRate; for (unsigned int i = 0; i < inNumElements; ++i) { desc.SetCanonical (inChannelsPerElement[i], false); result = SetFormat (inScope, i, desc); if (result) return result; } return noErr; } #pragma mark __Properties bool CAAudioUnit::CanBypass () const { Boolean outWritable; OSStatus result = AudioUnitGetPropertyInfo (AU(), kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0, NULL, &outWritable); return (!result && outWritable); } bool CAAudioUnit::GetBypass () const { UInt32 dataSize = sizeof (UInt32); UInt32 outBypass; OSStatus result = AudioUnitGetProperty (AU(), kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0, &outBypass, &dataSize); return (result ? false : outBypass); } OSStatus CAAudioUnit::SetBypass (bool inBypass) const { UInt32 bypass = inBypass ? 1 : 0; return AudioUnitSetProperty (AU(), kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0, &bypass, sizeof (UInt32)); } Float64 CAAudioUnit::Latency () const { Float64 secs; UInt32 size = sizeof(secs); if (GetProperty (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &secs, &size)) return 0; return secs; } OSStatus CAAudioUnit::GetAUPreset (CFPropertyListRef &outData) const { UInt32 dataSize = sizeof(outData); return AudioUnitGetProperty (AU(), kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &outData, &dataSize); } OSStatus CAAudioUnit::SetAUPreset (CFPropertyListRef &inData) { return AudioUnitSetProperty (AU(), kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &inData, sizeof (CFPropertyListRef)); } OSStatus CAAudioUnit::GetPresentPreset (AUPreset &outData) const { UInt32 dataSize = sizeof(outData); OSStatus result = AudioUnitGetProperty (AU(), kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0, &outData, &dataSize); if (result == kAudioUnitErr_InvalidProperty) { dataSize = sizeof(outData); result = AudioUnitGetProperty (AU(), kAudioUnitProperty_CurrentPreset, kAudioUnitScope_Global, 0, &outData, &dataSize); if (result == noErr) { // we now retain the CFString in the preset so for the client of this API // it is consistent (ie. the string should be released when done) if (outData.presetName) CFRetain (outData.presetName); } } return result; } OSStatus CAAudioUnit::SetPresentPreset (AUPreset &inData) { OSStatus result = AudioUnitSetProperty (AU(), kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0, &inData, sizeof (AUPreset)); if (result == kAudioUnitErr_InvalidProperty) { result = AudioUnitSetProperty (AU(), kAudioUnitProperty_CurrentPreset, kAudioUnitScope_Global, 0, &inData, sizeof (AUPreset)); } return result; } bool CAAudioUnit::HasCustomView () const { UInt32 dataSize = 0; OSStatus result = GetPropertyInfo(kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &dataSize, NULL); if (result || !dataSize) { dataSize = 0; result = GetPropertyInfo(kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, &dataSize, NULL); if (result || !dataSize) return false; } return true; } OSStatus CAAudioUnit::GetParameter(AudioUnitParameterID inID, AudioUnitScope scope, AudioUnitElement element, Float32 &outValue) const { return mDataPtr ? mDataPtr->GetParameter (inID, scope, element, outValue) : paramErr; } OSStatus CAAudioUnit::SetParameter(AudioUnitParameterID inID, AudioUnitScope scope, AudioUnitElement element, Float32 value, UInt32 bufferOffsetFrames) { return mDataPtr ? mDataPtr->SetParameter (inID, scope, element, value, bufferOffsetFrames) : paramErr; } OSStatus CAAudioUnit::MIDIEvent (UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame) { return mDataPtr ? mDataPtr->MIDIEvent (inStatus, inData1, inData2, inOffsetSampleFrame) : paramErr; } #pragma mark __Render OSStatus CAAudioUnit::Render (AudioUnitRenderActionFlags * ioActionFlags, const AudioTimeStamp * inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames, AudioBufferList * ioData) { return mDataPtr ? mDataPtr->Render (ioActionFlags, inTimeStamp, inOutputBusNumber, inNumberFrames, ioData) : paramErr; } static AURenderCallbackStruct sRenderCallback; static OSStatus PrerollRenderProc ( void * /*inRefCon*/, AudioUnitRenderActionFlags * /*inActionFlags*/, const AudioTimeStamp * /*inTimeStamp*/, UInt32 /*inBusNumber*/, UInt32 /*inNumFrames*/, AudioBufferList *ioData) { AudioBuffer *buf = ioData->mBuffers; for (UInt32 i = ioData->mNumberBuffers; i--; ++buf) memset((Byte *)buf->mData, 0, buf->mDataByteSize); return noErr; } OSStatus CAAudioUnit::Preroll (UInt32 inFrameSize) { CAStreamBasicDescription desc; OSStatus result = GetFormat (kAudioUnitScope_Input, 0, desc); bool hasInput = false; //we have input if (result == noErr) { sRenderCallback.inputProc = PrerollRenderProc; sRenderCallback.inputProcRefCon = 0; result = SetProperty (kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &sRenderCallback, sizeof(sRenderCallback)); if (result) return result; hasInput = true; } AudioUnitRenderActionFlags flags = 0; AudioTimeStamp time; memset (&time, 0, sizeof(time)); time.mFlags = kAudioTimeStampSampleTimeValid; CAStreamBasicDescription outputFormat; require_noerr (result = GetFormat (kAudioUnitScope_Output, 0, outputFormat), home); { AUOutputBL list (outputFormat, inFrameSize); list.Prepare (); require_noerr (result = Render (&flags, &time, 0, inFrameSize, list.ABL()), home); require_noerr (result = GlobalReset(), home); } home: if (hasInput) { // remove our installed callback sRenderCallback.inputProc = 0; sRenderCallback.inputProcRefCon = 0; SetProperty (kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &sRenderCallback, sizeof(sRenderCallback)); } return result; } #pragma mark __CAAUChanHelper CAAUChanHelper::CAAUChanHelper(const CAAudioUnit &inAU, AudioUnitScope inScope) :mChans(NULL), mNumEls(0), mDidAllocate(false) { UInt32 elCount; if (inAU.GetElementCount (inScope, elCount)) return; if (elCount > 8) { mChans = new UInt32[elCount]; mDidAllocate = true; memset (mChans, 0, sizeof(int) * elCount); } else { mChans = mStaticChans; memset (mChans, 0, sizeof(int) * 8); } for (unsigned int i = 0; i < elCount; ++i) { UInt32 numChans; if (inAU.NumberChannels (inScope, i, numChans)) return; mChans[i] = numChans; } mNumEls = elCount; } CAAUChanHelper::~CAAUChanHelper() { if (mDidAllocate) delete [] mChans; } CAAUChanHelper& CAAUChanHelper::operator= (const CAAUChanHelper &c) { if (mDidAllocate) delete [] mChans; if (c.mDidAllocate) { mChans = new UInt32[c.mNumEls]; mDidAllocate = true; } else { mDidAllocate = false; mChans = mStaticChans; } memcpy (mChans, c.mChans, c.mNumEls * sizeof(int)); return *this; } #pragma mark __Print Utilities void CAAudioUnit::Print (FILE* file) const { fprintf (file, "AudioUnit:0x%X\n", int(AU())); if (IsValid()) { fprintf (file, "\tnode=%ld\t", GetAUNode()); Comp().Print (file); } }