diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/pluginBridge/Bridge.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/pluginBridge/Bridge.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/pluginBridge/Bridge.cpp | 1318 |
1 files changed, 1318 insertions, 0 deletions
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 |