1319 lines
36 KiB
C++
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, ¶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
|