audacia/src/effects/audiounits/AudioUnitEffect.cpp

2701 lines
70 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??
*//*******************************************************************/
#if USE_AUDIO_UNITS
#include "AudioUnitEffect.h"
#include "../../ModuleManager.h"
#include <wx/defs.h>
#include <wx/base64.h>
#include <wx/button.h>
#include <wx/control.h>
#include <wx/crt.h>
#include <wx/dir.h>
#include <wx/ffile.h>
#ifdef __WXMAC__
#include <wx/evtloop.h>
#endif
#include <wx/filename.h>
#include <wx/frame.h>
#include <wx/listctrl.h>
#include <wx/log.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/AudacityMessageBox.h"
#include "../../widgets/valnum.h"
#include "../../widgets/wxPanelWrapper.h"
//
// When a plug-ins state is saved to the settings file (as a preset),
// it can be one of two formats, binary or XML. In either case, it
// gets base64 encoded before storing.
//
// The advantages of XML format is less chance of failures occurring
// when exporting. But, it can take a bit more space per preset int
// the Audacity settings file.
//
// Using binary for now. Use kCFPropertyListXMLFormat_v1_0 if XML
// format is desired.
//
#define PRESET_FORMAT kCFPropertyListBinaryFormat_v1_0
// Name of the settings key to use for the above value
#define PRESET_KEY wxT("Data")
// Where the presets are located
#define PRESET_LOCAL_PATH wxT("/Library/Audio/Presets")
#define PRESET_USER_PATH wxT("~/Library/Audio/Presets")
static const struct
{
OSType componentManufacturer;
OSType componentType;
OSType componentSubType;
}
BlackList[] =
{
{ 'appl', 'augn', 'afpl' }, // Apple: AUAudioFilePlayer
{ 'appl', 'augn', 'sspl' }, // Apple: AUScheduledSoundPlayer
{ 'appl', 'augn', 'ttsp' }, // Apple: AUSpeechSynthesis
{ 'appl', 'augn', 'nrcv' }, // Apple: AUNetReceive
{ 'appl', 'aumx', '3dmx' }, // Apple: AUMixer3D
{ 'appl', 'aumx', 'mspl' }, // Apple: AUMultiSplitter
{ 'appl', 'aumx', 'mxcm' }, // Apple: AUMultiChannelMixer
{ 'appl', 'aumx', 'mxmx' }, // Apple: AUMatrixMixer
{ 'appl', 'aumx', 'smxr' }, // Apple: AUMixer
};
struct CFReleaser
{
void operator () (const void *p) const
{
if (p) CFRelease(p);
}
};
template <typename T> using CFunique_ptr = std::unique_ptr<T, CFReleaser>;
// Uncomment to include parameter IDs in the final name. Only needed if it's
// discovered that many effects have duplicate names. It could even be done
// at runtime by scanning an effects parameters to determine if dups are present
// and, if so, enable the clump and parameter IDs.
#define USE_EXTENDED_NAMES
class ParameterInfo
{
public:
ParameterInfo()
{
info = {};
}
virtual ~ParameterInfo()
{
if (info.flags & kAudioUnitParameterFlag_HasCFNameString)
{
if (info.flags & kAudioUnitParameterFlag_CFNameRelease)
{
CFRelease(info.cfNameString);
}
}
}
bool Get(AudioUnit mUnit, AudioUnitParameterID parmID)
{
OSStatus result;
UInt32 dataSize;
info = {};
dataSize = sizeof(info);
result = AudioUnitGetProperty(mUnit,
kAudioUnitProperty_ParameterInfo,
kAudioUnitScope_Global,
parmID,
&info,
&dataSize);
if (result != noErr)
{
return false;
}
if (info.flags & kAudioUnitParameterFlag_HasCFNameString)
{
name = wxCFStringRef::AsString(info.cfNameString);
}
else
{
name = wxString(info.name);
}
#if defined(USE_EXTENDED_NAMES)
// If the parameter has a non-empty name, then the final parameter name will
// be either:
//
// <parmID,ParameterName>
//
// or (if the name isn't available):
//
// <parmID>
if (!name.empty())
{
name.Replace(idBeg, wxT('_'));
name.Replace(idSep, wxT('_'));
name.Replace(idEnd, wxT('_'));
name.Append(idSep);
}
name = wxString::Format(wxT("%c%s%x%c"),
idBeg,
name,
parmID,
idEnd);
// If the parameter has a clumpID, then the final parameter name will be
// either:
//
// <clumpID,clumpName><parmID,ParameterName>
//
// or (if the clumpName isn't available):
//
// <clumpID><parmID,ParameterName>
if (info.flags & kAudioUnitParameterFlag_HasClump)
{
wxString clumpName;
AudioUnitParameterNameInfo clumpInfo = {};
clumpInfo.inID = info.clumpID;
clumpInfo.inDesiredLength = kAudioUnitParameterName_Full;
dataSize = sizeof(clumpInfo);
result = AudioUnitGetProperty(mUnit,
kAudioUnitProperty_ParameterClumpName,
kAudioUnitScope_Global,
0,
&clumpInfo,
&dataSize);
if (result == noErr)
{
clumpName = wxCFStringRef::AsString(clumpInfo.outName);
clumpName.Replace(idBeg, wxT('_'));
clumpName.Replace(idSep, wxT('_'));
clumpName.Replace(idEnd, wxT('_'));
clumpName.Append(idSep);
}
name = wxString::Format(wxT("%c%s%x%c%s"),
idBeg,
clumpName,
info.clumpID,
idEnd,
name);
}
#endif
return true;
}
static const char idBeg = wxT('<');
static const char idSep = wxT(',');
static const char idEnd = wxT('>');
wxString name;
AudioUnitParameterInfo info;
};
// ============================================================================
// 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();
}
// ============================================================================
// Register this as a builtin module
// ============================================================================
DECLARE_BUILTIN_MODULE(AudioUnitEffectsBuiltin);
///////////////////////////////////////////////////////////////////////////////
//
// AudioUnitEffectsModule
//
///////////////////////////////////////////////////////////////////////////////
AudioUnitEffectsModule::AudioUnitEffectsModule()
{
}
AudioUnitEffectsModule::~AudioUnitEffectsModule()
{
}
// ============================================================================
// ComponentInterface implementation
// ============================================================================
PluginPath AudioUnitEffectsModule::GetPath()
{
return {};
}
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;
}
TranslatableString AudioUnitEffectsModule::GetDescription()
{
return XO("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;
}
EffectFamilySymbol AudioUnitEffectsModule::GetOptionalFamilySymbol()
{
#if USE_AUDIO_UNITS
return AUDIOUNITEFFECTS_FAMILY;
#else
return {};
#endif
}
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_Mixer, effects);
LoadAudioUnitsOfType(kAudioUnitType_MusicEffect, effects);
LoadAudioUnitsOfType(kAudioUnitType_Panner, effects);
return effects;
}
unsigned AudioUnitEffectsModule::DiscoverPluginsAtPath(
const PluginPath & path, TranslatableString &errMsg,
const RegistrationCallback &callback)
{
errMsg = {};
wxString name;
AudioComponent component = FindAudioUnit(path, name);
if (component == NULL)
{
errMsg = XO("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 = XO("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;
}
std::unique_ptr<ComponentInterface>
AudioUnitEffectsModule::CreateInstance(const PluginPath & path)
{
// Acquires a resource for the application.
if (wxString name; auto component = FindAudioUnit(path, name))
return std::make_unique<AudioUnitEffect>(path, name, component);
return nullptr;
}
// ============================================================================
// 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 path;
path.Printf(wxT("%-4.4s/%-4.4s/%-4.4s/%s"),
FromOSType(found.componentManufacturer),
FromOSType(found.componentType),
FromOSType(found.componentSubType),
wxCFStringRef::AsString(cfName));
for (int i = 0; i < WXSIZEOF(BlackList); ++i)
{
if (BlackList[i].componentType == found.componentType &&
BlackList[i].componentSubType == found.componentSubType &&
BlackList[i].componentManufacturer == found.componentManufacturer)
{
wxLogDebug(wxT("Blacklisted AU skipped: %s"), path);
result = !noErr;
break;
}
}
if (result == noErr)
{
effects.push_back(path);
}
}
}
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;
TranslatableString 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, XO("Audio Unit Effect Options"))
{
mHost = host;
mHost->GetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency, true);
// Expect one of three string values from the config file
wxString uiType;
mHost->GetSharedConfig(wxT("Options"), wxT("UIType"), uiType, wxT("Full"));
// Get the localization of the string for display to the user
mUIType = TranslatableString{ uiType, {} };
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(XO("Latency Compensation"));
{
S.AddVariableText(XO(
"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."),
false, 0, 650);
S.StartHorizontalLay(wxALIGN_LEFT);
{
S.TieCheckBox(XXO("Enable &compensation"),
mUseLatency);
}
S.EndHorizontalLay();
}
S.EndStatic();
S.StartStatic(XO("User Interface"));
{
S.AddVariableText(XO(
"Select \"Full\" to use the graphical interface if supplied by the Audio Unit."
" Select \"Generic\" to use the system supplied generic interface."
#if defined(HAVE_AUDIOUNIT_BASIC_SUPPORT)
" Select \"Basic\" for a basic text-only interface."
#endif
" Reopen the effect for this to take effect."),
false, 0, 650);
S.StartHorizontalLay(wxALIGN_LEFT);
{
S.TieChoice(XXO("Select &interface"),
mUIType,
{
XO("Full"),
XO("Generic"),
#if defined(HAVE_AUDIOUNIT_BASIC_SUPPORT)
XO("Basic")
#endif
});
}
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);
// un-translate the type
auto uiType = mUIType.MSGID().GET();
mHost->SetSharedConfig(wxT("Options"), wxT("UseLatency"), mUseLatency);
mHost->SetSharedConfig(wxT("Options"), wxT("UIType"), uiType);
EndModal(wxID_OK);
}
///////////////////////////////////////////////////////////////////////////////
//
// AudioUnitEffectImportDialog
//
///////////////////////////////////////////////////////////////////////////////
class AudioUnitEffectImportDialog final : public wxDialogWrapper
{
public:
AudioUnitEffectImportDialog(wxWindow * parent, AudioUnitEffect *effect);
virtual ~AudioUnitEffectImportDialog();
void PopulateOrExchange(ShuttleGui & S);
bool HasPresets();
TranslatableString Import(const wxString & path, const wxString & name);
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, XO("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(XO("Presets (may select multiple)"));
{
mList = S.Style(wxLC_REPORT | wxLC_HRULES | wxLC_VRULES |
wxLC_NO_SORT_HEADER)
.AddListControlReportMode({ XO("Preset"), XO("Location") });
}
S.EndStatic();
}
S.EndVerticalLay();
}
S.EndHorizontalLay();
S.AddStandardButtons();
FilePaths presets;
wxFileName fn;
// Generate the local domain path
wxString path;
path.Printf(wxT("%s/%s/%s"),
PRESET_LOCAL_PATH,
mEffect->mVendor,
mEffect->mName);
fn = path;
fn.Normalize();
// Get all presets in the local domain for this effect
wxDir::GetAllFiles(fn.GetFullPath(), &presets, wxT("*.aupreset"));
// Generate the user domain path
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();
}
bool AudioUnitEffectImportDialog::HasPresets()
{
return mList->GetItemCount() > 0;
}
TranslatableString AudioUnitEffectImportDialog::Import(
const wxString & path, const wxString & name)
{
// Generate the path
wxString fullPath;
fullPath.Printf(wxT("%s/%s.aupreset"),
path,
name);
// Open the preset
wxFFile f(fullPath, wxT("r"));
if (!f.IsOpened())
{
return XO("Couldn't open \"%s\"").Format(fullPath);
}
// Load it into the buffer
size_t len = f.Length();
wxMemoryBuffer buf(len);
if (f.Read(buf.GetData(), len) != len || f.Error())
{
return XO("Unable to read the preset from \"%s\"").Format(fullPath);
}
wxString parms = wxBase64Encode(buf.GetData(), len);
if (parms.IsEmpty())
{
return XO("Failed to encode preset from \"%s\"").Format(fullPath);
}
// And write it to the config
wxString group = mEffect->mHost->GetUserPresetsGroup(name);
if (!mEffect->mHost->SetPrivateConfig(group, PRESET_KEY, parms))
{
return XO("Unable to store preset in config file");
}
return {};
}
void AudioUnitEffectImportDialog::OnOk(wxCommandEvent & evt)
{
evt.Skip();
// Import all 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 = item.GetText();
wxString name = mList->GetItemText(sel);
auto msg = Import(path, name);
if (!msg.empty())
{
AudacityMessageBox(
XO("Could not import \"%s\" preset\n\n%s").Format(name, msg),
XO("Import Audio Unit Presets"),
wxOK | wxCENTRE,
this);
return;
}
}
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;
mpControl = NULL;
mUnit = NULL;
mBlockSize = 0.0;
mInteractive = false;
mIsGraphical = false;
mUIHost = NULL;
mDialog = NULL;
mParent = NULL;
mUnitInitialized = false;
mEventListenerRef = NULL;
mReady = false;
}
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);
}
TranslatableString AudioUnitEffect::GetDescription()
{
/* i18n-hint: Can mean "not available," "not applicable," "no answer" */
return XO("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++)
{
ParameterInfo pi;
if (pi.Get(mUnit, array[i]))
{
if (pi.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"));
bool haveDefaults;
mHost->GetPrivateConfig(mHost->GetFactoryDefaultsGroup(), wxT("Initialized"), haveDefaults, false);
if (!haveDefaults)
{
SavePreset(mHost->GetFactoryDefaultsGroup());
mHost->SetPrivateConfig(mHost->GetFactoryDefaultsGroup(), wxT("Initialized"), true);
}
LoadPreset(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 parameters
result = AudioUnitGetPropertyInfo(mUnit,
kAudioUnitProperty_ParameterList,
kAudioUnitScope_Global,
0,
&dataSize,
&isWritable);
if (result != noErr)
{
return false;
}
// And get them
UInt32 cnt = dataSize / sizeof(AudioUnitParameterID);
if (cnt != 0)
{
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;
}
size_t AudioUnitEffect::GetBlockSize() const
{
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;
}
if (!BypassEffect(false))
{
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()
{
if (!BypassEffect(true))
{
return false;
}
for (size_t i = 0, cnt = mSlaves.size(); i < cnt; i++)
{
if (!mSlaves[i]->BypassEffect(true))
{
return false;
}
}
return true;
}
bool AudioUnitEffect::RealtimeResume()
{
if (!BypassEffect(false))
{
return false;
}
for (size_t i = 0, cnt = mSlaves.size(); i < cnt; i++)
{
if (!mSlaves[i]->BypassEffect(false))
{
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,
const EffectDialogFactory &factory,
bool forceModal)
{
if (mDialog)
{
if (mDialog->Close(true))
{
mDialog = nullptr;
}
return false;
}
// mDialog is null
auto cleanup = valueRestorer(mDialog);
if (factory)
{
mDialog = factory(parent, mHost, 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++)
{
ParameterInfo pi;
if (!pi.Get(mUnit, array[i]))
{
// Probably failed because of invalid parameter which can happen
// if a plug-in is in a certain mode that doesn't contain the
// parameter. In any case, just ignore it.
continue;
}
AudioUnitParameterValue value;
result = AudioUnitGetParameter(mUnit,
array[i],
kAudioUnitScope_Global,
0,
&value);
if (result != noErr)
{
// Probably failed because of invalid parameter which can happen
// if a plug-in is in a certain mode that doesn't contain the
// parameter. In any case, just ignore it.
continue;
}
parms.Write(pi.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++)
{
ParameterInfo pi;
if (!pi.Get(mUnit, array[i]))
{
// Probably failed because of invalid parameter which can happen
// if a plug-in is in a certain mode that doesn't contain the
// parameter. In any case, just ignore it.
continue;
}
double d = 0.0;
if (parms.Read(pi.name, &d))
{
AudioUnitParameterValue value = d;
AudioUnitSetParameter(mUnit,
array[i],
kAudioUnitScope_Global,
0,
value,
0);
Notify(mUnit, array[i]);
}
}
return true;
}
bool AudioUnitEffect::LoadUserPreset(const RegistryPath & name)
{
return LoadPreset(name);
}
bool AudioUnitEffect::SaveUserPreset(const RegistryPath & name)
{
return SavePreset(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;
}
const AUPreset *preset = (const AUPreset *) CFArrayGetValueAtIndex(array, id);
result = AudioUnitSetProperty(mUnit,
kAudioUnitProperty_PresentPreset,
kAudioUnitScope_Global,
0,
preset,
sizeof(AUPreset));
if (result == noErr)
{
// Notify interested parties of change and propagate to slaves
Notify(mUnit, kAUParameterListener_AnyParameter);
}
return result == noErr;
}
bool AudioUnitEffect::LoadFactoryDefaults()
{
return LoadPreset(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(ShuttleGui &S)
{
// OSStatus result;
auto parent = S.GetParent();
mDialog = static_cast<wxDialog *>(wxGetTopLevelParent(parent));
mParent = parent;
mpControl = NULL;
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 defined(HAVE_AUDIOUNIT_BASIC_SUPPORT)
if (mUIType == wxT("Basic"))
{
if (!CreatePlain(mParent))
{
return false;
}
}
else
#endif
{
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
}
if (mpControl)
{
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;
}
#if defined(HAVE_AUDIOUNIT_BASIC_SUPPORT)
bool AudioUnitEffect::CreatePlain(wxWindow *parent)
{
// TODO??? Never implemented...
return false;
}
#endif
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)
{
mParent->RemoveEventHandler(this);
mpControl->Close();
mpControl = nullptr;
}
#endif
mUIHost = NULL;
mParent = NULL;
mDialog = NULL;
return true;
}
bool AudioUnitEffect::CanExportPresets()
{
return true;
}
void AudioUnitEffect::ExportPresets()
{
// Generate the user domain path
wxFileName fn;
fn.SetPath(PRESET_USER_PATH);
fn.AppendDir(mVendor);
fn.AppendDir(mName);
fn.Normalize();
FilePath path = fn.GetFullPath();
if (!fn.Mkdir(fn.GetFullPath(), 0755, wxPATH_MKDIR_FULL))
{
wxLogError(wxT("Couldn't create the \"%s\" directory"), fn.GetPath());
return;
}
// Ask the user for the name to use
//
// Passing a valid parent will cause some effects dialogs to malfunction
// upon returning from the FileNames::SelectFile().
path = FileNames::SelectFile(FileNames::Operation::_None,
XO("Export Audio Unit Preset As %s:").Format(fn.GetFullPath()),
fn.GetFullPath(),
wxEmptyString,
wxT("aupreset"),
{
{ XO("Standard Audio Unit preset file"), { wxT("aupreset") }, true },
},
wxFD_SAVE | wxFD_OVERWRITE_PROMPT | wxRESIZE_BORDER,
NULL);
// User canceled...
if (path.empty())
{
return;
}
auto msg = Export(path);
if (!msg.empty())
{
AudacityMessageBox(
XO("Could not export \"%s\" preset\n\n%s").Format(path, msg),
XO("Export Audio Unit Presets"),
wxOK | wxCENTRE,
mParent);
}
}
void AudioUnitEffect::ImportPresets()
{
// Generate the user domain path
wxFileName fn;
fn.SetPath(PRESET_USER_PATH);
fn.AppendDir(mVendor);
fn.AppendDir(mName);
fn.Normalize();
FilePath path = fn.GetFullPath();
// Ask the user for the name to use
//
// Passing a valid parent will cause some effects dialogs to malfunction
// upon returning from the FileNames::SelectFile().
path = FileNames::SelectFile(FileNames::Operation::_None,
XO("Import Audio Unit Preset As %s:").Format(fn.GetFullPath()),
fn.GetFullPath(),
wxEmptyString,
wxT("aupreset"),
{
{ XO("Standard Audio Unit preset file"), { wxT("aupreset") }, true },
},
wxFD_OPEN | wxRESIZE_BORDER,
NULL);
// User canceled...
if (path.empty())
{
return;
}
auto msg = Import(path);
if (!msg.empty())
{
AudacityMessageBox(
XO("Could not import \"%s\" preset\n\n%s").Format(path, msg),
XO("Import Audio Unit Presets"),
wxOK | wxCENTRE,
mParent);
}
}
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"));
}
}
// ============================================================================
// AudioUnitEffect Implementation
// ============================================================================
bool AudioUnitEffect::LoadPreset(const RegistryPath & group)
{
wxString parms;
// Attempt to load old preset parameters and resave using new method
if (mHost->GetPrivateConfig(group, wxT("Parameters"), parms, wxEmptyString))
{
CommandParameters eap;
if (eap.SetParameters(parms))
{
if (SetAutomationParameters(eap))
{
if (SavePreset(group))
{
mHost->RemovePrivateConfig(group, wxT("Parameters"));
}
}
}
return true;
}
// Retrieve the preset
if (!mHost->GetPrivateConfig(group, PRESET_KEY, parms, wxEmptyString))
{
// Commented "CurrentSettings" gets tried a lot and useless messages appear
// in the log
//wxLogError(wxT("Preset key \"%s\" not found in group \"%s\""), PRESET_KEY, group);
return false;
}
// Decode it
wxMemoryBuffer buf = wxBase64Decode(parms);
size_t bufLen = buf.GetDataLen();
if (!bufLen)
{
wxLogError(wxT("Failed to decode \"%s\" preset"), group);
return false;
}
const uint8_t *bufPtr = (uint8_t *) buf.GetData();
// Create a CFData object that references the decoded preset
CFunique_ptr<const __CFData> data
{
CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
bufPtr,
bufLen,
kCFAllocatorNull)
};
if (!data)
{
wxLogError(wxT("Failed to convert \"%s\" preset to internal format"), group);
return false;
}
// Convert it back to a property list.
CFPropertyListRef content
{
CFPropertyListCreateWithData(kCFAllocatorDefault,
data.get(),
kCFPropertyListImmutable,
NULL,
NULL)
};
if (!content)
{
wxLogError(wxT("Failed to create property list for \"%s\" preset"), group);
return false;
}
CFunique_ptr<char /* CFPropertyList */> ucontent { (char *) content };
// See AUView::viewWillDraw
if (mpControl)
{
mpControl->ForceRedraw();
}
// Finally, update the properties and parameters
OSStatus result;
result = AudioUnitSetProperty(mUnit,
kAudioUnitProperty_ClassInfo,
kAudioUnitScope_Global,
0,
&content,
sizeof(content));
if (result != noErr)
{
wxLogError(wxT("Failed to set class info for \"%s\" preset"), group);
return false;
}
// Notify interested parties of change and propagate to slaves
Notify(mUnit, kAUParameterListener_AnyParameter);
return true;
}
bool AudioUnitEffect::SavePreset(const RegistryPath & group)
{
// First set the name of the preset
wxCFStringRef cfname(wxFileNameFromPath(group));
// Define the preset property
AUPreset preset;
preset.presetNumber = -1; // indicates user preset
preset.presetName = cfname;
// And set it in the audio unit
AudioUnitSetProperty(mUnit,
kAudioUnitProperty_PresentPreset,
kAudioUnitScope_Global,
0,
&preset,
sizeof(preset));
// Now retrieve the preset content
CFPropertyListRef content;
UInt32 size = sizeof(content);
AudioUnitGetProperty(mUnit,
kAudioUnitProperty_ClassInfo,
kAudioUnitScope_Global,
0,
&content,
&size);
CFunique_ptr<char /* CFPropertyList */> ucontent { (char *) content };
// And convert it to serialized binary data
CFunique_ptr<const __CFData> data
{
CFPropertyListCreateData(kCFAllocatorDefault,
content,
PRESET_FORMAT,
0,
NULL)
};
if (!data)
{
return false;
}
// Nothing to do if we don't have any data
SInt32 length = CFDataGetLength(data.get());
if (length)
{
// Base64 encode the returned binary property list
wxString parms = wxBase64Encode(CFDataGetBytePtr(data.get()), length);
// And write it to the config
if (!mHost->SetPrivateConfig(group, PRESET_KEY, parms))
{
return false;
}
}
return true;
}
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;
// Retrieve the class state from the source AU
CFPropertyListRef content;
UInt32 size = sizeof(content);
result = AudioUnitGetProperty(srcUnit,
kAudioUnitProperty_ClassInfo,
kAudioUnitScope_Global,
0,
&content,
&size);
if (result != noErr)
{
return false;
}
// Make sure it get's freed
CFunique_ptr<char /* CFPropertyList */> ucontent { (char *) content };
// Set the destination AUs state from the source AU's content
result = AudioUnitSetProperty(dstUnit,
kAudioUnitProperty_ClassInfo,
kAudioUnitScope_Global,
0,
&content,
sizeof(content));
if (result != noErr)
{
return false;
}
// Notify interested parties
Notify(dstUnit, kAUParameterListener_AnyParameter);
return true;
}
unsigned AudioUnitEffect::GetChannelCount()
{
return mNumChannels;
}
void AudioUnitEffect::SetChannelCount(unsigned numChannels)
{
mNumChannels = numChannels;
}
TranslatableString AudioUnitEffect::Export(const wxString & path)
{
// Create the file
wxFFile f(path, wxT("wb"));
if (!f.IsOpened())
{
return XO("Couldn't open \"%s\"").Format(path);
}
// First set the name of the preset
wxCFStringRef cfname(wxFileName(path).GetName());
// Define the preset property
AUPreset preset;
preset.presetNumber = -1; // indicates user preset
preset.presetName = cfname;
// And set it in the audio unit
OSStatus result;
result = AudioUnitSetProperty(mUnit,
kAudioUnitProperty_PresentPreset,
kAudioUnitScope_Global,
0,
&preset,
sizeof(preset));
if (result != noErr)
{
return XO("Failed to set preset name");
}
// Now retrieve the preset content
CFPropertyListRef content;
UInt32 size = sizeof(content);
result = AudioUnitGetProperty(mUnit,
kAudioUnitProperty_ClassInfo,
kAudioUnitScope_Global,
0,
&content,
&size);
CFunique_ptr<char /* CFPropertyList */> ucontent { (char *) content };
if (result != noErr)
{
return XO("Failed to retrieve preset content");
}
// And convert it to serialized XML data
CFunique_ptr<const __CFData> data
{
CFPropertyListCreateData(kCFAllocatorDefault,
content,
kCFPropertyListXMLFormat_v1_0,
0,
NULL)
};
if (!data)
{
return XO("Failed to convert property list to XML data");
}
// Nothing to do if we don't have any data
SInt32 length = CFDataGetLength(data.get());
if (!length)
{
return XO("XML data is empty after conversion");
}
// Write XML data
if (f.Write(CFDataGetBytePtr(data.get()), length) != length || f.Error())
{
return XO("Failed to write XML preset to \"%s\"").Format(path);
}
f.Close();
return {};
}
TranslatableString AudioUnitEffect::Import(const wxString & path)
{
// Open the preset
wxFFile f(path, wxT("r"));
if (!f.IsOpened())
{
return XO("Couldn't open \"%s\"").Format(path);
}
// Load it into the buffer
size_t len = f.Length();
wxMemoryBuffer buf(len);
if (f.Read(buf.GetData(), len) != len || f.Error())
{
return XO("Unable to read the preset from \"%s\"").Format(path);
}
// Create a CFData object that references the decoded preset
CFunique_ptr<const __CFData> data
{
CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
(const UInt8 *) buf.GetData(),
len,
kCFAllocatorNull)
};
if (!data)
{
return XO("Failed to convert preset to internal format");
}
// Convert it back to a property list.
CFPropertyListRef content
{
CFPropertyListCreateWithData(kCFAllocatorDefault,
data.get(),
kCFPropertyListImmutable,
NULL,
NULL)
};
if (!content)
{
return XO("Failed to create property list for preset");
}
CFunique_ptr<char /* CFPropertyList */> ucontent { (char *) content };
// Finally, update the properties and parameters
OSStatus result;
result = AudioUnitSetProperty(mUnit,
kAudioUnitProperty_ClassInfo,
kAudioUnitScope_Global,
0,
&content,
sizeof(content));
if (result != noErr)
{
return XO("Failed to set class info for \"%s\" preset");
}
// Notify interested parties of change and propagate to slaves
Notify(mUnit, kAUParameterListener_AnyParameter);
return {};
}
void AudioUnitEffect::Notify(AudioUnit unit, AudioUnitParameterID parm)
{
// Notify any interested parties
AudioUnitParameter aup = {};
aup.mAudioUnit = unit;
aup.mParameterID = parm;
aup.mScope = kAudioUnitScope_Global;
aup.mElement = 0;
AUParameterListenerNotify(NULL, NULL, &aup);
}
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)
{
// Handle 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 propagate
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;
}
bool AudioUnitEffect::BypassEffect(bool bypass)
{
OSStatus result;
UInt32 value = (bypass ? 1 : 0);
result = AudioUnitSetProperty(mUnit,
kAudioUnitProperty_BypassEffect,
kAudioUnitScope_Global,
0,
&value,
sizeof(value));
if (result != noErr)
{
return false;
}
return true;
}
#endif