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

  Audacity: A Digital Audio Editor

  VSTWrapper.cpp

  Dominic Mazzoni

  Paul Licameli split from VSTEffect.cpp

  This class implements a VST Plug-in effect.  The plug-in must be
  loaded in a platform-specific way and passed into the constructor,
  but from here this class handles the interfacing.

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

#include "VSTWrapper.h"

#include <wx/log.h>
#include <wx/time.h>

#if defined(__WXMSW__)
#include <shlwapi.h>
#pragma comment(lib, "shlwapi")
#else
#include <dlfcn.h>
#endif

#if defined(__WXMAC__)
#include <wx/osx/core/private.h>
#endif

#include <cstring>

#include "BasicUI.h"
#include "FileNames.h"
#include "XMLFileReader.h"
#include "Base64.h"

static float reinterpretAsFloat(uint32_t x)
{
    static_assert(sizeof(float) == sizeof(uint32_t), "Cannot reinterpret uint32_t to float since sizes are different.");
    float f;
    std::memcpy(&f, &x, sizeof(float));
    return f;
}

static uint32_t reinterpretAsUint32(float f)
{
    static_assert(sizeof(float) == sizeof(uint32_t), "Cannot reinterpret float to uint32_t since sizes are different.");

    uint32_t x;
    std::memcpy(&x, &f, sizeof(uint32_t));
    return x;
}

typedef AEffect *(*vstPluginMain)(audioMasterCallback audioMaster);

intptr_t VSTWrapper::AudioMaster(AEffect * effect,
                                       int32_t opcode,
                                       int32_t index,
                                       intptr_t value,
                                       void * ptr,
                                       float opt)
{
   VSTWrapper* vst = (effect ? static_cast<VSTWrapper*>(effect->ptr2) : nullptr);

   // Handles operations during initialization...before VSTEffect has had a
   // chance to set its instance pointer.
   switch (opcode)
   {
      case audioMasterVersion:
         return (intptr_t) 2400;

      case audioMasterCurrentId:
         return vst->mCurrentEffectID;

      case audioMasterGetVendorString:
         strcpy((char *) ptr, "Audacity Team");    // Do not translate, max 64 + 1 for null terminator
         return 1;

      case audioMasterGetProductString:
         strcpy((char *) ptr, "Audacity");         // Do not translate, max 64 + 1 for null terminator
         return 1;

      case audioMasterGetVendorVersion:
         return (intptr_t) (AUDACITY_VERSION << 24 |
                            AUDACITY_RELEASE << 16 |
                            AUDACITY_REVISION << 8 |
                            AUDACITY_MODLEVEL);

      // Some (older) effects depend on an effIdle call when requested.  An
      // example is the Antress Modern plugins which uses the call to update
      // the editors display when the program (preset) changes.
      case audioMasterNeedIdle:
         if (vst)
         {
            vst->NeedIdle();
            return 1;
         }
         return 0;

      // We would normally get this if the effect editor is dipslayed and something "major"
      // has changed (like a program change) instead of multiple automation calls.
      // Since we don't do anything with the parameters while the editor is displayed,
      // there's no need for us to do anything.
      case audioMasterUpdateDisplay:
         if (vst)
         {
            return 1;
         }
         return 0;

      // Return the current time info.
      case audioMasterGetTime:
         if (vst)
         {
            return (intptr_t) vst->GetTimeInfo();
         }
         return 0;

      // Inputs, outputs, or initial delay has changed...all we care about is initial delay.
      case audioMasterIOChanged:
         if (vst)
         {
            vst->SetBufferDelay(effect->initialDelay);
            return 1;
         }
         return 0;

      case audioMasterGetSampleRate:
         if (vst)
         {
            return (intptr_t) vst->GetSampleRate();
         }
         return 0;

      case audioMasterIdle:
         if (vst)
            vst->Idle();
         return 1;

      case audioMasterGetCurrentProcessLevel:
         if (vst)
         {
            return vst->GetProcessLevel();
         }
         return 0;

      case audioMasterGetLanguage:
         return kVstLangEnglish;

      // We always replace, never accumulate
      case audioMasterWillReplaceOrAccumulate:
         return 1;

      // Resize the window to accommodate the effect size
      case audioMasterSizeWindow:
         if (vst)
         {
            vst->SizeWindow(index, value);
         }
         return 1;

      case audioMasterCanDo:
      {
         char *s = (char *) ptr;
         if (strcmp(s, "acceptIOChanges") == 0 ||
            strcmp(s, "sendVstTimeInfo") == 0 ||
            strcmp(s, "startStopProcess") == 0 ||
            strcmp(s, "shellCategory") == 0 ||
            strcmp(s, "sizeWindow") == 0)
         {
            return 1;
         }

#if defined(VST_DEBUG)
#if defined(__WXMSW__)
         wxLogDebug(wxT("VST canDo: %s"), wxString::FromAscii((char *)ptr));
#else
         wxPrintf(wxT("VST canDo: %s\n"), wxString::FromAscii((char *)ptr));
#endif
#endif

         return 0;
      }

      case audioMasterBeginEdit:
      case audioMasterEndEdit:
         return 0;

      case audioMasterAutomate:
         if (vst)
         {
            vst->Automate(index, opt);
         }
         return 0;

      // We're always connected (sort of)
      case audioMasterPinConnected:

      // We don't do MIDI yet
      case audioMasterWantMidi:
      case audioMasterProcessEvents:

         // Don't need to see any messages about these
         return 0;
   }

#if defined(VST_DEBUG)
#if defined(__WXMSW__)
   wxLogDebug(wxT("vst: %p opcode: %d index: %d value: %p ptr: %p opt: %f user: %p"),
              effect, (int) opcode, (int) index, (void *) value, ptr, opt, vst);
#else
   wxPrintf(wxT("vst: %p opcode: %d index: %d value: %p ptr: %p opt: %f user: %p\n"),
            effect, (int) opcode, (int) index, (void *) value, ptr, opt, vst);
#endif
#endif

   return 0;
}

#if !defined(__WXMSW__)
void VSTWrapper::ModuleDeleter::operator() (void* p) const
{
   if (p)
      dlclose(p);
}
#endif

#if defined(__WXMAC__)
void VSTWrapper::ResourceHandle::reset()
{
   if (mpHandle)
      CFBundleCloseBundleResourceMap(mpHandle, mNum);
   mpHandle = nullptr;
   mNum = 0;
}
#endif

VSTMessage::~VSTMessage() = default;

auto VSTMessage::Clone() const -> std::unique_ptr<Message>
{
   auto result = std::make_unique<VSTMessage>(*this);
   // Make sure of the chunk capacity
   result->mChunk.reserve(this->mChunk.capacity());

   return result;
}

void VSTMessage::Assign(Message && src)
{
   VSTMessage& vstSrc = static_cast<VSTMessage&>(src);

   mChunk = vstSrc.mChunk;
   vstSrc.mChunk.resize(0);     // capacity will be preserved though

   assert(mParamsVec.size() == vstSrc.mParamsVec.size());

   for (size_t i = 0; i < mParamsVec.size(); i++)
   {
      mParamsVec[i] = vstSrc.mParamsVec[i];

      // consume the source value
      vstSrc.mParamsVec[i] = std::nullopt;
   }
}

void VSTMessage::Merge(Message && src)
{
   VSTMessage& vstSrc = static_cast<VSTMessage&>(src);

   bool chunkWasAssigned = false;

   if ( ! vstSrc.mChunk.empty() )
   {
      mChunk = vstSrc.mChunk;
      chunkWasAssigned = true;
   }

   vstSrc.mChunk.resize(0);  // capacity will be preserved though

   assert(mParamsVec.size() == vstSrc.mParamsVec.size());

   for (size_t i = 0; i < mParamsVec.size(); i++)
   {
      if (chunkWasAssigned)
      {
         mParamsVec[i] = vstSrc.mParamsVec[i];
      }
      else
      {
         // if src val is nullopt, do not copy it to dest
         if (vstSrc.mParamsVec[i] != std::nullopt)
         {
            mParamsVec[i] = vstSrc.mParamsVec[i];
         }
      }

      // consume the source value
      vstSrc.mParamsVec[i] = std::nullopt;
   }

}

bool VSTWrapper::Load()
{
   vstPluginMain pluginMain;
   bool success = false;

   long effectID = 0;
   wxString realPath = mPath.BeforeFirst(wxT(';'));
   mPath.AfterFirst(wxT(';')).ToLong(&effectID);
   mCurrentEffectID = (intptr_t) effectID;

   mModule = NULL;
   mAEffect = NULL;

#if defined(__WXMAC__)
   // Start clean
   mBundleRef.reset();

   mResource = ResourceHandle{};

   // Convert the path to a CFSTring
   wxCFStringRef path(realPath);

   // Create the bundle using the URL
   BundleHandle bundleRef{ CFBundleCreate(kCFAllocatorDefault,
      // Convert the path to a URL
      CF_ptr<CFURLRef>{
         CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
            path, kCFURLPOSIXPathStyle, true)
      }.get()
   )};

   // Bail if the bundle wasn't created
   if (!bundleRef)
      return false;


   // Convert back to path
   UInt8 exePath[PLATFORM_MAX_PATH];
   Boolean good = CFURLGetFileSystemRepresentation(
      // Retrieve a reference to the executable
      CF_ptr<CFURLRef>{ CFBundleCopyExecutableURL(bundleRef.get()) }.get(),
      true, exePath, sizeof(exePath)
   );

   // Bail if we couldn't resolve the executable path
   if (good == FALSE)
      return false;

   // Attempt to open it
   mModule.reset((char*)dlopen((char *) exePath, RTLD_NOW | RTLD_LOCAL));
   if (!mModule)
      return false;

   // Try to locate the NEW plugin entry point
   pluginMain = (vstPluginMain) dlsym(mModule.get(), "VSTPluginMain");

   // If not found, try finding the old entry point
   if (pluginMain == NULL)
   {
      pluginMain = (vstPluginMain) dlsym(mModule.get(), "main_macho");
   }

   // Must not be a VST plugin
   if (pluginMain == NULL)
   {
      mModule.reset();
      return false;
   }

   // Need to keep the bundle reference around so we can map the
   // resources.
   mBundleRef = std::move(bundleRef);

   // Open the resource map ... some plugins (like GRM Tools) need this.
   mResource = ResourceHandle{
      mBundleRef.get(), CFBundleOpenBundleResourceMap(mBundleRef.get())
   };

#elif defined(__WXMSW__)

   {
      wxLogNull nolog;

      // Try to load the library
      auto lib = std::make_unique<wxDynamicLibrary>(realPath);
      if (!lib)
         return false;

      // Bail if it wasn't successful
      if (!lib->IsLoaded())
         return false;

      // Try to find the entry point, while suppressing error messages
      pluginMain = (vstPluginMain) lib->GetSymbol(wxT("VSTPluginMain"));
      if (pluginMain == NULL)
      {
         pluginMain = (vstPluginMain) lib->GetSymbol(wxT("main"));
         if (pluginMain == NULL)
            return false;
      }

      // Save the library reference
      mModule = std::move(lib);
   }

#else

   // Attempt to load it
   //
   // Spent a few days trying to figure out why some VSTs where running okay and
   // others were hit or miss.  The cause was that we export all of Audacity's
   // symbols and some of the loaded libraries were picking up Audacity's and
   // not their own.
   //
   // So far, I've only seen this issue on Linux, but we might just be getting
   // lucky on the Mac and Windows.  The sooner we stop exporting everything
   // the better.
   //
   // To get around the problem, I just added the RTLD_DEEPBIND flag to the load
   // and that "basically" puts Audacity last when the loader needs to resolve
   // symbols.
   //
   // Once we define a proper external API, the flags can be removed.
#ifndef RTLD_DEEPBIND
#define RTLD_DEEPBIND 0
#endif
   ModuleHandle lib {
      (char*) dlopen((const char *)wxString(realPath).ToUTF8(),
                     RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND)
   };
   if (!lib)
   {
      return false;
   }

   // Try to find the entry point, while suppressing error messages
   pluginMain = (vstPluginMain) dlsym(lib.get(), "VSTPluginMain");
   if (pluginMain == NULL)
   {
      pluginMain = (vstPluginMain) dlsym(lib.get(), "main");
      if (pluginMain == NULL)
         return false;
   }

   // Save the library reference
   mModule = std::move(lib);

#endif

   // Initialize the plugin
   try
   {
      mAEffect = pluginMain(VSTWrapper::AudioMaster);
   }
   catch (...)
   {
      wxLogMessage(wxT("VST plugin initialization failed\n"));
      mAEffect = NULL;
   }

   // Was it successful?
   if (mAEffect)
   {
      mGui = (mAEffect->flags & effFlagsHasEditor);

      // Must use the GUI editor if parameters aren't provided
      if (mAEffect->numParams == 0)
      {
         mGui = true;
      }

      // Save a reference to ourselves
      //
      // Note:  Some hosts use "user" and some use "ptr2/resvd2".  It might
      //        be worthwhile to check if user is NULL before using it and
      //        then falling back to "ptr2/resvd2".
      mAEffect->ptr2 = static_cast<VSTWrapper*>(this);

      // Give the plugin an initial sample rate and blocksize
      callDispatcher(effSetSampleRate, 0, 0, NULL, 48000.0);
      callDispatcher(effSetBlockSize, 0, 512, NULL, 0);

      // Ask the plugin to identify itself...might be needed for older plugins
      callDispatcher(effIdentify, 0, 0, NULL, 0);

      // Open the plugin
      callDispatcher(effOpen, 0, 0, NULL, 0.0);

      // Get the VST version the plugin understands
      mVstVersion = callDispatcher(effGetVstVersion, 0, 0, NULL, 0);

      // Set it again in case plugin ignored it before the effOpen
      callDispatcher(effSetSampleRate, 0, 0, NULL, 48000.0);
      callDispatcher(effSetBlockSize, 0, 512, NULL, 0);

      // Ensure that it looks like a plugin and can deal with ProcessReplacing
      // calls.  Also exclude synths for now.
      if (mAEffect->magic == kEffectMagic &&
         !(mAEffect->flags & effFlagsIsSynth) &&
         mAEffect->flags & effFlagsCanReplacing)
      {
         if (mVstVersion >= 2)
         {
            mName = GetString(effGetEffectName);
            if (mName.length() == 0)
            {
               mName = GetString(effGetProductString);
            }
         }
         if (mName.length() == 0)
         {
            mName = wxFileName{realPath}.GetName();
         }

         if (mVstVersion >= 2)
         {
            mVendor = GetString(effGetVendorString);
            mVersion = wxINT32_SWAP_ON_LE(callDispatcher(effGetVendorVersion, 0, 0, NULL, 0));
         }
         if (mVersion == 0)
         {
            mVersion = wxINT32_SWAP_ON_LE(mAEffect->version);
         }

         if (mAEffect->flags & effFlagsHasEditor || mAEffect->numParams != 0)
         {
            mInteractive = true;
         }

         mAudioIns = mAEffect->numInputs;
         mAudioOuts = mAEffect->numOutputs;

         // Check to see if parameters can be automated.  This isn't a guarantee
         // since it could be that the effect simply doesn't support the opcode.
         mAutomatable = false;
         for (int i = 0; i < mAEffect->numParams; i++)
         {
            if (callDispatcher(effCanBeAutomated, 0, i, NULL, 0.0))
            {
               mAutomatable = true;
               break;
            }
         }

         // Make sure we start out with a valid program selection
         // I've found one plugin (SoundHack +morphfilter) that will
         // crash Audacity when saving the initial default parameters
         // with this.
         callSetProgram(0);

         // Pretty confident that we're good to go
         success = true;
      }
   }

   if (!success)
   {
      Unload();
      ResetModuleAndHandle();
   }

   return success;
}

void VSTWrapper::Unload()
{
   if (mAEffect)
   {
      // Finally, close the plugin
      callDispatcher(effClose, 0, 0, NULL, 0.0);
      mAEffect = NULL;
   }

   //ResetModuleAndHandle();
}

void VSTWrapper::ResetModuleAndHandle()
{
   if (mModule)
   {
#if defined(__WXMAC__)
      mResource.reset();
      mBundleRef.reset();
#endif

      mModule.reset();
      mAEffect = NULL;
   }
}

VSTWrapper::~VSTWrapper()
{
   Unload();
   ResetModuleAndHandle();
}

VstPatchChunkInfo VSTWrapper::GetChunkInfo() const
{
   VstPatchChunkInfo info = { 1, mAEffect->uniqueID, mAEffect->version, mAEffect->numParams, "" };
   return info;
}

bool VSTWrapper::IsCompatible(const VstPatchChunkInfo& info) const
{
   return  (info.pluginUniqueID == mAEffect->uniqueID) &&
           (info.pluginVersion  == mAEffect->version) &&
           (info.numElements    == mAEffect->numParams);
}

void VSTUIWrapper::Flush()
{}

void VSTUIWrapper::Idle()
{
}

void VSTUIWrapper::NeedIdle()
{
}

VstTimeInfo* VSTWrapper::GetTimeInfo()
{
   mTimeInfo.nanoSeconds = wxGetUTCTimeMillis().ToDouble();
   return &mTimeInfo;
}

float VSTWrapper::GetSampleRate()
{
   return mTimeInfo.sampleRate;
}

int VSTWrapper::GetProcessLevel()
{
   return mProcessLevel;
}

void VSTUIWrapper::SizeWindow(int w, int h)
{
}

void VSTWrapper::SetBufferDelay(int)
{
}

int VSTWrapper::GetString(wxString & outstr, int opcode, int index) const
{
   char buf[256];

   memset(buf, 0, sizeof(buf));

   // Assume we are passed a read-only dispatcher function code
   constCallDispatcher(opcode, index, 0, buf, 0.0);

   outstr = wxString::FromUTF8(buf);

   return 0;
}

wxString VSTWrapper::GetString(int opcode, int index) const
{
   wxString str;

   GetString(str, opcode, index);

   return str;
}

void VSTWrapper::SetString(int opcode, const wxString & str, int index)
{
   char buf[256];
   strcpy(buf, str.Left(255).ToUTF8());

   callDispatcher(opcode, index, 0, buf, 0.0);
}

intptr_t VSTWrapper::callDispatcher(int opcode,
                                   int index, intptr_t value, void *ptr, float opt)
{
   // Needed since we might be in the dispatcher when the timer pops
   std::lock_guard guard(mDispatcherLock);

   return mAEffect->dispatcher(mAEffect, opcode, index, value, ptr, opt);
}

intptr_t VSTWrapper::constCallDispatcher(int opcode,
   int index, intptr_t value, void *ptr, float opt) const
{
   // Assume we are passed a read-only dispatcher function code
   return const_cast<VSTWrapper*>(this)
      ->callDispatcher(opcode, index, value, ptr, opt);
}

float VSTWrapper::callGetParameter(int index) const
{
   return mAEffect->getParameter(mAEffect, index);
}

void VSTWrapper::callSetParameter(int index, float value) const
{
   if (mVstVersion == 0 || constCallDispatcher(effCanBeAutomated, 0, index, NULL, 0.0))
   {
      mAEffect->setParameter(mAEffect, index, value);
   }
}

void VSTWrapper::callSetProgram(int index)
{
   callDispatcher(effBeginSetProgram, 0, 0, NULL, 0.0);

   callDispatcher(effSetProgram, 0, index, NULL, 0.0);

   callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);
}

void VSTWrapper::callSetChunk(bool isPgm, int len, void *buf)
{
   VstPatchChunkInfo info;

   memset(&info, 0, sizeof(info));
   info.version = 1;
   info.pluginUniqueID = mAEffect->uniqueID;
   info.pluginVersion = mAEffect->version;
   info.numElements = isPgm ? mAEffect->numParams : mAEffect->numPrograms;

   callSetChunk(isPgm, len, buf, &info);
}

void VSTWrapper::callSetChunk(bool isPgm, int len, void *buf, VstPatchChunkInfo *info) const
{
   if (isPgm)
   {
      // Ask the effect if this is an acceptable program
      if (constCallDispatcher(effBeginLoadProgram, 0, 0, info, 0.0) == -1)
      {
         return;
      }
   }
   else
   {
      // Ask the effect if this is an acceptable bank
      if (constCallDispatcher(effBeginLoadBank, 0, 0, info, 0.0) == -1)
      {
         return;
      }
   }

   constCallDispatcher(effBeginSetProgram, 0, 0, NULL, 0.0);
   constCallDispatcher(effSetChunk, isPgm ? 1 : 0, len, buf, 0.0);
   constCallDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);
}

bool VSTWrapper::LoadFXB(const wxFileName & fn)
{
   bool ret = false;

   // Try to open the file...will be closed automatically when method returns
   wxFFile f(fn.GetFullPath(), wxT("rb"));
   if (!f.IsOpened())
   {
      return false;
   }

   // Allocate memory for the contents
   ArrayOf<unsigned char> data{ size_t(f.Length()) };
   if (!data)
   {
      using namespace BasicUI;
      ShowMessageBox(
         XO("Unable to allocate memory when loading presets file."),
         MessageBoxOptions{}
            .Caption(XO("Error Loading VST Presets")));
      return false;
   }
   unsigned char *bptr = data.get();

   do
   {
      // Read in the whole file
      ssize_t len = f.Read((void *) bptr, f.Length());
      if (f.Error())
      {
         using namespace BasicUI;
         ShowMessageBox(
            XO("Unable to read presets file."),
            MessageBoxOptions{}
               .Caption(XO("Error Loading VST Presets")));
         break;
      }

      // Most references to the data are via an "int" array
      int32_t *iptr = (int32_t *) bptr;

      // Verify that we have at least enough for the header
      if (len < 156)
      {
         break;
      }

      // Verify that we probably have an FX file
      if (wxINT32_SWAP_ON_LE(iptr[0]) != CCONST('C', 'c', 'n', 'K'))
      {
         break;
      }

      // Ignore the size...sometimes it's there, other times it's zero

      // Get the version and verify
      int version = wxINT32_SWAP_ON_LE(iptr[3]);
      if (version != 1 && version != 2)
      {
         break;
      }

      VstPatchChunkInfo info =
      {
         1,
         wxINT32_SWAP_ON_LE(iptr[4]),
         wxINT32_SWAP_ON_LE(iptr[5]),
         wxINT32_SWAP_ON_LE(iptr[6]),
         ""
      };

      // Ensure this program looks to belong to the current plugin
      if ((info.pluginUniqueID != mAEffect->uniqueID) &&
          (info.pluginVersion != mAEffect->version) &&
          (info.numElements != mAEffect->numPrograms))
      {
         break;
      }

      // Get the number of programs
      int numProgs = info.numElements;

      // Get the current program index
      int curProg = 0;
      if (version >= 2)
      {
         curProg = wxINT32_SWAP_ON_LE(iptr[7]);
         if (curProg < 0 || curProg >= numProgs)
         {
            break;
         }
      }

      // Is it a bank of programs?
      if (wxINT32_SWAP_ON_LE(iptr[2]) == CCONST('F', 'x', 'B', 'k'))
      {
         // Drop the header
         bptr += 156;
         len -= 156;

         unsigned char *tempPtr = bptr;
         ssize_t tempLen = len;

         // Validate all of the programs
         for (int i = 0; i < numProgs; i++)
         {
            if (!LoadFXProgram(&tempPtr, tempLen, i, true))
            {
               break;
            }
         }

         // Ask the effect if this is an acceptable bank
         if (callDispatcher(effBeginLoadBank, 0, 0, &info, 0.0) == -1)
         {
            return false;
         }

         // Start loading the individual programs
         for (int i = 0; i < numProgs; i++)
         {
            ret = LoadFXProgram(&bptr, len, i, false);
         }
      }
      // Or maybe a bank chunk?
      else if (wxINT32_SWAP_ON_LE(iptr[2]) == CCONST('F', 'B', 'C', 'h'))
      {
         // Can't load programs chunks if the plugin doesn't support it
         if (!(mAEffect->flags & effFlagsProgramChunks))
         {
            break;
         }

         // Verify that we have enough to grab the chunk size
         if (len < 160)
         {
            break;
         }

         // Get the chunk size
         int size = wxINT32_SWAP_ON_LE(iptr[39]);

         // We finally know the full length of the program
         int proglen = 160 + size;

         // Verify that we have enough for the entire program
         if (len < proglen)
         {
            break;
         }

         // Set the entire bank in one shot
         callSetChunk(false, size, &iptr[40], &info);

         // Success
         ret = true;
      }
      // Unrecognizable type
      else
      {
         break;
      }

      // Set the active program
      if (ret && version >= 2)
      {
         callSetProgram(curProg);
      }
   } while (false);

   return ret;
}

bool VSTWrapper::LoadFXP(const wxFileName & fn)
{
   bool ret = false;

   // Try to open the file...will be closed automatically when method returns
   wxFFile f(fn.GetFullPath(), wxT("rb"));
   if (!f.IsOpened())
   {
      return false;
   }

   // Allocate memory for the contents
   ArrayOf<unsigned char> data{ size_t(f.Length()) };
   if (!data)
   {
      using namespace BasicUI;
      ShowMessageBox(
         XO("Unable to allocate memory when loading presets file."),
         MessageBoxOptions{}
            .Caption(XO("Error Loading VST Presets")));
      return false;
   }
   unsigned char *bptr = data.get();

   do
   {
      // Read in the whole file
      ssize_t len = f.Read((void *) bptr, f.Length());
      if (f.Error())
      {
         using namespace BasicUI;
         ShowMessageBox(
            XO("Unable to read presets file."),
            MessageBoxOptions{}
               .Caption(XO("Error Loading VST Presets")));
         break;
      }

      // Get (or default) currently selected program
      int i = 0; //mProgram->GetCurrentSelection();
      if (i < 0)
      {
         i = 0;   // default to first program
      }

      // Go verify and set the program
      ret = LoadFXProgram(&bptr, len, i, false);
   } while (false);

   return ret;
}

bool VSTWrapper::LoadFXProgram(unsigned char **bptr, ssize_t & len, int index, bool dryrun)
{
   // Most references to the data are via an "int" array
   int32_t *iptr = (int32_t *) *bptr;

   // Verify that we have at least enough for a program without parameters
   if (len < 28)
   {
      return false;
   }

   // Verify that we probably have an FX file
   if (wxINT32_SWAP_ON_LE(iptr[0]) != CCONST('C', 'c', 'n', 'K'))
   {
      return false;
   }

   // Ignore the size...sometimes it's there, other times it's zero

   // Get the version and verify
#if defined(IS_THIS_AN_FXP_ARTIFICAL_LIMITATION)
   int version = wxINT32_SWAP_ON_LE(iptr[3]);
   if (version != 1)
   {
      return false;
   }
#endif

   VstPatchChunkInfo info =
   {
      1,
      wxINT32_SWAP_ON_LE(iptr[4]),
      wxINT32_SWAP_ON_LE(iptr[5]),
      wxINT32_SWAP_ON_LE(iptr[6]),
      ""
   };

   // Ensure this program looks to belong to the current plugin
   if ((info.pluginUniqueID != mAEffect->uniqueID) &&
         (info.pluginVersion != mAEffect->version) &&
         (info.numElements != mAEffect->numParams))
   {
      return false;
   }

   // Get the number of parameters
   int numParams = info.numElements;

   // At this point, we have to have enough to include the program name as well
   if (len < 56)
   {
      return false;
   }

   // Get the program name
   wxString progName(wxString::From8BitData((char *)&iptr[7]));

   // Might be a regular program
   if (wxINT32_SWAP_ON_LE(iptr[2]) == CCONST('F', 'x', 'C', 'k'))
   {
      // We finally know the full length of the program
      int proglen = 56 + (numParams * sizeof(float));

      // Verify that we have enough for all of the parameter values
      if (len < proglen)
      {
         return false;
      }

      // Validate all of the parameter values
      for (int i = 0; i < numParams; i++)
      {
         uint32_t ival = wxUINT32_SWAP_ON_LE(iptr[14 + i]);
         float val = reinterpretAsFloat(ival);
         if (val < 0.0 || val > 1.0)
         {
            return false;
         }
      }

      // They look okay...time to start changing things
      if (!dryrun)
      {
         // Ask the effect if this is an acceptable program
         if (callDispatcher(effBeginLoadProgram, 0, 0, &info, 0.0) == -1)
         {
            return false;
         }

         // Load all of the parameters
         callDispatcher(effBeginSetProgram, 0, 0, NULL, 0.0);
         for (int i = 0; i < numParams; i++)
         {
            wxUint32 val = wxUINT32_SWAP_ON_LE(iptr[14 + i]);
            callSetParameter(i, reinterpretAsFloat(val));
         }
         callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);
      }

      // Update in case we're loading an "FxBk" format bank file
      *bptr += proglen;
      len -= proglen;
   }
   // Maybe we have a program chunk
   else if (wxINT32_SWAP_ON_LE(iptr[2]) == CCONST('F', 'P', 'C', 'h'))
   {
      // Can't load programs chunks if the plugin doesn't support it
      if (!(mAEffect->flags & effFlagsProgramChunks))
      {
         return false;
      }

      // Verify that we have enough to grab the chunk size
      if (len < 60)
      {
         return false;
      }

      // Get the chunk size
      int size = wxINT32_SWAP_ON_LE(iptr[14]);

      // We finally know the full length of the program
      int proglen = 60 + size;

      // Verify that we have enough for the entire program
      if (len < proglen)
      {
         return false;
      }

      // Set the entire program in one shot
      if (!dryrun)
      {
         callSetChunk(true, size, &iptr[15], &info);
      }

      // Update in case we're loading an "FxBk" format bank file
      *bptr += proglen;
      len -= proglen;
   }
   else
   {
      // Unknown type
      return false;
   }

   if (!dryrun)
   {
      SetString(effSetProgramName, wxString(progName), index);
   }

   return true;
}

bool VSTWrapper::LoadXML(const wxFileName & fn)
{
   mInChunk = false;
   mInSet = false;

   // default to read as XML file
   // Load the program
   XMLFileReader reader;
   bool ok = reader.Parse(this, fn.GetFullPath());

   // Something went wrong with the file, clean up
   if (mInSet)
   {
      callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);

      mInSet = false;
   }

   if (!ok)
   {
      using namespace BasicUI;
      // Inform user of load failure
      ShowMessageBox(
         reader.GetErrorStr(),
         MessageBoxOptions{}
            .Caption(XO("Error Loading VST Presets")));
      return false;
   }

   return true;
}

void VSTWrapper::SaveFXB(const wxFileName & fn) const
{
   // Create/Open the file
   const wxString fullPath{fn.GetFullPath()};
   wxFFile f(fullPath, wxT("wb"));
   if (!f.IsOpened())
   {
      using namespace BasicUI;
      ShowMessageBox(
         XO("Could not open file: \"%s\"").Format( fullPath ),
         MessageBoxOptions{}
            .Caption(XO("Error Saving VST Presets")));
      return;
   }

   wxMemoryBuffer buf;
   wxInt32 subType;
   void *chunkPtr = nullptr;
   int chunkSize = 0;
   int dataSize = 148;
   wxInt32 tab[8];
   int curProg = 0 ; //mProgram->GetCurrentSelection();

   if (mAEffect->flags & effFlagsProgramChunks)
   {
      subType = CCONST('F', 'B', 'C', 'h');

      // read-only dispatcher function
      chunkSize = constCallDispatcher(effGetChunk, 0, 0, &chunkPtr, 0.0);
      dataSize += 4 + chunkSize;
   }
   else
   {
      subType = CCONST('F', 'x', 'B', 'k');

      for (int i = 0; i < mAEffect->numPrograms; i++)
      {
         SaveFXProgram(buf, i);
      }

      dataSize += buf.GetDataLen();
   }

   tab[0] = wxINT32_SWAP_ON_LE(CCONST('C', 'c', 'n', 'K'));
   tab[1] = wxINT32_SWAP_ON_LE(dataSize);
   tab[2] = wxINT32_SWAP_ON_LE(subType);
   tab[3] = wxINT32_SWAP_ON_LE(curProg >= 0 ? 2 : 1);
   tab[4] = wxINT32_SWAP_ON_LE(mAEffect->uniqueID);
   tab[5] = wxINT32_SWAP_ON_LE(mAEffect->version);
   tab[6] = wxINT32_SWAP_ON_LE(mAEffect->numPrograms);
   tab[7] = wxINT32_SWAP_ON_LE(curProg >= 0 ? curProg : 0);

   f.Write(tab, sizeof(tab));
   if (!f.Error())
   {
      char padding[124];
      memset(padding, 0, sizeof(padding));
      f.Write(padding, sizeof(padding));

      if (!f.Error())
      {
         if (mAEffect->flags & effFlagsProgramChunks)
         {
            wxInt32 size = wxINT32_SWAP_ON_LE(chunkSize);
            f.Write(&size, sizeof(size));
            f.Write(chunkPtr, chunkSize);
         }
         else
         {
            f.Write(buf.GetData(), buf.GetDataLen());
         }
      }
   }

   if (f.Error())
   {
      using namespace BasicUI;
      ShowMessageBox(
         XO("Error writing to file: \"%s\"").Format( fullPath ),
         MessageBoxOptions{}
            .Caption(XO("Error Saving VST Presets")));
   }

   f.Close();

   return;
}

void VSTWrapper::SaveFXP(const wxFileName & fn) const
{
   // Create/Open the file
   const wxString fullPath{ fn.GetFullPath() };
   wxFFile f(fullPath, wxT("wb"));
   if (!f.IsOpened())
   {
      using namespace BasicUI;
      ShowMessageBox(
         XO("Could not open file: \"%s\"").Format( fullPath ),
         MessageBoxOptions{}
            .Caption(XO("Error Saving VST Presets")));
      return;
   }

   wxMemoryBuffer buf;

   // read-only dispatcher function
   int ndx = constCallDispatcher(effGetProgram, 0, 0, NULL, 0.0);
   SaveFXProgram(buf, ndx);

   f.Write(buf.GetData(), buf.GetDataLen());
   if (f.Error())
   {
      using namespace BasicUI;
      ShowMessageBox(
         XO("Error writing to file: \"%s\"").Format( fullPath ),
         MessageBoxOptions{}
            .Caption(XO("Error Saving VST Presets")));
   }

   f.Close();

   return;
}

void VSTWrapper::SaveFXProgram(wxMemoryBuffer & buf, int index) const
{
   wxInt32 subType;
   void *chunkPtr;
   int chunkSize;
   int dataSize = 48;
   char progName[28];
   wxInt32 tab[7];

   // read-only dispatcher function
   constCallDispatcher(effGetProgramNameIndexed, index, 0, &progName, 0.0);
   progName[27] = '\0';
   chunkSize = strlen(progName);
   memset(&progName[chunkSize], 0, sizeof(progName) - chunkSize);

   if (mAEffect->flags & effFlagsProgramChunks)
   {
      subType = CCONST('F', 'P', 'C', 'h');

      // read-only dispatcher function
      chunkSize = constCallDispatcher(effGetChunk, 1, 0, &chunkPtr, 0.0);
      dataSize += 4 + chunkSize;
   }
   else
   {
      subType = CCONST('F', 'x', 'C', 'k');

      dataSize += (mAEffect->numParams << 2);
   }

   tab[0] = wxINT32_SWAP_ON_LE(CCONST('C', 'c', 'n', 'K'));
   tab[1] = wxINT32_SWAP_ON_LE(dataSize);
   tab[2] = wxINT32_SWAP_ON_LE(subType);
   tab[3] = wxINT32_SWAP_ON_LE(1);
   tab[4] = wxINT32_SWAP_ON_LE(mAEffect->uniqueID);
   tab[5] = wxINT32_SWAP_ON_LE(mAEffect->version);
   tab[6] = wxINT32_SWAP_ON_LE(mAEffect->numParams);

   buf.AppendData(tab, sizeof(tab));
   buf.AppendData(progName, sizeof(progName));

   if (mAEffect->flags & effFlagsProgramChunks)
   {
      wxInt32 size = wxINT32_SWAP_ON_LE(chunkSize);
      buf.AppendData(&size, sizeof(size));
      buf.AppendData(chunkPtr, chunkSize);
   }
   else
   {
      for (int i = 0; i < mAEffect->numParams; i++)
      {
         float val = callGetParameter(i);
         wxUint32 ival = wxUINT32_SWAP_ON_LE(reinterpretAsUint32(val));
         buf.AppendData(&ival, sizeof(ival));
      }
   }

   return;
}

// Throws exceptions rather than giving error return.
void VSTWrapper::SaveXML(const wxFileName & fn) const
// may throw
{
   XMLFileWriter xmlFile{ fn.GetFullPath(), XO("Error Saving Effect Presets") };

   xmlFile.StartTag(wxT("vstprogrampersistence"));
   xmlFile.WriteAttr(wxT("version"), wxT("2"));

   xmlFile.StartTag(wxT("effect"));
   // Use internal name only in persistent information
   xmlFile.WriteAttr(wxT("name"), GetSymbol().Internal());
   xmlFile.WriteAttr(wxT("uniqueID"), mAEffect->uniqueID);
   xmlFile.WriteAttr(wxT("version"), mAEffect->version);
   xmlFile.WriteAttr(wxT("numParams"), mAEffect->numParams);

   xmlFile.StartTag(wxT("program"));
   xmlFile.WriteAttr(wxT("name"), wxEmptyString); //mProgram->GetValue());

   int clen = 0;
   if (mAEffect->flags & effFlagsProgramChunks)
   {
      void *chunk = NULL;

      // read-only dispatcher function
      clen = (int) constCallDispatcher(effGetChunk, 1, 0, &chunk, 0.0);
      if (clen != 0)
      {
         xmlFile.StartTag(wxT("chunk"));
         xmlFile.WriteSubTree(Base64::Encode(chunk, clen) + wxT('\n'));
         xmlFile.EndTag(wxT("chunk"));
      }
   }

   if (clen == 0)
   {
      for (int i = 0; i < mAEffect->numParams; i++)
      {
         xmlFile.StartTag(wxT("param"));

         xmlFile.WriteAttr(wxT("index"), i);
         xmlFile.WriteAttr(wxT("name"),
                           GetString(effGetParamName, i));
         xmlFile.WriteAttr(wxT("value"),
                           wxString::Format(wxT("%f"),
                           callGetParameter(i)));

         xmlFile.EndTag(wxT("param"));
      }
   }

   xmlFile.EndTag(wxT("program"));

   xmlFile.EndTag(wxT("effect"));

   xmlFile.EndTag(wxT("vstprogrampersistence"));

   xmlFile.Commit();
}

bool VSTWrapper::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
{
   if (tag == "vstprogrampersistence")
   {
      for (auto pair : attrs)
      {
         auto attr = pair.first;
         auto value = pair.second;

         if (attr == "version")
         {
            if (!value.TryGet(mXMLVersion))
            {
               return false;
            }

            if (mXMLVersion < 1 || mXMLVersion > 2)
            {
               return false;
            }
         }
         else
         {
            return false;
         }
      }

      return true;
   }

   if (tag == "effect")
   {
      memset(&mXMLInfo, 0, sizeof(mXMLInfo));
      mXMLInfo.version = 1;
      mXMLInfo.pluginUniqueID = mAEffect->uniqueID;
      mXMLInfo.pluginVersion = mAEffect->version;
      mXMLInfo.numElements = mAEffect->numParams;

      for (auto pair : attrs)
      {
         auto attr = pair.first;
         auto value = pair.second;

         if (attr == "name")
         {
            wxString strValue = value.ToWString();

            if (strValue != GetSymbol().Internal())
            {
               using namespace BasicUI;
               auto msg = XO("This parameter file was saved from %s. Continue?")
                  .Format( strValue );
               auto result = ShowMessageBox(
                  msg,
                  MessageBoxOptions{}
                     .Caption(XO("Confirm"))
                     .ButtonStyle(Button::YesNo));
               if (result == MessageBoxResult::No)
                  return false;
            }
         }
         else if (attr == "version")
         {
            long version;
            if (!value.TryGet(version))
            {
               return false;
            }

            mXMLInfo.pluginVersion = (int) version;
         }
         else if (mXMLVersion > 1 && attr == "uniqueID")
         {
            long uniqueID;
            if (!value.TryGet(uniqueID))
            {
               return false;
            }

            mXMLInfo.pluginUniqueID = (int) uniqueID;
         }
         else if (mXMLVersion > 1 && attr == "numParams")
         {
            long numParams;
            if (!value.TryGet(numParams))
            {
               return false;
            }

            mXMLInfo.numElements = (int) numParams;
         }
         else
         {
            return false;
         }
      }

      return true;
   }

   if (tag == "program")
   {
      for (auto pair : attrs)
      {
         auto attr = pair.first;
         auto value = pair.second;

         if (attr == "name")
         {
            const wxString strValue = value.ToWString();

            if (strValue.length() > 24)
            {
               return false;
            }

            int ndx = 0; //mProgram->GetCurrentSelection();
            if (ndx == wxNOT_FOUND)
            {
               ndx = 0;
            }

            SetString(effSetProgramName, strValue, ndx);
         }
         else
         {
            return false;
         }
      }

      mInChunk = false;

      if (callDispatcher(effBeginLoadProgram, 0, 0, &mXMLInfo, 0.0) == -1)
      {
         return false;
      }

      callDispatcher(effBeginSetProgram, 0, 0, NULL, 0.0);

      mInSet = true;

      return true;
   }

   if (tag == "param")
   {
      long ndx = -1;
      double val = -1.0;

      for (auto pair : attrs)
      {
         auto attr = pair.first;
         auto value = pair.second;

         if (attr == "index")
         {
            if (!value.TryGet(ndx))
            {
               return false;
            }

            if (ndx < 0 || ndx >= mAEffect->numParams)
            {
               // Could be a different version of the effect...probably should
               // tell the user
               return false;
            }
         }
         // "name" attribute is ignored for params
         /* else if (attr == "name")
         {

            // Nothing to do with it for now
         }*/
         else if (attr == "value")
         {
            if (!value.TryGet(val))
            {
               return false;
            }

            if (val < 0.0 || val > 1.0)
            {
               return false;
            }
         }
      }

      if (ndx == -1 || val == -1.0)
      {
         return false;
      }

      callSetParameter(ndx, val);

      return true;
   }

   if (tag == "chunk")
   {
      mInChunk = true;
      return true;
   }

   return false;
}

void VSTWrapper::HandleXMLEndTag(const std::string_view& tag)
{
   if (tag == "chunk")
   {
      if (mChunk.length())
      {
         ArrayOf<char> buf{ mChunk.length() / 4 * 3 };

         int len = Base64::Decode(mChunk, buf.get());
         if (len)
         {
            callSetChunk(true, len, buf.get(), &mXMLInfo);
         }

         mChunk.clear();
      }
      mInChunk = false;
   }

   if (tag == "program")
   {
      if (mInSet)
      {
         callDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);

         mInSet = false;
      }
   }
}

void VSTWrapper::HandleXMLContent(const std::string_view& content)
{
   if (mInChunk)
   {
      mChunk += wxString(std::string(content)).Trim(true).Trim(false);
   }
}

XMLTagHandler *VSTWrapper::HandleXMLChild(const std::string_view& tag)
{
   if (tag == "vstprogrampersistence")
   {
      return this;
   }

   if (tag == "effect")
   {
      return this;
   }

   if (tag == "program")
   {
      return this;
   }

   if (tag == "param")
   {
      return this;
   }

   if (tag == "chunk")
   {
      return this;
   }

   return NULL;
}

void VSTWrapper::ForEachParameter(ParameterVisitor visitor) const
{
   for (int i = 0; i < mAEffect->numParams; i++)
   {
      wxString name = GetString(effGetParamName, i);
      if (name.empty())
      {
         name.Printf(wxT("parm_%d"), i);
      }
      else
         /* Easy fix for now for issue 3854, but this should be reconsidered
          There is the possibility that two parameter names might collide
          after normalizing.  A question is whether the normalizing was ever
          really needed for saving in a wxConfigFile.  Maybe not.  But then
          redefinition of the keys stored in the file may introduce versioning
          difficulties if there is an attempt to fix this in future Audacity.
          */
         name = CommandParameters::NormalizeName(name);

      ParameterInfo pi{ i, name };

      if (!visitor(pi))
         break;
   }
}

bool VSTWrapper::FetchSettings(VSTSettings& vstSettings, bool doFetch) const
{
   // Get the fallback ID-value parameters
   ForEachParameter
   (
      [&](const ParameterInfo& pi)
      {
         if (doFetch)
         {
            float val = callGetParameter(pi.mID);
            vstSettings.mParamsMap[pi.mName] = val;
         }
         else
         {
            vstSettings.mParamsMap[pi.mName] = std::nullopt;
         }
         return true;
      }
   );

   // These are here to be checked against for compatibility later
   vstSettings.mVersion   = mAEffect->version;
   vstSettings.mUniqueID  = mAEffect->uniqueID;
   vstSettings.mNumParams = mAEffect->numParams;

   // Get the chunk (if supported)
   vstSettings.mChunk.resize(0);

   if (mAEffect->flags & effFlagsProgramChunks)
   {
      void* chunk = nullptr;
      int clen = (int)constCallDispatcher(effGetChunk, 1, 0, &chunk, 0.0);
      if (clen > 0 && chunk) {
         vstSettings.mChunk.resize(clen);
         memcpy(vstSettings.mChunk.data(), chunk, clen);
      }

      if (!doFetch)
      {
         // Don't keep the contents, but keep a sufficiently allocated string,
         // with some extra space in case chunk length might vary
         auto size = vstSettings.mChunk.size();
         vstSettings.mChunk.resize(0);
         vstSettings.mChunk.reserve(2 * size);
      }
   }

   return true;
}

bool VSTWrapper::StoreSettings(const VSTSettings& vstSettings) const
{
   // First, make sure settings are compatibile with the plugin
   if ((vstSettings.mUniqueID  != mAEffect->uniqueID)   ||
//       (vstSettings.mVersion   != mAEffect->version)    ||
       (vstSettings.mNumParams != mAEffect->numParams)      )
   {
      return false;
   }


   // Try using the chunk first (if available)
   auto &chunk = vstSettings.mChunk;
   if (!chunk.empty())
   {
      VstPatchChunkInfo info = { 1, mAEffect->uniqueID, mAEffect->version, mAEffect->numParams, "" };
      callSetChunk(true, chunk.size(), const_cast<char *>(chunk.data()), &info);
   }


   // Settings (like the message) may store both a chunk, and also accumulated
   // slider movements to reapply after the chunk change.  Or it might be
   // no chunk and id-value pairs only

   constCallDispatcher(effBeginSetProgram, 0, 0, NULL, 0.0);

   ForEachParameter
   (
      [&](const ParameterInfo& pi)
      {
         const auto itr = vstSettings.mParamsMap.find(pi.mName);
         if (itr != vstSettings.mParamsMap.end())
         {
            const float& value = *(itr->second);

            if (value >= -1.0 && value <= 1.0)
            {
               callSetParameter(pi.mID, value);
            }
         }
         return true;
      }
   );

   constCallDispatcher(effEndSetProgram, 0, 0, NULL, 0.0);

   return true;
}

ComponentInterfaceSymbol VSTWrapper::GetSymbol() const
{
   return mName;
}

std::unique_ptr<EffectInstance::Message>
VSTWrapper::MakeMessageFS(const VSTSettings &settings) const
{
   VSTMessage::ParamVector paramVector;
   paramVector.resize(mAEffect->numParams, std::nullopt);

   ForEachParameter
   (
      [&](const VSTWrapper::ParameterInfo& pi)
      {
         auto &slot = paramVector[pi.mID]; // operator [] may make a nullopt
         const auto iter = settings.mParamsMap.find(pi.mName),
            end = settings.mParamsMap.end();
         if (iter != end)
            slot = iter->second;
         return true;
      }
   );

   return std::make_unique<VSTMessage>(
      settings.mChunk /* vector copy */, std::move(paramVector));
}

void VSTUIWrapper::Automate(int index, float value)
{
}
