diff options
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/pluginBridge')
11 files changed, 4111 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/pluginBridge/AEffectWrapper.h b/Src/external_dependencies/openmpt-trunk/pluginBridge/AEffectWrapper.h new file mode 100644 index 00000000..93c64db0 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/pluginBridge/AEffectWrapper.h @@ -0,0 +1,274 @@ +/* + * AEffectWrapper.h + * ---------------- + * Purpose: Helper functions and structs for translating the VST AEffect struct between bit boundaries. + * Notes : (currently none) + * Authors: Johannes Schultz (OpenMPT Devs) + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + +#include <vector> +#include "../mptrack/plugins/VstDefinitions.h" + +OPENMPT_NAMESPACE_BEGIN + +#pragma pack(push, 8) + +template <typename ptr_t> +struct AEffectProto +{ + int32 magic; + ptr_t dispatcher; + ptr_t process; + ptr_t setParameter; + ptr_t getParameter; + + int32 numPrograms; + uint32 numParams; + int32 numInputs; + int32 numOutputs; + + int32 flags; + + ptr_t resvd1; + ptr_t resvd2; + + int32 initialDelay; + + int32 realQualities; + int32 offQualities; + float ioRatio; + + ptr_t object; + ptr_t user; + + int32 uniqueID; + int32 version; + + ptr_t processReplacing; + ptr_t processDoubleReplacing; + char future[56]; + + // Convert native representation to bridge representation. + // Don't overwrite any values managed by the bridge wrapper or host in general. + void FromNative(const Vst::AEffect &in) + { + magic = in.magic; + + numPrograms = in.numPrograms; + numParams = in.numParams; + numInputs = in.numInputs; + numOutputs = in.numOutputs; + + flags = in.flags; + + initialDelay = in.initialDelay; + + realQualities = in.realQualities; + offQualities = in.offQualities; + ioRatio = in.ioRatio; + + uniqueID = in.uniqueID; + version = in.version; + + if(in.processReplacing == nullptr) + flags &= ~Vst::effFlagsCanReplacing; + if(in.processDoubleReplacing == nullptr) + flags &= ~Vst::effFlagsCanDoubleReplacing; + } +}; + +using AEffect32 = AEffectProto<int32>; +using AEffect64 = AEffectProto<int64>; + +#pragma pack(pop) + + +// Translate a VSTEvents struct to bridge format (placed in data vector) +static void TranslateVstEventsToBridge(std::vector<char> &outData, const Vst::VstEvents &events, int32 targetPtrSize) +{ + outData.reserve(outData.size() + sizeof(int32) + sizeof(Vst::VstMidiEvent) * events.numEvents); + // Write number of events + PushToVector(outData, events.numEvents); + // Write events + for(const auto event : events) + { + if(event->type == Vst::kVstSysExType) + { + // This is going to be messy since the VstMidiSysexEvent event has a different size than other events on 64-bit platforms. + // We are going to write the event using the target process pointer size. + auto sysExEvent = *static_cast<const Vst::VstMidiSysexEvent *>(event); + sysExEvent.byteSize = 4 * sizeof(int32) + 4 * targetPtrSize; // It's 5 int32s and 3 pointers but that means that on 64-bit platforms, the fifth int32 is padded for alignment. + PushToVector(outData, sysExEvent, 5 * sizeof(int32)); // Exclude the three pointers at the end for now + if(targetPtrSize > static_cast<int32>(sizeof(int32))) // Padding for 64-bit required? + outData.insert(outData.end(), targetPtrSize - sizeof(int32), 0); + outData.insert(outData.end(), 3 * targetPtrSize, 0); // Make space for pointer + two reserved intptr_ts + // Embed SysEx dump as well... + auto sysex = reinterpret_cast<const char *>(sysExEvent.sysexDump); + outData.insert(outData.end(), sysex, sysex + sysExEvent.dumpBytes); + } else if(event->type == Vst::kVstMidiType) + { + // randomid by Insert Piz Here sends events of type kVstMidiType, but with a claimed size of 24 bytes instead of 32. + Vst::VstMidiEvent midiEvent; + std::memcpy(&midiEvent, event, sizeof(midiEvent)); + midiEvent.byteSize = sizeof(midiEvent); + PushToVector(outData, midiEvent, sizeof(midiEvent)); + } else + { + PushToVector(outData, *event, event->byteSize); + } + } +} + + +// Translate bridge format (void *ptr) back to VSTEvents struct (placed in data vector) +static void TranslateBridgeToVstEvents(std::vector<char> &outData, const void *inData) +{ + const int32 numEvents = *static_cast<const int32 *>(inData); + + // First element is really a int32, but in case of 64-bit builds, the next field gets aligned anyway. + const size_t headerSize = sizeof(intptr_t) + sizeof(intptr_t) + sizeof(Vst::VstEvent *) * numEvents; + outData.reserve(headerSize + sizeof(Vst::VstMidiEvent) * numEvents); + outData.resize(headerSize, 0); + if(numEvents == 0) + return; + + // Copy over event data (this is required for dumb SynthEdit plugins that don't copy over the event data during effProcessEvents) + const char *readOffset = static_cast<const char *>(inData) + sizeof(int32); + for(int32 i = 0; i < numEvents; i++) + { + auto *event = reinterpret_cast<const Vst::VstEvent *>(readOffset); + outData.insert(outData.end(), readOffset, readOffset + event->byteSize); + readOffset += event->byteSize; + + if(event->type == Vst::kVstSysExType) + { + // Copy over sysex dump + auto *sysExEvent = static_cast<const Vst::VstMidiSysexEvent *>(event); + outData.insert(outData.end(), readOffset, readOffset + sysExEvent->dumpBytes); + readOffset += sysExEvent->dumpBytes; + } + } + + // Write pointers + auto events = reinterpret_cast<Vst::VstEvents *>(outData.data()); + events->numEvents = numEvents; + char *offset = outData.data() + headerSize; + for(int32 i = 0; i < numEvents; i++) + { + events->events[i] = reinterpret_cast<Vst::VstEvent *>(offset); + offset += events->events[i]->byteSize; + if(events->events[i]->type == Vst::kVstSysExType) + { + auto sysExEvent = static_cast<Vst::VstMidiSysexEvent *>(events->events[i]); + sysExEvent->sysexDump = reinterpret_cast<const std::byte *>(offset); + offset += sysExEvent->dumpBytes; + } + } +} + + +// Calculate the size total of the VSTEvents (without header) in bridge format +static size_t BridgeVstEventsSize(const void *ptr) +{ + const int32 numEvents = *static_cast<const int32 *>(ptr); + size_t size = 0; + for(int32 i = 0; i < numEvents; i++) + { + const auto event = reinterpret_cast<const Vst::VstEvent *>(static_cast<const char *>(ptr) + sizeof(int32) + size); + size += event->byteSize; + if(event->type == Vst::kVstSysExType) + { + size += static_cast<const Vst::VstMidiSysexEvent *>(event)->dumpBytes; + } + } + return size; +} + + +static void TranslateVstFileSelectToBridge(std::vector<char> &outData, const Vst::VstFileSelect &fileSelect, int32 targetPtrSize) +{ + outData.reserve(outData.size() + sizeof(Vst::VstFileSelect) + fileSelect.numFileTypes * sizeof(Vst::VstFileType)); + PushToVector(outData, fileSelect.command); + PushToVector(outData, fileSelect.type); + PushToVector(outData, fileSelect.macCreator); + PushToVector(outData, fileSelect.numFileTypes); + outData.insert(outData.end(), targetPtrSize, 0); // fileTypes + PushToVector(outData, fileSelect.title); + outData.insert(outData.end(), 2 * targetPtrSize, 0); // initialPath, returnPath + PushToVector(outData, fileSelect.sizeReturnPath); + if(targetPtrSize > static_cast<int32>(sizeof(int32))) + outData.insert(outData.end(), targetPtrSize - sizeof(int32), 0); // padding + outData.insert(outData.end(), targetPtrSize, 0); // returnMultiplePaths + PushToVector(outData, fileSelect.numReturnPaths); + outData.insert(outData.end(), targetPtrSize, 0); // reserved + PushToVector(outData, fileSelect.reserved2); + + if(fileSelect.command != Vst::kVstDirectorySelect) + { + for(int32 i = 0; i < fileSelect.numFileTypes; i++) + { + PushToVector(outData, fileSelect.fileTypes[i]); + } + } + + if(fileSelect.command == Vst::kVstMultipleFilesLoad) + { + outData.insert(outData.end(), fileSelect.numReturnPaths * targetPtrSize, 0); + for(int32 i = 0; i < fileSelect.numReturnPaths; i++) + { + PushZStringToVector(outData, fileSelect.returnMultiplePaths[i]); + } + } + + PushZStringToVector(outData, fileSelect.initialPath); + PushZStringToVector(outData, fileSelect.returnPath); +} + + +static void TranslateBridgeToVstFileSelect(std::vector<char> &outData, const void *inData, size_t srcSize) +{ + outData.assign(static_cast<const char *>(inData), static_cast<const char *>(inData) + srcSize); + + // Fixup pointers + Vst::VstFileSelect &fileSelect = *reinterpret_cast<Vst::VstFileSelect *>(outData.data()); + auto ptrOffset = outData.data() + sizeof(Vst::VstFileSelect); + + if(fileSelect.command != Vst::kVstDirectorySelect) + { + fileSelect.fileTypes = reinterpret_cast<Vst::VstFileType *>(ptrOffset); + ptrOffset += fileSelect.numFileTypes * sizeof(Vst::VstFileType); + } else + { + fileSelect.fileTypes = nullptr; + } + + if(fileSelect.command == Vst::kVstMultipleFilesLoad) + { + fileSelect.returnMultiplePaths = reinterpret_cast<char **>(ptrOffset); + ptrOffset += fileSelect.numReturnPaths * sizeof(char *); + + for(int32 i = 0; i < fileSelect.numReturnPaths; i++) + { + fileSelect.returnMultiplePaths[i] = ptrOffset; + ptrOffset += strlen(fileSelect.returnMultiplePaths[i]) + 1; + } + } else + { + fileSelect.returnMultiplePaths = nullptr; + } + + fileSelect.initialPath = ptrOffset; + ptrOffset += strlen(fileSelect.initialPath) + 1; + fileSelect.returnPath = ptrOffset; + fileSelect.sizeReturnPath = static_cast<int32>(srcSize - std::distance(outData.data(), ptrOffset)); + ptrOffset += strlen(fileSelect.returnPath) + 1; +} + + +OPENMPT_NAMESPACE_END diff --git a/Src/external_dependencies/openmpt-trunk/pluginBridge/Bridge.cpp b/Src/external_dependencies/openmpt-trunk/pluginBridge/Bridge.cpp new file mode 100644 index 00000000..e819c97f --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/pluginBridge/Bridge.cpp @@ -0,0 +1,1318 @@ +/* + * Bridge.cpp + * ---------- + * Purpose: VST plugin bridge (plugin side) + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +// TODO: Translate pointer-sized members in remaining structs: VstVariableIo, VstOfflineTask, VstAudioFile, VstWindow (all these are currently not supported by OpenMPT, so not urgent at all) + +#include "openmpt/all/BuildSettings.hpp" +#include "../common/mptBaseMacros.h" +#include "../common/mptBaseTypes.h" +#include "../common/mptBaseUtils.h" +#include <Windows.h> +#include <ShellAPI.h> +#include <ShlObj.h> +#include <CommDlg.h> +#include <tchar.h> +#include <algorithm> +#include <string> + +#if defined(MPT_BUILD_MSVC) +#pragma comment(lib, "comdlg32.lib") +#pragma comment(lib, "ole32.lib") +#pragma comment(lib, "shell32.lib") +#endif + + +#if MPT_BUILD_DEBUG +#include <intrin.h> +#define MPT_ASSERT(x) \ + MPT_MAYBE_CONSTANT_IF(!(x)) \ + { \ + if(IsDebuggerPresent()) \ + __debugbreak(); \ + ::MessageBoxA(nullptr, "Debug Assertion Failed:\n\n" #x, "OpenMPT Plugin Bridge", MB_ICONERROR); \ + } +#else +#define MPT_ASSERT(x) +#endif + +#include "../misc/WriteMemoryDump.h" +#include "Bridge.h" + + +// Crash handler for writing memory dumps +static LONG WINAPI CrashHandler(_EXCEPTION_POINTERS *pExceptionInfo) +{ + WCHAR tempPath[MAX_PATH + 2]; + DWORD result = GetTempPathW(MAX_PATH + 1, tempPath); + if(result > 0 && result <= MAX_PATH + 1) + { + std::wstring filename = tempPath; + filename += L"OpenMPT Crash Files\\"; + CreateDirectoryW(filename.c_str(), nullptr); + + tempPath[0] = 0; + const int ch = GetDateFormatW(LOCALE_SYSTEM_DEFAULT, 0, nullptr, L"'PluginBridge 'yyyy'-'MM'-'dd ", tempPath, mpt::saturate_cast<int>(std::size(tempPath))); + if(ch) + GetTimeFormatW(LOCALE_SYSTEM_DEFAULT, 0, nullptr, L"HH'.'mm'.'ss'.dmp'", tempPath + ch - 1, mpt::saturate_cast<int>(std::size(tempPath)) - ch + 1); + filename += tempPath; + OPENMPT_NAMESPACE::WriteMemoryDump(pExceptionInfo, filename.c_str(), OPENMPT_NAMESPACE::PluginBridge::m_fullMemDump); + } + + // Let Windows handle the exception... + return EXCEPTION_CONTINUE_SEARCH; +} + + +int _tmain(int argc, TCHAR *argv[]) +{ + if(argc != 2) + { + MessageBox(nullptr, _T("This executable is part of OpenMPT. You do not need to run it by yourself."), _T("OpenMPT Plugin Bridge"), 0); + return -1; + } + + ::SetUnhandledExceptionFilter(CrashHandler); + + // We don't need COM, but some plugins do and don't initialize it themselves. + // Note 1: Which plugins? This was added in r6459 on 2016-05-31 but with no remark whether it fixed a specific plugin, + // but the fix doesn't seem to make a lot of sense since back then no plugin code was ever running on the main thread. + // Could it have been for file dialogs, which were added a while before? + // Note 2: M1 editor crashes if it runs on this thread and it was initialized with COINIT_MULTITHREADED + const bool comInitialized = SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)); + + OPENMPT_NAMESPACE::PluginBridge::MainLoop(argv); + + if(comInitialized) + CoUninitialize(); + + return 0; +} + + +int WINAPI WinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ LPSTR /*lpCmdLine*/, _In_ int /*nCmdShow*/) +{ + int argc = 0; + auto argv = CommandLineToArgvW(GetCommandLineW(), &argc); + return _tmain(argc, argv); +} + + +OPENMPT_NAMESPACE_BEGIN + +using namespace Vst; + +std::vector<BridgeCommon *> BridgeCommon::m_plugins; +HWND BridgeCommon::m_communicationWindow = nullptr; +int BridgeCommon::m_instanceCount = 0; +thread_local bool BridgeCommon::m_isAudioThread = false; + +// This is kind of a back-up pointer in case we couldn't sneak our pointer into the AEffect struct yet. +// It always points to the last initialized PluginBridge object. +PluginBridge *PluginBridge::m_latestInstance = nullptr; +ATOM PluginBridge::m_editorClassAtom = 0; +bool PluginBridge::m_fullMemDump = false; + +void PluginBridge::MainLoop(TCHAR *argv[]) +{ + WNDCLASSEX editorWndClass; + editorWndClass.cbSize = sizeof(WNDCLASSEX); + editorWndClass.style = CS_HREDRAW | CS_VREDRAW; + editorWndClass.lpfnWndProc = WindowProc; + editorWndClass.cbClsExtra = 0; + editorWndClass.cbWndExtra = 0; + editorWndClass.hInstance = GetModuleHandle(nullptr); + editorWndClass.hIcon = nullptr; + editorWndClass.hCursor = LoadCursor(nullptr, IDC_ARROW); + editorWndClass.hbrBackground = nullptr; + editorWndClass.lpszMenuName = nullptr; + editorWndClass.lpszClassName = _T("OpenMPTPluginBridgeEditor"); + editorWndClass.hIconSm = nullptr; + m_editorClassAtom = RegisterClassEx(&editorWndClass); + + CreateCommunicationWindow(WindowProc); + SetTimer(m_communicationWindow, TIMER_IDLE, 20, IdleTimerProc); + + uint32 parentProcessId = _ttoi(argv[1]); + new PluginBridge(argv[0], OpenProcess(SYNCHRONIZE, FALSE, parentProcessId)); + + MSG msg; + while(::GetMessage(&msg, nullptr, 0, 0)) + { + // Let host pre-process key messages like it does for non-bridged plugins + if(msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) + { + HWND owner = nullptr; + for(HWND hwnd = msg.hwnd; hwnd != nullptr; hwnd = GetParent(hwnd)) + { + // Does it come from a child window? (e.g. Kirnu editor) + if(GetClassWord(hwnd, GCW_ATOM) == m_editorClassAtom) + { + owner = GetParent(GetParent(hwnd)); + break; + } + // Does the message come from a top-level window? This is required e.g. for the slider pop-up windows and patch browser in Synth1. + if(!(GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD)) + { + owner = GetWindow(hwnd, GW_OWNER); + break; + } + } + // Send to top-level VST editor window in host + if(owner && SendMessage(owner, msg.message + WM_BRIDGE_KEYFIRST - WM_KEYFIRST, msg.wParam, msg.lParam)) + continue; + } + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + DestroyWindow(m_communicationWindow); +} + + +PluginBridge::PluginBridge(const wchar_t *memName, HANDLE otherProcess) +{ + PluginBridge::m_latestInstance = this; + + m_thisPluginID = static_cast<int32>(m_plugins.size()); + m_plugins.push_back(this); + + if(!m_queueMem.Open(memName) + || !CreateSignals(memName)) + { + MessageBox(nullptr, _T("Could not connect to OpenMPT."), _T("OpenMPT Plugin Bridge"), 0); + delete this; + return; + } + + m_sharedMem = m_queueMem.Data<SharedMemLayout>(); + + // Store parent process handle so that we can terminate the bridge process when OpenMPT closes (e.g. through a crash). + m_otherProcess.DuplicateFrom(otherProcess); + + m_sigThreadExit.Create(true); + DWORD dummy = 0; // For Win9x + m_audioThread = CreateThread(NULL, 0, &PluginBridge::AudioThread, this, 0, &dummy); + + m_sharedMem->bridgeCommWindow = m_communicationWindow; + m_sharedMem->bridgePluginID = m_thisPluginID; + m_sigBridgeReady.Trigger(); +} + + +PluginBridge::~PluginBridge() +{ + SignalObjectAndWait(m_sigThreadExit, m_audioThread, INFINITE, FALSE); + CloseHandle(m_audioThread); + BridgeMessage dispatchMsg; + dispatchMsg.Dispatch(effClose, 0, 0, 0, 0.0f, 0); + DispatchToPlugin(dispatchMsg.dispatch); + m_plugins[m_thisPluginID] = nullptr; + if(m_instanceCount == 1) + PostQuitMessage(0); +} + + +void PluginBridge::RequestDelete() +{ + PostMessage(m_communicationWindow, WM_BRIDGE_DELETE_PLUGIN, m_thisPluginID, 0); +} + + +// Send an arbitrary message to the host. +// Returns true if the message was processed by the host. +bool PluginBridge::SendToHost(BridgeMessage &sendMsg) +{ + auto &messages = m_sharedMem->ipcMessages; + const auto msgID = CopyToSharedMemory(sendMsg, messages); + if(msgID < 0) + return false; + BridgeMessage &sharedMsg = messages[msgID]; + + if(!m_isAudioThread) + { + if(SendMessage(m_sharedMem->hostCommWindow, WM_BRIDGE_MESSAGE_TO_HOST, m_otherPluginID, msgID) == WM_BRIDGE_SUCCESS) + { + sharedMsg.CopyTo(sendMsg); + return true; + } + return false; + } + + // Audio thread: Use signals instead of window messages + m_sharedMem->audioThreadToHostMsgID = msgID; + m_sigToHostAudio.Send(); + + // Wait until we get the result from the host. + DWORD result; + const HANDLE objects[] = {m_sigToHostAudio.confirm, m_sigToBridgeAudio.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->audioThreadToBridgeMsgID); + m_sigToBridgeAudio.Confirm(); + } + } while(result != WAIT_OBJECT_0 + 2 && result != WAIT_FAILED); + + return (result == WAIT_OBJECT_0); +} + + +// Copy AEffect to shared memory. +void PluginBridge::UpdateEffectStruct() +{ + if(m_nativeEffect == nullptr) + return; + else if(m_otherPtrSize == 4) + m_sharedMem->effect32.FromNative(*m_nativeEffect); + else if(m_otherPtrSize == 8) + m_sharedMem->effect64.FromNative(*m_nativeEffect); + else + MPT_ASSERT(false); +} + + +// Create the memory-mapped file containing the processing message and audio buffers +void PluginBridge::CreateProcessingFile(std::vector<char> &dispatchData) +{ + static uint32 plugId = 0; + wchar_t mapName[64]; + swprintf(mapName, std::size(mapName), L"Local\\openmpt-%u-%u", GetCurrentProcessId(), plugId++); + + PushToVector(dispatchData, mapName[0], sizeof(mapName)); + + if(!m_processMem.Create(mapName, sizeof(ProcessMsg) + m_mixBufSize * (m_nativeEffect->numInputs + m_nativeEffect->numOutputs) * sizeof(double))) + { + SendErrorMessage(L"Could not initialize plugin bridge audio memory."); + return; + } +} + + +// Receive a message from the host and translate it. +void PluginBridge::ParseNextMessage(int msgID) +{ + auto &msg = m_sharedMem->ipcMessages[msgID]; + switch(msg.header.type) + { + case MsgHeader::newInstance: + NewInstance(msg.newInstance); + break; + case MsgHeader::init: + InitBridge(msg.init); + break; + case MsgHeader::dispatch: + DispatchToPlugin(msg.dispatch); + break; + case MsgHeader::setParameter: + SetParameter(msg.parameter); + break; + case MsgHeader::getParameter: + GetParameter(msg.parameter); + break; + case MsgHeader::automate: + AutomateParameters(); + break; + } +} + + +// Create a new bridge instance within this one (creates a new thread). +void PluginBridge::NewInstance(NewInstanceMsg &msg) +{ + msg.memName[mpt::array_size<decltype(msg.memName)>::size - 1] = 0; + new PluginBridge(msg.memName, m_otherProcess); +} + + +// Load the plugin. +void PluginBridge::InitBridge(InitMsg &msg) +{ + m_otherPtrSize = msg.hostPtrSize; + m_mixBufSize = msg.mixBufSize; + m_otherPluginID = msg.pluginID; + m_fullMemDump = msg.fullMemDump != 0; + msg.result = 0; + msg.str[mpt::array_size<decltype(msg.str)>::size - 1] = 0; + +#ifdef _CONSOLE + SetConsoleTitleW(msg->str); +#endif + + m_nativeEffect = nullptr; + __try + { + m_library = LoadLibraryW(msg.str); + } __except(EXCEPTION_EXECUTE_HANDLER) + { + m_library = nullptr; + } + + if(m_library == nullptr) + { + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), msg.str, mpt::saturate_cast<DWORD>(std::size(msg.str)), nullptr); + RequestDelete(); + return; + } + + auto mainProc = (Vst::MainProc)GetProcAddress(m_library, "VSTPluginMain"); + if(mainProc == nullptr) + { + mainProc = (Vst::MainProc)GetProcAddress(m_library, "main"); + } + + if(mainProc != nullptr) + { + __try + { + m_nativeEffect = mainProc(MasterCallback); + } __except(EXCEPTION_EXECUTE_HANDLER) + { + m_nativeEffect = nullptr; + } + } + + if(m_nativeEffect == nullptr || m_nativeEffect->dispatcher == nullptr || m_nativeEffect->magic != kEffectMagic) + { + FreeLibrary(m_library); + m_library = nullptr; + + wcscpy(msg.str, L"File is not a valid plugin"); + RequestDelete(); + return; + } + + m_nativeEffect->reservedForHost1 = this; + + msg.result = 1; + + UpdateEffectStruct(); + + // Init process buffer + DispatchToHost(audioMasterVendorSpecific, kVendorOpenMPT, kUpdateProcessingBuffer, nullptr, 0.0f); +} + + +void PluginBridge::SendErrorMessage(const wchar_t *str) +{ + BridgeMessage msg; + msg.Error(str); + SendToHost(msg); +} + + +// Wrapper for VST dispatch call with structured exception handling. +static intptr_t DispatchSEH(AEffect *effect, VstOpcodeToPlugin opCode, int32 index, intptr_t value, void *ptr, float opt, bool &exception) +{ + __try + { + if(effect->dispatcher != nullptr) + { + return effect->dispatcher(effect, opCode, index, value, ptr, opt); + } + } __except(EXCEPTION_EXECUTE_HANDLER) + { + exception = true; + } + return 0; +} + + +// Host-to-plugin opcode dispatcher +void PluginBridge::DispatchToPlugin(DispatchMsg &msg) +{ + if(m_nativeEffect == nullptr) + { + return; + } + + // Various dispatch data - depending on the opcode, one of those might be used. + std::vector<char> extraData; + size_t extraDataSize = 0; + + 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 effGetProgramName: + case effGetParamLabel: + case effGetParamDisplay: + case effGetParamName: + case effString2Parameter: + case effGetProgramNameIndexed: + case effGetEffectName: + case effGetErrorText: + case effGetVendorString: + case effGetProductString: + case effShellGetNextPlugin: + // Name in [ptr] + extraDataSize = 256; + break; + + case effMainsChanged: + // [value]: 0 means "turn off", 1 means "turn on" + ::SetThreadPriority(m_audioThread, msg.value ? THREAD_PRIORITY_ABOVE_NORMAL : THREAD_PRIORITY_NORMAL); + m_sharedMem->tailSize = static_cast<int32>(Dispatch(effGetTailSize, 0, 0, nullptr, 0.0f)); + break; + + case effEditGetRect: + // ERect** in [ptr] + extraDataSize = sizeof(void *); + 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) + { + TCHAR str[_MAX_PATH]; + GetModuleFileName(m_library, str, mpt::saturate_cast<DWORD>(std::size(str))); + + const auto parentWindow = reinterpret_cast<HWND>(msg.ptr); + ptr = m_window = CreateWindow( + MAKEINTATOM(m_editorClassAtom), + str, + WS_CHILD | WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, + 1, 1, + parentWindow, + nullptr, + GetModuleHandle(nullptr), + nullptr); + SetWindowLongPtr(m_window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this)); + } + break; + + case effGetChunk: + // void** in [ptr] for chunk data address + extraDataSize = sizeof(void *); + break; + + case effProcessEvents: + // VstEvents* in [ptr] + TranslateBridgeToVstEvents(m_eventCache, ptr); + ptr = m_eventCache.data(); + break; + + case effOfflineNotify: + // VstAudioFile* in [ptr] + extraData.resize(sizeof(VstAudioFile *) * static_cast<size_t>(msg.value)); + ptr = extraData.data(); + for(int64 i = 0; i < msg.value; i++) + { + // TODO create pointers + } + break; + + case effOfflinePrepare: + case effOfflineRun: + // VstOfflineTask* in [ptr] + extraData.resize(sizeof(VstOfflineTask *) * static_cast<size_t>(msg.value)); + ptr = extraData.data(); + for(int64 i = 0; i < msg.value; i++) + { + // TODO create pointers + } + break; + + case effSetSpeakerArrangement: + case effGetSpeakerArrangement: + // VstSpeakerArrangement* in [value] and [ptr] + msg.value = reinterpret_cast<int64>(ptr) + sizeof(VstSpeakerArrangement); + break; + + case effVendorSpecific: + // Let's implement some custom opcodes! + if(msg.index == kVendorOpenMPT) + { + msg.result = 1; + switch(msg.value) + { + case kUpdateEffectStruct: + UpdateEffectStruct(); + break; + case kUpdateEventMemName: + if(ptr) + m_eventMem.Open(static_cast<const wchar_t *>(ptr)); + break; + case kCacheProgramNames: + if(ptr) + { + int32 progMin = static_cast<const int32 *>(ptr)[0]; + int32 progMax = static_cast<const int32 *>(ptr)[1]; + char *name = static_cast<char *>(ptr); + for(int32 i = progMin; i < progMax; i++) + { + strcpy(name, ""); + if(m_nativeEffect->numPrograms <= 0 || Dispatch(effGetProgramNameIndexed, i, -1, name, 0) != 1) + { + // Fallback: Try to get current program name. + strcpy(name, ""); + int32 curProg = static_cast<int32>(Dispatch(effGetProgram, 0, 0, nullptr, 0.0f)); + if(i != curProg) + { + Dispatch(effSetProgram, 0, i, nullptr, 0.0f); + } + Dispatch(effGetProgramName, 0, 0, name, 0); + if(i != curProg) + { + Dispatch(effSetProgram, 0, curProg, nullptr, 0.0f); + } + } + name[kCachedProgramNameLength - 1] = '\0'; + name += kCachedProgramNameLength; + } + } + break; + case kCacheParameterInfo: + if(ptr) + { + int32 paramMin = static_cast<const int32 *>(ptr)[0]; + int32 paramMax = static_cast<const int32 *>(ptr)[1]; + ParameterInfo *param = static_cast<ParameterInfo *>(ptr); + for(int32 i = paramMin; i < paramMax; i++, param++) + { + strcpy(param->name, ""); + strcpy(param->label, ""); + strcpy(param->display, ""); + Dispatch(effGetParamName, i, 0, param->name, 0.0f); + Dispatch(effGetParamLabel, i, 0, param->label, 0.0f); + Dispatch(effGetParamDisplay, i, 0, param->display, 0.0f); + param->name[mpt::array_size<decltype(param->label)>::size - 1] = '\0'; + param->label[mpt::array_size<decltype(param->label)>::size - 1] = '\0'; + param->display[mpt::array_size<decltype(param->display)>::size - 1] = '\0'; + + if(Dispatch(effGetParameterProperties, i, 0, ¶m->props, 0.0f) != 1) + { + memset(¶m->props, 0, sizeof(param->props)); + strncpy(param->props.label, param->name, std::size(param->props.label)); + } + } + } + break; + case kBeginGetProgram: + if(ptr) + { + int32 numParams = static_cast<int32>((msg.size - sizeof(DispatchMsg)) / sizeof(float)); + float *params = static_cast<float *>(ptr); + for(int32 i = 0; i < numParams; i++) + { + params[i] = m_nativeEffect->getParameter(m_nativeEffect, i); + } + } + break; + default: + msg.result = 0; + } + return; + } + break; + } + + if(extraDataSize != 0) + { + extraData.resize(extraDataSize, 0); + ptr = extraData.data(); + } + + //std::cout << "about to dispatch " << msg.opcode << " to effect..."; + //std::flush(std::cout); + bool exception = false; + msg.result = static_cast<int32>(DispatchSEH(m_nativeEffect, static_cast<VstOpcodeToPlugin>(msg.opcode), msg.index, static_cast<intptr_t>(msg.value), ptr, msg.opt, exception)); + if(exception && msg.opcode != effClose) + { + msg.type = MsgHeader::exceptionMsg; + return; + } + //std::cout << "done" << std::endl; + + // Post-fix some opcodes + switch(msg.opcode) + { + case effClose: + m_nativeEffect = nullptr; + FreeLibrary(m_library); + m_library = nullptr; + RequestDelete(); + return; + + 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] + { + extraData.back() = 0; + char *dst = static_cast<char *>(origPtr); + size_t length = static_cast<size_t>(msg.ptr - 1); + strncpy(dst, extraData.data(), length); + dst[length] = 0; + break; + } + + case effEditGetRect: + // ERect** in [ptr] + { + ERect *rectPtr = *reinterpret_cast<ERect **>(extraData.data()); + if(rectPtr != nullptr && origPtr != nullptr) + { + MPT_ASSERT(static_cast<size_t>(msg.ptr) >= sizeof(ERect)); + std::memcpy(origPtr, rectPtr, std::min(sizeof(ERect), static_cast<size_t>(msg.ptr))); + m_windowWidth = rectPtr->right - rectPtr->left; + m_windowHeight = rectPtr->bottom - rectPtr->top; + + // For plugins that don't know their size until after effEditOpen is done. + if(m_window) + { + SetWindowPos(m_window, nullptr, 0, 0, m_windowWidth, m_windowHeight, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + } + } + break; + } + + case effEditClose: + DestroyWindow(m_window); + m_window = nullptr; + break; + + case effGetChunk: + // void** in [ptr] for chunk data address + if(m_getChunkMem.Create(static_cast<const wchar_t *>(origPtr), msg.result)) + { + std::memcpy(m_getChunkMem.Data(), *reinterpret_cast<void **>(extraData.data()), msg.result); + } + break; + } + + UpdateEffectStruct(); // Regularly update the struct +} + + +intptr_t PluginBridge::Dispatch(VstOpcodeToPlugin opcode, int32 index, intptr_t value, void *ptr, float opt) +{ + __try + { + return m_nativeEffect->dispatcher(m_nativeEffect, opcode, index, value, ptr, opt); + } __except(EXCEPTION_EXECUTE_HANDLER) + { + SendErrorMessage(L"Exception in dispatch()!"); + } + return 0; +} + + +// Set a plugin parameter. +void PluginBridge::SetParameter(ParameterMsg &msg) +{ + __try + { + m_nativeEffect->setParameter(m_nativeEffect, msg.index, msg.value); + } __except(EXCEPTION_EXECUTE_HANDLER) + { + msg.type = MsgHeader::exceptionMsg; + } +} + + +// Get a plugin parameter. +void PluginBridge::GetParameter(ParameterMsg &msg) +{ + __try + { + msg.value = m_nativeEffect->getParameter(m_nativeEffect, msg.index); + } __except(EXCEPTION_EXECUTE_HANDLER) + { + msg.type = MsgHeader::exceptionMsg; + } +} + + +// Execute received parameter automation messages +void PluginBridge::AutomateParameters() +{ + __try + { + const AutomationQueue::Parameter *param = m_sharedMem->automationQueue.params; + const AutomationQueue::Parameter *paramEnd = param + std::min(m_sharedMem->automationQueue.pendingEvents.exchange(0), static_cast<int32>(std::size(m_sharedMem->automationQueue.params))); + while(param != paramEnd) + { + m_nativeEffect->setParameter(m_nativeEffect, param->index, param->value); + param++; + } + } __except(EXCEPTION_EXECUTE_HANDLER) + { + SendErrorMessage(L"Exception in setParameter()!"); + } +} + + +// Audio rendering thread +DWORD WINAPI PluginBridge::AudioThread(LPVOID param) +{ + static_cast<PluginBridge*>(param)->AudioThread(); + return 0; +} +void PluginBridge::AudioThread() +{ + m_isAudioThread = true; + + const HANDLE objects[] = {m_sigProcessAudio.send, m_sigToBridgeAudio.send, m_sigThreadExit, m_otherProcess}; + DWORD result = 0; + do + { + result = WaitForMultipleObjects(mpt::saturate_cast<DWORD>(std::size(objects)), objects, FALSE, INFINITE); + if(result == WAIT_OBJECT_0) + { + ProcessMsg *msg = m_processMem.Data<ProcessMsg>(); + AutomateParameters(); + + m_sharedMem->tailSize = static_cast<int32>(Dispatch(effGetTailSize, 0, 0, nullptr, 0.0f)); + m_isProcessing = true; + + // Prepare VstEvents. + if(auto *events = m_eventMem.Data<int32>(); events != nullptr && *events != 0) + { + TranslateBridgeToVstEvents(m_eventCache, events); + *events = 0; + Dispatch(effProcessEvents, 0, 0, m_eventCache.data(), 0.0f); + } + + switch(msg->processType) + { + case ProcessMsg::process: + Process(); + break; + case ProcessMsg::processReplacing: + ProcessReplacing(); + break; + case ProcessMsg::processDoubleReplacing: + ProcessDoubleReplacing(); + break; + } + + m_isProcessing = false; + m_sigProcessAudio.Confirm(); + } else if(result == WAIT_OBJECT_0 + 1) + { + ParseNextMessage(m_sharedMem->audioThreadToBridgeMsgID); + m_sigToBridgeAudio.Confirm(); + } else if(result == WAIT_OBJECT_0 + 2) + { + // Main thread asked for termination + break; + } else if(result == WAIT_OBJECT_0 + 3) + { + // Host process died + RequestDelete(); + break; + } + } while(result != WAIT_FAILED); +} + + +// Process audio. +void PluginBridge::Process() +{ + if(m_nativeEffect->process) + { + float **inPointers, **outPointers; + int32 sampleFrames = BuildProcessPointers(inPointers, outPointers); + __try + { + m_nativeEffect->process(m_nativeEffect, inPointers, outPointers, sampleFrames); + } __except(EXCEPTION_EXECUTE_HANDLER) + { + SendErrorMessage(L"Exception in process()!"); + } + } +} + + +// Process audio. +void PluginBridge::ProcessReplacing() +{ + if(m_nativeEffect->processReplacing) + { + float **inPointers, **outPointers; + int32 sampleFrames = BuildProcessPointers(inPointers, outPointers); + __try + { + m_nativeEffect->processReplacing(m_nativeEffect, inPointers, outPointers, sampleFrames); + } __except(EXCEPTION_EXECUTE_HANDLER) + { + SendErrorMessage(L"Exception in processReplacing()!"); + } + } +} + + +// Process audio. +void PluginBridge::ProcessDoubleReplacing() +{ + if(m_nativeEffect->processDoubleReplacing) + { + double **inPointers, **outPointers; + int32 sampleFrames = BuildProcessPointers(inPointers, outPointers); + __try + { + m_nativeEffect->processDoubleReplacing(m_nativeEffect, inPointers, outPointers, sampleFrames); + } __except(EXCEPTION_EXECUTE_HANDLER) + { + SendErrorMessage(L"Exception in processDoubleReplacing()!"); + } + } +} + + +// Helper function to build the pointer arrays required by the VST process functions. +template <typename buf_t> +int32 PluginBridge::BuildProcessPointers(buf_t **(&inPointers), buf_t **(&outPointers)) +{ + MPT_ASSERT(m_processMem.Good()); + ProcessMsg &msg = *m_processMem.Data<ProcessMsg>(); + + const size_t numPtrs = msg.numInputs + msg.numOutputs; + m_sampleBuffers.resize(numPtrs, 0); + + if(numPtrs) + { + buf_t *offset = reinterpret_cast<buf_t *>(&msg + 1); + for(size_t i = 0; i < numPtrs; i++) + { + m_sampleBuffers[i] = offset; + offset += msg.sampleFrames; + } + inPointers = reinterpret_cast<buf_t **>(m_sampleBuffers.data()); + outPointers = inPointers + msg.numInputs; + } + + return msg.sampleFrames; +} + + +// Send a message to the host. +intptr_t PluginBridge::DispatchToHost(VstOpcodeToHost opcode, int32 index, intptr_t value, void *ptr, float opt) +{ + std::vector<char> dispatchData(sizeof(DispatchMsg), 0); + int64 ptrOut = 0; + char *ptrC = static_cast<char *>(ptr); + + switch(opcode) + { + case audioMasterAutomate: + case audioMasterVersion: + case audioMasterCurrentId: + case audioMasterIdle: + case audioMasterPinConnected: + break; + + case audioMasterWantMidi: + return 1; + + case audioMasterGetTime: + // VstTimeInfo* in [return value] + if(m_isProcessing) + { + // During processing, read the cached time info. It won't change during the call + // and we can save some valuable inter-process calls that way. + return ToIntPtr<VstTimeInfo>(&m_sharedMem->timeInfo); + } + break; + + case audioMasterProcessEvents: + // VstEvents* in [ptr] + if(ptr == nullptr) + return 0; + TranslateVstEventsToBridge(dispatchData, *static_cast<VstEvents *>(ptr), m_otherPtrSize); + ptrOut = dispatchData.size() - sizeof(DispatchMsg); + // If we are currently processing, try to return the events as part of the process call + if(m_isAudioThread && m_isProcessing && m_eventMem.Good()) + { + auto *memBytes = m_eventMem.Data<char>(); + auto *eventBytes = m_eventMem.Data<char>() + sizeof(int32); + int32 &memNumEvents = *m_eventMem.Data<int32>(); + const auto memEventsSize = BridgeVstEventsSize(memBytes); + if(m_eventMem.Size() >= static_cast<size_t>(ptrOut) + memEventsSize) + { + // Enough shared memory for possibly pre-existing and new events; add new events at the end + memNumEvents += static_cast<VstEvents *>(ptr)->numEvents; + std::memcpy(eventBytes + memEventsSize, dispatchData.data() + sizeof(DispatchMsg) + sizeof(int32), static_cast<size_t>(ptrOut) - sizeof(int32)); + return 1; + } else if(memNumEvents) + { + // Not enough memory; merge what we have and what we want to add so that it arrives in the correct order + dispatchData.insert(dispatchData.begin() + sizeof(DispatchMsg) + sizeof(int32), eventBytes, eventBytes + memEventsSize); + *reinterpret_cast<int32 *>(dispatchData.data() + sizeof(DispatchMsg)) += memNumEvents; + memNumEvents = 0; + ptrOut += memEventsSize; + } + } + break; + + case audioMasterSetTime: + case audioMasterTempoAt: + case audioMasterGetNumAutomatableParameters: + case audioMasterGetParameterQuantization: + break; + + case audioMasterVendorSpecific: + if(index != kVendorOpenMPT || value != kUpdateProcessingBuffer) + { + if(ptr != 0) + { + // Cannot translate this. + return 0; + } + break; + } + [[fallthrough]]; + case audioMasterIOChanged: + // We need to be sure that the new values are known to the master. + if(m_nativeEffect != nullptr) + { + UpdateEffectStruct(); + CreateProcessingFile(dispatchData); + ptrOut = dispatchData.size() - sizeof(DispatchMsg); + } + break; + + case audioMasterNeedIdle: + m_needIdle = true; + return 1; + + case audioMasterSizeWindow: + if(m_window) + { + SetWindowPos(m_window, nullptr, 0, 0, index, static_cast<int>(value), SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + } + break; + + case audioMasterGetSampleRate: + case audioMasterGetBlockSize: + case audioMasterGetInputLatency: + case audioMasterGetOutputLatency: + break; + + case audioMasterGetPreviousPlug: + case audioMasterGetNextPlug: + // Don't even bother, this would explode :) + return 0; + + case audioMasterWillReplaceOrAccumulate: + case audioMasterGetCurrentProcessLevel: + case audioMasterGetAutomationState: + break; + + case audioMasterOfflineStart: + case audioMasterOfflineRead: + case audioMasterOfflineWrite: + case audioMasterOfflineGetCurrentPass: + case audioMasterOfflineGetCurrentMetaPass: + // Currently not supported in OpenMPT + return 0; + + case audioMasterSetOutputSampleRate: + break; + + case audioMasterGetOutputSpeakerArrangement: + case audioMasterGetInputSpeakerArrangement: + // VstSpeakerArrangement* in [return value] + ptrOut = sizeof(VstSpeakerArrangement); + break; + + case audioMasterGetVendorString: + case audioMasterGetProductString: + // Name in [ptr] + ptrOut = 256; + break; + + case audioMasterGetVendorVersion: + case audioMasterSetIcon: + break; + + case audioMasterCanDo: + // Name in [ptr] + ptrOut = strlen(ptrC) + 1; + dispatchData.insert(dispatchData.end(), ptrC, ptrC + ptrOut); + break; + + case audioMasterGetLanguage: + case audioMasterOpenWindow: + case audioMasterCloseWindow: + break; + + case audioMasterGetDirectory: + // Name in [return value] + ptrOut = 256; + break; + + case audioMasterUpdateDisplay: + case audioMasterBeginEdit: + case audioMasterEndEdit: + break; + + case audioMasterOpenFileSelector: + // VstFileSelect* in [ptr] + if(ptr != nullptr) + { + auto &fileSel = *static_cast<VstFileSelect *>(ptr); + fileSel.returnMultiplePaths = nullptr; + fileSel.numReturnPaths = 0; + TranslateVstFileSelectToBridge(dispatchData, fileSel, m_otherPtrSize); + ptrOut = dispatchData.size() - sizeof(DispatchMsg) + 65536; // enough space for return paths + } + break; + + case audioMasterCloseFileSelector: + // VstFileSelect* in [ptr] + if(auto *fileSel = static_cast<VstFileSelect *>(ptr); fileSel != nullptr && fileSel->reserved == 1) + { + fileSel->returnPath = nullptr; + fileSel->returnMultiplePaths = nullptr; + } + m_fileSelectCache.clear(); + m_fileSelectCache.shrink_to_fit(); + return 1; + + case audioMasterEditFile: + break; + + case audioMasterGetChunkFile: + // Name in [ptr] + ptrOut = 256; + dispatchData.insert(dispatchData.end(), ptrC, ptrC + ptrOut); + break; + + default: +#ifdef MPT_BUILD_DEBUG + if(ptr != nullptr) + __debugbreak(); +#endif + break; + } + + if(ptrOut != 0) + { + // 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); + MappedMemory auxMem; + if(useAuxMem) + { + // Extra data doesn't fit in message - use secondary memory + wchar_t auxMemName[64]; + static_assert(sizeof(DispatchMsg) + sizeof(auxMemName) <= sizeof(BridgeMessage), "Check message sizes, this will crash!"); + swprintf(auxMemName, std::size(auxMemName), L"Local\\openmpt-%u-auxmem-%u", GetCurrentProcessId(), GetCurrentThreadId()); + if(auxMem.Create(auxMemName, extraSize)) + { + // Move message data to shared memory and then move shared memory name to message data + std::memcpy(auxMem.Data(), &dispatchData[sizeof(DispatchMsg)], extraSize); + std::memcpy(&dispatchData[sizeof(DispatchMsg)], auxMemName, sizeof(auxMemName)); + } else + { + return 0; + } + } + + //std::cout << "about to dispatch " << opcode << " to host..."; + //std::flush(std::cout); + if(!SendToHost(*msg)) + { + return 0; + } + //std::cout << "done." << std::endl; + const DispatchMsg *resultMsg = &msg->dispatch; + + const char *extraData = useAuxMem ? auxMem.Data<const char>() : reinterpret_cast<const char *>(resultMsg + 1); + // Post-fix some opcodes + switch(opcode) + { + case audioMasterGetTime: + // VstTimeInfo* in [return value] + return ToIntPtr<VstTimeInfo>(&m_sharedMem->timeInfo); + + case audioMasterGetOutputSpeakerArrangement: + case audioMasterGetInputSpeakerArrangement: + // VstSpeakerArrangement* in [return value] + std::memcpy(&m_host2PlugMem.speakerArrangement, extraData, sizeof(VstSpeakerArrangement)); + return ToIntPtr<VstSpeakerArrangement>(&m_host2PlugMem.speakerArrangement); + + case audioMasterGetVendorString: + case audioMasterGetProductString: + // Name in [ptr] + strcpy(ptrC, extraData); + break; + + case audioMasterGetDirectory: + // Name in [return value] + strncpy(m_host2PlugMem.name, extraData, std::size(m_host2PlugMem.name) - 1); + m_host2PlugMem.name[std::size(m_host2PlugMem.name) - 1] = 0; + return ToIntPtr<char>(m_host2PlugMem.name); + + case audioMasterOpenFileSelector: + if(resultMsg->result != 0 && ptr != nullptr) + { + TranslateBridgeToVstFileSelect(m_fileSelectCache, extraData, static_cast<size_t>(ptrOut)); + auto &fileSel = *static_cast<VstFileSelect *>(ptr); + const auto &fileSelResult = *reinterpret_cast<const VstFileSelect *>(m_fileSelectCache.data()); + + if((fileSel.command == kVstFileLoad || fileSel.command == kVstFileSave)) + { + if(FourCC("VOPM") == m_nativeEffect->uniqueID) + { + fileSel.sizeReturnPath = _MAX_PATH; + } + } else if(fileSel.command == kVstDirectorySelect) + { + if(FourCC("VSTr") == m_nativeEffect->uniqueID && 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 = static_cast<int32>(strlen(fileSelResult.returnPath) + 1); + } + } + fileSel.numReturnPaths = fileSelResult.numReturnPaths; + fileSel.reserved = 1; + if(fileSel.command == kVstMultipleFilesLoad) + { + fileSel.returnMultiplePaths = fileSelResult.returnMultiplePaths; + } else if(fileSel.returnPath != nullptr && fileSel.sizeReturnPath != 0) + { + // Plugin provides memory + const auto len = strnlen(fileSelResult.returnPath, fileSel.sizeReturnPath - 1); + strncpy(fileSel.returnPath, fileSelResult.returnPath, len); + fileSel.returnPath[len] = '\0'; + fileSel.reserved = 0; + } else + { + fileSel.returnPath = fileSelResult.returnPath; + fileSel.sizeReturnPath = fileSelResult.sizeReturnPath; + } + } + break; + + case audioMasterGetChunkFile: + // Name in [ptr] + strcpy(ptrC, extraData); + break; + } + + return static_cast<intptr_t>(resultMsg->result); +} + + +// Helper function for sending messages to the host. +intptr_t VSTCALLBACK PluginBridge::MasterCallback(AEffect *effect, VstOpcodeToHost opcode, int32 index, intptr_t value, void *ptr, float opt) +{ + PluginBridge *instance = (effect != nullptr && effect->reservedForHost1 != nullptr) ? static_cast<PluginBridge *>(effect->reservedForHost1) : PluginBridge::m_latestInstance; + return instance->DispatchToHost(opcode, index, value, ptr, opt); +} + + +LRESULT CALLBACK PluginBridge::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + PluginBridge *that = nullptr; + if(hwnd == m_communicationWindow && wParam < m_plugins.size()) + that = static_cast<PluginBridge *>(m_plugins[wParam]); + else // Editor windows + that = reinterpret_cast<PluginBridge *>(GetWindowLongPtr(hwnd, GWLP_USERDATA)); + + if(that == nullptr) + return DefWindowProc(hwnd, uMsg, wParam, lParam); + + switch(uMsg) + { + case WM_ERASEBKGND: + // Pretend that we erased the background + return 1; + + case WM_SIZE: + { + // For plugins that change their size but do not notify the host, e.g. Roland D-50 + RECT rect{0, 0, 0, 0}; + GetClientRect(hwnd, &rect); + const int width = rect.right - rect.left, height = rect.bottom - rect.top; + if(width > 0 && height > 0 && (width != that->m_windowWidth || height != that->m_windowHeight)) + { + that->m_windowWidth = width; + that->m_windowHeight = height; + that->DispatchToHost(audioMasterSizeWindow, width, height, nullptr, 0.0f); + } + break; + } + + case WM_BRIDGE_MESSAGE_TO_BRIDGE: + that->ParseNextMessage(static_cast<int>(lParam)); + return WM_BRIDGE_SUCCESS; + + case WM_BRIDGE_DELETE_PLUGIN: + delete that; + return 0; + } + + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + + +void PluginBridge::IdleTimerProc(HWND, UINT, UINT_PTR idTimer, DWORD) +{ + if(idTimer != TIMER_IDLE) + return; + for(auto *plugin : m_plugins) + { + auto *that = static_cast<PluginBridge *>(plugin); + if(that == nullptr) + continue; + if(that->m_needIdle) + { + that->Dispatch(effIdle, 0, 0, nullptr, 0.0f); + that->m_needIdle = false; + } + if(that->m_window) + { + that->Dispatch(effEditIdle, 0, 0, nullptr, 0.0f); + } + } +} + + +OPENMPT_NAMESPACE_END diff --git a/Src/external_dependencies/openmpt-trunk/pluginBridge/Bridge.h b/Src/external_dependencies/openmpt-trunk/pluginBridge/Bridge.h new file mode 100644 index 00000000..3967edcc --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/pluginBridge/Bridge.h @@ -0,0 +1,104 @@ +/* + * Bridge.h + * -------- + * Purpose: VST plugin bridge (plugin side) + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" +#include <atomic> + +#include "BridgeCommon.h" + + +OPENMPT_NAMESPACE_BEGIN + +class PluginBridge : private BridgeCommon +{ +public: + static bool m_fullMemDump; + +protected: + enum TimerID : UINT + { + TIMER_IDLE = 1, + }; + + static PluginBridge *m_latestInstance; + static ATOM m_editorClassAtom; + + // Plugin + Vst::AEffect *m_nativeEffect = nullptr; + HMODULE m_library = nullptr; + HWND m_window = nullptr; + int m_windowWidth = 0, m_windowHeight = 0; + std::atomic<bool> m_isProcessing = false; + + // Static memory for host-to-plugin pointers + union + { + Vst::VstSpeakerArrangement speakerArrangement; + char name[256]; + } m_host2PlugMem; + std::vector<char> m_eventCache; // Cached VST (MIDI) events + std::vector<char> m_fileSelectCache; // Cached VstFileSelect data + + // Pointers to sample data + std::vector<void *> m_sampleBuffers; + uint32 m_mixBufSize = 0; + + HANDLE m_audioThread = nullptr; + Event m_sigThreadExit; // Signal to kill audio thread + + bool m_needIdle = false; // Plugin needs idle time + +public: + PluginBridge(const wchar_t *memName, HANDLE otherProcess); + ~PluginBridge(); + + PluginBridge(const PluginBridge&) = delete; + PluginBridge &operator=(const PluginBridge&) = delete; + + static void MainLoop(TCHAR *argv[]); + +protected: + void RequestDelete(); + + bool SendToHost(BridgeMessage &sendMsg); + + void UpdateEffectStruct(); + void CreateProcessingFile(std::vector<char> &dispatchData); + + void ParseNextMessage(int msgID); + void NewInstance(NewInstanceMsg &msg); + void InitBridge(InitMsg &msg); + void DispatchToPlugin(DispatchMsg &msg); + void SetParameter(ParameterMsg &msg); + void GetParameter(ParameterMsg &msg); + void AutomateParameters(); + void Process(); + void ProcessReplacing(); + void ProcessDoubleReplacing(); + intptr_t DispatchToHost(Vst::VstOpcodeToHost opcode, int32 index, intptr_t value, void *ptr, float opt); + void SendErrorMessage(const wchar_t *str); + intptr_t Dispatch(Vst::VstOpcodeToPlugin opcode, int32 index, intptr_t value, void *ptr, float opt); + + template<typename buf_t> + int32 BuildProcessPointers(buf_t **(&inPointers), buf_t **(&outPointers)); + + static DWORD WINAPI AudioThread(LPVOID param); + void AudioThread(); + + static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + static void CALLBACK IdleTimerProc(HWND hwnd, UINT message, UINT_PTR idTimer, DWORD dwTime); + + static intptr_t VSTCALLBACK MasterCallback(Vst::AEffect *effect, Vst::VstOpcodeToHost, int32 index, intptr_t value, void *ptr, float opt); +}; + + +OPENMPT_NAMESPACE_END diff --git a/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeCommon.h b/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeCommon.h new file mode 100644 index 00000000..405bd9be --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeCommon.h @@ -0,0 +1,679 @@ +/* + * BridgeCommon.h + * -------------- + * Purpose: Declarations of stuff that's common between the VST bridge and bridge wrapper. + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + +#include <vector> + +#if (_WIN32_WINNT < _WIN32_WINNT_VISTA) +#include <intrin.h> +#endif + + +OPENMPT_NAMESPACE_BEGIN + +// Insert some object at the end of a char vector. +template <typename T> +static void PushToVector(std::vector<char> &data, const T &obj, size_t writeSize = sizeof(T)) +{ + static_assert(!std::is_pointer<T>::value, "Won't push pointers to data vectors."); + const char *objC = reinterpret_cast<const char *>(&obj); + data.insert(data.end(), objC, objC + writeSize); +} + +static void PushZStringToVector(std::vector<char> &data, const char *str) +{ + if(str != nullptr) + data.insert(data.end(), str, str + strlen(str)); + PushToVector(data, char(0)); +} + +OPENMPT_NAMESPACE_END + +#include "AEffectWrapper.h" +#include "BridgeOpCodes.h" + +OPENMPT_NAMESPACE_BEGIN + + +// Internal data structures + + +// Event to notify other threads +class Event +{ +private: + HANDLE handle = nullptr; + +public: + Event() = default; + ~Event() { Close(); } + + // Create a new event + bool Create(bool manual = false, const wchar_t *name = nullptr) + { + Close(); + handle = CreateEventW(nullptr, manual ? TRUE : FALSE, FALSE, name); + return handle != nullptr; + } + + // Duplicate a local event + bool DuplicateFrom(HANDLE source) + { + Close(); + return DuplicateHandle(GetCurrentProcess(), source, GetCurrentProcess(), &handle, 0, FALSE, DUPLICATE_SAME_ACCESS) != FALSE; + } + + void Close() + { + CloseHandle(handle); + } + + void Trigger() + { + SetEvent(handle); + } + + void Reset() + { + ResetEvent(handle); + } + + void operator=(const HANDLE other) { handle = other; } + operator const HANDLE() const { return handle; } +}; + + +// Two-way event +class Signal +{ +public: + Event send, confirm; + + // Create new (local) signal + bool Create() + { + return send.Create() && confirm.Create(); + } + + // Create signal from name (for inter-process communication) + bool Create(const wchar_t *name, const wchar_t *addendum) + { + wchar_t fullName[64 + 1]; + wcscpy(fullName, name); + wcscat(fullName, addendum); + fullName[std::size(fullName) - 1] = L'\0'; + size_t nameLen = wcslen(fullName); + wcscpy(fullName + nameLen, L"-s"); + + bool success = send.Create(false, fullName); + wcscpy(fullName + nameLen, L"-a"); + return success && confirm.Create(false, fullName); + } + + // Create signal from other signal + bool DuplicateFrom(const Signal &other) + { + return send.DuplicateFrom(other.send) + && confirm.DuplicateFrom(other.confirm); + } + + void Send() + { + send.Trigger(); + } + + void Confirm() + { + confirm.Trigger(); + } +}; + + +// Memory that can be shared between processes +class MappedMemory +{ +protected: + struct Header + { + uint32 size; + }; + + HANDLE mapFile = nullptr; + Header *view = nullptr; + +public: + MappedMemory() = default; + ~MappedMemory() { Close(); } + + // Create a shared memory object. + bool Create(const wchar_t *name, uint32 size) + { + Close(); + + mapFile = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, size + sizeof(Header), name); + if(!mapFile) + { + return false; + } + view = static_cast<Header *>(MapViewOfFile(mapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0)); + if(!view) + { + return false; + } + view->size = size; + return Good(); + } + + // Open an existing shared memory object. + bool Open(const wchar_t *name) + { + Close(); + + mapFile = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, name); + if(!mapFile) + { + return false; + } + view = static_cast<Header *>(MapViewOfFile(mapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0)); + if(!view) + { + return false; + } + return Good(); + } + + // Close this shared memory object. + void Close() + { + if(mapFile) + { + if(view) + { + UnmapViewOfFile(view); + view = nullptr; + } + CloseHandle(mapFile); + mapFile = nullptr; + } + } + + template <typename T = void> + T *Data() const + { + if(view == nullptr) + return nullptr; + else + return reinterpret_cast<T *>(view + 1); + } + + size_t Size() const + { + if(!view) + return 0; + else + return view->size; + } + + bool Good() const { return view != nullptr; } + + // Make a copy and detach it from the other object + void CopyFrom(MappedMemory &other) + { + Close(); + mapFile = other.mapFile; + view = other.view; + other.mapFile = nullptr; + other.view = nullptr; + } +}; + + +// Bridge communication data + +#pragma pack(push, 8) + +// Simple atomic value that has a guaranteed size and layout for the shared memory +template <typename T> +struct BridgeAtomic +{ +public: + BridgeAtomic() = default; + BridgeAtomic<T> &operator=(const T value) + { + static_assert(sizeof(m_value) >= sizeof(T)); + MPT_ASSERT((intptr_t(&m_value) & 3) == 0); +#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA) + InterlockedExchange(&m_value, static_cast<LONG>(value)); +#else + _InterlockedExchange(&m_value, static_cast<LONG>(value)); +#endif + return *this; + } + operator T() const + { +#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA) + return static_cast<T>(InterlockedAdd(&m_value, 0)); +#else + return static_cast<T>(_InterlockedExchangeAdd(&m_value, 0)); +#endif + } + + T exchange(T desired) + { +#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA) + return static_cast<T>(InterlockedExchange(&m_value, static_cast<LONG>(desired))); +#else + return static_cast<T>(_InterlockedExchange(&m_value, static_cast<LONG>(desired))); +#endif + } + + T fetch_add(T arg) + { +#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA) + return static_cast<T>(InterlockedExchangeAdd(&m_value, static_cast<LONG>(arg))); +#else + return static_cast<T>(_InterlockedExchangeAdd(&m_value, static_cast<LONG>(arg))); +#endif + } + + bool compare_exchange_strong(T &expected, T desired) + { +#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA) + return InterlockedCompareExchange(&m_value, static_cast<LONG>(desired), static_cast<LONG>(expected)) == static_cast<LONG>(expected); +#else + return _InterlockedCompareExchange(&m_value, static_cast<LONG>(desired), static_cast<LONG>(expected)) == static_cast<LONG>(expected); +#endif + } + +private: + mutable LONG m_value; +}; + + +// Host-to-bridge parameter automation message +struct AutomationQueue +{ + struct Parameter + { + uint32 index; + float value; + }; + + BridgeAtomic<int32> pendingEvents; // Number of pending automation events + Parameter params[64]; // Automation events +}; + + +// Host-to-bridge message to initiate a process call. +struct ProcessMsg +{ + enum ProcessType : int32 + { + process = 0, + processReplacing, + processDoubleReplacing, + }; + + int32 processType; + int32 numInputs; + int32 numOutputs; + int32 sampleFrames; + // Input and output buffers follow +}; + + +// General message header +struct MsgHeader +{ + enum BridgeMessageType : uint32 + { + // Management messages, host to bridge + newInstance, + init, + // Management messages, bridge to host + errorMsg, + exceptionMsg, + + // VST messages, common + dispatch, + // VST messages, host to bridge + setParameter, + getParameter, + automate, + }; + + BridgeAtomic<bool> isUsed; + uint32 size; // Size of complete message, including this header + uint32 type; // See BridgeMessageType +}; + + +// Host-to-bridge new instance message +struct NewInstanceMsg : public MsgHeader +{ + wchar_t memName[64]; // Shared memory object name; +}; + + +// Host-to-bridge initialization message +struct InitMsg : public MsgHeader +{ + int32 result; + int32 hostPtrSize; // Size of VstIntPtr in host + uint32 mixBufSize; // Interal mix buffer size (for shared memory audio buffers) + int32 pluginID; // ID to use when sending messages to host + uint32 fullMemDump; // When crashing, create full memory dumps instead of stack dumps + wchar_t str[_MAX_PATH]; // Plugin file to load. Out: Error message if result != 0. +}; + + +// Host-to-bridge or bridge-to-host VST dispatch message +struct DispatchMsg : public MsgHeader +{ + int32 opcode; + int32 index; + int64 value; + int64 ptr; // Usually, this will be the size of whatever ptr points to. In that case, the data itself is stored after this struct. + float opt; + int32 result; +}; + + +// Host-to-bridge VST setParameter / getParameter message +struct ParameterMsg : public MsgHeader +{ + int32 index; // Parameter ID + float value; // Parameter value (in/out) +}; + + +// Bridge-to-host error message +struct ErrorMsg : public MsgHeader +{ + wchar_t str[64]; +}; + + +// Universal bridge message format +union BridgeMessage +{ + MsgHeader header; + NewInstanceMsg newInstance; + InitMsg init; + DispatchMsg dispatch; + ParameterMsg parameter; + ErrorMsg error; + uint8 dummy[2048]; // Enough space for most default structs, e.g. 2x speaker negotiation struct + + void SetType(MsgHeader::BridgeMessageType msgType, uint32 size) + { + header.isUsed = true; + header.size = size; + header.type = msgType; + } + + void NewInstance(const wchar_t *memName) + { + SetType(MsgHeader::newInstance, sizeof(NewInstanceMsg)); + + wcsncpy(newInstance.memName, memName, std::size(newInstance.memName) - 1); + } + + void Init(const wchar_t *pluginPath, uint32 mixBufSize, int32 pluginID, bool fullMemDump) + { + SetType(MsgHeader::init, sizeof(InitMsg)); + + init.result = 0; + init.hostPtrSize = sizeof(intptr_t); + init.mixBufSize = mixBufSize; + init.pluginID = pluginID; + init.fullMemDump = fullMemDump; + wcsncpy(init.str, pluginPath, std::size(init.str) - 1); + } + + void Dispatch(int32 opcode, int32 index, int64 value, int64 ptr, float opt, uint32 extraDataSize) + { + SetType(MsgHeader::dispatch, sizeof(DispatchMsg) + extraDataSize); + + dispatch.result = 0; + dispatch.opcode = opcode; + dispatch.index = index; + dispatch.value = value; + dispatch.ptr = ptr; + dispatch.opt = opt; + } + + void Dispatch(Vst::VstOpcodeToHost opcode, int32 index, int64 value, int64 ptr, float opt, uint32 extraDataSize) + { + Dispatch(static_cast<int32>(opcode), index, value, ptr, opt, extraDataSize); + } + + void Dispatch(Vst::VstOpcodeToPlugin opcode, int32 index, int64 value, int64 ptr, float opt, uint32 extraDataSize) + { + Dispatch(static_cast<int32>(opcode), index, value, ptr, opt, extraDataSize); + } + + void SetParameter(int32 index, float value) + { + SetType(MsgHeader::setParameter, sizeof(ParameterMsg)); + + parameter.index = index; + parameter.value = value; + } + + void GetParameter(int32 index) + { + SetType(MsgHeader::getParameter, sizeof(ParameterMsg)); + + parameter.index = index; + parameter.value = 0.0f; + } + + void Automate() + { + // Dummy message + SetType(MsgHeader::automate, sizeof(MsgHeader)); + } + + void Error(const wchar_t *text) + { + SetType(MsgHeader::errorMsg, sizeof(ErrorMsg)); + + wcsncpy(error.str, text, std::size(error.str) - 1); + error.str[std::size(error.str) - 1] = 0; + } + + // Copy message to target and clear delivery status + void CopyTo(BridgeMessage &target) + { + std::memcpy(&target, this, std::min(static_cast<size_t>(header.size), sizeof(BridgeMessage))); + header.isUsed = false; + } +}; + +// This is the maximum size of dispatch data that can be sent in a message. If you want to dispatch more data, use a secondary medium for sending it along. +inline constexpr size_t DISPATCH_DATA_SIZE = (sizeof(BridgeMessage) - sizeof(DispatchMsg)); +static_assert(DISPATCH_DATA_SIZE >= 256, "There should be room for at least 256 bytes of dispatch data!"); + + +// The array size should be more than enough for any realistic scenario with nested and simultaneous dispatch calls +inline constexpr int MSG_STACK_SIZE = 16; +using MsgStack = std::array<BridgeMessage, MSG_STACK_SIZE>; + + +// Ensuring that our HWND looks the same to both 32-bit and 64-bit processes +struct BridgeHWND +{ +public: + void operator=(HWND handle) { m_handle = static_cast<int32>(reinterpret_cast<intptr_t>(handle)); } + operator HWND() const { return reinterpret_cast<HWND>(static_cast<intptr_t>(m_handle)); } +protected: + BridgeAtomic<int32> m_handle; +}; + + +// Layout of the shared memory chunk +struct SharedMemLayout +{ + union + { + Vst::AEffect effect; // Native layout from host perspective + AEffect32 effect32; + AEffect64 effect64; + }; + MsgStack ipcMessages; + AutomationQueue automationQueue; + Vst::VstTimeInfo timeInfo; + BridgeHWND hostCommWindow; + BridgeHWND bridgeCommWindow; + int32 bridgePluginID; + BridgeAtomic<int32> tailSize; + BridgeAtomic<int32> audioThreadToHostMsgID; + BridgeAtomic<int32> audioThreadToBridgeMsgID; +}; +static_assert(sizeof(Vst::AEffect) <= sizeof(AEffect64), "Something's going very wrong here."); + + +// For caching parameter information +struct ParameterInfo +{ + Vst::VstParameterProperties props; + char name[64]; + char label[64]; + char display[64]; +}; + + +#pragma pack(pop) + +// Common variables that we will find in both the host and plugin side of the bridge (this is not shared memory) +class BridgeCommon +{ +public: + BridgeCommon() + { + m_instanceCount++; + } + + ~BridgeCommon() + { + m_instanceCount--; + } + +protected: + enum WindowMessage : UINT + { + WM_BRIDGE_KEYFIRST = WM_USER + 4000, // Must be consistent with VSTEditor.cpp! + WM_BRIDGE_KEYLAST = WM_BRIDGE_KEYFIRST + WM_KEYLAST - WM_KEYFIRST, + WM_BRIDGE_MESSAGE_TO_BRIDGE, + WM_BRIDGE_MESSAGE_TO_HOST, + WM_BRIDGE_DELETE_PLUGIN, + + WM_BRIDGE_SUCCESS = 1337, + }; + + static std::vector<BridgeCommon *> m_plugins; + static HWND m_communicationWindow; + static int m_instanceCount; + static thread_local bool m_isAudioThread; + + // Signals for host <-> bridge communication + Signal m_sigToHostAudio, m_sigToBridgeAudio; + Signal m_sigProcessAudio; + Event m_sigBridgeReady; + + Event m_otherProcess; // Handle of "other" process (host handle in the bridge and vice versa) + + // Shared memory segments + MappedMemory m_queueMem; // AEffect, message, some fixed size VST structures + MappedMemory m_processMem; // Process message + sample buffer + MappedMemory m_getChunkMem; // effGetChunk temporary memory + MappedMemory m_eventMem; // VstEvents memory + + // Pointer into shared memory + SharedMemLayout /*volatile*/ *m_sharedMem = nullptr; + + // Pointer size of the "other" side of the bridge, in bytes + int32 m_otherPtrSize = 0; + + int32 m_thisPluginID = 0; + int32 m_otherPluginID = 0; + + static void CreateCommunicationWindow(WNDPROC windowProc) + { + static constexpr TCHAR windowClassName[] = _T("OpenMPTPluginBridgeCommunication"); + static bool registered = false; + if(!registered) + { + registered = true; + WNDCLASSEX wndClass; + wndClass.cbSize = sizeof(WNDCLASSEX); + wndClass.style = CS_HREDRAW | CS_VREDRAW; + wndClass.lpfnWndProc = windowProc; + wndClass.cbClsExtra = 0; + wndClass.cbWndExtra = 0; + wndClass.hInstance = GetModuleHandle(nullptr); + wndClass.hIcon = nullptr; + wndClass.hCursor = LoadCursor(nullptr, IDC_ARROW); + wndClass.hbrBackground = nullptr; + wndClass.lpszMenuName = nullptr; + wndClass.lpszClassName = windowClassName; + wndClass.hIconSm = nullptr; + RegisterClassEx(&wndClass); + } + + m_communicationWindow = CreateWindow( + windowClassName, + _T("OpenMPT Plugin Bridge Communication"), + WS_POPUP, + CW_USEDEFAULT, + CW_USEDEFAULT, + 1, + 1, + HWND_MESSAGE, + nullptr, + GetModuleHandle(nullptr), + nullptr); + } + + bool CreateSignals(const wchar_t *mapName) + { + wchar_t readyName[64]; + wcscpy(readyName, mapName); + wcscat(readyName, L"rdy"); + return m_sigToHostAudio.Create(mapName, L"sha") + && m_sigToBridgeAudio.Create(mapName, L"sba") + && m_sigProcessAudio.Create(mapName, L"prc") + && m_sigBridgeReady.Create(false, readyName); + } + + // Copy a message to shared memory and return relative position. + int CopyToSharedMemory(const BridgeMessage &msg, MsgStack &stack) + { + MPT_ASSERT(msg.header.isUsed); + for(int i = 0; i < MSG_STACK_SIZE; i++) + { + BridgeMessage &targetMsg = stack[i]; + bool expected = false; + if(targetMsg.header.isUsed.compare_exchange_strong(expected, true)) + { + std::memcpy(&targetMsg, &msg, std::min(sizeof(BridgeMessage), size_t(msg.header.size))); + return i; + } + } + return -1; + } +}; + + +OPENMPT_NAMESPACE_END diff --git a/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeOpCodes.h b/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeOpCodes.h new file mode 100644 index 00000000..e527e3f9 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeOpCodes.h @@ -0,0 +1,46 @@ +/* + * BridgeOpCodes.h + * --------------- + * Purpose: Various dispatch opcodes for communication between OpenMPT and its plugin bridge. + * Notes : (currently none) + * Authors: Johannes Schultz (OpenMPT Devs) + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + +#include "../mptrack/plugins/VstDefinitions.h" + +OPENMPT_NAMESPACE_BEGIN + +enum VendorSpecificOpCodes : int32 +{ + // Explicitely tell the plugin bridge to update the AEffect struct cache + kUpdateEffectStruct = 0, + // Notify the host that it needs to resize its processing buffers + kUpdateProcessingBuffer, + // Returns the pointer to the bridge wrapper object for the current plugin + kGetWrapperPointer, + // Sets name for new shared memory object - name in ptr + kUpdateEventMemName, + kCloseOldProcessingMemory, + // Cache program names - ptr points to 2x int32 containing the start and end program index - caches names in range [start, end[ + kCacheProgramNames, + // Cache parameter information - ptr points to 2x int32 containing the start and end parameter index - caches info in range [start, end[ + kCacheParameterInfo, + // Cache parameter values + kBeginGetProgram, + // Stop using parameter value cache + kEndGetProgram, + + // Constant for identifying our vendor-specific opcodes + kVendorOpenMPT = Vst::FourCC("OMPT"), + + // Maximum length of cached program names + kCachedProgramNameLength = 256, +}; + +OPENMPT_NAMESPACE_END 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 diff --git a/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeWrapper.h b/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeWrapper.h new file mode 100644 index 00000000..47df012f --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/pluginBridge/BridgeWrapper.h @@ -0,0 +1,237 @@ +/* + * BridgeWrapper.h + * --------------- + * 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. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + +#ifdef MPT_WITH_VST + +#include "BridgeCommon.h" +#include "../common/ComponentManager.h" + +OPENMPT_NAMESPACE_BEGIN + +struct VSTPluginLib; + +enum PluginArch : int +{ + PluginArch_unknown = 0, + PluginArch_x86 = 32, + PluginArch_amd64 = 64, + PluginArch_arm = 128 + 32, + PluginArch_arm64 = 128 + 64, +}; + +std::size_t GetPluginArchPointerSize(PluginArch arch); + +enum class Generation +{ + Legacy, + Modern, +}; + +class ComponentPluginBridge + : public ComponentBase +{ +public: + enum Availability + { + AvailabilityUnknown = 0, + AvailabilityOK = 1, + AvailabilityMissing = -1, + AvailabilityWrongVersion = -2, + }; +private: + const PluginArch arch; + const Generation generation; + mpt::PathString exeName; + Availability availability = AvailabilityUnknown; +protected: + ComponentPluginBridge(PluginArch arch, Generation generation); +protected: + bool DoInitialize() override; +public: + Availability GetAvailability() const { return availability; } + mpt::PathString GetFileName() const { return exeName; } +}; + +class ComponentPluginBridge_x86 + : public ComponentPluginBridge +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentPluginBridge_x86, "PluginBridge-x86") +public: + ComponentPluginBridge_x86() : ComponentPluginBridge(PluginArch_x86, Generation::Modern) { } +}; + +class ComponentPluginBridgeLegacy_x86 + : public ComponentPluginBridge +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentPluginBridgeLegacy_x86, "PluginBridgeLegacy-x86") +public: + ComponentPluginBridgeLegacy_x86() : ComponentPluginBridge(PluginArch_x86, Generation::Legacy) { } +}; + +class ComponentPluginBridge_amd64 + : public ComponentPluginBridge +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentPluginBridge_amd64, "PluginBridge-amd64") +public: + ComponentPluginBridge_amd64() : ComponentPluginBridge(PluginArch_amd64, Generation::Modern) { } +}; + +class ComponentPluginBridgeLegacy_amd64 + : public ComponentPluginBridge +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentPluginBridgeLegacy_amd64, "PluginBridgeLegacy-amd64") +public: + ComponentPluginBridgeLegacy_amd64() : ComponentPluginBridge(PluginArch_amd64, Generation::Legacy) { } +}; + +#if defined(MPT_WITH_WINDOWS10) + +class ComponentPluginBridge_arm + : public ComponentPluginBridge +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentPluginBridge_arm, "PluginBridge-arm") +public: + ComponentPluginBridge_arm() : ComponentPluginBridge(PluginArch_arm, Generation::Modern) { } +}; + +class ComponentPluginBridgeLegacy_arm + : public ComponentPluginBridge +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentPluginBridgeLegacy_arm, "PluginBridgeLegacy-arm") +public: + ComponentPluginBridgeLegacy_arm() : ComponentPluginBridge(PluginArch_arm, Generation::Legacy) { } +}; + +class ComponentPluginBridge_arm64 + : public ComponentPluginBridge +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentPluginBridge_arm64, "PluginBridge-arm64") +public: + ComponentPluginBridge_arm64() : ComponentPluginBridge(PluginArch_arm64, Generation::Modern) { } +}; + +class ComponentPluginBridgeLegacy_arm64 + : public ComponentPluginBridge +{ + MPT_DECLARE_COMPONENT_MEMBERS(ComponentPluginBridgeLegacy_arm64, "PluginBridgeLegacy-arm64") +public: + ComponentPluginBridgeLegacy_arm64() : ComponentPluginBridge(PluginArch_arm64, Generation::Legacy) { } +}; + + + +#endif // MPT_WITH_WINDOWS10 + +class BridgeWrapper : private BridgeCommon +{ +protected: + Event m_sigAutomation; + MappedMemory m_oldProcessMem; + + // Helper struct for keeping track of auxiliary shared memory + struct AuxMem + { + std::atomic<bool> used = false; + std::atomic<uint32> size = 0; + MappedMemory memory; + wchar_t name[64]; + }; + AuxMem m_auxMems[MSG_STACK_SIZE]; + + std::vector<char> m_cachedProgNames; + std::vector<ParameterInfo> m_cachedParamInfo; + std::vector<float> m_cachedParamValues; + int32 m_cachedProgNameStart = 0, m_cachedParamInfoStart = 0; + + bool m_isSettingProgram = false; + + Vst::ERect m_editRect; + Vst::VstSpeakerArrangement m_speakers[2]; + + ComponentHandle<ComponentPluginBridge_x86> pluginBridge_x86; + ComponentHandle<ComponentPluginBridgeLegacy_x86> pluginBridgeLegacy_x86; + ComponentHandle<ComponentPluginBridge_amd64> pluginBridge_amd64; + ComponentHandle<ComponentPluginBridgeLegacy_amd64> pluginBridgeLegacy_amd64; +#if defined(MPT_WITH_WINDOWS10) + ComponentHandle<ComponentPluginBridge_arm> pluginBridge_arm; + ComponentHandle<ComponentPluginBridgeLegacy_arm> pluginBridgeLegacy_arm; + ComponentHandle<ComponentPluginBridge_arm64> pluginBridge_arm64; + ComponentHandle<ComponentPluginBridgeLegacy_arm64> pluginBridgeLegacy_arm64; +#endif // MPT_WITH_WINDOWS10 + + Generation m_Generation = Generation::Modern; + +public: + + // Generic bridge exception + class BridgeException : public std::exception + { + public: + BridgeException(const char *str) : std::exception(str) { } + BridgeException() { } + }; + class BridgeNotFoundException : public BridgeException { }; + + // Exception from bridge process + class BridgeRemoteException + { + protected: + wchar_t *str; + public: + BridgeRemoteException(const wchar_t *str_) : str(_wcsdup(str_)) { } + BridgeRemoteException(const BridgeRemoteException &) = delete; + BridgeRemoteException & operator=(const BridgeRemoteException &) = delete; + ~BridgeRemoteException() { free(str); } + const wchar_t *what() const { return str; } + }; + +public: + static PluginArch GetNativePluginBinaryType(); + static PluginArch GetPluginBinaryType(const mpt::PathString &pluginPath); + static bool IsPluginNative(const mpt::PathString &pluginPath) { return GetPluginBinaryType(pluginPath) == GetNativePluginBinaryType(); } + static uint64 GetFileVersion(const WCHAR *exePath); + + static Vst::AEffect *Create(const VSTPluginLib &plugin, bool forceLegacy); + +protected: + BridgeWrapper(); + ~BridgeWrapper(); + + bool Init(const mpt::PathString &pluginPath, Generation bridgeGeneration, BridgeWrapper *sharedInstace); + + void ParseNextMessage(int msgID); + void DispatchToHost(DispatchMsg &msg); + bool SendToBridge(BridgeMessage &sendMsg); + void SendAutomationQueue(); + AuxMem *GetAuxMemory(uint32 size); + + static intptr_t VSTCALLBACK DispatchToPlugin(Vst::AEffect *effect, Vst::VstOpcodeToPlugin opcode, int32 index, intptr_t value, void *ptr, float opt); + intptr_t DispatchToPlugin(Vst::VstOpcodeToPlugin opcode, int32 index, intptr_t value, void *ptr, float opt); + static void VSTCALLBACK SetParameter(Vst::AEffect *effect, int32 index, float parameter); + void SetParameter(int32 index, float parameter); + static float VSTCALLBACK GetParameter(Vst::AEffect *effect, int32 index); + float GetParameter(int32 index); + static void VSTCALLBACK Process(Vst::AEffect *effect, float **inputs, float **outputs, int32 sampleFrames); + static void VSTCALLBACK ProcessReplacing(Vst::AEffect *effect, float **inputs, float **outputs, int32 sampleFrames); + static void VSTCALLBACK ProcessDoubleReplacing(Vst::AEffect *effect, double **inputs, double **outputs, int32 sampleFrames); + + template<typename buf_t> + void BuildProcessBuffer(ProcessMsg::ProcessType type, int32 numInputs, int32 numOutputs, buf_t **inputs, buf_t **outputs, int32 sampleFrames); + + static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +}; + +OPENMPT_NAMESPACE_END + +#endif // MPT_WITH_VST diff --git a/Src/external_dependencies/openmpt-trunk/pluginBridge/PluginBridge-win10.manifest b/Src/external_dependencies/openmpt-trunk/pluginBridge/PluginBridge-win10.manifest new file mode 100644 index 00000000..7e52c389 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/pluginBridge/PluginBridge-win10.manifest @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> + <assemblyIdentity + type="win32" + name="OpenMPT.PluginBridge" + version="1.0.0.0" + /> + <description>OpenMPT PluginBridge</description> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <!-- Windows 10 --> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + </application> + </compatibility> +</assembly> diff --git a/Src/external_dependencies/openmpt-trunk/pluginBridge/PluginBridge-win7.manifest b/Src/external_dependencies/openmpt-trunk/pluginBridge/PluginBridge-win7.manifest new file mode 100644 index 00000000..e7fb589d --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/pluginBridge/PluginBridge-win7.manifest @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> + <assemblyIdentity + type="win32" + name="OpenMPT.PluginBridge" + version="1.0.0.0" + /> + <description>OpenMPT PluginBridge</description> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <!-- Windows 10 --> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + <!-- Windows 8.1 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <!-- Windows 8 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + <!-- Windows 7 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> + </application> + </compatibility> +</assembly> diff --git a/Src/external_dependencies/openmpt-trunk/pluginBridge/PluginBridge-win81.manifest b/Src/external_dependencies/openmpt-trunk/pluginBridge/PluginBridge-win81.manifest new file mode 100644 index 00000000..b7f2aed3 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/pluginBridge/PluginBridge-win81.manifest @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> + <assemblyIdentity + type="win32" + name="OpenMPT.PluginBridge" + version="1.0.0.0" + /> + <description>OpenMPT PluginBridge</description> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <!-- Windows 10 --> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + <!-- Windows 8.1 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + </application> + </compatibility> +</assembly> diff --git a/Src/external_dependencies/openmpt-trunk/pluginBridge/PluginBridge.rc b/Src/external_dependencies/openmpt-trunk/pluginBridge/PluginBridge.rc new file mode 100644 index 00000000..dda3bea7 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/pluginBridge/PluginBridge.rc @@ -0,0 +1,109 @@ +// Microsoft Visual C++ generated resource script. +// + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// German (Germany) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DEU) +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL + + +#include "../common/versionNumber.h" + +#define VER_HELPER_STRINGIZE(x) #x +#define VER_STRINGIZE(x) VER_HELPER_STRINGIZE(x) + +#define VER_FILEVERSION VER_MAJORMAJOR,VER_MAJOR,VER_MINOR,VER_MINORMINOR +#define VER_FILEVERSION_DOT VER_MAJORMAJOR.VER_MAJOR.VER_MINOR.VER_MINORMINOR +#define VER_FILEVERSION_STR VER_STRINGIZE(VER_FILEVERSION_DOT) + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VER_FILEVERSION + PRODUCTVERSION VER_FILEVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "CompanyName", "OpenMPT" + VALUE "FileDescription", "OpenMPT Plugin Bridge" + VALUE "FileVersion", VER_FILEVERSION_STR + VALUE "InternalName", "PluginBridge.exe" + VALUE "LegalCopyright", "Copyright © 2013-2022 OpenMPT Project Developers and Contributors" + VALUE "OriginalFilename", "PluginBridge.exe" + VALUE "ProductName", "OpenMPT" + VALUE "ProductVersion", VER_FILEVERSION_STR + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END + +#endif // German (Germany) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + |