/* * 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 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 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(e)); if(Reporting::Confirm(msg, _T("OpenMPT Plugin Bridge")) == cnfNo) { return nullptr; } } else { Reporting::Error(mpt::get_exception_text(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(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 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------------------------------------------------------ // is a filter which is currently ignored - DEPRECATED in VST 2.4 case audioMasterWantMidi: return 1; // returns const VstTimeInfo* (or 0 if not supported) // 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(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(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 - 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(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(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(pVstPlugin->GetOutputLatency() * pVstPlugin->GetSoundFile().GetSampleRate()); } break; // input pin in (-1: first to come), returns cEffect* - DEPRECATED in VST 2.4 case audioMasterGetPreviousPlug: if(pVstPlugin != nullptr) { std::vector list; if(pVstPlugin->GetInputPlugList(list) != 0) { // We don't assign plugins to pins... CVstPlugin *plugin = dynamic_cast(list[0]); if(plugin != nullptr) { return ToIntPtr(&plugin->m_Effect); } } } break; // output pin in (-1: first to come), returns cEffect* - DEPRECATED in VST 2.4 case audioMasterGetNextPlug: if(pVstPlugin != nullptr) { std::vector list; if(pVstPlugin->GetOutputPlugList(list) != 0) { // We don't assign plugins to pins... CVstPlugin *plugin = dynamic_cast(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 - 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 , 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 - 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 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 case audioMasterEndEdit: MPT_LOG_GLOBAL(LogDebug, "VST", U_("VST plugin to host: End Edit")); break; // open a fileselector window with VstFileSelect* in case audioMasterOpenFileSelector: //---from here VST 2.2 extension opcodes------------------------------------------------------ // close a fileselector operation with VstFileSelect* in : Must be always called after an open ! case audioMasterCloseFileSelector: if(pVstPlugin != nullptr && ptr != nullptr) { return pVstPlugin->VstFileSelector(opcode == audioMasterCloseFileSelector, *static_cast(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 (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(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(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(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(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(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(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(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(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(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(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(Dispatch(effGetProgram, 0, 0, nullptr, 0)); } return 0; } CString CVstPlugin::GetCurrentProgramName() { std::vector 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(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 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 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 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(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(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(ev)->midiData); } else if(ev->type == kVstSysExType) { auto event = static_cast(ev); plugin->MidiSysexSend(mpt::as_span(mpt::byte_cast(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(ev); ::SendNotifyMessage(CMainFrame::GetMainFrame()->GetMidiRecordWnd(), WM_MOD_MIDIMSG, event->midiData, reinterpret_cast(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(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(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(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(chunk.data()), 0); } OPENMPT_NAMESPACE_END #endif // MPT_WITH_VST