winamp/Src/external_dependencies/openmpt-trunk/mptrack/Vstplug.cpp

1763 lines
51 KiB
C++
Raw Permalink Normal View History

2024-09-24 13:54:57 +01:00
/*
* 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