Archived
1
0
This repository has been archived on 2024-10-17. You can view files and clone it, but cannot push or open issues or pull requests.
winamp/Src/external_dependencies/openmpt-trunk/pluginBridge/Bridge.cpp
2024-09-24 14:54:57 +02:00

1319 lines
36 KiB
C++

/*
* 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, &param->props, 0.0f) != 1)
{
memset(&param->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