/********************************************************************** Audacity: A Digital Audio Editor AudioUnitEffect.cpp Dominic Mazzoni Leland Lucius *******************************************************************//** \class AudioUnitEffect \brief An Effect class that handles a wide range of effects. ??Mac only?? *//*******************************************************************/ #include "../../Audacity.h" // for USE_* macros #if USE_AUDIO_UNITS #include "AudioUnitEffect.h" #include #include #include #include #include #ifdef __WXMAC__ #include #endif #include #include #include #include #include #include #include #include #include "../../ShuttleGui.h" #include "../../widgets/valnum.h" #include "../../widgets/wxPanelWrapper.h" #include "../../Internat.h" struct CFReleaser { void operator () (const void *p) const { if (p) CFRelease(p); } }; template using CFunique_ptr = std::unique_ptr; struct AudioUnitParameterInfoEx : AudioUnitParameterInfo { ~AudioUnitParameterInfoEx () { if ((flags & kAudioUnitParameterFlag_HasCFNameString) && (flags & kAudioUnitParameterFlag_CFNameRelease)) CFRelease( cfNameString ); } }; // ============================================================================ // Module registration entry point // // This is the symbol that Audacity looks for when the module is built as a // dynamic library. // // When the module is builtin to Audacity, we use the same function, but it is // declared static so as not to clash with other builtin modules. // ============================================================================ DECLARE_MODULE_ENTRY(AudacityModule) { // Create and register the importer // Trust the module manager not to leak this return safenew AudioUnitEffectsModule(moduleManager, path); } // ============================================================================ // Register this as a builtin module // ============================================================================ DECLARE_BUILTIN_MODULE(AudioUnitEffectsBuiltin); /////////////////////////////////////////////////////////////////////////////// // // AudioUnitEffectsModule // /////////////////////////////////////////////////////////////////////////////// AudioUnitEffectsModule::AudioUnitEffectsModule(ModuleManagerInterface *moduleManager, const wxString *path) { mModMan = moduleManager; if (path) { mPath = *path; } } AudioUnitEffectsModule::~AudioUnitEffectsModule() { mPath.clear(); } // ============================================================================ // ComponentInterface implementation // ============================================================================ PluginPath AudioUnitEffectsModule::GetPath() { return mPath; } ComponentInterfaceSymbol AudioUnitEffectsModule::GetSymbol() { /* i18n-hint: Audio Unit is the name of an Apple audio software protocol */ return XO("Audio Unit Effects"); } VendorSymbol AudioUnitEffectsModule::GetVendor() { return XO("The Audacity Team"); } wxString AudioUnitEffectsModule::GetVersion() { // This "may" be different if this were to be maintained as a separate DLL return AUDIOUNITEFFECTS_VERSION; } wxString AudioUnitEffectsModule::GetDescription() { return _("Provides Audio Unit Effects support to Audacity"); } // ============================================================================ // ModuleInterface implementation // ============================================================================ const FileExtensions &AudioUnitEffectsModule::GetFileExtensions() { static FileExtensions result{{ _T("au") }}; return result; } bool AudioUnitEffectsModule::Initialize() { // Nothing to do here return true; } void AudioUnitEffectsModule::Terminate() { // Nothing to do here return; } bool AudioUnitEffectsModule::AutoRegisterPlugins(PluginManagerInterface & pm) { // Nothing to be done here return true; } PluginPaths AudioUnitEffectsModule::FindPluginPaths(PluginManagerInterface & pm) { PluginPaths effects; LoadAudioUnitsOfType(kAudioUnitType_Effect, effects); LoadAudioUnitsOfType(kAudioUnitType_Generator, effects); LoadAudioUnitsOfType(kAudioUnitType_MusicEffect, effects); LoadAudioUnitsOfType(kAudioUnitType_Mixer, effects); LoadAudioUnitsOfType(kAudioUnitType_Panner, effects); return effects; } unsigned AudioUnitEffectsModule::DiscoverPluginsAtPath( const PluginPath & path, wxString &errMsg, const RegistrationCallback &callback) { errMsg.clear(); wxString name; AudioComponent component = FindAudioUnit(path, name); if (component == NULL) { errMsg = _("Could not find component"); return 0; } AudioUnitEffect effect(path, name, component); if (!effect.SetHost(NULL)) { // TODO: Is it worth it to discriminate all the ways SetHost might // return false? errMsg = _("Could not initialize component"); return 0; } if(callback) callback(this, &effect); return 1; } bool AudioUnitEffectsModule::IsPluginValid( const PluginPath & path, bool bFast) { if( bFast ) return true; wxString name; return FindAudioUnit(path, name) != NULL; } ComponentInterface *AudioUnitEffectsModule::CreateInstance(const PluginPath & path) { // Acquires a resource for the application. wxString name; AudioComponent component = FindAudioUnit(path, name); if (component == NULL) { return NULL; } // Safety of this depends on complementary calls to DeleteInstance on the module manager side. return safenew AudioUnitEffect(path, name, component); } void AudioUnitEffectsModule::DeleteInstance(ComponentInterface *instance) { std::unique_ptr < AudioUnitEffect > { dynamic_cast(instance) }; } // ============================================================================ // AudioUnitEffectsModule implementation // ============================================================================ void AudioUnitEffectsModule::LoadAudioUnitsOfType(OSType inAUType, PluginPaths & effects) { AudioComponentDescription desc; AudioComponent component; desc.componentType = inAUType; desc.componentSubType = 0; desc.componentManufacturer = 0; desc.componentFlags = 0; desc.componentFlagsMask = 0; component = AudioComponentFindNext(NULL, &desc); while (component != NULL) { OSStatus result; AudioComponentDescription found; result = AudioComponentGetDescription(component, &found); if (result == noErr) { CFStringRef cfName{}; result = AudioComponentCopyName(component, &cfName); CFunique_ptr uName{ cfName }; if (result == noErr) { wxString name = wxCFStringRef::AsString(cfName); effects.push_back(wxString::Format(wxT("%-4.4s/%-4.4s/%-4.4s/%s"), FromOSType(found.componentManufacturer), FromOSType(found.componentType), FromOSType(found.componentSubType), name)); } } component = AudioComponentFindNext(component, &desc); } } AudioComponent AudioUnitEffectsModule::FindAudioUnit(const PluginPath & path, wxString & name) { wxStringTokenizer tokens(path, wxT("/")); AudioComponentDescription desc; desc.componentManufacturer = ToOSType(tokens.GetNextToken()); desc.componentType = ToOSType(tokens.GetNextToken()); desc.componentSubType = ToOSType(tokens.GetNextToken()); desc.componentFlags = 0; desc.componentFlagsMask = 0; name = tokens.GetNextToken(); return AudioComponentFindNext(NULL, &desc); } wxString AudioUnitEffectsModule::FromOSType(OSType type) { OSType rev = (type & 0xff000000) >> 24 | (type & 0x00ff0000) >> 8 | (type & 0x0000ff00) << 8 | (type & 0x000000ff) << 24; return wxString::FromUTF8((char *)&rev, 4); } OSType AudioUnitEffectsModule::ToOSType(const wxString & type) { wxCharBuffer buf = type.ToUTF8(); OSType rev = ((unsigned char)buf.data()[0]) << 24 | ((unsigned char)buf.data()[1]) << 16 | ((unsigned char)buf.data()[2]) << 8 | ((unsigned char)buf.data()[3]); return rev; } /////////////////////////////////////////////////////////////////////////////// // // AudioUnitEffectOptionsDialog // /////////////////////////////////////////////////////////////////////////////// class AudioUnitEffectOptionsDialog final : public wxDialogWrapper { public: AudioUnitEffectOptionsDialog(wxWindow * parent, EffectHostInterface *host); virtual ~AudioUnitEffectOptionsDialog(); void PopulateOrExchange(ShuttleGui & S); void OnOk(wxCommandEvent & evt); private: EffectHostInterface *mHost; bool mUseLatency; wxString mUIType; DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(AudioUnitEffectOptionsDialog, wxDialogWrapper) EVT_BUTTON(wxID_OK, AudioUnitEffectOptionsDialog::OnOk) END_EVENT_TABLE() AudioUnitEffectOptionsDialog::AudioUnitEffectOptionsDialog(wxWindow * parent, EffectHostInterface *host) : wxDialogWrapper(parent, wxID_ANY, wxString(_("Audio Unit Effect Options"))) { mHost = host; mHost->GetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency, true); mHost->GetSharedConfig(wxT("Options"), wxT("UIType"), mUIType, wxT("Full")); mUIType = wxGetTranslation(mUIType); ShuttleGui S(this, eIsCreating); PopulateOrExchange(S); } AudioUnitEffectOptionsDialog::~AudioUnitEffectOptionsDialog() { } void AudioUnitEffectOptionsDialog::PopulateOrExchange(ShuttleGui & S) { S.SetBorder(5); S.StartHorizontalLay(wxEXPAND, 1); { S.StartVerticalLay(false); { S.StartStatic(_("Latency Compensation")); { S.AddVariableText(wxString() + _("As part of their processing, some Audio Unit effects must delay returning ") + _("audio to Audacity. When not compensating for this delay, you will ") + _("notice that small silences have been inserted into the audio. ") + _("Enabling this option will provide that compensation, but it may ") + _("not work for all Audio Unit effects."))->Wrap(650); S.StartHorizontalLay(wxALIGN_LEFT); { S.TieCheckBox(_("Enable &compensation"), mUseLatency); } S.EndHorizontalLay(); } S.EndStatic(); S.StartStatic(_("User Interface")); { S.AddVariableText(wxString() + _("Select \"Full\" to use the graphical interface if supplied by the Audio Unit.") + _(" Select \"Generic\" to use the system supplied generic interface.") + _(" Select \"Basic\" for a basic text-only interface.") + _(" Reopen the effect for this to take effect."))->Wrap(650); S.StartHorizontalLay(wxALIGN_LEFT); { S.TieChoice(_("Select &interface"), mUIType, { _("Full"), _("Generic"), _("Basic") }); } S.EndHorizontalLay(); } S.EndStatic(); } S.EndVerticalLay(); } S.EndHorizontalLay(); S.AddStandardButtons(); Layout(); Fit(); Center(); } void AudioUnitEffectOptionsDialog::OnOk(wxCommandEvent & WXUNUSED(evt)) { if (!Validate()) { return; } ShuttleGui S(this, eIsGettingFromDialog); PopulateOrExchange(S); if (mUIType == _("Full")) { mUIType = wxT("Full"); } else if (mUIType == _("Generic")) { mUIType = wxT("Generic"); } else if (mUIType == _("Basic")) { mUIType = wxT("Basic"); } mHost->SetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency); mHost->SetSharedConfig(wxT("Options"), wxT("UIType"), mUIType); EndModal(wxID_OK); } /////////////////////////////////////////////////////////////////////////////// // // AudioUnitEffectExportDialog // /////////////////////////////////////////////////////////////////////////////// #define PRESET_LOCAL_PATH wxT("/Library/Audio/Presets") #define PRESET_USER_PATH wxT("~/Library/Audio/Presets") class AudioUnitEffectExportDialog final : public wxDialogWrapper { public: AudioUnitEffectExportDialog(wxWindow * parent, AudioUnitEffect *effect); virtual ~AudioUnitEffectExportDialog(); void PopulateOrExchange(ShuttleGui & S); void OnOk(wxCommandEvent & evt); private: wxWindow *mParent; AudioUnitEffect *mEffect; wxListCtrl *mList; DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(AudioUnitEffectExportDialog, wxDialogWrapper) EVT_BUTTON(wxID_OK, AudioUnitEffectExportDialog::OnOk) END_EVENT_TABLE() AudioUnitEffectExportDialog::AudioUnitEffectExportDialog(wxWindow * parent, AudioUnitEffect *effect) : wxDialogWrapper(parent, wxID_ANY, wxString(_("Export Audio Unit Presets"))) { mEffect = effect; ShuttleGui S(this, eIsCreating); PopulateOrExchange(S); } AudioUnitEffectExportDialog::~AudioUnitEffectExportDialog() { } void AudioUnitEffectExportDialog::PopulateOrExchange(ShuttleGui & S) { S.SetBorder(5); S.StartHorizontalLay(wxEXPAND, 1); { S.StartVerticalLay(true); { S.StartStatic(_("Presets (may select multiple)")); { S.SetStyle(wxLC_REPORT | wxLC_HRULES | wxLC_VRULES | wxLC_NO_SORT_HEADER); mList = S.AddListControlReportMode(); mList->InsertColumn(0, _("Preset"), wxLIST_FORMAT_LEFT); } S.EndStatic(); } S.EndVerticalLay(); } S.EndHorizontalLay(); S.AddStandardButtons(); RegistryPaths presets; mEffect->mHost->GetPrivateConfigSubgroups(mEffect->mHost->GetUserPresetsGroup(wxEmptyString), presets); std::sort( presets.begin(), presets.end() ); for (size_t i = 0, cnt = presets.size(); i < cnt; i++) { mList->InsertItem(i, presets[i]); } mList->SetColumnWidth(0, wxLIST_AUTOSIZE); // Set the list size...with a little extra for good measure wxSize sz = mList->GetBestSize(); sz.x += 5; sz.y += 5; mList->SetMinSize(sz); Layout(); Fit(); Center(); } void AudioUnitEffectExportDialog::OnOk(wxCommandEvent & WXUNUSED(evt)) { // Save active settings wxString settingsName(wxT("Export Save")); mEffect->SaveParameters(settingsName); // Look for selected presets long sel = mList->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); while (sel >= 0) { wxString name = mList->GetItemText(sel); // Make the preset current mEffect->LoadParameters(mEffect->mHost->GetUserPresetsGroup(name)); // Make sure the user preset directory exists wxString path; path.Printf(wxT("%s/%s/%s/%s.aupreset"), PRESET_USER_PATH, mEffect->mVendor, mEffect->mName, name); wxFileName fn(path); fn.Normalize(); fn.Mkdir(0755, wxPATH_MKDIR_FULL); path = fn.GetFullPath(); // First set the name of the preset wxCFStringRef cfname(name); AUPreset preset; preset.presetNumber = -1; // indicates user preset preset.presetName = cfname; AudioUnitSetProperty(mEffect->mUnit, kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0, &preset, sizeof(preset)); // Now retrieve the preset content CFPropertyListRef content; UInt32 size = sizeof(content); AudioUnitGetProperty(mEffect->mUnit, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &content, &size); // And convert it to XML CFunique_ptr xml { CFPropertyListCreateXMLData(kCFAllocatorDefault, content) }; if (xml) { // Create the CFURL for the path CFunique_ptr url { CFURLCreateWithFileSystemPath(kCFAllocatorDefault, wxCFStringRef(path), kCFURLPOSIXPathStyle, false) }; if (url) { SInt32 error; Boolean res = CFURLWriteDataAndPropertiesToResource(url.get(), xml.get(), NULL, &error); } } // And continue to the next selected preset sel = mList->GetNextItem(sel, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); } // Restore active settings mEffect->LoadParameters(settingsName); mEffect->mHost->RemovePrivateConfigSubgroup(settingsName); EndModal(wxID_OK); } /////////////////////////////////////////////////////////////////////////////// // // AudioUnitEffectImportDialog // /////////////////////////////////////////////////////////////////////////////// class AudioUnitEffectImportDialog final : public wxDialogWrapper { public: AudioUnitEffectImportDialog(wxWindow * parent, AudioUnitEffect *effect); virtual ~AudioUnitEffectImportDialog(); void PopulateOrExchange(ShuttleGui & S); void OnOk(wxCommandEvent & evt); private: wxWindow *mParent; AudioUnitEffect *mEffect; wxListCtrl *mList; DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(AudioUnitEffectImportDialog, wxDialogWrapper) EVT_BUTTON(wxID_OK, AudioUnitEffectImportDialog::OnOk) END_EVENT_TABLE() AudioUnitEffectImportDialog::AudioUnitEffectImportDialog(wxWindow * parent, AudioUnitEffect *effect) : wxDialogWrapper(parent, wxID_ANY, wxString(_("Import Audio Unit Presets"))) { mEffect = effect; ShuttleGui S(this, eIsCreating); PopulateOrExchange(S); } AudioUnitEffectImportDialog::~AudioUnitEffectImportDialog() { } void AudioUnitEffectImportDialog::PopulateOrExchange(ShuttleGui & S) { S.SetBorder(5); S.StartHorizontalLay(wxEXPAND, 1); { S.StartVerticalLay(true); { S.StartStatic(_("Presets (may select multiple)")); { S.SetStyle(wxLC_REPORT | wxLC_HRULES | wxLC_VRULES | wxLC_NO_SORT_HEADER); mList = S.AddListControlReportMode(); mList->InsertColumn(0, _("Preset"), wxLIST_FORMAT_LEFT); mList->InsertColumn(1, _("Location"), wxLIST_FORMAT_LEFT); } S.EndStatic(); } S.EndVerticalLay(); } S.EndHorizontalLay(); S.AddStandardButtons(); FilePaths presets; // Make sure the user preset directory exists wxString path; path.Printf(wxT("%s/%s/%s"), PRESET_LOCAL_PATH, mEffect->mVendor, mEffect->mName); wxFileName fn(path); fn.Normalize(); // Get all presets in the local domain for this effect wxDir::GetAllFiles(fn.GetFullPath(), &presets, wxT("*.aupreset")); path.Printf(wxT("%s/%s/%s"), PRESET_USER_PATH, mEffect->mVendor, mEffect->mName); fn = path; fn.Normalize(); // Get all presets in the user domain for this effect wxDir::GetAllFiles(fn.GetFullPath(), &presets, wxT("*.aupreset")); presets.Sort(); for (size_t i = 0, cnt = presets.size(); i < cnt; i++) { fn = presets[i]; mList->InsertItem(i, fn.GetName()); mList->SetItem(i, 1, fn.GetPath()); } mList->SetColumnWidth(0, wxLIST_AUTOSIZE); mList->SetColumnWidth(1, wxLIST_AUTOSIZE); // Set the list size...with a little extra for good measure wxSize sz = mList->GetBestSize(); sz.x += 5; sz.y += 5; mList->SetMinSize(sz); Layout(); Fit(); Center(); } void AudioUnitEffectImportDialog::OnOk(wxCommandEvent & WXUNUSED(evt)) { // Save active settings wxString settingsName(wxT("Import Save")); mEffect->SaveParameters(settingsName); // Look for selected presets long sel = -1; while ((sel = mList->GetNextItem(sel, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) >= 0) { wxListItem item; item.SetId(sel); item.SetColumn(1); item.SetMask(wxLIST_MASK_TEXT); mList->GetItem(item); wxString path; path.Printf(wxT("%s/%s.aupreset"), item.GetText(), mList->GetItemText(sel)); // Create the CFURL for the path CFunique_ptr url { CFURLCreateWithFileSystemPath(kCFAllocatorDefault, wxCFStringRef(path), kCFURLPOSIXPathStyle, false) }; if (!url) { continue; } CFDataRef xml{}; SInt32 error; Boolean res = CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, url.get(), &xml, NULL, NULL, &error); CFunique_ptr uxml { xml }; if (!res) { continue; } CFunique_ptr content { (char*)CFPropertyListCreateFromXMLData(kCFAllocatorDefault, xml, kCFPropertyListImmutable, NULL) }; if (!content) { continue; } OSStatus result = AudioUnitSetProperty(mEffect->mUnit, kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0, &content, sizeof(content)); mEffect->SaveUserPreset(mEffect->mHost->GetUserPresetsGroup(mList->GetItemText(sel))); } // Restore active settings mEffect->LoadParameters(settingsName); mEffect->mHost->RemovePrivateConfigSubgroup(settingsName); EndModal(wxID_OK); } /////////////////////////////////////////////////////////////////////////////// // // AudioUnitEffect // /////////////////////////////////////////////////////////////////////////////// AudioUnitEffect::AudioUnitEffect(const PluginPath & path, const wxString & name, AudioComponent component, AudioUnitEffect *master) { mPath = path; mName = name.AfterFirst(wxT(':')).Trim(true).Trim(false); mVendor = name.BeforeFirst(wxT(':')).Trim(true).Trim(false); mComponent = component; mMaster = master; mUnit = NULL; mBlockSize = 0.0; mInteractive = false; mIsGraphical = false; mUIHost = NULL; mDialog = NULL; mParent = NULL; mUnitInitialized = false; mEventListenerRef = NULL; } AudioUnitEffect::~AudioUnitEffect() { if (mUnitInitialized) { AudioUnitUninitialize(mUnit); } if (mEventListenerRef) { AUListenerDispose(mEventListenerRef); } if (mUnit) { AudioComponentInstanceDispose(mUnit); } } // ============================================================================ // ComponentInterface implementation // ============================================================================ PluginPath AudioUnitEffect::GetPath() { return mPath; } ComponentInterfaceSymbol AudioUnitEffect::GetSymbol() { return mName; } VendorSymbol AudioUnitEffect::GetVendor() { return { mVendor }; } wxString AudioUnitEffect::GetVersion() { UInt32 version; OSStatus result = AudioComponentGetVersion(mComponent, &version); return wxString::Format(wxT("%d.%d.%d"), (version >> 16) & 0xffff, (version >> 8) & 0xff, version & 0xff); } wxString AudioUnitEffect::GetDescription() { /* i18n-hint: Can mean "not available," "not applicable," "no answer" */ return _("n/a"); } // ============================================================================ // EffectComponentInterface implementation // ============================================================================ EffectType AudioUnitEffect::GetType() { if (mAudioIns == 0 && mAudioOuts == 0) { return EffectTypeNone; } if (mAudioIns == 0) { return EffectTypeGenerate; } if (mAudioOuts == 0) { return EffectTypeAnalyze; } return EffectTypeProcess; } EffectFamilySymbol AudioUnitEffect::GetFamily() { return AUDIOUNITEFFECTS_FAMILY; } bool AudioUnitEffect::IsInteractive() { return mInteractive; } bool AudioUnitEffect::IsDefault() { return false; } bool AudioUnitEffect::IsLegacy() { return false; } bool AudioUnitEffect::SupportsRealtime() { return GetType() == EffectTypeProcess; } bool AudioUnitEffect::SupportsAutomation() { OSStatus result; UInt32 dataSize; Boolean isWritable; result = AudioUnitGetPropertyInfo(mUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, &dataSize, &isWritable); if (result != noErr) { return false; } UInt32 cnt = dataSize / sizeof(AudioUnitParameterID); ArrayOf < AudioUnitParameterID > array{cnt}; result = AudioUnitGetProperty(mUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, array.get(), &dataSize); if (result != noErr) { return false; } for (int i = 0; i < cnt; i++) { AudioUnitParameterInfoEx info; dataSize = sizeof(info); result = AudioUnitGetProperty(mUnit, kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, array[i], &info, &dataSize); if (result != noErr) { return false; } if (info.flags & kAudioUnitParameterFlag_IsWritable) { // All we need is one return true; } } return false; } // ============================================================================ // EffectClientInterface Implementation // ============================================================================ bool AudioUnitEffect::SetHost(EffectHostInterface *host) { OSStatus result; mHost = host; mSampleRate = 44100; result = AudioComponentInstanceNew(mComponent, &mUnit); if (!mUnit) { return false; } GetChannelCounts(); SetRateAndChannels(); // Retrieve the desired number of frames per slice UInt32 dataSize = sizeof(mBlockSize); mBlockSize = 512; AudioUnitGetProperty(mUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &mBlockSize, &dataSize); // mHost will be null during registration if (mHost) { mHost->GetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency, true); mHost->GetSharedConfig(wxT("Options"), wxT("UIType"), mUIType, wxT("Full")); mUIType = wxGetTranslation(mUIType); bool haveDefaults; mHost->GetPrivateConfig(mHost->GetFactoryDefaultsGroup(), wxT("Initialized"), haveDefaults, false); if (!haveDefaults) { SaveParameters(mHost->GetFactoryDefaultsGroup()); mHost->SetPrivateConfig(mHost->GetFactoryDefaultsGroup(), wxT("Initialized"), true); } LoadParameters(mHost->GetCurrentSettingsGroup()); } if (!mMaster) { result = AUEventListenerCreate(AudioUnitEffect::EventListenerCallback, this, (CFRunLoopRef)GetCFRunLoopFromEventLoop(GetCurrentEventLoop()), kCFRunLoopDefaultMode, 0.0, 0.0, &mEventListenerRef); if (result != noErr) { return false; } AudioUnitEvent event; event.mEventType = kAudioUnitEvent_ParameterValueChange; event.mArgument.mParameter.mAudioUnit = mUnit; event.mArgument.mParameter.mScope = kAudioUnitScope_Global; event.mArgument.mParameter.mElement = 0; UInt32 dataSize; Boolean isWritable; // Retrieve the list of properties result = AudioUnitGetPropertyInfo(mUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, &dataSize, &isWritable); if (result != noErr) { return false; } // And get them UInt32 cnt = dataSize / sizeof(AudioUnitParameterID); ArrayOf array {cnt}; result = AudioUnitGetProperty(mUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, array.get(), &dataSize); if (result != noErr) { return false; } // Register them as something we're interested in for (int i = 0; i < cnt; i++) { event.mArgument.mParameter.mParameterID = array[i]; result = AUEventListenerAddEventType(mEventListenerRef, this, &event); if (result != noErr) { return false; } } event.mEventType = kAudioUnitEvent_PropertyChange; event.mArgument.mProperty.mAudioUnit = mUnit; event.mArgument.mProperty.mPropertyID = kAudioUnitProperty_Latency; event.mArgument.mProperty.mScope = kAudioUnitScope_Global; event.mArgument.mProperty.mElement = 0; result = AUEventListenerAddEventType(mEventListenerRef, this, &event); if (result != noErr) { return false; } AudioUnitCocoaViewInfo cocoaViewInfo; dataSize = sizeof(AudioUnitCocoaViewInfo); // Check for a Cocoa UI result = AudioUnitGetProperty(mUnit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, &cocoaViewInfo, &dataSize); bool hasCocoa = result == noErr; // Check for a Carbon UI AudioComponentDescription compDesc; dataSize = sizeof(compDesc); result = AudioUnitGetProperty(mUnit, kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &compDesc, &dataSize); bool hasCarbon = result == noErr; mInteractive = (cnt > 0) || hasCocoa || hasCarbon; } return true; } unsigned AudioUnitEffect::GetAudioInCount() { return mAudioIns; } unsigned AudioUnitEffect::GetAudioOutCount() { return mAudioOuts; } int AudioUnitEffect::GetMidiInCount() { return 0; } int AudioUnitEffect::GetMidiOutCount() { return 0; } void AudioUnitEffect::SetSampleRate(double rate) { mSampleRate = rate; } size_t AudioUnitEffect::SetBlockSize(size_t maxBlockSize) { return mBlockSize; } sampleCount AudioUnitEffect::GetLatency() { // Retrieve the latency (can be updated via an event) if (mUseLatency && !mLatencyDone) { mLatencyDone = true; Float64 latency = 0.0; UInt32 dataSize = sizeof(latency); AudioUnitGetProperty(mUnit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &latency, &dataSize); return sampleCount( latency * mSampleRate ); } return 0; } size_t AudioUnitEffect::GetTailSize() { // Retrieve the tail time Float64 tailTime = 0.0; UInt32 dataSize = sizeof(tailTime); AudioUnitGetProperty(mUnit, kAudioUnitProperty_TailTime, kAudioUnitScope_Global, 0, &tailTime, &dataSize); return tailTime * mSampleRate; } bool AudioUnitEffect::IsReady() { return mReady; } bool AudioUnitEffect::ProcessInitialize(sampleCount WXUNUSED(totalLen), ChannelNames WXUNUSED(chanMap)) { OSStatus result; mInputList.reinit( mAudioIns ); mInputList[0].mNumberBuffers = mAudioIns; mOutputList.reinit( mAudioOuts ); mOutputList[0].mNumberBuffers = mAudioOuts; memset(&mTimeStamp, 0, sizeof(AudioTimeStamp)); mTimeStamp.mSampleTime = 0; // This is a double-precision number that should // accumulate the number of frames processed so far mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid; if (!SetRateAndChannels()) { return false; } AURenderCallbackStruct callbackStruct; callbackStruct.inputProc = RenderCallback; callbackStruct.inputProcRefCon = this; result = AudioUnitSetProperty(mUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callbackStruct, sizeof(AURenderCallbackStruct)); if (result != noErr) { wxPrintf("Setting input render callback failed.\n"); return false; } result = AudioUnitReset(mUnit, kAudioUnitScope_Global, 0); if (result != noErr) { return false; } mLatencyDone = false; mReady = true; return true; } bool AudioUnitEffect::ProcessFinalize() { mReady = false; mOutputList.reset(); mInputList.reset(); return true; } size_t AudioUnitEffect::ProcessBlock(float **inBlock, float **outBlock, size_t blockLen) { for (size_t i = 0; i < mAudioIns; i++) { mInputList[0].mBuffers[i].mNumberChannels = 1; mInputList[0].mBuffers[i].mData = inBlock[i]; mInputList[0].mBuffers[i].mDataByteSize = sizeof(float) * blockLen; } for (size_t i = 0; i < mAudioOuts; i++) { mOutputList[0].mBuffers[i].mNumberChannels = 1; mOutputList[0].mBuffers[i].mData = outBlock[i]; mOutputList[0].mBuffers[i].mDataByteSize = sizeof(float) * blockLen; } AudioUnitRenderActionFlags flags = 0; OSStatus result; result = AudioUnitRender(mUnit, &flags, &mTimeStamp, 0, blockLen, mOutputList.get()); if (result != noErr) { wxPrintf("Render failed: %d %4.4s\n", (int)result, (char *)&result); return 0; } mTimeStamp.mSampleTime += blockLen; return blockLen; } bool AudioUnitEffect::RealtimeInitialize() { mMasterIn.reinit(mAudioIns, mBlockSize, true); mMasterOut.reinit( mAudioOuts, mBlockSize ); return ProcessInitialize(0); } bool AudioUnitEffect::RealtimeAddProcessor(unsigned numChannels, float sampleRate) { auto slave = std::make_unique(mPath, mName, mComponent, this); if (!slave->SetHost(NULL)) return false; slave->SetBlockSize(mBlockSize); slave->SetChannelCount(numChannels); slave->SetSampleRate(sampleRate); if (!CopyParameters(mUnit, slave->mUnit)) return false; auto pSlave = slave.get(); mSlaves.push_back(std::move(slave)); return pSlave->ProcessInitialize(0); } bool AudioUnitEffect::RealtimeFinalize() { for (size_t i = 0, cnt = mSlaves.size(); i < cnt; i++) mSlaves[i]->ProcessFinalize(); mSlaves.clear(); mMasterIn.reset(); mMasterOut.reset(); return ProcessFinalize(); } bool AudioUnitEffect::RealtimeSuspend() { return true; } bool AudioUnitEffect::RealtimeResume() { OSStatus result; result = AudioUnitReset(mUnit, kAudioUnitScope_Global, 0); if (result != noErr) { return false; } return true; } bool AudioUnitEffect::RealtimeProcessStart() { for (size_t i = 0; i < mAudioIns; i++) memset(mMasterIn[i].get(), 0, mBlockSize * sizeof(float)); mNumSamples = 0; return true; } size_t AudioUnitEffect::RealtimeProcess(int group, float **inbuf, float **outbuf, size_t numSamples) { wxASSERT(numSamples <= mBlockSize); for (size_t c = 0; c < mAudioIns; c++) { for (decltype(numSamples) s = 0; s < numSamples; s++) { mMasterIn[c][s] += inbuf[c][s]; } } mNumSamples = wxMax(numSamples, mNumSamples); return mSlaves[group]->ProcessBlock(inbuf, outbuf, numSamples); } bool AudioUnitEffect::RealtimeProcessEnd() { ProcessBlock( reinterpret_cast(mMasterIn.get()), reinterpret_cast(mMasterOut.get()), mNumSamples); return true; } bool AudioUnitEffect::ShowInterface(wxWindow *parent, bool forceModal) { if (mDialog) { if( mDialog->Close(true) ) mDialog = nullptr; return false; } // mDialog is null auto cleanup = valueRestorer( mDialog ); mDialog = mHost->CreateUI(parent, this); if (!mDialog) { return false; } if ((SupportsRealtime() || GetType() == EffectTypeAnalyze) && !forceModal) { mDialog->Show(); cleanup.release(); return false; } bool res = mDialog->ShowModal() != 0; return res; } bool AudioUnitEffect::GetAutomationParameters(CommandParameters & parms) { OSStatus result; UInt32 dataSize; Boolean isWritable; result = AudioUnitGetPropertyInfo(mUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, &dataSize, &isWritable); if (result != noErr) { return false; } UInt32 cnt = dataSize / sizeof(AudioUnitParameterID); ArrayOf array {cnt}; result = AudioUnitGetProperty(mUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, array.get(), &dataSize); if (result != noErr) { return false; } for (int i = 0; i < cnt; i++) { AudioUnitParameterInfoEx info; dataSize = sizeof(info); result = AudioUnitGetProperty(mUnit, kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, array[i], &info, &dataSize); if (result != noErr) { return false; } wxString name; if (info.flags & kAudioUnitParameterFlag_HasCFNameString) { name = wxCFStringRef::AsString(info.cfNameString); } if (name.empty()) { continue; } AudioUnitParameterValue value; result = AudioUnitGetParameter(mUnit, array[i], kAudioUnitScope_Global, 0, &value); if (result != noErr) { return false; } parms.Write(name, value); } return true; } bool AudioUnitEffect::SetAutomationParameters(CommandParameters & parms) { OSStatus result; UInt32 dataSize; Boolean isWritable; result = AudioUnitGetPropertyInfo(mUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, &dataSize, &isWritable); if (result != noErr) { return false; } UInt32 cnt = dataSize / sizeof(AudioUnitParameterID); ArrayOf array {cnt}; result = AudioUnitGetProperty(mUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, array.get(), &dataSize); if (result != noErr) { return false; } for (int i = 0; i < cnt; i++) { AudioUnitParameterInfoEx info; dataSize = sizeof(info); result = AudioUnitGetProperty(mUnit, kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, array[i], &info, &dataSize); if (result != noErr) { return false; } wxString name; if (info.flags & kAudioUnitParameterFlag_HasCFNameString) { name = wxCFStringRef::AsString(info.cfNameString); } if (name.empty()) { continue; } double d = 0.0; if (!parms.Read(name, &d)) { return false; } AudioUnitParameterValue value = d; result = AudioUnitSetParameter(mUnit, array[i], kAudioUnitScope_Global, 0, value, 0); if (result != noErr) { return false; } } AudioUnitParameter aup; aup.mAudioUnit = mUnit; aup.mParameterID = kAUParameterListener_AnyParameter; aup.mScope = kAudioUnitScope_Global; aup.mElement = 0; AUParameterListenerNotify(NULL, NULL, &aup); return true; } bool AudioUnitEffect::LoadUserPreset(const RegistryPath & name) { return LoadParameters(name); } bool AudioUnitEffect::SaveUserPreset(const RegistryPath & name) { return SaveParameters(name); } bool AudioUnitEffect::LoadFactoryPreset(int id) { OSStatus result; // Retrieve the list of factory presets CFArrayRef array{}; UInt32 dataSize = sizeof(CFArrayRef); result = AudioUnitGetProperty(mUnit, kAudioUnitProperty_FactoryPresets, kAudioUnitScope_Global, 0, &array, &dataSize); CFunique_ptr < const __CFArray > uarray { array }; if (result != noErr) { return false; } if (id < 0 || id >= CFArrayGetCount(array)) { return false; } AUPreset *preset = (AUPreset *) CFArrayGetValueAtIndex(array, id); result = AudioUnitSetProperty(mUnit, kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0, preset, sizeof(AUPreset)); if (result == noErr) { AudioUnitParameter aup; aup.mAudioUnit = mUnit; aup.mParameterID = kAUParameterListener_AnyParameter; aup.mScope = kAudioUnitScope_Global; aup.mElement = 0; AUParameterListenerNotify(NULL, NULL, &aup); } return result == noErr; } bool AudioUnitEffect::LoadFactoryDefaults() { return LoadParameters(mHost->GetFactoryDefaultsGroup()); } RegistryPaths AudioUnitEffect::GetFactoryPresets() { OSStatus result; RegistryPaths presets; // Retrieve the list of factory presets CFArrayRef array{}; UInt32 dataSize = sizeof(CFArrayRef); result = AudioUnitGetProperty(mUnit, kAudioUnitProperty_FactoryPresets, kAudioUnitScope_Global, 0, &array, &dataSize); CFunique_ptr< const __CFArray > uarray { array }; if (result == noErr) { for (CFIndex i = 0, cnt = CFArrayGetCount(array); i < cnt; i++) { AUPreset *preset = (AUPreset *) CFArrayGetValueAtIndex(array, i); presets.push_back(wxCFStringRef::AsString(preset->presetName)); } } return presets; } // ============================================================================ // EffectUIClientInterface Implementation // ============================================================================ void AudioUnitEffect::SetHostUI(EffectUIHostInterface *host) { mUIHost = host; } bool AudioUnitEffect::PopulateUI(wxWindow *parent) { // OSStatus result; mDialog = static_cast(wxGetTopLevelParent(parent)); mParent = parent; wxPanel *container; { auto mainSizer = std::make_unique(wxVERTICAL); wxASSERT(mParent); // To justify safenew container = safenew wxPanelWrapper(mParent, wxID_ANY); mainSizer->Add(container, 1, wxEXPAND); mParent->SetSizer(mainSizer.release()); } if (mUIType == wxT("Plain")) { if (!CreatePlain(mParent)) { return false; } } else { auto pControl = Destroy_ptr( safenew AUControl ); if (!pControl) { return false; } if (!pControl->Create(container, mComponent, mUnit, mUIType == wxT("Full"))) { return false; } { auto innerSizer = std::make_unique(wxVERTICAL); innerSizer->Add((mpControl = pControl.release()), 1, wxEXPAND); container->SetSizer(innerSizer.release()); } mParent->SetMinSize(wxDefaultSize); #ifdef __WXMAC__ #ifdef __WX_EVTLOOP_BUSY_WAITING__ wxEventLoop::SetBusyWaiting(true); #endif #endif } mParent->PushEventHandler(this); return true; } bool AudioUnitEffect::IsGraphicalUI() { return mUIType != wxT("Plain"); } bool AudioUnitEffect::ValidateUI() { #if 0 if (!mParent->Validate()) { return false; } if (GetType() == EffectTypeGenerate) { mHost->SetDuration(mDuration->GetValue()); } #endif return true; } bool AudioUnitEffect::CreatePlain(wxWindow *parent) { return false; } bool AudioUnitEffect::HideUI() { #if 0 if (GetType() == EffectTypeAnalyze || mNumOutputControls > 0) { return false; } #endif return true; } bool AudioUnitEffect::CloseUI() { #ifdef __WXMAC__ #ifdef __WX_EVTLOOP_BUSY_WAITING__ wxEventLoop::SetBusyWaiting(false); #endif if (mpControl) mpControl->Close(), mpControl = nullptr; #endif mParent->RemoveEventHandler(this); mUIHost = NULL; mParent = NULL; mDialog = NULL; return true; } bool AudioUnitEffect::CanExportPresets() { return true; } void AudioUnitEffect::ExportPresets() { AudioUnitEffectExportDialog dlg(mDialog, this); dlg.ShowModal(); } void AudioUnitEffect::ImportPresets() { AudioUnitEffectImportDialog dlg(mDialog, this); dlg.ShowModal(); } bool AudioUnitEffect::HasOptions() { return true; } void AudioUnitEffect::ShowOptions() { AudioUnitEffectOptionsDialog dlg(mParent, mHost); if (dlg.ShowModal()) { // Reinitialize configuration settings mHost->GetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency, true); mHost->GetSharedConfig(wxT("Options"), wxT("UIType"), mUIType, wxT("Full")); mUIType = wxGetTranslation(mUIType); } } // ============================================================================ // AudioUnitEffect Implementation // ============================================================================ bool AudioUnitEffect::LoadParameters(const RegistryPath & group) { wxString parms; if (!mHost->GetPrivateConfig(group, wxT("Parameters"), parms, wxEmptyString)) { return false; } CommandParameters eap; if (!eap.SetParameters(parms)) { return false; } return SetAutomationParameters(eap); } bool AudioUnitEffect::SaveParameters(const RegistryPath & group) { CommandParameters eap; if (!GetAutomationParameters(eap)) { return false; } wxString parms; if (!eap.GetParameters(parms)) { return false; } return mHost->SetPrivateConfig(group, wxT("Parameters"), parms); } bool AudioUnitEffect::SetRateAndChannels() { OSStatus result; if (mUnitInitialized) { AudioUnitUninitialize(mUnit); mUnitInitialized = false; } AudioStreamBasicDescription streamFormat { // Float64 mSampleRate; mSampleRate, // UInt32 mFormatID; kAudioFormatLinearPCM, // UInt32 mFormatFlags; (kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved), // UInt32 mBytesPerPacket; sizeof(float), // UInt32 mFramesPerPacket; 1, // UInt32 mBytesPerFrame; sizeof(float), // UInt32 mChannelsPerFrame; mAudioIns, // UInt32 mBitsPerChannel; sizeof(float) * 8, // UInt32 mReserved; 0 }; result = AudioUnitSetProperty(mUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Global, 0, &mSampleRate, sizeof(Float64)); if (result != noErr) { wxPrintf("%ls Didn't accept sample rate on global\n", // Exposing internal name only in debug printf GetSymbol().Internal().wx_str()); return false; } if (mAudioIns > 0) { result = AudioUnitSetProperty(mUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Input, 0, &mSampleRate, sizeof(Float64)); if (result != noErr) { wxPrintf("%ls Didn't accept sample rate on input\n", // Exposing internal name only in debug printf GetSymbol().Internal().wx_str()); return false; } result = AudioUnitSetProperty(mUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamFormat, sizeof(AudioStreamBasicDescription)); if (result != noErr) { wxPrintf("%ls didn't accept stream format on input\n", // Exposing internal name only in debug printf GetSymbol().Internal().wx_str()); return false; } } if (mAudioOuts > 0) { result = AudioUnitSetProperty(mUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &mSampleRate, sizeof(Float64)); if (result != noErr) { wxPrintf("%ls Didn't accept sample rate on output\n", // Exposing internal name only in debug printf GetSymbol().Internal().wx_str()); return false; } streamFormat.mChannelsPerFrame = mAudioOuts; result = AudioUnitSetProperty(mUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamFormat, sizeof(AudioStreamBasicDescription)); if (result != noErr) { wxPrintf("%ls didn't accept stream format on output\n", // Exposing internal name only in debug printf GetSymbol().Internal().wx_str()); return false; } } result = AudioUnitInitialize(mUnit); if (result != noErr) { wxPrintf("Couldn't initialize audio unit\n"); return false; } mUnitInitialized = true; return true; } bool AudioUnitEffect::CopyParameters(AudioUnit srcUnit, AudioUnit dstUnit) { OSStatus result; Float32 parameterValue; UInt32 size; // Get number of parameters by passing NULL in the data field and // getting back the size of the parameter list size = 0; result = AudioUnitGetProperty(srcUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, NULL, &size); if (result != 0) { wxPrintf("Couldn't get number of parameters\n"); return false; } // Now get the list of all parameter IDs auto numParameters = size / sizeof(AudioUnitParameterID); ArrayOf parameters{ numParameters }; result = AudioUnitGetProperty(srcUnit, kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0, parameters.get(), &size); if (result != 0) { wxPrintf("Couldn't get parameter list\n"); return false; } // Copy the parameters from the main unit to the unit specific to // this track for (unsigned i = 0; i < numParameters; i++) { result = AudioUnitGetParameter(srcUnit, parameters[i], kAudioUnitScope_Global, 0, ¶meterValue); if (result != 0) { wxPrintf("Couldn't get parameter %d: ID=%d\n", i, (int)parameters[i]); continue; } result = AudioUnitSetParameter(dstUnit, parameters[i], kAudioUnitScope_Global, 0, parameterValue, 0); if (result != 0) { wxPrintf("Couldn't set parameter %d: ID=%d\n", i, (int)parameters[i]); } } return true; } unsigned AudioUnitEffect::GetChannelCount() { return mNumChannels; } void AudioUnitEffect::SetChannelCount(unsigned numChannels) { mNumChannels = numChannels; } OSStatus AudioUnitEffect::Render(AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData) { for (int i = 0; i < ioData->mNumberBuffers; i++) ioData->mBuffers[i].mData = mInputList[0].mBuffers[i].mData; return 0; } // static OSStatus AudioUnitEffect::RenderCallback(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData) { return ((AudioUnitEffect *) inRefCon)->Render(inActionFlags, inTimeStamp, inBusNumber, inNumFrames, ioData); } void AudioUnitEffect::EventListener(const AudioUnitEvent *inEvent, AudioUnitParameterValue inParameterValue) { // Handle property changes if (inEvent->mEventType == kAudioUnitEvent_PropertyChange) { // We're only registered for Latency changes if (inEvent->mArgument.mProperty.mPropertyID == kAudioUnitProperty_Latency) { // Allow change to be used //mLatencyDone = false; } return; } // Only parameter changes at this point if (mMaster) { // We're a slave, so just set the parameter AudioUnitSetParameter(mUnit, inEvent->mArgument.mParameter.mParameterID, kAudioUnitScope_Global, 0, inParameterValue, 0); } else { // We're the master, so propogate for (size_t i = 0, cnt = mSlaves.size(); i < cnt; i++) mSlaves[i]->EventListener(inEvent, inParameterValue); } } // static void AudioUnitEffect::EventListenerCallback(void *inCallbackRefCon, void *inObject, const AudioUnitEvent *inEvent, UInt64 inEventHostTime, AudioUnitParameterValue inParameterValue) { ((AudioUnitEffect *) inCallbackRefCon)->EventListener(inEvent, inParameterValue); } void AudioUnitEffect::GetChannelCounts() { Boolean isWritable = 0; UInt32 dataSize = 0; OSStatus result; // Does AU have channel info result = AudioUnitGetPropertyInfo(mUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, &dataSize, &isWritable); if (result) { // None supplied. Apparently all FX type units can do any number of INs // and OUTs as long as they are the same number. In this case, we'll // just say stereo. // // We should probably check to make sure we're dealing with an FX type. mAudioIns = 2; mAudioOuts = 2; return; } ArrayOf buffer{ dataSize }; auto info = (AUChannelInfo *) buffer.get(); // Retrieve the channel info result = AudioUnitGetProperty(mUnit, kAudioUnitProperty_SupportedNumChannels, kAudioUnitScope_Global, 0, info, &dataSize); if (result) { // Oh well, not much we can do out this case mAudioIns = 2; mAudioOuts = 2; return; } // This is where it gets weird...not sure what is the best // way to do this really. If we knew how many ins/outs we // really needed, we could make a better choice. bool haven2m = false; // nothing -> mono bool haven2s = false; // nothing -> stereo bool havem2n = false; // mono -> nothing bool haves2n = false; // stereo -> nothing bool havem2m = false; // mono -> mono bool haves2s = false; // stereo -> stereo bool havem2s = false; // mono -> stereo bool haves2m = false; // stereo -> mono mAudioIns = 2; mAudioOuts = 2; // Look only for exact channel constraints for (int i = 0; i < dataSize / sizeof(AUChannelInfo); i++) { AUChannelInfo *ci = &info[i]; int ic = ci->inChannels; int oc = ci->outChannels; if (ic < 0 && oc >= 0) { ic = 2; } else if (ic >= 0 && oc < 0) { oc = 2; } else if (ic < 0 && oc < 0) { ic = 2; oc = 2; } if (ic == 2 && oc == 2) { haves2s = true; } else if (ic == 1 && oc == 1) { havem2m = true; } else if (ic == 1 && oc == 2) { havem2s = true; } else if (ic == 2 && oc == 1) { haves2m = true; } else if (ic == 0 && oc == 2) { haven2s = true; } else if (ic == 0 && oc == 1) { haven2m = true; } else if (ic == 1 && oc == 0) { havem2n = true; } else if (ic == 2 && oc == 0) { haves2n = true; } } if (haves2s) { mAudioIns = 2; mAudioOuts = 2; } else if (havem2m) { mAudioIns = 1; mAudioOuts = 1; } else if (havem2s) { mAudioIns = 1; mAudioOuts = 2; } else if (haves2m) { mAudioIns = 2; mAudioOuts = 1; } else if (haven2m) { mAudioIns = 0; mAudioOuts = 1; } else if (haven2s) { mAudioIns = 0; mAudioOuts = 2; } else if (haves2n) { mAudioIns = 2; mAudioOuts = 0; } else if (havem2n) { mAudioIns = 1; mAudioOuts = 0; } return; } #endif