aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/mptrack/Vstplug.cpp
diff options
context:
space:
mode:
authorJean-Francois Mauguit <jfmauguit@mac.com>2024-09-24 09:03:25 -0400
committerGitHub <noreply@github.com>2024-09-24 09:03:25 -0400
commitbab614c421ed7ae329d26bf028c4a3b1d2450f5a (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/mptrack/Vstplug.cpp
parent4bde6044fddf053f31795b9eaccdd2a5a527d21f (diff)
parent20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (diff)
downloadwinamp-bab614c421ed7ae329d26bf028c4a3b1d2450f5a.tar.gz
Merge pull request #5 from WinampDesktop/community
Merge to main
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/mptrack/Vstplug.cpp')
-rw-r--r--Src/external_dependencies/openmpt-trunk/mptrack/Vstplug.cpp1762
1 files changed, 1762 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/mptrack/Vstplug.cpp b/Src/external_dependencies/openmpt-trunk/mptrack/Vstplug.cpp
new file mode 100644
index 00000000..54a8e058
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/mptrack/Vstplug.cpp
@@ -0,0 +1,1762 @@
+/*
+ * Vstplug.cpp
+ * -----------
+ * Purpose: VST Plugin handling / processing
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifdef MPT_WITH_VST
+
+#include "Vstplug.h"
+#ifdef MODPLUG_TRACKER
+#include "Moddoc.h"
+#include "Mainfrm.h"
+#include "AbstractVstEditor.h"
+#include "VSTEditor.h"
+#include "DefaultVstEditor.h"
+#include "ExceptionHandler.h"
+#endif // MODPLUG_TRACKER
+#include "../soundlib/Sndfile.h"
+#include "../soundlib/MIDIEvents.h"
+#include "MIDIMappingDialog.h"
+#include "../common/mptStringBuffer.h"
+#include "FileDialog.h"
+#include "../pluginBridge/BridgeWrapper.h"
+#include "../pluginBridge/BridgeOpCodes.h"
+#include "../soundlib/plugins/OpCodes.h"
+#include "../soundlib/plugins/PluginManager.h"
+#include "../misc/mptOSException.h"
+
+using namespace Vst;
+DECLARE_FLAGSET(Vst::VstTimeInfoFlags)
+
+OPENMPT_NAMESPACE_BEGIN
+
+static VstTimeInfo g_timeInfoFallback = { 0 };
+
+#ifdef MPT_ALL_LOGGING
+#define VST_LOG
+#endif
+
+
+using VstCrash = Windows::SEH::Code;
+
+
+bool CVstPlugin::MaskCrashes() noexcept
+{
+ return m_maskCrashes;
+}
+
+
+template <typename Tfn>
+DWORD CVstPlugin::SETryOrError(bool maskCrashes, Tfn fn)
+{
+ DWORD exception = 0;
+ if(maskCrashes)
+ {
+ exception = Windows::SEH::TryOrError(fn);
+ if(exception)
+ {
+ ExceptionHandler::TaintProcess(ExceptionHandler::TaintReason::Plugin);
+ }
+ } else
+ {
+ fn();
+ }
+ return exception;
+}
+
+
+template <typename Tfn>
+DWORD CVstPlugin::SETryOrError(Tfn fn)
+{
+ DWORD exception = 0;
+ if(MaskCrashes())
+ {
+ exception = Windows::SEH::TryOrError(fn);
+ if(exception)
+ {
+ ExceptionHandler::TaintProcess(ExceptionHandler::TaintReason::Plugin);
+ }
+ } else
+ {
+#ifdef MODPLUG_TRACKER
+ ExceptionHandler::ContextSetter ectxguard{&m_Ectx};
+#endif // MODPLUG_TRACKER
+ fn();
+ }
+ return exception;
+}
+
+
+AEffect *CVstPlugin::LoadPlugin(bool maskCrashes, VSTPluginLib &plugin, HMODULE &library, BridgeMode bridgeMode)
+{
+ const mpt::PathString &pluginPath = plugin.dllPath;
+
+ AEffect *effect = nullptr;
+ library = nullptr;
+
+ const bool isNative = plugin.IsNative(false);
+ if(bridgeMode != BridgeMode::Automatic || plugin.useBridge || !isNative)
+ {
+ if(bridgeMode == BridgeMode::DetectRequiredBridgeMode)
+ {
+ // First try modern bridge, then legacy bridge
+ plugin.modernBridge = true;
+ try
+ {
+ effect = BridgeWrapper::Create(plugin, false);
+ if(effect != nullptr)
+ {
+ return effect;
+ }
+ } catch(BridgeWrapper::BridgeNotFoundException &)
+ {
+ } catch(BridgeWrapper::BridgeException &)
+ {
+ }
+ // Retry with legacy bridge
+ plugin.useBridge = true;
+ plugin.modernBridge = false;
+ }
+
+ try
+ {
+ effect = BridgeWrapper::Create(plugin, bridgeMode == BridgeMode::DetectRequiredBridgeMode);
+ if(effect != nullptr)
+ {
+ return effect;
+ }
+ } catch(BridgeWrapper::BridgeNotFoundException &)
+ {
+ // Try normal loading
+ if(!isNative)
+ {
+ Reporting::Error("Could not locate the plugin bridge executable, which is required for running non-native plugins.", "OpenMPT Plugin Bridge");
+ return nullptr;
+ }
+ } catch(BridgeWrapper::BridgeException &e)
+ {
+ // If there was some error, don't try normal loading as well... unless the user really wants it.
+ if(isNative)
+ {
+ const CString msg =
+ MPT_CFORMAT("The following error occurred while trying to load\n{}\n\n{}\n\nDo you want to try to load the plugin natively?")
+ (plugin.dllPath, mpt::get_exception_text<mpt::ustring>(e));
+ if(Reporting::Confirm(msg, _T("OpenMPT Plugin Bridge")) == cnfNo)
+ {
+ return nullptr;
+ }
+ } else
+ {
+ Reporting::Error(mpt::get_exception_text<mpt::ustring>(e), "OpenMPT Plugin Bridge");
+ return nullptr;
+ }
+ }
+ // If plugin was marked to use the plugin bridge but this somehow doesn't work (e.g. because the bridge is missing),
+ // disable the plugin bridge for this plugin.
+ plugin.useBridge = false;
+ plugin.modernBridge = true;
+ }
+
+ {
+#ifdef MODPLUG_TRACKER
+ ExceptionHandler::Context ectx{ MPT_UFORMAT("VST Plugin: {}")(plugin.dllPath.ToUnicode()) };
+ ExceptionHandler::ContextSetter ectxguard{&ectx};
+#endif // MODPLUG_TRACKER
+ DWORD exception = SETryOrError(maskCrashes, [&](){ library = LoadLibrary(pluginPath.AsNative().c_str()); });
+ if(exception)
+ {
+ CVstPluginManager::ReportPlugException(MPT_UFORMAT("Exception caught while loading {}")(pluginPath));
+ return nullptr;
+ }
+ }
+ if(library == nullptr)
+ {
+ DWORD error = GetLastError();
+ if(error == ERROR_MOD_NOT_FOUND)
+ {
+ return nullptr;
+ } else if(error == ERROR_DLL_INIT_FAILED)
+ {
+ // A likely reason for this error is that Fiber Local Storage slots are exhausted, e.g. because too many plugins ship with a statically linked runtime.
+ // Before Windows 10 1903, there was a limit of 128 FLS slots per process, and the VS2017 runtime uses two FLS slots, so this could cause a worst-case limit
+ // of 62 different plugins per process (assuming they all use a statically-linked runtime).
+ // In Windows 10 1903, the FLS limit was finally raised, so this message is mostly relevant for older systems.
+ CVstPluginManager::ReportPlugException(U_("Plugin initialization failed. This may be caused by loading too many plugins.\nTry activating the Plugin Bridge for this plugin."));
+ }
+
+#ifdef _DEBUG
+ mpt::ustring buf = MPT_UFORMAT("Warning: encountered problem when loading plugin dll. Error {}: {}")
+ ( mpt::ufmt::hex(error)
+ , mpt::ToUnicode(mpt::windows::GetErrorMessage(error))
+ );
+ Reporting::Error(buf, "DEBUG: Error when loading plugin dll");
+#endif //_DEBUG
+ }
+
+ if(library != nullptr && library != INVALID_HANDLE_VALUE)
+ {
+ auto pMainProc = (Vst::MainProc)GetProcAddress(library, "VSTPluginMain");
+ if(pMainProc == nullptr)
+ {
+ pMainProc = (Vst::MainProc)GetProcAddress(library, "main");
+ }
+
+ if(pMainProc != nullptr)
+ {
+#ifdef MODPLUG_TRACKER
+ ExceptionHandler::Context ectx{ MPT_UFORMAT("VST Plugin: {}")(plugin.dllPath.ToUnicode()) };
+ ExceptionHandler::ContextSetter ectxguard{&ectx};
+#endif // MODPLUG_TRACKER
+ DWORD exception = SETryOrError(maskCrashes, [&](){ effect = pMainProc(CVstPlugin::MasterCallBack); });
+ if(exception)
+ {
+ return nullptr;
+ }
+ } else
+ {
+#ifdef VST_LOG
+ MPT_LOG_GLOBAL(LogDebug, "VST", MPT_UFORMAT("Entry point not found! (handle={})")(mpt::ufmt::PTR(library)));
+#endif // VST_LOG
+ return nullptr;
+ }
+ }
+
+ return effect;
+}
+
+static void operator|= (Vst::VstTimeInfoFlags &lhs, Vst::VstTimeInfoFlags rhs)
+{
+ lhs = (lhs | rhs).as_enum();
+}
+
+intptr_t VSTCALLBACK CVstPlugin::MasterCallBack(AEffect *effect, VstOpcodeToHost opcode, int32 index, intptr_t value, void *ptr, float opt)
+{
+#ifdef VST_LOG
+ MPT_LOG_GLOBAL(LogDebug, "VST", MPT_UFORMAT("VST plugin to host: Eff: {}, Opcode = {}, Index = {}, Value = {}, PTR = {}, OPT = {}\n")(
+ mpt::ufmt::PTR(effect), mpt::ufmt::val(opcode),
+ mpt::ufmt::val(index), mpt::ufmt::PTR(value), mpt::ufmt::PTR(ptr), mpt::ufmt::flt(opt, 3)));
+ MPT_TRACE();
+#else
+ MPT_UNREFERENCED_PARAMETER(opt);
+#endif
+
+ enum
+ {
+ HostDoNotKnow = 0,
+ HostCanDo = 1,
+ HostCanNotDo = -1
+ };
+
+ CVstPlugin *pVstPlugin = nullptr;
+ if(effect != nullptr)
+ {
+ pVstPlugin = static_cast<CVstPlugin *>(effect->reservedForHost1);
+ }
+
+ switch(opcode)
+ {
+ // Called when plugin param is changed via gui
+ case audioMasterAutomate:
+ // Strum Acoustic GS-1 and Strum Electric GS-1 send audioMasterAutomate during effOpen (WTF #1),
+ // but when sending back effCanBeAutomated, they just crash (WTF #2).
+ // As a consequence, just generally forbid this action while the plugin is not fully initialized yet.
+ if(pVstPlugin != nullptr && pVstPlugin->m_isInitialized && pVstPlugin->CanAutomateParameter(index))
+ {
+ // This parameter can be automated. Ugo Motion constantly sends automation callback events for parameters that cannot be automated...
+ pVstPlugin->AutomateParameter((PlugParamIndex)index);
+ }
+ return 0;
+
+ // Called when plugin asks for VST version supported by host
+ case audioMasterVersion:
+ return kVstVersion;
+
+ // Returns the unique id of a plugin that's currently loading
+ // We don't support shell plugins currently, so we only support one effect ID as well.
+ case audioMasterCurrentId:
+ return (effect != nullptr) ? effect->uniqueID : 0;
+
+ // Call application idle routine (this will call effEditIdle for all open editors too)
+ case audioMasterIdle:
+ theApp.GetPluginManager()->OnIdle();
+ return 0;
+
+ // Inquire if an input or output is beeing connected; index enumerates input or output counting from zero,
+ // value is 0 for input and != 0 otherwise. note: the return value is 0 for <true> such that older versions
+ // will always return true.
+ case audioMasterPinConnected:
+ if (value) //input:
+ return (index < 2) ? 0 : 1; //we only support up to 2 inputs. Remember: 0 means yes.
+ else //output:
+ return (index < 2) ? 0 : 1; //2 outputs max too
+
+ //---from here VST 2.0 extension opcodes------------------------------------------------------
+
+ // <value> is a filter which is currently ignored - DEPRECATED in VST 2.4
+ case audioMasterWantMidi:
+ return 1;
+
+ // returns const VstTimeInfo* (or 0 if not supported)
+ // <value> should contain a mask indicating which fields are required
+ case audioMasterGetTime:
+
+ if(pVstPlugin)
+ {
+ VstTimeInfo &timeInfo = pVstPlugin->timeInfo;
+ MemsetZero(timeInfo);
+
+ timeInfo.sampleRate = pVstPlugin->m_nSampleRate;
+ CSoundFile &sndFile = pVstPlugin->GetSoundFile();
+ if(pVstPlugin->IsSongPlaying())
+ {
+ timeInfo.flags |= kVstTransportPlaying;
+ if(pVstPlugin->GetSoundFile().m_SongFlags[SONG_PATTERNLOOP]) timeInfo.flags |= kVstTransportCycleActive;
+ timeInfo.samplePos = sndFile.GetTotalSampleCount();
+ if(pVstPlugin->m_positionChanged)
+ {
+ timeInfo.flags |= kVstTransportChanged;
+ pVstPlugin->lastBarStartPos = -1.0;
+ }
+ } else
+ {
+ timeInfo.flags |= kVstTransportChanged; //just stopped.
+ timeInfo.samplePos = 0;
+ pVstPlugin->lastBarStartPos = -1.0;
+ }
+ if((value & kVstNanosValid))
+ {
+ timeInfo.flags |= kVstNanosValid;
+ timeInfo.nanoSeconds = static_cast<double>(Util::mul32to64_unsigned(timeGetTime(), 1000000));
+ }
+ if((value & kVstPpqPosValid))
+ {
+ timeInfo.flags |= kVstPpqPosValid;
+ if (timeInfo.flags & kVstTransportPlaying)
+ {
+ timeInfo.ppqPos = (timeInfo.samplePos / timeInfo.sampleRate) * (sndFile.GetCurrentBPM() / 60.0);
+ } else
+ {
+ timeInfo.ppqPos = 0;
+ }
+
+ ROWINDEX rpm = pVstPlugin->GetSoundFile().m_PlayState.m_nCurrentRowsPerMeasure;
+ if(!rpm)
+ rpm = 4;
+ if((pVstPlugin->GetSoundFile().m_PlayState.m_nRow % rpm) == 0)
+ {
+ pVstPlugin->lastBarStartPos = std::floor(timeInfo.ppqPos);
+ }
+ if(pVstPlugin->lastBarStartPos >= 0)
+ {
+ timeInfo.barStartPos = pVstPlugin->lastBarStartPos;
+ timeInfo.flags |= kVstBarsValid;
+ }
+ }
+ if((value & kVstTempoValid))
+ {
+ timeInfo.tempo = sndFile.GetCurrentBPM();
+ if (timeInfo.tempo)
+ {
+ timeInfo.flags |= kVstTempoValid;
+ }
+ }
+ if((value & kVstTimeSigValid))
+ {
+ timeInfo.flags |= kVstTimeSigValid;
+
+ // Time signature. numerator = rows per beats / rows pear measure (should sound somewhat logical to you).
+ // the denominator is a bit more tricky, since it cannot be set explicitely. so we just assume quarters for now.
+ ROWINDEX rpb = std::max(sndFile.m_PlayState.m_nCurrentRowsPerBeat, ROWINDEX(1));
+ timeInfo.timeSigNumerator = std::max(sndFile.m_PlayState.m_nCurrentRowsPerMeasure, rpb) / rpb;
+ timeInfo.timeSigDenominator = 4; //std::gcd(pSndFile->m_nCurrentRowsPerMeasure, pSndFile->m_nCurrentRowsPerBeat);
+ }
+ return ToIntPtr(&timeInfo);
+ } else
+ {
+ MemsetZero(g_timeInfoFallback);
+ return ToIntPtr(&g_timeInfoFallback);
+ }
+
+ // Receive MIDI events from plugin
+ case audioMasterProcessEvents:
+ if(pVstPlugin != nullptr && ptr != nullptr)
+ {
+ pVstPlugin->ReceiveVSTEvents(static_cast<VstEvents *>(ptr));
+ return 1;
+ }
+ break;
+
+ // DEPRECATED in VST 2.4
+ case audioMasterSetTime:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Set Time"));
+ break;
+
+ // returns tempo (in bpm * 10000) at sample frame location passed in <value> - DEPRECATED in VST 2.4
+ case audioMasterTempoAt:
+ // Screw it! Let's just return the tempo at this point in time (might be a bit wrong).
+ if (pVstPlugin != nullptr)
+ {
+ return mpt::saturate_round<int32>(pVstPlugin->GetSoundFile().GetCurrentBPM() * 10000);
+ }
+ return (125 * 10000);
+
+ // parameters - DEPRECATED in VST 2.4
+ case audioMasterGetNumAutomatableParameters:
+ //MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Get Num Automatable Parameters"));
+ if(pVstPlugin != nullptr)
+ {
+ return pVstPlugin->GetNumParameters();
+ }
+ break;
+
+ // Apparently, this one is broken in VST SDK anyway. - DEPRECATED in VST 2.4
+ case audioMasterGetParameterQuantization:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Audio Master Get Parameter Quantization"));
+ break;
+
+ // numInputs and/or numOutputs has changed
+ case audioMasterIOChanged:
+ if(pVstPlugin != nullptr)
+ {
+ CriticalSection cs;
+ return pVstPlugin->InitializeIOBuffers() ? 1 : 0;
+ }
+ break;
+
+ // Plugin needs idle calls (outside its editor window) - DEPRECATED in VST 2.4
+ case audioMasterNeedIdle:
+ if(pVstPlugin != nullptr)
+ {
+ pVstPlugin->m_needIdle = true;
+ }
+
+ return 1;
+
+ // index: width, value: height
+ case audioMasterSizeWindow:
+ if(pVstPlugin != nullptr)
+ {
+ CAbstractVstEditor *pVstEditor = pVstPlugin->GetEditor();
+ if (pVstEditor && pVstEditor->IsResizable())
+ {
+ pVstEditor->SetSize(index, static_cast<int>(value));
+ }
+ }
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Size Window"));
+ return 1;
+
+ case audioMasterGetSampleRate:
+ if(pVstPlugin)
+ {
+ return pVstPlugin->m_nSampleRate;
+ } else
+ {
+ // HERCs Abakos queries the sample rate while the plugin is being created and then never again...
+ return TrackerSettings::Instance().GetMixerSettings().gdwMixingFreq;
+ }
+
+ case audioMasterGetBlockSize:
+ return MIXBUFFERSIZE;
+
+ case audioMasterGetInputLatency:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Get Input Latency"));
+ break;
+
+ case audioMasterGetOutputLatency:
+ if(pVstPlugin)
+ {
+ return mpt::saturate_round<intptr_t>(pVstPlugin->GetOutputLatency() * pVstPlugin->GetSoundFile().GetSampleRate());
+ }
+ break;
+
+ // input pin in <value> (-1: first to come), returns cEffect* - DEPRECATED in VST 2.4
+ case audioMasterGetPreviousPlug:
+ if(pVstPlugin != nullptr)
+ {
+ std::vector<IMixPlugin *> list;
+ if(pVstPlugin->GetInputPlugList(list) != 0)
+ {
+ // We don't assign plugins to pins...
+ CVstPlugin *plugin = dynamic_cast<CVstPlugin *>(list[0]);
+ if(plugin != nullptr)
+ {
+ return ToIntPtr(&plugin->m_Effect);
+ }
+ }
+ }
+ break;
+
+ // output pin in <value> (-1: first to come), returns cEffect* - DEPRECATED in VST 2.4
+ case audioMasterGetNextPlug:
+ if(pVstPlugin != nullptr)
+ {
+ std::vector<IMixPlugin *> list;
+ if(pVstPlugin->GetOutputPlugList(list) != 0)
+ {
+ // We don't assign plugins to pins...
+ CVstPlugin *plugin = dynamic_cast<CVstPlugin *>(list[0]);
+ if(plugin != nullptr)
+ {
+ return ToIntPtr(&plugin->m_Effect);
+ }
+ }
+ }
+ break;
+
+ // realtime info
+ // returns: 0: not supported, 1: replace, 2: accumulate - DEPRECATED in VST 2.4 (replace is default)
+ case audioMasterWillReplaceOrAccumulate:
+ return 1; //we replace.
+
+ case audioMasterGetCurrentProcessLevel:
+ if(pVstPlugin != nullptr && pVstPlugin->GetSoundFile().IsRenderingToDisc())
+ return kVstProcessLevelOffline;
+ else
+ return kVstProcessLevelRealtime;
+ break;
+
+ // returns 0: not supported, 1: off, 2:read, 3:write, 4:read/write
+ case audioMasterGetAutomationState:
+ // Not entirely sure what this means. We can write automation TO the plug.
+ // Is that "read" in this context?
+ //MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Get Automation State"));
+ return kVstAutomationReadWrite;
+
+ case audioMasterOfflineStart:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Offlinestart"));
+ break;
+
+ case audioMasterOfflineRead:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Offlineread"));
+ break;
+
+ case audioMasterOfflineWrite:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Offlinewrite"));
+ break;
+
+ case audioMasterOfflineGetCurrentPass:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: OfflineGetcurrentpass"));
+ break;
+
+ case audioMasterOfflineGetCurrentMetaPass:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: OfflineGetCurrentMetapass"));
+ break;
+
+ // for variable i/o, sample rate in <opt> - DEPRECATED in VST 2.4
+ case audioMasterSetOutputSampleRate:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Set Output Sample Rate"));
+ break;
+
+ // result in ret - DEPRECATED in VST 2.4
+ case audioMasterGetOutputSpeakerArrangement:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Get Output Speaker Arrangement"));
+ break;
+
+ case audioMasterGetVendorString:
+ strcpy((char *) ptr, TrackerSettings::Instance().vstHostVendorString.Get().c_str());
+ return 1;
+
+ case audioMasterGetProductString:
+ strcpy((char *) ptr, TrackerSettings::Instance().vstHostProductString.Get().c_str());
+ return 1;
+
+ case audioMasterGetVendorVersion:
+ return TrackerSettings::Instance().vstHostVendorVersion;
+
+ case audioMasterVendorSpecific:
+ return 0;
+
+ // void* in <ptr>, format not defined yet - DEPRECATED in VST 2.4
+ case audioMasterSetIcon:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Set Icon"));
+ break;
+
+ // string in ptr, see below
+ case audioMasterCanDo:
+ //Other possible Can Do strings are:
+ if(!strcmp((char*)ptr, HostCanDo::sendVstEvents)
+ || !strcmp((char *)ptr, HostCanDo::sendVstMidiEvent)
+ || !strcmp((char *)ptr, HostCanDo::sendVstTimeInfo)
+ || !strcmp((char *)ptr, HostCanDo::receiveVstEvents)
+ || !strcmp((char *)ptr, HostCanDo::receiveVstMidiEvent)
+ || !strcmp((char *)ptr, HostCanDo::supplyIdle)
+ || !strcmp((char *)ptr, HostCanDo::sizeWindow)
+ || !strcmp((char *)ptr, HostCanDo::openFileSelector)
+ || !strcmp((char *)ptr, HostCanDo::closeFileSelector)
+ || !strcmp((char *)ptr, HostCanDo::acceptIOChanges)
+ || !strcmp((char *)ptr, HostCanDo::reportConnectionChanges))
+ {
+ return HostCanDo;
+ } else
+ {
+ return HostCanNotDo;
+ }
+
+ case audioMasterGetLanguage:
+ return kVstLangEnglish;
+
+ // returns platform specific ptr - DEPRECATED in VST 2.4
+ case audioMasterOpenWindow:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Open Window"));
+ break;
+
+ // close window, platform specific handle in <ptr> - DEPRECATED in VST 2.4
+ case audioMasterCloseWindow:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Close Window"));
+ break;
+
+ // get plugin directory, FSSpec on MAC, else char*
+ case audioMasterGetDirectory:
+ //MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Get Directory"));
+ // Need to allocate space for path only, but I guess noone relies on this anyway.
+ //return ToVstPtr(pVstPlugin->GetPluginFactory().dllPath.GetPath().ToLocale());
+ //return ToVstPtr(TrackerSettings::Instance().PathPlugins.GetDefaultDir());
+ break;
+
+ // something has changed, update 'multi-fx' display
+ case audioMasterUpdateDisplay:
+ if(pVstPlugin != nullptr)
+ {
+ // Note to self for testing: Electri-Q sends opcode. Korg M1 sends this when switching between Combi and Multi mode to update the preset names.
+ CAbstractVstEditor *pVstEditor = pVstPlugin->GetEditor();
+ if(pVstEditor && ::IsWindow(pVstEditor->m_hWnd))
+ {
+ pVstEditor->UpdateDisplay();
+ }
+ }
+ return 0;
+
+ //---from here VST 2.1 extension opcodes------------------------------------------------------
+
+ // begin of automation session (when mouse down), parameter index in <index>
+ case audioMasterBeginEdit:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Begin Edit"));
+ break;
+
+ // end of automation session (when mouse up), parameter index in <index>
+ case audioMasterEndEdit:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: End Edit"));
+ break;
+
+ // open a fileselector window with VstFileSelect* in <ptr>
+ case audioMasterOpenFileSelector:
+
+ //---from here VST 2.2 extension opcodes------------------------------------------------------
+
+ // close a fileselector operation with VstFileSelect* in <ptr>: Must be always called after an open !
+ case audioMasterCloseFileSelector:
+ if(pVstPlugin != nullptr && ptr != nullptr)
+ {
+ return pVstPlugin->VstFileSelector(opcode == audioMasterCloseFileSelector, *static_cast<VstFileSelect *>(ptr));
+ }
+
+ // open an editor for audio (defined by XML text in ptr) - DEPRECATED in VST 2.4
+ case audioMasterEditFile:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Edit File"));
+ break;
+
+ // get the native path of currently loading bank or project
+ // (called from writeChunk) void* in <ptr> (char[2048], or sizeof(FSSpec)) - DEPRECATED in VST 2.4
+ // Note: The shortcircuit VSTi actually uses this feature.
+ case audioMasterGetChunkFile:
+#ifdef MODPLUG_TRACKER
+ if(pVstPlugin && pVstPlugin->GetModDoc())
+ {
+ mpt::ustring pathStr = TrackerSettings::Instance().pluginProjectPath;
+ if(pathStr.empty())
+ {
+ pathStr = U_("%1");
+ }
+ const mpt::PathString projectPath = pVstPlugin->GetModDoc()->GetPathNameMpt().GetPath();
+ const mpt::PathString projectFile = pVstPlugin->GetModDoc()->GetPathNameMpt().GetFullFileName();
+ pathStr = mpt::String::Replace(pathStr, U_("%1"), U_("?1?"));
+ pathStr = mpt::String::Replace(pathStr, U_("%2"), U_("?2?"));
+ pathStr = mpt::String::Replace(pathStr, U_("?1?"), projectPath.ToUnicode());
+ pathStr = mpt::String::Replace(pathStr, U_("?2?"), projectFile.ToUnicode());
+ mpt::PathString path = mpt::PathString::FromUnicode(pathStr);
+ if(path.empty())
+ {
+ return 0;
+ }
+ path.EnsureTrailingSlash();
+ ::SHCreateDirectoryEx(NULL, path.AsNative().c_str(), nullptr);
+ path += projectFile;
+ strcpy(static_cast<char*>(ptr), path.ToLocale().c_str());
+ return 1;
+ }
+#endif
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Get Chunk File"));
+ break;
+
+ //---from here VST 2.3 extension opcodes------------------------------------------------------
+
+ // result a VstSpeakerArrangement in ret - DEPRECATED in VST 2.4
+ case audioMasterGetInputSpeakerArrangement:
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: Get Input Speaker Arrangement"));
+ break;
+
+ }
+
+ // Unknown codes:
+
+ return 0;
+}
+
+
+// Helper function for file selection dialog stuff.
+intptr_t CVstPlugin::VstFileSelector(bool destructor, VstFileSelect &fileSel)
+{
+ if(!destructor)
+ {
+ fileSel.returnMultiplePaths = nullptr;
+ fileSel.numReturnPaths = 0;
+ fileSel.reserved = 0;
+
+ std::string returnPath;
+ if(fileSel.command != kVstDirectorySelect)
+ {
+ // Plugin wants to load or save a file.
+ std::string extensions, workingDir;
+ for(int32 i = 0; i < fileSel.numFileTypes; i++)
+ {
+ const VstFileType &type = fileSel.fileTypes[i];
+ extensions += type.name;
+ extensions += "|";
+#if MPT_OS_WINDOWS
+ extensions += "*.";
+ extensions += type.dosType;
+#elif MPT_OS_MACOSX_OR_IOS
+ extensions += "*";
+ extensions += type.macType;
+#elif MPT_OS_GENERIC_UNIX
+ extensions += "*.";
+ extensions += type.unixType;
+#else
+#error Platform-specific code missing
+#endif
+ extensions += "|";
+ }
+ extensions += "|";
+
+ if(fileSel.initialPath != nullptr)
+ {
+ workingDir = fileSel.initialPath;
+ } else
+ {
+ // Plugins are probably looking for presets...?
+ //workingDir = TrackerSettings::Instance().PathPluginPresets.GetWorkingDir();
+ }
+
+ FileDialog dlg = OpenFileDialog();
+ if(fileSel.command == kVstFileSave)
+ {
+ dlg = SaveFileDialog();
+ } else if(fileSel.command == kVstMultipleFilesLoad)
+ {
+ dlg = OpenFileDialog().AllowMultiSelect();
+ }
+ dlg.ExtensionFilter(extensions)
+ .WorkingDirectory(mpt::PathString::FromLocale(workingDir))
+ .AddPlace(GetPluginFactory().dllPath.GetPath());
+ if(!dlg.Show(GetEditor()))
+ return 0;
+
+ if(fileSel.command == kVstMultipleFilesLoad)
+ {
+ // Multiple paths
+ const auto &files = dlg.GetFilenames();
+ fileSel.numReturnPaths = mpt::saturate_cast<int32>(files.size());
+ fileSel.returnMultiplePaths = new (std::nothrow) char *[fileSel.numReturnPaths];
+ if(!fileSel.returnMultiplePaths)
+ return 0;
+ for(int32 i = 0; i < fileSel.numReturnPaths; i++)
+ {
+ const std::string fname_ = files[i].ToLocale();
+ char *fname = new (std::nothrow) char[fname_.length() + 1];
+ if(fname)
+ strcpy(fname, fname_.c_str());
+ fileSel.returnMultiplePaths[i] = fname;
+ }
+ return 1;
+ } else
+ {
+ // Single path
+
+ // VOPM doesn't initialize required information properly (it doesn't memset the struct to 0)...
+ if(FourCC("VOPM") == GetUID())
+ {
+ fileSel.sizeReturnPath = _MAX_PATH;
+ }
+
+ returnPath = dlg.GetFirstFile().ToLocale();
+ }
+ } else
+ {
+ // Plugin wants a directory
+ BrowseForFolder dlg(mpt::PathString::FromLocale(fileSel.initialPath != nullptr ? fileSel.initialPath : ""), mpt::ToCString(mpt::Charset::Locale, fileSel.title != nullptr ? fileSel.title : ""));
+ if(!dlg.Show(GetEditor()))
+ return 0;
+
+ returnPath = dlg.GetDirectory().ToLocale();
+ if(FourCC("VSTr") == GetUID() && fileSel.returnPath != nullptr && fileSel.sizeReturnPath == 0)
+ {
+ // Old versions of reViSiT (which still relied on the host's file selector) seem to be dodgy.
+ // They report a path size of 0, but when using an own buffer, they will crash.
+ // So we'll just assume that reViSiT can handle long enough (_MAX_PATH) paths here.
+ fileSel.sizeReturnPath = mpt::saturate_cast<int32>(returnPath.length() + 1);
+ }
+ }
+
+ // Return single path (file or directory)
+ if(fileSel.returnPath == nullptr || fileSel.sizeReturnPath == 0)
+ {
+ // Provide some memory for the return path.
+ fileSel.sizeReturnPath = mpt::saturate_cast<int32>(returnPath.length() + 1);
+ fileSel.returnPath = new(std::nothrow) char[fileSel.sizeReturnPath];
+ if(fileSel.returnPath == nullptr)
+ {
+ return 0;
+ }
+ fileSel.reserved = 1;
+ } else
+ {
+ fileSel.reserved = 0;
+ }
+ const auto len = std::min(returnPath.size(), static_cast<size_t>(fileSel.sizeReturnPath - 1));
+ strncpy(fileSel.returnPath, returnPath.data(), len);
+ fileSel.returnPath[len] = '\0';
+ fileSel.numReturnPaths = 1;
+ fileSel.returnMultiplePaths = nullptr;
+ return 1;
+ } else
+ {
+ // Close file selector - delete allocated strings.
+ if(fileSel.command == kVstMultipleFilesLoad && fileSel.returnMultiplePaths != nullptr)
+ {
+ for(int32 i = 0; i < fileSel.numReturnPaths; i++)
+ {
+ if(fileSel.returnMultiplePaths[i] != nullptr)
+ {
+ delete[] fileSel.returnMultiplePaths[i];
+ }
+ }
+ delete[] fileSel.returnMultiplePaths;
+ fileSel.returnMultiplePaths = nullptr;
+ } else
+ {
+ if(fileSel.reserved == 1 && fileSel.returnPath != nullptr)
+ {
+ delete[] fileSel.returnPath;
+ fileSel.returnPath = nullptr;
+ }
+ }
+ return 1;
+ }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// CVstPlugin
+//
+
+CVstPlugin::CVstPlugin(bool maskCrashes, HMODULE hLibrary, VSTPluginLib &factory, SNDMIXPLUGIN &mixStruct, AEffect &effect, CSoundFile &sndFile)
+ : IMidiPlugin(factory, sndFile, &mixStruct)
+ , m_maskCrashes(maskCrashes)
+ , m_Effect(effect)
+ , timeInfo{}
+ , isBridged(!memcmp(&effect.reservedForHost2, "OMPT", 4))
+ , m_hLibrary(hLibrary)
+ , m_nSampleRate(sndFile.GetSampleRate())
+ , m_isInitialized(false)
+ , m_needIdle(false)
+{
+ // Open plugin and initialize data structures
+ Initialize();
+ InsertIntoFactoryList();
+
+ m_isInitialized = true;
+}
+
+
+void CVstPlugin::Initialize()
+{
+
+ m_Ectx = { MPT_UFORMAT("VST Plugin: {}")(m_Factory.dllPath.ToUnicode()) };
+
+ // If filename matched during load but plugin ID didn't, make sure it's updated.
+ m_pMixStruct->Info.dwPluginId1 = m_Factory.pluginId1 = m_Effect.magic;
+ m_pMixStruct->Info.dwPluginId2 = m_Factory.pluginId2 = m_Effect.uniqueID;
+
+ // Store a pointer so we can get the CVstPlugin object from the basic VST effect object.
+ m_Effect.reservedForHost1 = this;
+ m_nSampleRate = m_SndFile.GetSampleRate();
+
+ // First try to let the plugin know the render parameters.
+ Dispatch(effSetSampleRate, 0, 0, nullptr, static_cast<float>(m_nSampleRate));
+ Dispatch(effSetBlockSize, 0, MIXBUFFERSIZE, nullptr, 0.0f);
+
+ Dispatch(effOpen, 0, 0, nullptr, 0.0f);
+
+ // VST 2.0 plugins return 2 here, VST 2.4 plugins return 2400... Great!
+ m_isVst2 = Dispatch(effGetVstVersion, 0,0, nullptr, 0.0f) >= 2;
+ if(m_isVst2)
+ {
+ // Set VST speaker in/out setup to Stereo. Required for some plugins (e.g. Voxengo SPAN 2)
+ // All this might get more interesting when adding sidechaining support...
+ VstSpeakerArrangement sa{};
+ sa.numChannels = 2;
+ sa.type = kSpeakerArrStereo;
+ for(std::size_t i = 0; i < std::size(sa.speakers); i++)
+ {
+ // For now, only left and right speaker are used.
+ switch(i)
+ {
+ case 0:
+ sa.speakers[i].type = kSpeakerL;
+ mpt::String::WriteAutoBuf(sa.speakers[i].name) = "Left";
+ break;
+ case 1:
+ sa.speakers[i].type = kSpeakerR;
+ mpt::String::WriteAutoBuf(sa.speakers[i].name) = "Right";
+ break;
+ default:
+ sa.speakers[i].type = kSpeakerUndefined;
+ break;
+ }
+ }
+
+ // For some reason, this call crashes in a call to free() in AdmiralQuality NaiveLPF / SCAMP 1.2 (newer versions are fine).
+ // This does not happen when running the plugin in pretty much any host, or when running in OpenMPT 1.22 and older
+ // (EXCEPT when recompiling those old versions with VS2010), so it sounds like an ASLR issue to me.
+ // AdmiralQuality also doesn't know what to do.
+ if(GetUID() != FourCC("CSI4"))
+ {
+ // For now, input setup = output setup.
+ Dispatch(effSetSpeakerArrangement, 0, ToIntPtr(&sa), &sa, 0.0f);
+ }
+
+ // Dummy pin properties collection.
+ // We don't use them but some plugs might do inits in here.
+ VstPinProperties tempPinProperties;
+ Dispatch(effGetInputProperties, 0, 0, &tempPinProperties, 0);
+ Dispatch(effGetOutputProperties, 0, 0, &tempPinProperties, 0);
+
+ Dispatch(effConnectInput, 0, 1, nullptr, 0.0f);
+ if (m_Effect.numInputs > 1) Dispatch(effConnectInput, 1, 1, nullptr, 0.0f);
+ Dispatch(effConnectOutput, 0, 1, nullptr, 0.0f);
+ if (m_Effect.numOutputs > 1) Dispatch(effConnectOutput, 1, 1, nullptr, 0.0f);
+ // Disable all inputs and outputs beyond stereo left and right:
+ for(int32 i = 2; i < m_Effect.numInputs; i++)
+ Dispatch(effConnectInput, i, 0, nullptr, 0.0f);
+ for(int32 i = 2; i < m_Effect.numOutputs; i++)
+ Dispatch(effConnectOutput, i, 0, nullptr, 0.0f);
+ }
+
+ // Second try to let the plugin know the render parameters.
+ Dispatch(effSetSampleRate, 0, 0, nullptr, static_cast<float>(m_nSampleRate));
+ Dispatch(effSetBlockSize, 0, MIXBUFFERSIZE, nullptr, 0.0f);
+ if(m_Effect.numPrograms > 0)
+ {
+ BeginSetProgram(0);
+ EndSetProgram();
+ }
+
+ InitializeIOBuffers();
+
+ Dispatch(effSetProcessPrecision, 0, kVstProcessPrecision32, nullptr, 0.0f);
+
+ m_isInstrument = IsInstrument();
+ RecalculateGain();
+ m_pProcessFP = (m_Effect.flags & effFlagsCanReplacing) ? m_Effect.processReplacing : m_Effect.process;
+
+ // Issue samplerate again here, cos some plugs like it before the block size, other like it right at the end.
+ Dispatch(effSetSampleRate, 0, 0, nullptr, static_cast<float>(m_nSampleRate));
+
+ // Korg Wavestation GUI won't work until plugin was resumed at least once.
+ // On the other hand, some other plugins (notably Synthedit plugins like Superwave P8 2.3 or Rez 3.0) don't like this
+ // and won't load their stored plugin data instantly, so only do this for the troublesome plugins...
+ // Also apply this fix for Korg's M1 plugin, as this will fixes older versions of said plugin, newer versions don't require the fix.
+ // EZDrummer / Superior Drummer won't load their samples until playback has started.
+ if(GetUID() == FourCC("KLWV") // Wavestation
+ || GetUID() == FourCC("KLM1") // M1
+ || GetUID() == FourCC("dfhe") // EZDrummer
+ || GetUID() == FourCC("dfh2")) // Superior Drummer
+ {
+ Resume();
+ Suspend();
+ }
+}
+
+
+bool CVstPlugin::InitializeIOBuffers()
+{
+ // Input pointer array size must be >= 2 for now - the input buffer assignment might write to non allocated mem. otherwise
+ // In case of a bridged plugin, the AEffect struct has been updated before calling this opcode, so we don't have to worry about it being up-to-date.
+ return m_mixBuffer.Initialize(std::max(m_Effect.numInputs, int32(2)), m_Effect.numOutputs);
+}
+
+
+CVstPlugin::~CVstPlugin()
+{
+ CriticalSection cs;
+
+ CloseEditor();
+ if (m_isVst2)
+ {
+ Dispatch(effConnectInput, 0, 0, nullptr, 0);
+ if (m_Effect.numInputs > 1) Dispatch(effConnectInput, 1, 0, nullptr, 0);
+ Dispatch(effConnectOutput, 0, 0, nullptr, 0);
+ if (m_Effect.numOutputs > 1) Dispatch(effConnectOutput, 1, 0, nullptr, 0);
+ }
+ CVstPlugin::Suspend();
+ m_isInitialized = false;
+
+ Dispatch(effClose, 0, 0, nullptr, 0);
+ if(TrackerSettings::Instance().BrokenPluginsWorkaroundVSTNeverUnloadAnyPlugin)
+ {
+ // Buggy SynthEdit 1.4 plugins: Showing a SynthEdit 1.4 plugin's editor, fully unloading the plugin,
+ // then loading another (unrelated) SynthEdit 1.4 plugin and showing its editor causes a crash.
+ } else
+ {
+ if(m_hLibrary)
+ {
+ FreeLibrary(m_hLibrary);
+ }
+ }
+
+}
+
+
+void CVstPlugin::Release()
+{
+ delete this;
+}
+
+
+void CVstPlugin::Idle()
+{
+ if(m_needIdle)
+ {
+ if(!(Dispatch(effIdle, 0, 0, nullptr, 0.0f)))
+ m_needIdle = false;
+ }
+ if (m_pEditor && m_pEditor->m_hWnd)
+ {
+ Dispatch(effEditIdle, 0, 0, nullptr, 0.0f);
+ }
+}
+
+
+int32 CVstPlugin::GetNumPrograms() const
+{
+ return std::max(m_Effect.numPrograms, int32(0));
+}
+
+
+PlugParamIndex CVstPlugin::GetNumParameters() const
+{
+ return m_Effect.numParams;
+}
+
+
+// Check whether a VST parameter can be automated
+bool CVstPlugin::CanAutomateParameter(PlugParamIndex index)
+{
+ return (Dispatch(effCanBeAutomated, index, 0, nullptr, 0.0f) != 0);
+}
+
+
+int32 CVstPlugin::GetUID() const
+{
+ return m_Effect.uniqueID;
+}
+
+
+int32 CVstPlugin::GetVersion() const
+{
+ return m_Effect.version;
+}
+
+
+// Wrapper for VST dispatch call with structured exception handling.
+intptr_t CVstPlugin::DispatchSEH(bool maskCrashes, AEffect *effect, VstOpcodeToPlugin opCode, int32 index, intptr_t value, void *ptr, float opt, unsigned long &exception)
+{
+ if(effect->dispatcher != nullptr)
+ {
+ intptr_t result = 0;
+ DWORD e = SETryOrError(maskCrashes, [&](){ result = effect->dispatcher(effect, opCode, index, value, ptr, opt); });
+ if(e)
+ {
+ exception = e;
+ }
+ return result;
+ }
+ return 0;
+}
+
+
+intptr_t CVstPlugin::Dispatch(VstOpcodeToPlugin opCode, int32 index, intptr_t value, void *ptr, float opt)
+{
+#ifdef VST_LOG
+ {
+ mpt::ustring codeStr;
+ if(opCode >= 0 && static_cast<std::size_t>(opCode) < std::size(VstOpCodes))
+ {
+ codeStr = mpt::ToUnicode(mpt::Charset::ASCII, VstOpCodes[opCode]);
+ } else
+ {
+ codeStr = mpt::ufmt::val(opCode);
+ }
+ MPT_LOG_GLOBAL(LogDebug, "VST", MPT_UFORMAT("About to Dispatch({}) (Plugin=\"{}\"), index: {}, value: {}, ptr: {}, opt: {}!\n")(codeStr, m_Factory.libraryName, index, mpt::ufmt::PTR(value), mpt::ufmt::PTR(ptr), mpt::ufmt::flt(opt, 3)));
+ }
+#endif
+ if(!m_Effect.dispatcher)
+ {
+ return 0;
+ }
+ intptr_t result = 0;
+ {
+ DWORD exception = SETryOrError([&](){ result = m_Effect.dispatcher(&m_Effect, opCode, index, value, ptr, opt); });
+ if(exception)
+ {
+ mpt::ustring codeStr;
+ if(opCode < mpt::saturate_cast<int32>(std::size(VstOpCodes)))
+ {
+ codeStr = mpt::ToUnicode(mpt::Charset::ASCII, VstOpCodes[opCode]);
+ } else
+ {
+ codeStr = mpt::ufmt::val(opCode);
+ }
+ ReportPlugException(MPT_UFORMAT("Exception {} in Dispatch({})")(mpt::ufmt::HEX0<8>(exception), codeStr));
+ }
+ }
+ return result;
+}
+
+
+int32 CVstPlugin::GetCurrentProgram()
+{
+ if(m_Effect.numPrograms > 0)
+ {
+ return static_cast<int32>(Dispatch(effGetProgram, 0, 0, nullptr, 0));
+ }
+ return 0;
+}
+
+
+CString CVstPlugin::GetCurrentProgramName()
+{
+ std::vector<char> s(256, 0);
+ // kVstMaxProgNameLen is 24... too short for some plugins, so use at least 256 bytes.
+ Dispatch(effGetProgramName, 0, 0, s.data(), 0);
+ return mpt::ToCString(mpt::Charset::Locale, s.data());
+}
+
+
+void CVstPlugin::SetCurrentProgramName(const CString &name)
+{
+ Dispatch(effSetProgramName, 0, 0, const_cast<char *>(mpt::ToCharset(mpt::Charset::Locale, name.Left(kVstMaxProgNameLen)).c_str()), 0.0f);
+}
+
+
+CString CVstPlugin::GetProgramName(int32 program)
+{
+ // kVstMaxProgNameLen is 24... too short for some plugins, so use at least 256 bytes.
+ std::vector<char> rawname(256, 0);
+ if(program < m_Effect.numPrograms)
+ {
+ if(Dispatch(effGetProgramNameIndexed, program, -1 /*category*/, rawname.data(), 0) != 1)
+ {
+ // Fallback: Try to get current program name.
+ rawname.assign(256, 0);
+ int32 curProg = GetCurrentProgram();
+ if(program != curProg)
+ {
+ SetCurrentProgram(program);
+ }
+ Dispatch(effGetProgramName, 0, 0, rawname.data(), 0);
+ if(program != curProg)
+ {
+ SetCurrentProgram(curProg);
+ }
+ }
+ }
+ return mpt::ToCString(mpt::Charset::Locale, rawname.data());
+}
+
+
+void CVstPlugin::SetCurrentProgram(int32 nIndex)
+{
+ if(m_Effect.numPrograms > 0)
+ {
+ if(nIndex < m_Effect.numPrograms)
+ {
+ BeginSetProgram(nIndex);
+ EndSetProgram();
+ }
+ }
+}
+
+
+void CVstPlugin::BeginSetProgram(int32 program)
+{
+ Dispatch(effBeginSetProgram, 0, 0, nullptr, 0);
+ if(program != -1)
+ Dispatch(effSetProgram, 0, program, nullptr, 0);
+}
+
+
+void CVstPlugin::EndSetProgram()
+{
+ Dispatch(effEndSetProgram, 0, 0, nullptr, 0);
+}
+
+
+void CVstPlugin::BeginGetProgram(int32 program)
+{
+ if(program != -1)
+ Dispatch(effSetProgram, 0, program, nullptr, 0);
+ if(isBridged)
+ Dispatch(effVendorSpecific, kVendorOpenMPT, kBeginGetProgram, nullptr, 0);
+}
+
+
+void CVstPlugin::EndGetProgram()
+{
+ if(isBridged)
+ Dispatch(effVendorSpecific, kVendorOpenMPT, kEndGetProgram, nullptr, 0);
+}
+
+
+PlugParamValue CVstPlugin::GetParameter(PlugParamIndex nIndex)
+{
+ float fResult = 0;
+ if(nIndex < m_Effect.numParams && m_Effect.getParameter != nullptr)
+ {
+ DWORD exception = SETryOrError([&](){ fResult = m_Effect.getParameter(&m_Effect, nIndex); });
+ if(exception)
+ {
+ //ReportPlugException(U_("Exception in getParameter (Plugin=\"{}\")!\n"), m_Factory.szLibraryName);
+ }
+ }
+ return fResult;
+}
+
+
+void CVstPlugin::SetParameter(PlugParamIndex nIndex, PlugParamValue fValue)
+{
+ DWORD exception = 0;
+ if(nIndex < m_Effect.numParams && m_Effect.setParameter)
+ {
+ exception = SETryOrError([&](){ m_Effect.setParameter(&m_Effect, nIndex, fValue); });
+ }
+ ResetSilence();
+ if(exception)
+ {
+ //ReportPlugException(mpt::format(U_("Exception in SetParameter({}, {})!"))(nIndex, fValue));
+ }
+}
+
+
+// Helper function for retreiving parameter name / label / display
+CString CVstPlugin::GetParamPropertyString(PlugParamIndex param, Vst::VstOpcodeToPlugin opcode)
+{
+ if(m_Effect.numParams > 0 && param < m_Effect.numParams)
+ {
+ // Increased to 256 bytes since SynthMaster 2.8 writes more than 64 bytes of 0-padding. Kind of ridiculous if you consider that kVstMaxParamStrLen = 8...
+ std::vector<char> s(256, 0);
+ Dispatch(opcode, param, 0, s.data(), 0);
+ return mpt::ToCString(mpt::Charset::Locale, s.data());
+ }
+ return CString();
+}
+
+
+CString CVstPlugin::GetParamName(PlugParamIndex param)
+{
+ VstParameterProperties properties{};
+ if(param < m_Effect.numParams && Dispatch(effGetParameterProperties, param, 0, &properties, 0.0f) == 1)
+ {
+ mpt::String::SetNullTerminator(properties.label);
+ return mpt::ToCString(mpt::Charset::Locale, properties.label);
+ } else
+ {
+ return GetParamPropertyString(param, effGetParamName);
+ }
+}
+
+
+CString CVstPlugin::GetDefaultEffectName()
+{
+ if(m_isVst2)
+ {
+ std::vector<char> s(256, 0);
+ Dispatch(effGetEffectName, 0, 0, s.data(), 0);
+ return mpt::ToCString(mpt::Charset::Locale, s.data());
+ }
+ return CString();
+}
+
+
+void CVstPlugin::Resume()
+{
+ const uint32 sampleRate = m_SndFile.GetSampleRate();
+
+ //reset some stuff
+ m_MixState.nVolDecayL = 0;
+ m_MixState.nVolDecayR = 0;
+ if(m_isResumed)
+ {
+ Dispatch(effStopProcess, 0, 0, nullptr, 0.0f);
+ Dispatch(effMainsChanged, 0, 0, nullptr, 0.0f); // calls plugin's suspend
+ }
+ if (sampleRate != m_nSampleRate)
+ {
+ m_nSampleRate = sampleRate;
+ Dispatch(effSetSampleRate, 0, 0, nullptr, static_cast<float>(m_nSampleRate));
+ }
+ Dispatch(effSetBlockSize, 0, MIXBUFFERSIZE, nullptr, 0.0f);
+ //start off some stuff
+ Dispatch(effMainsChanged, 0, 1, nullptr, 0.0f); // calls plugin's resume
+ Dispatch(effStartProcess, 0, 0, nullptr, 0.0f);
+ m_isResumed = true;
+}
+
+
+void CVstPlugin::Suspend()
+{
+ if(m_isResumed)
+ {
+ Dispatch(effStopProcess, 0, 0, nullptr, 0.0f);
+ Dispatch(effMainsChanged, 0, 0, nullptr, 0.0f); // calls plugin's suspend (theoretically, plugins should clean their buffers here, but oh well, the number of plugins which don't do this is surprisingly high.)
+ m_isResumed = false;
+ }
+}
+
+
+// Send events to plugin. Returns true if there are events left to be processed.
+void CVstPlugin::ProcessVSTEvents()
+{
+ // Process VST events
+ if(m_Effect.dispatcher != nullptr && vstEvents.Finalise() > 0)
+ {
+ DWORD exception = SETryOrError([&](){ m_Effect.dispatcher(&m_Effect, effProcessEvents, 0, 0, &vstEvents, 0); });
+ ResetSilence();
+ if(exception)
+ {
+ ReportPlugException(MPT_UFORMAT("Exception {} in ProcessVSTEvents(numEvents:{})!")(
+ mpt::ufmt::HEX0<8>(exception),
+ vstEvents.size()));
+ }
+ }
+}
+
+
+// Receive events from plugin and send them to the next plugin in the chain.
+void CVstPlugin::ReceiveVSTEvents(const VstEvents *events)
+{
+ if(m_pMixStruct == nullptr)
+ {
+ return;
+ }
+
+ ResetSilence();
+
+ // I think we should only route events to plugins that are explicitely specified as output plugins of the current plugin.
+ // This should probably use GetOutputPlugList here if we ever get to support multiple output plugins.
+ PLUGINDEX receiver = m_pMixStruct->GetOutputPlugin();
+
+ if(receiver != PLUGINDEX_INVALID)
+ {
+ IMixPlugin *plugin = m_SndFile.m_MixPlugins[receiver].pMixPlugin;
+ CVstPlugin *vstPlugin = dynamic_cast<CVstPlugin *>(plugin);
+ // Add all events to the plugin's queue.
+ for(const auto &ev : *events)
+ {
+ if(vstPlugin != nullptr)
+ {
+ // Directly enqueue the message and preserve as much of the event data as possible (e.g. delta frames, which are currently not used by OpenMPT but might be by plugins)
+ vstPlugin->vstEvents.Enqueue(ev);
+ } else if(plugin != nullptr)
+ {
+ if(ev->type == kVstMidiType)
+ {
+ plugin->MidiSend(static_cast<const VstMidiEvent *>(ev)->midiData);
+ } else if(ev->type == kVstSysExType)
+ {
+ auto event = static_cast<const VstMidiSysexEvent *>(ev);
+ plugin->MidiSysexSend(mpt::as_span(mpt::byte_cast<const std::byte *>(event->sysexDump), event->dumpBytes));
+ }
+ }
+ }
+ }
+
+#ifdef MODPLUG_TRACKER
+ if(m_recordMIDIOut)
+ {
+ // Spam MIDI data to all views
+ for(const auto &ev : *events)
+ {
+ if(ev->type == kVstMidiType)
+ {
+ VstMidiEvent *event = static_cast<VstMidiEvent *>(ev);
+ ::SendNotifyMessage(CMainFrame::GetMainFrame()->GetMidiRecordWnd(), WM_MOD_MIDIMSG, event->midiData, reinterpret_cast<LPARAM>(this));
+ }
+ }
+ }
+#endif // MODPLUG_TRACKER
+}
+
+
+void CVstPlugin::Process(float *pOutL, float *pOutR, uint32 numFrames)
+{
+ ProcessVSTEvents();
+
+ // If the plugin is found & ok, continue
+ if(m_pProcessFP != nullptr && m_mixBuffer.Ok())
+ {
+ int32 numInputs = m_Effect.numInputs, numOutputs = m_Effect.numOutputs;
+ //RecalculateGain();
+
+ // Merge stereo input before sending to the plugin if it can only handle one input.
+ if (numInputs == 1)
+ {
+ float *plugInputL = m_mixBuffer.GetInputBuffer(0);
+ float *plugInputR = m_mixBuffer.GetInputBuffer(1);
+ for (uint32 i = 0; i < numFrames; i++)
+ {
+ plugInputL[i] = 0.5f * (plugInputL[i] + plugInputR[i]);
+ }
+ }
+
+ float **outputBuffers = m_mixBuffer.GetOutputBufferArray();
+ if(!isBridged)
+ {
+ m_mixBuffer.ClearOutputBuffers(numFrames);
+ }
+
+ // Do the VST processing magic
+ MPT_ASSERT(numFrames <= MIXBUFFERSIZE);
+ {
+ DWORD exception = SETryOrError([&](){ m_pProcessFP(&m_Effect, m_mixBuffer.GetInputBufferArray(), outputBuffers, numFrames); });
+ if(exception)
+ {
+ Bypass();
+ mpt::ustring processMethod = (m_Effect.flags & effFlagsCanReplacing) ? U_("processReplacing") : U_("process");
+ ReportPlugException(MPT_UFORMAT("The plugin threw an exception ({}) in {}. It has automatically been set to \"Bypass\".")(mpt::ufmt::HEX0<8>(exception), processMethod));
+ }
+ }
+
+ // Mix outputs of multi-output VSTs:
+ if(numOutputs > 2)
+ {
+ MPT_ASSERT(outputBuffers != nullptr);
+ // first, mix extra outputs on a stereo basis
+ int32 outs = numOutputs;
+ // so if nOuts is not even, let process the last output later
+ if((outs % 2u) == 1) outs--;
+
+ // mix extra stereo outputs
+ for(int32 iOut = 2; iOut < outs; iOut++)
+ {
+ for(uint32 i = 0; i < numFrames; i++)
+ {
+ outputBuffers[iOut % 2u][i] += outputBuffers[iOut][i]; // assumed stereo.
+ }
+ }
+
+ // if m_Effect.numOutputs is odd, mix half the signal of last output to each channel
+ if(outs != numOutputs)
+ {
+ // trick : if we are here, numOutputs = m_Effect.numOutputs - 1 !!!
+ for(uint32 i = 0; i < numFrames; i++)
+ {
+ float v = 0.5f * outputBuffers[outs][i];
+ outputBuffers[0][i] += v;
+ outputBuffers[1][i] += v;
+ }
+ }
+ }
+
+ if(numOutputs != 0)
+ {
+ MPT_ASSERT(outputBuffers != nullptr);
+ ProcessMixOps(pOutL, pOutR, outputBuffers[0], outputBuffers[numOutputs > 1 ? 1 : 0], numFrames);
+ }
+
+ // If the I/O format of the bridge changed in the meanwhile, update it now.
+ if(isBridged && Dispatch(effVendorSpecific, kVendorOpenMPT, kCloseOldProcessingMemory, nullptr, 0.0f) != 0)
+ {
+ InitializeIOBuffers();
+ }
+ }
+
+ vstEvents.Clear();
+ m_positionChanged = false;
+}
+
+
+bool CVstPlugin::MidiSend(uint32 dwMidiCode)
+{
+ // Note-Offs go at the start of the queue (since OpenMPT 1.17). Needed for situations like this:
+ // ... ..|C-5 01
+ // C-5 01|=== ..
+ // TODO: Should not be used with real-time notes! Letting the key go too quickly
+ // (e.g. while output device is being initalized) will cause the note to be stuck!
+ bool insertAtFront = (MIDIEvents::GetTypeFromEvent(dwMidiCode) == MIDIEvents::evNoteOff);
+
+ VstMidiEvent event{};
+ event.type = kVstMidiType;
+ event.byteSize = sizeof(event);
+ event.midiData = dwMidiCode;
+
+ ResetSilence();
+ return vstEvents.Enqueue(&event, insertAtFront);
+}
+
+
+bool CVstPlugin::MidiSysexSend(mpt::const_byte_span sysex)
+{
+ VstMidiSysexEvent event{};
+ event.type = kVstSysExType;
+ event.byteSize = sizeof(event);
+ event.dumpBytes = mpt::saturate_cast<int32>(sysex.size());
+ event.sysexDump = sysex.data(); // We will make our own copy in VstEventQueue::Enqueue
+
+ ResetSilence();
+ return vstEvents.Enqueue(&event);
+}
+
+
+void CVstPlugin::HardAllNotesOff()
+{
+ constexpr uint32 SCRATCH_BUFFER_SIZE = 64;
+ float out[2][SCRATCH_BUFFER_SIZE]; // scratch buffers
+
+ // The JUCE framework doesn't like processing while being suspended.
+ const bool wasSuspended = !IsResumed();
+ if(wasSuspended)
+ {
+ Resume();
+ }
+
+ const bool isWavestation = GetUID() == FourCC("KLWV");
+ const bool isSawer = GetUID() == FourCC("SaWR");
+ for(uint8 mc = 0; mc < m_MidiCh.size(); mc++)
+ {
+ PlugInstrChannel &channel = m_MidiCh[mc];
+ channel.ResetProgram();
+
+ SendMidiPitchBend(mc, EncodePitchBendParam(MIDIEvents::pitchBendCentre)); // centre pitch bend
+
+ if(!isWavestation && !isSawer)
+ {
+ // Korg Wavestation doesn't seem to like this CC, it can introduce ghost notes or
+ // prevent new notes from being played.
+ // Image-Line Sawer does not like it either and resets some parameters so that the plugin is all
+ // distorted afterwards.
+ MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_AllControllersOff, mc, 0));
+ }
+ if(!isSawer)
+ {
+ // Image-Line Sawer takes ages to execute this CC.
+ MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_AllNotesOff, mc, 0));
+ }
+ MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_AllSoundOff, mc, 0));
+
+ for(std::size_t i = 0; i < std::size(channel.noteOnMap); i++) //all notes
+ {
+ for(auto &c : channel.noteOnMap[i])
+ {
+ while(c != 0)
+ {
+ MidiSend(MIDIEvents::NoteOff(mc, static_cast<uint8>(i), 0));
+ c--;
+ }
+ }
+ }
+ }
+ // Let plugin process events
+ while(vstEvents.GetNumQueuedEvents() > 0)
+ {
+ Process(out[0], out[1], SCRATCH_BUFFER_SIZE);
+ }
+
+ if(wasSuspended)
+ {
+ Suspend();
+ }
+}
+
+
+void CVstPlugin::SaveAllParameters()
+{
+ if(m_pMixStruct == nullptr)
+ {
+ return;
+ }
+ m_pMixStruct->defaultProgram = -1;
+
+ if(ProgramsAreChunks())
+ {
+ void *p = nullptr;
+
+ // Try to get whole bank
+ intptr_t byteSize = Dispatch(effGetChunk, 0, 0, &p, 0);
+
+ if (!p)
+ {
+ // Getting bank failed, try to get just preset
+ byteSize = Dispatch(effGetChunk, 1, 0, &p, 0);
+ } else
+ {
+ // We managed to get the bank, now we need to remember which program we're on.
+ m_pMixStruct->defaultProgram = GetCurrentProgram();
+ }
+ if (p != nullptr)
+ {
+ LimitMax(byteSize, Util::MaxValueOfType(byteSize) - 4);
+ try
+ {
+ m_pMixStruct->pluginData.resize(byteSize + 4);
+ auto data = m_pMixStruct->pluginData.data();
+ memcpy(data, "fEvN", 4); // 'NvEf', return value of deprecated effIdentify call
+ memcpy(data + 4, p, byteSize);
+ return;
+ } catch(mpt::out_of_memory e)
+ {
+ mpt::delete_out_of_memory(e);
+ }
+ }
+ }
+ // This plugin doesn't support chunks: save parameters
+ IMixPlugin::SaveAllParameters();
+}
+
+
+void CVstPlugin::RestoreAllParameters(int32 program)
+{
+ if(m_pMixStruct != nullptr && m_pMixStruct->pluginData.size() >= 4)
+ {
+ auto data = m_pMixStruct->pluginData.data();
+ if (!memcmp(data, "fEvN", 4)) // 'NvEf', return value of deprecated effIdentify call
+ {
+ if ((program>=0) && (program < m_Effect.numPrograms))
+ {
+ // Bank
+ Dispatch(effSetChunk, 0, m_pMixStruct->pluginData.size() - 4, data + 4, 0);
+ SetCurrentProgram(program);
+ } else
+ {
+ // Program
+ BeginSetProgram(-1);
+ Dispatch(effSetChunk, 1, m_pMixStruct->pluginData.size() - 4, data + 4, 0);
+ EndSetProgram();
+ }
+ } else
+ {
+ IMixPlugin::RestoreAllParameters(program);
+ }
+ }
+}
+
+
+CAbstractVstEditor *CVstPlugin::OpenEditor()
+{
+ try
+ {
+ if(HasEditor())
+ return new COwnerVstEditor(*this);
+ else
+ return new CDefaultVstEditor(*this);
+ } catch(mpt::out_of_memory e)
+ {
+ mpt::delete_out_of_memory(e);
+ ReportPlugException(U_("Exception in OpenEditor()"));
+ return nullptr;
+ }
+}
+
+
+void CVstPlugin::Bypass(bool bypass)
+{
+ Dispatch(effSetBypass, bypass ? 1 : 0, 0, nullptr, 0.0f);
+ IMixPlugin::Bypass(bypass);
+}
+
+
+void CVstPlugin::NotifySongPlaying(bool playing)
+{
+ m_isSongPlaying = playing;
+}
+
+
+bool CVstPlugin::IsInstrument() const
+{
+ return ((m_Effect.flags & effFlagsIsSynth) || (!m_Effect.numInputs));
+}
+
+
+bool CVstPlugin::CanRecieveMidiEvents()
+{
+ return Dispatch(effCanDo, 0, 0, const_cast<char *>(PluginCanDo::receiveVstMidiEvent), 0.0f) != 0;
+}
+
+
+void CVstPlugin::ReportPlugException(const mpt::ustring &text) const
+{
+ CVstPluginManager::ReportPlugException(MPT_UFORMAT("{} (Plugin: {})")(text, m_Factory.libraryName));
+}
+
+
+// Cache program names for plugin bridge
+void CVstPlugin::CacheProgramNames(int32 firstProg, int32 lastProg)
+{
+ if(isBridged)
+ {
+ int32 offsets[2] = { firstProg, lastProg };
+ Dispatch(effVendorSpecific, kVendorOpenMPT, kCacheProgramNames, offsets, 0.0f);
+ }
+}
+
+
+// Cache parameter names for plugin bridge
+void CVstPlugin::CacheParameterNames(int32 firstParam, int32 lastParam)
+{
+ if(isBridged)
+ {
+ int32 offsets[2] = { firstParam, lastParam };
+ Dispatch(effVendorSpecific, kVendorOpenMPT, kCacheParameterInfo, offsets, 0.0f);
+ }
+}
+
+
+IMixPlugin::ChunkData CVstPlugin::GetChunk(bool isBank)
+{
+ std::byte *chunk = nullptr;
+ auto size = Dispatch(effGetChunk, isBank ? 0 : 1, 0, &chunk, 0);
+ if(chunk == nullptr)
+ {
+ size = 0;
+ }
+ return ChunkData(chunk, size);
+}
+
+
+void CVstPlugin::SetChunk(const ChunkData &chunk, bool isBank)
+{
+ Dispatch(effSetChunk, isBank ? 0 : 1, chunk.size(), const_cast<std::byte *>(chunk.data()), 0);
+}
+
+
+OPENMPT_NAMESPACE_END
+
+#endif // MPT_WITH_VST