diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeWrapper.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeWrapper.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeWrapper.cpp | 1291 |
1 files changed, 1291 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeWrapper.cpp b/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeWrapper.cpp new file mode 100644 index 00000000..4693a323 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeWrapper.cpp @@ -0,0 +1,1291 @@ +/* + * BridgeWrapper.cpp + * ----------------- + * Purpose: VST plugin bridge wrapper (host side) + * 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 "BridgeWrapper.h" +#include "../soundlib/plugins/PluginManager.h" +#include "../mptrack/Mainfrm.h" +#include "../mptrack/Mptrack.h" +#include "../mptrack/Vstplug.h" +#include "../mptrack/ExceptionHandler.h" +#include "../common/mptFileIO.h" +#include "../common/mptStringBuffer.h" +#include "../common/misc_util.h" + +using namespace Vst; + + +OPENMPT_NAMESPACE_BEGIN + +std::vector<BridgeCommon *> BridgeCommon::m_plugins; +HWND BridgeCommon::m_communicationWindow = nullptr; +int BridgeCommon::m_instanceCount = 0; +thread_local bool BridgeCommon::m_isAudioThread = false; + +std::size_t GetPluginArchPointerSize(PluginArch arch) +{ + std::size_t result = 0; + switch(arch) + { + case PluginArch_x86: + result = 4; + break; + case PluginArch_amd64: + result = 8; + break; + case PluginArch_arm: + result = 4; + break; + case PluginArch_arm64: + result = 8; + break; + default: + result = 0; + break; + } + return result; +} + + +ComponentPluginBridge::ComponentPluginBridge(PluginArch arch, Generation generation) + : ComponentBase(ComponentTypeBundled) + , arch(arch) + , generation(generation) +{ +} + + +bool ComponentPluginBridge::DoInitialize() +{ + mpt::PathString archName; + switch(arch) + { + case PluginArch_x86: + if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::Architecture::x86) == mpt::OS::Windows::EmulationLevel::NA) + { + return false; + } + archName = P_("x86"); + break; + case PluginArch_amd64: + if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::Architecture::amd64) == mpt::OS::Windows::EmulationLevel::NA) + { + return false; + } + archName = P_("amd64"); + break; + case PluginArch_arm: + if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::Architecture::arm) == mpt::OS::Windows::EmulationLevel::NA) + { + return false; + } + archName = P_("arm"); + break; + case PluginArch_arm64: + if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::Architecture::arm64) == mpt::OS::Windows::EmulationLevel::NA) + { + return false; + } + archName = P_("arm64"); + break; + default: + break; + } + if(archName.empty()) + { + return false; + } + exeName = mpt::PathString(); + const mpt::PathString generationSuffix = (generation == Generation::Legacy) ? P_("Legacy") : P_(""); + const mpt::PathString exeNames[] = + { + theApp.GetInstallPath() + P_("PluginBridge") + generationSuffix + P_("-") + archName + P_(".exe"), // Local + theApp.GetInstallBinPath() + archName + P_("\\") + P_("PluginBridge") + generationSuffix + P_(".exe"), // Multi-arch + theApp.GetInstallBinPath() + archName + P_("\\") + P_("PluginBridge") + generationSuffix + P_("-") + archName + P_(".exe") // Multi-arch transitional + }; + for(const auto &candidate : exeNames) + { + if(candidate.IsFile()) + { + exeName = candidate; + break; + } + } + if(exeName.empty()) + { + availability = AvailabilityMissing; + return false; + } + std::vector<WCHAR> exePath(MAX_PATH); + while(GetModuleFileNameW(0, exePath.data(), mpt::saturate_cast<DWORD>(exePath.size())) >= exePath.size()) + { + exePath.resize(exePath.size() * 2); + } + uint64 mptVersion = BridgeWrapper::GetFileVersion(exePath.data()); + uint64 bridgeVersion = BridgeWrapper::GetFileVersion(exeName.ToWide().c_str()); + if(bridgeVersion != mptVersion) + { + availability = AvailabilityWrongVersion; + return false; + } + availability = AvailabilityOK; + return true; +} + + +PluginArch BridgeWrapper::GetNativePluginBinaryType() +{ + PluginArch result = PluginArch_unknown; + switch(mpt::OS::Windows::GetProcessArchitecture()) + { + case mpt::OS::Windows::Architecture::x86: + result = PluginArch_x86; + break; + case mpt::OS::Windows::Architecture::amd64: + result = PluginArch_amd64; + break; + case mpt::OS::Windows::Architecture::arm: + result = PluginArch_arm; + break; + case mpt::OS::Windows::Architecture::arm64: + result = PluginArch_arm64; + break; + default: + result = PluginArch_unknown; + break; + } + return result; +} + + +// Check whether we need to load a 32-bit or 64-bit wrapper. +PluginArch BridgeWrapper::GetPluginBinaryType(const mpt::PathString &pluginPath) +{ + PluginArch type = PluginArch_unknown; + mpt::ifstream file(pluginPath, std::ios::in | std::ios::binary); + if(file.is_open()) + { + IMAGE_DOS_HEADER dosHeader; + IMAGE_NT_HEADERS ntHeader; + file.read(reinterpret_cast<char *>(&dosHeader), sizeof(dosHeader)); + if(dosHeader.e_magic == IMAGE_DOS_SIGNATURE) + { + file.seekg(dosHeader.e_lfanew); + file.read(reinterpret_cast<char *>(&ntHeader), sizeof(ntHeader)); + + MPT_ASSERT((ntHeader.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0); + switch(ntHeader.FileHeader.Machine) + { + case IMAGE_FILE_MACHINE_I386: + type = PluginArch_x86; + break; + case IMAGE_FILE_MACHINE_AMD64: + type = PluginArch_amd64; + break; +#if defined(MPT_WITH_WINDOWS10) + case IMAGE_FILE_MACHINE_ARM: + type = PluginArch_arm; + break; + case IMAGE_FILE_MACHINE_ARM64: + type = PluginArch_arm64; + break; +#endif // MPT_WITH_WINDOWS10 + default: + type = PluginArch_unknown; + break; + } + } + } + return type; +} + + +uint64 BridgeWrapper::GetFileVersion(const WCHAR *exePath) +{ + DWORD verHandle = 0; + DWORD verSize = GetFileVersionInfoSizeW(exePath, &verHandle); + uint64 result = 0; + if(verSize == 0) + { + return result; + } + + char *verData = new(std::nothrow) char[verSize]; + if(verData && GetFileVersionInfoW(exePath, verHandle, verSize, verData)) + { + UINT size = 0; + void *lpBuffer = nullptr; + if(VerQueryValue(verData, _T("\\"), &lpBuffer, &size) && size != 0) + { + auto *verInfo = static_cast<const VS_FIXEDFILEINFO *>(lpBuffer); + if(verInfo->dwSignature == 0xfeef04bd) + { + result = (uint64(HIWORD(verInfo->dwFileVersionMS)) << 48) + | (uint64(LOWORD(verInfo->dwFileVersionMS)) << 32) + | (uint64(HIWORD(verInfo->dwFileVersionLS)) << 16) + | uint64(LOWORD(verInfo->dwFileVersionLS)); + } + } + } + delete[] verData; + return result; +} + + +// Create a plugin bridge object +AEffect *BridgeWrapper::Create(const VSTPluginLib &plugin, bool forceLegacy) +{ + BridgeWrapper *wrapper = new(std::nothrow) BridgeWrapper(); + BridgeWrapper *sharedInstance = nullptr; + const Generation wantedGeneration = (plugin.modernBridge && !forceLegacy) ? Generation::Modern : Generation::Legacy; + + // Should we share instances? + if(plugin.shareBridgeInstance) + { + // Well, then find some instance to share with! + CVstPlugin *vstPlug = dynamic_cast<CVstPlugin *>(plugin.pPluginsList); + while(vstPlug != nullptr) + { + if(vstPlug->isBridged) + { + BridgeWrapper *instance = FromIntPtr<BridgeWrapper>(vstPlug->Dispatch(effVendorSpecific, kVendorOpenMPT, kGetWrapperPointer, nullptr, 0.0f)); + if(wantedGeneration == instance->m_Generation) + { + sharedInstance = instance; + break; + } + } + vstPlug = dynamic_cast<CVstPlugin *>(vstPlug->GetNextInstance()); + } + } + + try + { + if(wrapper != nullptr && wrapper->Init(plugin.dllPath, wantedGeneration, sharedInstance) && wrapper->m_queueMem.Good()) + { + return &wrapper->m_sharedMem->effect; + } + delete wrapper; + return nullptr; + } catch(BridgeException &) + { + delete wrapper; + throw; + } +} + + +BridgeWrapper::BridgeWrapper() +{ + m_thisPluginID = static_cast<int32>(m_plugins.size()); + m_plugins.push_back(this); + + if(m_instanceCount == 1) + CreateCommunicationWindow(WindowProc); +} + + +BridgeWrapper::~BridgeWrapper() +{ + if(m_instanceCount == 1) + DestroyWindow(m_communicationWindow); +} + + +// Initialize and launch bridge +bool BridgeWrapper::Init(const mpt::PathString &pluginPath, Generation bridgeGeneration, BridgeWrapper *sharedInstace) +{ + static uint32 plugId = 0; + plugId++; + const DWORD procId = GetCurrentProcessId(); + + const std::wstring mapName = MPT_WFORMAT("Local\\openmpt-{}-{}")(procId, plugId); + + // Create our shared memory object. + if(!m_queueMem.Create(mapName.c_str(), sizeof(SharedMemLayout)) + || !CreateSignals(mapName.c_str())) + { + throw BridgeException("Could not initialize plugin bridge memory."); + } + m_sharedMem = m_queueMem.Data<SharedMemLayout>(); + + if(sharedInstace == nullptr) + { + // Create a new bridge instance + const PluginArch arch = GetPluginBinaryType(pluginPath); + bool available = false; + ComponentPluginBridge::Availability availability = ComponentPluginBridge::AvailabilityUnknown; + switch(arch) + { + case PluginArch_x86: + if(available = (bridgeGeneration == Generation::Modern) ? IsComponentAvailable(pluginBridge_x86) : IsComponentAvailable(pluginBridgeLegacy_x86); !available) + availability = (bridgeGeneration == Generation::Modern) ? pluginBridge_x86->GetAvailability() : pluginBridgeLegacy_x86->GetAvailability(); + break; + case PluginArch_amd64: + if(available = (bridgeGeneration == Generation::Modern) ? IsComponentAvailable(pluginBridge_amd64) : IsComponentAvailable(pluginBridgeLegacy_amd64); !available) + availability = (bridgeGeneration == Generation::Modern) ? pluginBridge_amd64->GetAvailability() : pluginBridgeLegacy_amd64->GetAvailability(); + break; +#if defined(MPT_WITH_WINDOWS10) + case PluginArch_arm: + if(available = (bridgeGeneration == Generation::Modern) ? IsComponentAvailable(pluginBridge_arm) : IsComponentAvailable(pluginBridgeLegacy_arm); !available) + availability = (bridgeGeneration == Generation::Modern) ? pluginBridge_arm->GetAvailability() : pluginBridgeLegacy_arm->GetAvailability(); + break; + case PluginArch_arm64: + if(available = (bridgeGeneration == Generation::Modern) ? IsComponentAvailable(pluginBridge_arm64) : IsComponentAvailable(pluginBridgeLegacy_arm64); !available) + availability = (bridgeGeneration == Generation::Modern) ? pluginBridge_arm64->GetAvailability() : pluginBridgeLegacy_arm64->GetAvailability(); + break; +#endif // MPT_WITH_WINDOWS10 + default: + break; + } + if(arch == PluginArch_unknown) + { + return false; + } + if(!available) + { + switch(availability) + { + case ComponentPluginBridge::AvailabilityMissing: + // Silently fail if bridge is missing. + throw BridgeNotFoundException(); + break; + case ComponentPluginBridge::AvailabilityWrongVersion: + throw BridgeException("The plugin bridge version does not match your OpenMPT version."); + break; + default: + throw BridgeNotFoundException(); + break; + } + } + const ComponentPluginBridge *const pluginBridge = + (arch == PluginArch_x86 && bridgeGeneration == Generation::Modern) ? static_cast<const ComponentPluginBridge *>(pluginBridge_x86.get()) : + (arch == PluginArch_x86 && bridgeGeneration == Generation::Legacy) ? static_cast<const ComponentPluginBridge *>(pluginBridgeLegacy_x86.get()) : + (arch == PluginArch_amd64 && bridgeGeneration == Generation::Modern) ? static_cast<const ComponentPluginBridge *>(pluginBridge_amd64.get()) : + (arch == PluginArch_amd64 && bridgeGeneration == Generation::Legacy) ? static_cast<const ComponentPluginBridge *>(pluginBridgeLegacy_amd64.get()) : +#if defined(MPT_WITH_WINDOWS10) + (arch == PluginArch_arm && bridgeGeneration == Generation::Modern) ? static_cast<const ComponentPluginBridge *>(pluginBridge_arm.get()) : + (arch == PluginArch_arm && bridgeGeneration == Generation::Legacy) ? static_cast<const ComponentPluginBridge *>(pluginBridgeLegacy_arm.get()) : + (arch == PluginArch_arm64 && bridgeGeneration == Generation::Modern) ? static_cast<const ComponentPluginBridge *>(pluginBridge_arm64.get()) : + (arch == PluginArch_arm64 && bridgeGeneration == Generation::Legacy) ? static_cast<const ComponentPluginBridge *>(pluginBridgeLegacy_arm64.get()) : +#endif // MPT_WITH_WINDOWS10 + nullptr; + if(!pluginBridge) + { + return false; + } + m_Generation = bridgeGeneration; + const mpt::PathString exeName = pluginBridge->GetFileName(); + + m_otherPtrSize = static_cast<int32>(GetPluginArchPointerSize(arch)); + + std::wstring cmdLine = MPT_WFORMAT("{} {}")(mapName, procId); + + STARTUPINFOW info; + MemsetZero(info); + info.cb = sizeof(info); + PROCESS_INFORMATION processInfo; + MemsetZero(processInfo); + + if(!CreateProcessW(exeName.ToWide().c_str(), cmdLine.data(), NULL, NULL, FALSE, 0, NULL, NULL, &info, &processInfo)) + { + throw BridgeException("Failed to launch plugin bridge."); + } + CloseHandle(processInfo.hThread); + m_otherProcess = processInfo.hProcess; + } else + { + // Re-use existing bridge instance + m_otherPtrSize = sharedInstace->m_otherPtrSize; + m_otherProcess.DuplicateFrom(sharedInstace->m_otherProcess); + + BridgeMessage msg; + msg.NewInstance(mapName.c_str()); + if(!sharedInstace->SendToBridge(msg)) + { + // Something went wrong, try a new instance + return Init(pluginPath, bridgeGeneration, nullptr); + } + } + + // Initialize bridge + m_sharedMem->effect.object = this; + m_sharedMem->effect.dispatcher = DispatchToPlugin; + m_sharedMem->effect.setParameter = SetParameter; + m_sharedMem->effect.getParameter = GetParameter; + m_sharedMem->effect.process = Process; + std::memcpy(&(m_sharedMem->effect.reservedForHost2), "OMPT", 4); + + m_sigAutomation.Create(true); + + m_sharedMem->hostCommWindow = m_communicationWindow; + + const HANDLE objects[] = {m_sigBridgeReady, m_otherProcess}; + if(WaitForMultipleObjects(mpt::saturate_cast<DWORD>(std::size(objects)), objects, FALSE, 10000) != WAIT_OBJECT_0) + { + throw BridgeException("Could not connect to plugin bridge, it probably crashed."); + } + m_otherPluginID = m_sharedMem->bridgePluginID; + + BridgeMessage initMsg; + initMsg.Init(pluginPath.ToWide().c_str(), MIXBUFFERSIZE, m_thisPluginID, ExceptionHandler::fullMemDump); + + if(!SendToBridge(initMsg)) + { + throw BridgeException("Could not initialize plugin bridge, it probably crashed."); + } else if(initMsg.init.result != 1) + { + throw BridgeException(mpt::ToCharset(mpt::Charset::UTF8, initMsg.init.str).c_str()); + } + + if(m_sharedMem->effect.flags & effFlagsCanReplacing) + m_sharedMem->effect.processReplacing = ProcessReplacing; + if(m_sharedMem->effect.flags & effFlagsCanDoubleReplacing) + m_sharedMem->effect.processDoubleReplacing = ProcessDoubleReplacing; + return true; +} + + +// Send an arbitrary message to the bridge. +// Returns true if the message was processed by the bridge. +bool BridgeWrapper::SendToBridge(BridgeMessage &sendMsg) +{ + const bool inAudioThread = m_isAudioThread || CMainFrame::GetMainFrame()->InAudioThread(); + auto &messages = m_sharedMem->ipcMessages; + const auto msgID = CopyToSharedMemory(sendMsg, messages); + if(msgID < 0) + return false; + BridgeMessage &sharedMsg = messages[msgID]; + + if(!inAudioThread) + { + if(SendMessage(m_sharedMem->bridgeCommWindow, WM_BRIDGE_MESSAGE_TO_BRIDGE, m_otherPluginID, msgID) == WM_BRIDGE_SUCCESS) + { + sharedMsg.CopyTo(sendMsg); + return true; + } + return false; + } + + // Audio thread: Use signals instead of window messages + m_sharedMem->audioThreadToBridgeMsgID = msgID; + m_sigToBridgeAudio.Send(); + + // Wait until we get the result from the bridge + DWORD result; + const HANDLE objects[] = {m_sigToBridgeAudio.confirm, m_sigToHostAudio.send, m_otherProcess}; + do + { + result = WaitForMultipleObjects(mpt::saturate_cast<DWORD>(std::size(objects)), objects, FALSE, INFINITE); + if(result == WAIT_OBJECT_0) + { + // Message got answered + sharedMsg.CopyTo(sendMsg); + break; + } else if(result == WAIT_OBJECT_0 + 1) + { + ParseNextMessage(m_sharedMem->audioThreadToHostMsgID); + m_sigToHostAudio.Confirm(); + } + } while(result != WAIT_OBJECT_0 + 2 && result != WAIT_FAILED); + + return (result == WAIT_OBJECT_0); +} + + +// Receive a message from the host and translate it. +void BridgeWrapper::ParseNextMessage(int msgID) +{ + auto &msg = m_sharedMem->ipcMessages[msgID]; + switch(msg.header.type) + { + case MsgHeader::dispatch: + DispatchToHost(msg.dispatch); + break; + + case MsgHeader::errorMsg: + // TODO Showing a message box here will deadlock as the main thread can be in a waiting state + //throw BridgeErrorException(msg.error.str); + break; + } +} + + +void BridgeWrapper::DispatchToHost(DispatchMsg &msg) +{ + // Various dispatch data - depending on the opcode, one of those might be used. + std::vector<char> extraData; + + MappedMemory auxMem; + + // Content of ptr is usually stored right after the message header, ptr field indicates size. + void *ptr = (msg.ptr != 0) ? (&msg + 1) : nullptr; + if(msg.size > sizeof(BridgeMessage)) + { + if(!auxMem.Open(static_cast<const wchar_t *>(ptr))) + { + return; + } + ptr = auxMem.Data(); + } + void *origPtr = ptr; + + switch(msg.opcode) + { + case audioMasterProcessEvents: + // VstEvents* in [ptr] + TranslateBridgeToVstEvents(extraData, ptr); + ptr = extraData.data(); + break; + + case audioMasterVendorSpecific: + if(msg.index != kVendorOpenMPT || msg.value != kUpdateProcessingBuffer) + { + break; + } + [[fallthrough]]; + case audioMasterIOChanged: + { + // If the song is playing, the rendering thread might be active at the moment, + // so we should keep the current processing memory alive until it is done for sure. + const CVstPlugin *plug = static_cast<CVstPlugin *>(m_sharedMem->effect.reservedForHost1); + const bool isPlaying = plug != nullptr && plug->IsResumed(); + if(isPlaying) + { + m_oldProcessMem.CopyFrom(m_processMem); + } + // Set up new processing file + m_processMem.Open(static_cast<wchar_t *>(ptr)); + if(isPlaying) + { + msg.result = 1; + return; + } + } + break; + + case audioMasterUpdateDisplay: + m_cachedProgNames.clear(); + m_cachedParamInfo.clear(); + break; + + case audioMasterOpenFileSelector: + TranslateBridgeToVstFileSelect(extraData, ptr, static_cast<size_t>(msg.ptr)); + ptr = extraData.data(); + break; + } + + intptr_t result = CVstPlugin::MasterCallBack(&m_sharedMem->effect, static_cast<VstOpcodeToHost>(msg.opcode), msg.index, static_cast<intptr_t>(msg.value), ptr, msg.opt); + msg.result = static_cast<int32>(result); + + // Post-fix some opcodes + switch(msg.opcode) + { + case audioMasterGetTime: + // VstTimeInfo* in [return value] + if(msg.result != 0) + { + m_sharedMem->timeInfo = *FromIntPtr<VstTimeInfo>(result); + } + break; + + case audioMasterGetDirectory: + // char* in [return value] + if(msg.result != 0) + { + char *target = static_cast<char *>(ptr); + strncpy(target, FromIntPtr<const char>(result), static_cast<size_t>(msg.ptr - 1)); + target[msg.ptr - 1] = 0; + } + break; + + case audioMasterOpenFileSelector: + if(msg.result != 0) + { + std::vector<char> fileSelect; + TranslateVstFileSelectToBridge(fileSelect, *static_cast<const VstFileSelect *>(ptr), m_otherPtrSize); + std::memcpy(origPtr, fileSelect.data(), std::min(fileSelect.size(), static_cast<size_t>(msg.ptr))); + // Directly free memory on host side, we don't need it anymore + CVstPlugin::MasterCallBack(&m_sharedMem->effect, audioMasterCloseFileSelector, msg.index, static_cast<intptr_t>(msg.value), ptr, msg.opt); + } + break; + } +} + + +intptr_t VSTCALLBACK BridgeWrapper::DispatchToPlugin(AEffect *effect, VstOpcodeToPlugin opcode, int32 index, intptr_t value, void *ptr, float opt) +{ + BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object); + if(that != nullptr) + { + return that->DispatchToPlugin(opcode, index, value, ptr, opt); + } + return 0; +} + + +intptr_t BridgeWrapper::DispatchToPlugin(VstOpcodeToPlugin opcode, int32 index, intptr_t value, void *ptr, float opt) +{ + std::vector<char> dispatchData(sizeof(DispatchMsg), 0); + int64 ptrOut = 0; + bool copyPtrBack = false, ptrIsSize = true; + char *ptrC = static_cast<char *>(ptr); + + switch(opcode) + { + case effGetParamLabel: + case effGetParamDisplay: + case effGetParamName: + if(index >= m_cachedParamInfoStart && index < m_cachedParamInfoStart + mpt::saturate_cast<int32>(m_cachedParamInfo.size())) + { + if(opcode == effGetParamLabel) + strcpy(ptrC, m_cachedParamInfo[index - m_cachedParamInfoStart].label); + else if(opcode == effGetParamDisplay) + strcpy(ptrC, m_cachedParamInfo[index - m_cachedParamInfoStart].display); + else if(opcode == effGetParamName) + strcpy(ptrC, m_cachedParamInfo[index - m_cachedParamInfoStart].name); + return 1; + } + [[fallthrough]]; + case effGetProgramName: + case effString2Parameter: + case effGetProgramNameIndexed: + case effGetEffectName: + case effGetErrorText: + case effGetVendorString: + case effGetProductString: + case effShellGetNextPlugin: + // Name in [ptr] + if(opcode == effGetProgramNameIndexed && !m_cachedProgNames.empty()) + { + // First check if we have cached this program name + if(index >= m_cachedProgNameStart && index < m_cachedProgNameStart + mpt::saturate_cast<int32>(m_cachedProgNames.size() / kCachedProgramNameLength)) + { + strcpy(ptrC, &m_cachedProgNames[(index - m_cachedProgNameStart) * kCachedProgramNameLength]); + return 1; + } + } + ptrOut = 256; + copyPtrBack = true; + break; + + case effSetProgramName: + m_cachedProgNames.clear(); + [[fallthrough]]; + case effCanDo: + // char* in [ptr] + ptrOut = strlen(ptrC) + 1; + dispatchData.insert(dispatchData.end(), ptrC, ptrC + ptrOut); + break; + + case effIdle: + // The plugin bridge will generate these messages by itself + return 0; + + case effEditGetRect: + // ERect** in [ptr] + ptrOut = sizeof(ERect); + copyPtrBack = true; + break; + + case effEditOpen: + // HWND in [ptr] - Note: Window handles are interoperable between 32-bit and 64-bit applications in Windows (http://msdn.microsoft.com/en-us/library/windows/desktop/aa384203%28v=vs.85%29.aspx) + ptrOut = reinterpret_cast<int64>(ptr); + ptrIsSize = false; + m_cachedProgNames.clear(); + m_cachedParamInfo.clear(); + break; + + case effEditIdle: + // The plugin bridge will generate these messages by itself + return 0; + + case effGetChunk: + // void** in [ptr] for chunk data address + { + static uint32 chunkId = 0; + const std::wstring mapName = L"Local\\openmpt-" + mpt::wfmt::val(GetCurrentProcessId()) + L"-chunkdata-" + mpt::wfmt::val(chunkId++); + ptrOut = (mapName.length() + 1) * sizeof(wchar_t); + PushToVector(dispatchData, *mapName.c_str(), static_cast<size_t>(ptrOut)); + } + break; + + case effSetChunk: + // void* in [ptr] for chunk data + ptrOut = value; + dispatchData.insert(dispatchData.end(), ptrC, ptrC + value); + m_cachedProgNames.clear(); + m_cachedParamInfo.clear(); + break; + + case effProcessEvents: + // VstEvents* in [ptr] + // We process in a separate memory segment to save a bridge communication message. + { + std::vector<char> events; + TranslateVstEventsToBridge(events, *static_cast<VstEvents *>(ptr), m_otherPtrSize); + if(m_eventMem.Size() < events.size()) + { + // Resize memory + static uint32 chunkId = 0; + const std::wstring mapName = L"Local\\openmpt-" + mpt::wfmt::val(GetCurrentProcessId()) + L"-events-" + mpt::wfmt::val(chunkId++); + ptrOut = (mapName.length() + 1) * sizeof(wchar_t); + PushToVector(dispatchData, *mapName.c_str(), static_cast<size_t>(ptrOut)); + m_eventMem.Create(mapName.c_str(), static_cast<uint32>(events.size() + 1024)); + + opcode = effVendorSpecific; + index = kVendorOpenMPT; + value = kUpdateEventMemName; + } + std::memcpy(m_eventMem.Data(), events.data(), events.size()); + } + if(opcode != effVendorSpecific) + { + return 1; + } + break; + + case effGetInputProperties: + case effGetOutputProperties: + // VstPinProperties* in [ptr] + ptrOut = sizeof(VstPinProperties); + copyPtrBack = true; + break; + + case effOfflineNotify: + // VstAudioFile* in [ptr] + ptrOut = sizeof(VstAudioFile) * value; + // TODO + return 0; + break; + + case effOfflinePrepare: + case effOfflineRun: + // VstOfflineTask* in [ptr] + ptrOut = sizeof(VstOfflineTask) * value; + // TODO + return 0; + break; + + case effProcessVarIo: + // VstVariableIo* in [ptr] + ptrOut = sizeof(VstVariableIo); + // TODO + return 0; + break; + + case effSetSpeakerArrangement: + // VstSpeakerArrangement* in [value] and [ptr] + ptrOut = sizeof(VstSpeakerArrangement) * 2; + PushToVector(dispatchData, *static_cast<VstSpeakerArrangement *>(ptr)); + PushToVector(dispatchData, *FromIntPtr<VstSpeakerArrangement>(value)); + break; + + case effVendorSpecific: + if(index == kVendorOpenMPT) + { + switch(value) + { + case kGetWrapperPointer: + return ToIntPtr<BridgeWrapper>(this); + + case kCloseOldProcessingMemory: + { + intptr_t result = m_oldProcessMem.Good(); + m_oldProcessMem.Close(); + return result; + } + + case kCacheProgramNames: + { + int32 *prog = static_cast<int32 *>(ptr); + m_cachedProgNameStart = prog[0]; + ptrOut = std::max(static_cast<int64>(sizeof(int32) * 2), static_cast<int64>((prog[1] - prog[0]) * kCachedProgramNameLength)); + dispatchData.insert(dispatchData.end(), ptrC, ptrC + 2 * sizeof(int32)); + } + break; + + case kCacheParameterInfo: + { + int32 *param = static_cast<int32 *>(ptr); + m_cachedParamInfoStart = param[0]; + ptrOut = std::max(static_cast<int64>(sizeof(int32) * 2), static_cast<int64>((param[1] - param[0]) * sizeof(ParameterInfo))); + dispatchData.insert(dispatchData.end(), ptrC, ptrC + 2 * sizeof(int32)); + } + break; + + case kBeginGetProgram: + ptrOut = m_sharedMem->effect.numParams * sizeof(float); + break; + + case kEndGetProgram: + m_cachedParamValues.clear(); + return 1; + } + } + break; + + case effGetTailSize: + return m_sharedMem->tailSize; + + case effGetParameterProperties: + // VstParameterProperties* in [ptr] + if(index >= m_cachedParamInfoStart && index < m_cachedParamInfoStart + mpt::saturate_cast<int32>(m_cachedParamInfo.size())) + { + *static_cast<VstParameterProperties *>(ptr) = m_cachedParamInfo[index - m_cachedParamInfoStart].props; + return 1; + } + ptrOut = sizeof(VstParameterProperties); + copyPtrBack = true; + break; + + case effGetMidiProgramName: + case effGetCurrentMidiProgram: + // MidiProgramName* in [ptr] + ptrOut = sizeof(MidiProgramName); + copyPtrBack = true; + break; + + case effGetMidiProgramCategory: + // MidiProgramCategory* in [ptr] + ptrOut = sizeof(MidiProgramCategory); + copyPtrBack = true; + break; + + case effGetMidiKeyName: + // MidiKeyName* in [ptr] + ptrOut = sizeof(MidiKeyName); + copyPtrBack = true; + break; + + case effBeginSetProgram: + m_isSettingProgram = true; + break; + + case effEndSetProgram: + m_isSettingProgram = false; + if(m_sharedMem->automationQueue.pendingEvents) + { + SendAutomationQueue(); + } + m_cachedProgNames.clear(); + m_cachedParamInfo.clear(); + break; + + case effGetSpeakerArrangement: + // VstSpeakerArrangement* in [value] and [ptr] + ptrOut = sizeof(VstSpeakerArrangement) * 2; + copyPtrBack = true; + break; + + case effBeginLoadBank: + case effBeginLoadProgram: + // VstPatchChunkInfo* in [ptr] + ptrOut = sizeof(VstPatchChunkInfo); + m_cachedProgNames.clear(); + m_cachedParamInfo.clear(); + break; + + default: + MPT_ASSERT(ptr == nullptr); + } + + if(ptrOut != 0 && ptrIsSize) + { + // In case we only reserve space and don't copy stuff over... + dispatchData.resize(sizeof(DispatchMsg) + static_cast<size_t>(ptrOut), 0); + } + + uint32 extraSize = static_cast<uint32>(dispatchData.size() - sizeof(DispatchMsg)); + + // Create message header + BridgeMessage &msg = *reinterpret_cast<BridgeMessage *>(dispatchData.data()); + msg.Dispatch(opcode, index, value, ptrOut, opt, extraSize); + + const bool useAuxMem = dispatchData.size() > sizeof(BridgeMessage); + AuxMem *auxMem = nullptr; + if(useAuxMem) + { + // Extra data doesn't fit in message - use secondary memory + if(dispatchData.size() > std::numeric_limits<uint32>::max()) + return 0; + auxMem = GetAuxMemory(mpt::saturate_cast<uint32>(dispatchData.size())); + if(auxMem == nullptr) + return 0; + + // First, move message data to shared memory... + std::memcpy(auxMem->memory.Data(), &dispatchData[sizeof(DispatchMsg)], extraSize); + // ...Now put the shared memory name in the message instead. + std::memcpy(&dispatchData[sizeof(DispatchMsg)], auxMem->name, sizeof(auxMem->name)); + } + + try + { + if(!SendToBridge(msg) && opcode != effClose) + { + return 0; + } + } catch(...) + { + // Don't do anything for now. +#if 0 + if(opcode != effClose) + { + throw; + } +#endif + } + + const DispatchMsg &resultMsg = msg.dispatch; + + // cppcheck false-positive + // cppcheck-suppress nullPointerRedundantCheck + const void *extraData = useAuxMem ? auxMem->memory.Data<const char>() : reinterpret_cast<const char *>(&resultMsg + 1); + // Post-fix some opcodes + switch(opcode) + { + case effClose: + m_sharedMem->effect.object = nullptr; + delete this; + return 0; + + case effGetProgramName: + case effGetParamLabel: + case effGetParamDisplay: + case effGetParamName: + case effString2Parameter: + case effGetProgramNameIndexed: + case effGetEffectName: + case effGetErrorText: + case effGetVendorString: + case effGetProductString: + case effShellGetNextPlugin: + // Name in [ptr] + strcpy(ptrC, static_cast<const char *>(extraData)); + break; + + case effEditGetRect: + // ERect** in [ptr] + m_editRect = *static_cast<const ERect *>(extraData); + *static_cast<const ERect **>(ptr) = &m_editRect; + break; + + case effGetChunk: + // void** in [ptr] for chunk data address + if(const wchar_t *str = static_cast<const wchar_t *>(extraData); m_getChunkMem.Open(str)) + *static_cast<void **>(ptr) = m_getChunkMem.Data(); + else + return 0; + break; + + case effVendorSpecific: + if(index == kVendorOpenMPT && resultMsg.result == 1) + { + switch(value) + { + case kCacheProgramNames: + m_cachedProgNames.assign(static_cast<const char *>(extraData), static_cast<const char *>(extraData) + ptrOut); + break; + case kCacheParameterInfo: + { + const ParameterInfo *params = static_cast<const ParameterInfo *>(extraData); + m_cachedParamInfo.assign(params, params + ptrOut / sizeof(ParameterInfo)); + break; + } + case kBeginGetProgram: + m_cachedParamValues.assign(static_cast<const float *>(extraData), static_cast<const float *>(extraData) + ptrOut / sizeof(float)); + break; + } + } + break; + + case effGetSpeakerArrangement: + // VstSpeakerArrangement* in [value] and [ptr] + m_speakers[0] = *static_cast<const VstSpeakerArrangement *>(extraData); + m_speakers[1] = *(static_cast<const VstSpeakerArrangement *>(extraData) + 1); + *static_cast<VstSpeakerArrangement *>(ptr) = m_speakers[0]; + *FromIntPtr<VstSpeakerArrangement>(value) = m_speakers[1]; + break; + + default: + // TODO: Translate VstVariableIo, offline tasks + if(copyPtrBack) + { + std::memcpy(ptr, extraData, static_cast<size_t>(ptrOut)); + } + } + + if(auxMem != nullptr) + { + auxMem->used = false; + } + + return static_cast<intptr_t>(resultMsg.result); +} + + +// Allocate auxiliary shared memory for too long bridge messages +BridgeWrapper::AuxMem *BridgeWrapper::GetAuxMemory(uint32 size) +{ + std::size_t index = std::size(m_auxMems); + for(int pass = 0; pass < 2; pass++) + { + for(std::size_t i = 0; i < std::size(m_auxMems); i++) + { + if(m_auxMems[i].size >= size || pass == 1) + { + // Good candidate - is it taken yet? + bool expected = false; + if(m_auxMems[i].used.compare_exchange_strong(expected, true)) + { + index = i; + break; + } + } + } + if(index != std::size(m_auxMems)) + break; + } + if(index == std::size(m_auxMems)) + return nullptr; + + AuxMem &auxMem = m_auxMems[index]; + if(auxMem.size >= size && auxMem.memory.Good()) + { + // Re-use as-is + return &auxMem; + } + // Create new memory with appropriate size + static_assert(sizeof(DispatchMsg) + sizeof(auxMem.name) <= sizeof(BridgeMessage), "Check message sizes, this will crash!"); + static unsigned int auxMemCount = 0; + mpt::String::WriteAutoBuf(auxMem.name) = MPT_WFORMAT("Local\\openmpt-{}-auxmem-{}")(GetCurrentProcessId(), auxMemCount++); + if(auxMem.memory.Create(auxMem.name, size)) + { + auxMem.size = size; + return &auxMem; + } else + { + auxMem.used = false; + return nullptr; + } +} + + +// Send any pending automation events +void BridgeWrapper::SendAutomationQueue() +{ + m_sigAutomation.Reset(); + BridgeMessage msg; + msg.Automate(); + if(!SendToBridge(msg)) + { + // Failed (plugin probably crashed) - auto-fix event count + m_sharedMem->automationQueue.pendingEvents = 0; + } + m_sigAutomation.Trigger(); +} + +void VSTCALLBACK BridgeWrapper::SetParameter(AEffect *effect, int32 index, float parameter) +{ + BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object); + if(that) + { + try + { + that->SetParameter(index, parameter); + } catch(...) + { + // Be quiet about exceptions here + } + } +} + + +void BridgeWrapper::SetParameter(int32 index, float parameter) +{ + const CVstPlugin *plug = static_cast<CVstPlugin *>(m_sharedMem->effect.reservedForHost1); + AutomationQueue &autoQueue = m_sharedMem->automationQueue; + if(m_isSettingProgram || (plug && plug->IsResumed())) + { + // Queue up messages while rendering to reduce latency introduced by every single bridge call + uint32 i; + while((i = autoQueue.pendingEvents.fetch_add(1)) >= std::size(autoQueue.params)) + { + // Queue full! + if(i == std::size(autoQueue.params)) + { + // We're the first to notice that it's full + SendAutomationQueue(); + } else + { + // Wait until queue is emptied by someone else (this branch is very unlikely to happen) + WaitForSingleObject(m_sigAutomation, INFINITE); + } + } + + autoQueue.params[i].index = index; + autoQueue.params[i].value = parameter; + return; + } else if(autoQueue.pendingEvents) + { + // Actually, this should never happen as pending events are cleared before processing and at the end of a set program event. + SendAutomationQueue(); + } + + BridgeMessage msg; + msg.SetParameter(index, parameter); + SendToBridge(msg); +} + + +float VSTCALLBACK BridgeWrapper::GetParameter(AEffect *effect, int32 index) +{ + BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object); + if(that) + { + if(static_cast<size_t>(index) < that->m_cachedParamValues.size()) + return that->m_cachedParamValues[index]; + + try + { + return that->GetParameter(index); + } catch(...) + { + // Be quiet about exceptions here + } + } + return 0.0f; +} + + +float BridgeWrapper::GetParameter(int32 index) +{ + BridgeMessage msg; + msg.GetParameter(index); + if(SendToBridge(msg)) + { + return msg.parameter.value; + } + return 0.0f; +} + + +void VSTCALLBACK BridgeWrapper::Process(AEffect *effect, float **inputs, float **outputs, int32 sampleFrames) +{ + BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object); + if(sampleFrames != 0 && that != nullptr) + { + that->BuildProcessBuffer(ProcessMsg::process, effect->numInputs, effect->numOutputs, inputs, outputs, sampleFrames); + } +} + + +void VSTCALLBACK BridgeWrapper::ProcessReplacing(AEffect *effect, float **inputs, float **outputs, int32 sampleFrames) +{ + BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object); + if(sampleFrames != 0 && that != nullptr) + { + that->BuildProcessBuffer(ProcessMsg::processReplacing, effect->numInputs, effect->numOutputs, inputs, outputs, sampleFrames); + } +} + + +void VSTCALLBACK BridgeWrapper::ProcessDoubleReplacing(AEffect *effect, double **inputs, double **outputs, int32 sampleFrames) +{ + BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object); + if(sampleFrames != 0 && that != nullptr) + { + that->BuildProcessBuffer(ProcessMsg::processDoubleReplacing, effect->numInputs, effect->numOutputs, inputs, outputs, sampleFrames); + } +} + + +template <typename buf_t> +void BridgeWrapper::BuildProcessBuffer(ProcessMsg::ProcessType type, int32 numInputs, int32 numOutputs, buf_t **inputs, buf_t **outputs, int32 sampleFrames) +{ + if(!m_processMem.Good()) + { + MPT_ASSERT_NOTREACHED(); + return; + } + + ProcessMsg *processMsg = m_processMem.Data<ProcessMsg>(); + new(processMsg) ProcessMsg{type, numInputs, numOutputs, sampleFrames}; + + // Anticipate that many plugins will query the play position in a process call and send it along the process call + // to save some valuable inter-process calls. + m_sharedMem->timeInfo = *FromIntPtr<VstTimeInfo>(CVstPlugin::MasterCallBack(&m_sharedMem->effect, audioMasterGetTime, 0, kVstNanosValid | kVstPpqPosValid | kVstTempoValid | kVstBarsValid | kVstCyclePosValid | kVstTimeSigValid | kVstSmpteValid | kVstClockValid, nullptr, 0.0f)); + + buf_t *ptr = reinterpret_cast<buf_t *>(processMsg + 1); + for(int32 i = 0; i < numInputs; i++) + { + std::memcpy(ptr, inputs[i], sampleFrames * sizeof(buf_t)); + ptr += sampleFrames; + } + // Theoretically, we should memcpy() instead of memset() here in process(), but OpenMPT always clears the output buffer before processing so it doesn't matter. + memset(ptr, 0, numOutputs * sampleFrames * sizeof(buf_t)); + + // In case we called Process() from the GUI thread (panic button or song stop => CSoundFile::SuspendPlugins) + m_isAudioThread = true; + + m_sigProcessAudio.Send(); + const HANDLE objects[] = {m_sigProcessAudio.confirm, m_sigToHostAudio.send, m_otherProcess}; + DWORD result; + do + { + result = WaitForMultipleObjects(mpt::saturate_cast<DWORD>(std::size(objects)), objects, FALSE, INFINITE); + if(result == WAIT_OBJECT_0 + 1) + { + ParseNextMessage(m_sharedMem->audioThreadToHostMsgID); + m_sigToHostAudio.Confirm(); + } + } while(result != WAIT_OBJECT_0 && result != WAIT_OBJECT_0 + 2 && result != WAIT_FAILED); + + m_isAudioThread = false; + + for(int32 i = 0; i < numOutputs; i++) + { + //std::memcpy(outputs[i], ptr, sampleFrames * sizeof(buf_t)); + outputs[i] = ptr; // Exactly what you don't want plugins to do usually (bend your output pointers)... muahahaha! + ptr += sampleFrames; + } + + // Did we receive any audioMasterProcessEvents data? + if(auto *events = m_eventMem.Data<int32>(); events != nullptr && *events != 0) + { + std::vector<char> eventCache; + TranslateBridgeToVstEvents(eventCache, events); + *events = 0; + CVstPlugin::MasterCallBack(&m_sharedMem->effect, audioMasterProcessEvents, 0, 0, eventCache.data(), 0.0f); + } +} + + +LRESULT CALLBACK BridgeWrapper::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if(hwnd == m_communicationWindow && wParam < m_plugins.size()) + { + auto *that = static_cast<BridgeWrapper *>(m_plugins[wParam]); + if(that != nullptr && uMsg == WM_BRIDGE_MESSAGE_TO_HOST) + { + that->ParseNextMessage(static_cast<int>(lParam)); + return WM_BRIDGE_SUCCESS; + } + } + + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +#endif // MPT_WITH_VST + + +OPENMPT_NAMESPACE_END |