/**********************************************************************

  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
