1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-16 16:10:06 +02:00
audacity/src/effects/audiounits/AudioUnitEffect.cpp
Paul Licameli 5e7d41ec07 Each .cpp/.mm file includes corresponding header before any other...
... except Audacity.h

This forces us to make each header contain all forward declarations or nested
headers that it requires, rather than depend on context.
2019-03-17 22:54:52 -04:00

2347 lines
61 KiB
C++

/**********************************************************************
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"
#if USE_AUDIO_UNITS
#include "AudioUnitEffect.h"
#include <wx/defs.h>
#include <wx/button.h>
#include <wx/control.h>
#include <wx/dir.h>
#include <wx/crt.h>
#ifdef __WXMAC__
#include <wx/evtloop.h>
#endif
#include <wx/filename.h>
#include <wx/frame.h>
#include <wx/listctrl.h>
#include <wx/sizer.h>
#include <wx/settings.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/tokenzr.h>
#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 <typename T>
using CFunique_ptr = std::unique_ptr<T, CFReleaser>;
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()
{
/* 18n-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
// ============================================================================
FileExtensions AudioUnitEffectsModule::GetFileExtensions()
{
return {{ _T("au") }};
}
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<AudioUnitEffect *>(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<const __CFString> 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<const __CFData> xml {
CFPropertyListCreateXMLData(kCFAllocatorDefault, content)
};
if (xml)
{
// Create the CFURL for the path
CFunique_ptr<const __CFURL> 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<const __CFURL> 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<const __CFData> uxml { xml };
if (!res)
{
continue;
}
CFunique_ptr<char> 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<AudioUnitParameterID> 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<AudioUnitEffect>(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<float**>(mMasterIn.get()),
reinterpret_cast<float**>(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<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;
}
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<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;
}
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<wxDialog *>(wxGetTopLevelParent(parent));
mParent = parent;
wxPanel *container;
{
auto mainSizer = std::make_unique<wxBoxSizer>(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<AUControl>( safenew AUControl );
if (!pControl)
{
return false;
}
if (!pControl->Create(container, mComponent, mUnit, mUIType == wxT("Full")))
{
return false;
}
{
auto innerSizer = std::make_unique<wxBoxSizer>(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<AudioUnitParameterID> 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,
&parameterValue);
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<char> 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