680 lines
16 KiB
C++
680 lines
16 KiB
C++
/*
|
|
* BridgeCommon.h
|
|
* --------------
|
|
* Purpose: Declarations of stuff that's common between the VST bridge and bridge wrapper.
|
|
* Notes : (currently none)
|
|
* Authors: OpenMPT Devs
|
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
|
*/
|
|
|
|
|
|
#pragma once
|
|
|
|
#include "openmpt/all/BuildSettings.hpp"
|
|
|
|
#include <vector>
|
|
|
|
#if (_WIN32_WINNT < _WIN32_WINNT_VISTA)
|
|
#include <intrin.h>
|
|
#endif
|
|
|
|
|
|
OPENMPT_NAMESPACE_BEGIN
|
|
|
|
// Insert some object at the end of a char vector.
|
|
template <typename T>
|
|
static void PushToVector(std::vector<char> &data, const T &obj, size_t writeSize = sizeof(T))
|
|
{
|
|
static_assert(!std::is_pointer<T>::value, "Won't push pointers to data vectors.");
|
|
const char *objC = reinterpret_cast<const char *>(&obj);
|
|
data.insert(data.end(), objC, objC + writeSize);
|
|
}
|
|
|
|
static void PushZStringToVector(std::vector<char> &data, const char *str)
|
|
{
|
|
if(str != nullptr)
|
|
data.insert(data.end(), str, str + strlen(str));
|
|
PushToVector(data, char(0));
|
|
}
|
|
|
|
OPENMPT_NAMESPACE_END
|
|
|
|
#include "AEffectWrapper.h"
|
|
#include "BridgeOpCodes.h"
|
|
|
|
OPENMPT_NAMESPACE_BEGIN
|
|
|
|
|
|
// Internal data structures
|
|
|
|
|
|
// Event to notify other threads
|
|
class Event
|
|
{
|
|
private:
|
|
HANDLE handle = nullptr;
|
|
|
|
public:
|
|
Event() = default;
|
|
~Event() { Close(); }
|
|
|
|
// Create a new event
|
|
bool Create(bool manual = false, const wchar_t *name = nullptr)
|
|
{
|
|
Close();
|
|
handle = CreateEventW(nullptr, manual ? TRUE : FALSE, FALSE, name);
|
|
return handle != nullptr;
|
|
}
|
|
|
|
// Duplicate a local event
|
|
bool DuplicateFrom(HANDLE source)
|
|
{
|
|
Close();
|
|
return DuplicateHandle(GetCurrentProcess(), source, GetCurrentProcess(), &handle, 0, FALSE, DUPLICATE_SAME_ACCESS) != FALSE;
|
|
}
|
|
|
|
void Close()
|
|
{
|
|
CloseHandle(handle);
|
|
}
|
|
|
|
void Trigger()
|
|
{
|
|
SetEvent(handle);
|
|
}
|
|
|
|
void Reset()
|
|
{
|
|
ResetEvent(handle);
|
|
}
|
|
|
|
void operator=(const HANDLE other) { handle = other; }
|
|
operator const HANDLE() const { return handle; }
|
|
};
|
|
|
|
|
|
// Two-way event
|
|
class Signal
|
|
{
|
|
public:
|
|
Event send, confirm;
|
|
|
|
// Create new (local) signal
|
|
bool Create()
|
|
{
|
|
return send.Create() && confirm.Create();
|
|
}
|
|
|
|
// Create signal from name (for inter-process communication)
|
|
bool Create(const wchar_t *name, const wchar_t *addendum)
|
|
{
|
|
wchar_t fullName[64 + 1];
|
|
wcscpy(fullName, name);
|
|
wcscat(fullName, addendum);
|
|
fullName[std::size(fullName) - 1] = L'\0';
|
|
size_t nameLen = wcslen(fullName);
|
|
wcscpy(fullName + nameLen, L"-s");
|
|
|
|
bool success = send.Create(false, fullName);
|
|
wcscpy(fullName + nameLen, L"-a");
|
|
return success && confirm.Create(false, fullName);
|
|
}
|
|
|
|
// Create signal from other signal
|
|
bool DuplicateFrom(const Signal &other)
|
|
{
|
|
return send.DuplicateFrom(other.send)
|
|
&& confirm.DuplicateFrom(other.confirm);
|
|
}
|
|
|
|
void Send()
|
|
{
|
|
send.Trigger();
|
|
}
|
|
|
|
void Confirm()
|
|
{
|
|
confirm.Trigger();
|
|
}
|
|
};
|
|
|
|
|
|
// Memory that can be shared between processes
|
|
class MappedMemory
|
|
{
|
|
protected:
|
|
struct Header
|
|
{
|
|
uint32 size;
|
|
};
|
|
|
|
HANDLE mapFile = nullptr;
|
|
Header *view = nullptr;
|
|
|
|
public:
|
|
MappedMemory() = default;
|
|
~MappedMemory() { Close(); }
|
|
|
|
// Create a shared memory object.
|
|
bool Create(const wchar_t *name, uint32 size)
|
|
{
|
|
Close();
|
|
|
|
mapFile = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, size + sizeof(Header), name);
|
|
if(!mapFile)
|
|
{
|
|
return false;
|
|
}
|
|
view = static_cast<Header *>(MapViewOfFile(mapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0));
|
|
if(!view)
|
|
{
|
|
return false;
|
|
}
|
|
view->size = size;
|
|
return Good();
|
|
}
|
|
|
|
// Open an existing shared memory object.
|
|
bool Open(const wchar_t *name)
|
|
{
|
|
Close();
|
|
|
|
mapFile = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, name);
|
|
if(!mapFile)
|
|
{
|
|
return false;
|
|
}
|
|
view = static_cast<Header *>(MapViewOfFile(mapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0));
|
|
if(!view)
|
|
{
|
|
return false;
|
|
}
|
|
return Good();
|
|
}
|
|
|
|
// Close this shared memory object.
|
|
void Close()
|
|
{
|
|
if(mapFile)
|
|
{
|
|
if(view)
|
|
{
|
|
UnmapViewOfFile(view);
|
|
view = nullptr;
|
|
}
|
|
CloseHandle(mapFile);
|
|
mapFile = nullptr;
|
|
}
|
|
}
|
|
|
|
template <typename T = void>
|
|
T *Data() const
|
|
{
|
|
if(view == nullptr)
|
|
return nullptr;
|
|
else
|
|
return reinterpret_cast<T *>(view + 1);
|
|
}
|
|
|
|
size_t Size() const
|
|
{
|
|
if(!view)
|
|
return 0;
|
|
else
|
|
return view->size;
|
|
}
|
|
|
|
bool Good() const { return view != nullptr; }
|
|
|
|
// Make a copy and detach it from the other object
|
|
void CopyFrom(MappedMemory &other)
|
|
{
|
|
Close();
|
|
mapFile = other.mapFile;
|
|
view = other.view;
|
|
other.mapFile = nullptr;
|
|
other.view = nullptr;
|
|
}
|
|
};
|
|
|
|
|
|
// Bridge communication data
|
|
|
|
#pragma pack(push, 8)
|
|
|
|
// Simple atomic value that has a guaranteed size and layout for the shared memory
|
|
template <typename T>
|
|
struct BridgeAtomic
|
|
{
|
|
public:
|
|
BridgeAtomic() = default;
|
|
BridgeAtomic<T> &operator=(const T value)
|
|
{
|
|
static_assert(sizeof(m_value) >= sizeof(T));
|
|
MPT_ASSERT((intptr_t(&m_value) & 3) == 0);
|
|
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
|
|
InterlockedExchange(&m_value, static_cast<LONG>(value));
|
|
#else
|
|
_InterlockedExchange(&m_value, static_cast<LONG>(value));
|
|
#endif
|
|
return *this;
|
|
}
|
|
operator T() const
|
|
{
|
|
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
|
|
return static_cast<T>(InterlockedAdd(&m_value, 0));
|
|
#else
|
|
return static_cast<T>(_InterlockedExchangeAdd(&m_value, 0));
|
|
#endif
|
|
}
|
|
|
|
T exchange(T desired)
|
|
{
|
|
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
|
|
return static_cast<T>(InterlockedExchange(&m_value, static_cast<LONG>(desired)));
|
|
#else
|
|
return static_cast<T>(_InterlockedExchange(&m_value, static_cast<LONG>(desired)));
|
|
#endif
|
|
}
|
|
|
|
T fetch_add(T arg)
|
|
{
|
|
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
|
|
return static_cast<T>(InterlockedExchangeAdd(&m_value, static_cast<LONG>(arg)));
|
|
#else
|
|
return static_cast<T>(_InterlockedExchangeAdd(&m_value, static_cast<LONG>(arg)));
|
|
#endif
|
|
}
|
|
|
|
bool compare_exchange_strong(T &expected, T desired)
|
|
{
|
|
#if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
|
|
return InterlockedCompareExchange(&m_value, static_cast<LONG>(desired), static_cast<LONG>(expected)) == static_cast<LONG>(expected);
|
|
#else
|
|
return _InterlockedCompareExchange(&m_value, static_cast<LONG>(desired), static_cast<LONG>(expected)) == static_cast<LONG>(expected);
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
mutable LONG m_value;
|
|
};
|
|
|
|
|
|
// Host-to-bridge parameter automation message
|
|
struct AutomationQueue
|
|
{
|
|
struct Parameter
|
|
{
|
|
uint32 index;
|
|
float value;
|
|
};
|
|
|
|
BridgeAtomic<int32> pendingEvents; // Number of pending automation events
|
|
Parameter params[64]; // Automation events
|
|
};
|
|
|
|
|
|
// Host-to-bridge message to initiate a process call.
|
|
struct ProcessMsg
|
|
{
|
|
enum ProcessType : int32
|
|
{
|
|
process = 0,
|
|
processReplacing,
|
|
processDoubleReplacing,
|
|
};
|
|
|
|
int32 processType;
|
|
int32 numInputs;
|
|
int32 numOutputs;
|
|
int32 sampleFrames;
|
|
// Input and output buffers follow
|
|
};
|
|
|
|
|
|
// General message header
|
|
struct MsgHeader
|
|
{
|
|
enum BridgeMessageType : uint32
|
|
{
|
|
// Management messages, host to bridge
|
|
newInstance,
|
|
init,
|
|
// Management messages, bridge to host
|
|
errorMsg,
|
|
exceptionMsg,
|
|
|
|
// VST messages, common
|
|
dispatch,
|
|
// VST messages, host to bridge
|
|
setParameter,
|
|
getParameter,
|
|
automate,
|
|
};
|
|
|
|
BridgeAtomic<bool> isUsed;
|
|
uint32 size; // Size of complete message, including this header
|
|
uint32 type; // See BridgeMessageType
|
|
};
|
|
|
|
|
|
// Host-to-bridge new instance message
|
|
struct NewInstanceMsg : public MsgHeader
|
|
{
|
|
wchar_t memName[64]; // Shared memory object name;
|
|
};
|
|
|
|
|
|
// Host-to-bridge initialization message
|
|
struct InitMsg : public MsgHeader
|
|
{
|
|
int32 result;
|
|
int32 hostPtrSize; // Size of VstIntPtr in host
|
|
uint32 mixBufSize; // Interal mix buffer size (for shared memory audio buffers)
|
|
int32 pluginID; // ID to use when sending messages to host
|
|
uint32 fullMemDump; // When crashing, create full memory dumps instead of stack dumps
|
|
wchar_t str[_MAX_PATH]; // Plugin file to load. Out: Error message if result != 0.
|
|
};
|
|
|
|
|
|
// Host-to-bridge or bridge-to-host VST dispatch message
|
|
struct DispatchMsg : public MsgHeader
|
|
{
|
|
int32 opcode;
|
|
int32 index;
|
|
int64 value;
|
|
int64 ptr; // Usually, this will be the size of whatever ptr points to. In that case, the data itself is stored after this struct.
|
|
float opt;
|
|
int32 result;
|
|
};
|
|
|
|
|
|
// Host-to-bridge VST setParameter / getParameter message
|
|
struct ParameterMsg : public MsgHeader
|
|
{
|
|
int32 index; // Parameter ID
|
|
float value; // Parameter value (in/out)
|
|
};
|
|
|
|
|
|
// Bridge-to-host error message
|
|
struct ErrorMsg : public MsgHeader
|
|
{
|
|
wchar_t str[64];
|
|
};
|
|
|
|
|
|
// Universal bridge message format
|
|
union BridgeMessage
|
|
{
|
|
MsgHeader header;
|
|
NewInstanceMsg newInstance;
|
|
InitMsg init;
|
|
DispatchMsg dispatch;
|
|
ParameterMsg parameter;
|
|
ErrorMsg error;
|
|
uint8 dummy[2048]; // Enough space for most default structs, e.g. 2x speaker negotiation struct
|
|
|
|
void SetType(MsgHeader::BridgeMessageType msgType, uint32 size)
|
|
{
|
|
header.isUsed = true;
|
|
header.size = size;
|
|
header.type = msgType;
|
|
}
|
|
|
|
void NewInstance(const wchar_t *memName)
|
|
{
|
|
SetType(MsgHeader::newInstance, sizeof(NewInstanceMsg));
|
|
|
|
wcsncpy(newInstance.memName, memName, std::size(newInstance.memName) - 1);
|
|
}
|
|
|
|
void Init(const wchar_t *pluginPath, uint32 mixBufSize, int32 pluginID, bool fullMemDump)
|
|
{
|
|
SetType(MsgHeader::init, sizeof(InitMsg));
|
|
|
|
init.result = 0;
|
|
init.hostPtrSize = sizeof(intptr_t);
|
|
init.mixBufSize = mixBufSize;
|
|
init.pluginID = pluginID;
|
|
init.fullMemDump = fullMemDump;
|
|
wcsncpy(init.str, pluginPath, std::size(init.str) - 1);
|
|
}
|
|
|
|
void Dispatch(int32 opcode, int32 index, int64 value, int64 ptr, float opt, uint32 extraDataSize)
|
|
{
|
|
SetType(MsgHeader::dispatch, sizeof(DispatchMsg) + extraDataSize);
|
|
|
|
dispatch.result = 0;
|
|
dispatch.opcode = opcode;
|
|
dispatch.index = index;
|
|
dispatch.value = value;
|
|
dispatch.ptr = ptr;
|
|
dispatch.opt = opt;
|
|
}
|
|
|
|
void Dispatch(Vst::VstOpcodeToHost opcode, int32 index, int64 value, int64 ptr, float opt, uint32 extraDataSize)
|
|
{
|
|
Dispatch(static_cast<int32>(opcode), index, value, ptr, opt, extraDataSize);
|
|
}
|
|
|
|
void Dispatch(Vst::VstOpcodeToPlugin opcode, int32 index, int64 value, int64 ptr, float opt, uint32 extraDataSize)
|
|
{
|
|
Dispatch(static_cast<int32>(opcode), index, value, ptr, opt, extraDataSize);
|
|
}
|
|
|
|
void SetParameter(int32 index, float value)
|
|
{
|
|
SetType(MsgHeader::setParameter, sizeof(ParameterMsg));
|
|
|
|
parameter.index = index;
|
|
parameter.value = value;
|
|
}
|
|
|
|
void GetParameter(int32 index)
|
|
{
|
|
SetType(MsgHeader::getParameter, sizeof(ParameterMsg));
|
|
|
|
parameter.index = index;
|
|
parameter.value = 0.0f;
|
|
}
|
|
|
|
void Automate()
|
|
{
|
|
// Dummy message
|
|
SetType(MsgHeader::automate, sizeof(MsgHeader));
|
|
}
|
|
|
|
void Error(const wchar_t *text)
|
|
{
|
|
SetType(MsgHeader::errorMsg, sizeof(ErrorMsg));
|
|
|
|
wcsncpy(error.str, text, std::size(error.str) - 1);
|
|
error.str[std::size(error.str) - 1] = 0;
|
|
}
|
|
|
|
// Copy message to target and clear delivery status
|
|
void CopyTo(BridgeMessage &target)
|
|
{
|
|
std::memcpy(&target, this, std::min(static_cast<size_t>(header.size), sizeof(BridgeMessage)));
|
|
header.isUsed = false;
|
|
}
|
|
};
|
|
|
|
// This is the maximum size of dispatch data that can be sent in a message. If you want to dispatch more data, use a secondary medium for sending it along.
|
|
inline constexpr size_t DISPATCH_DATA_SIZE = (sizeof(BridgeMessage) - sizeof(DispatchMsg));
|
|
static_assert(DISPATCH_DATA_SIZE >= 256, "There should be room for at least 256 bytes of dispatch data!");
|
|
|
|
|
|
// The array size should be more than enough for any realistic scenario with nested and simultaneous dispatch calls
|
|
inline constexpr int MSG_STACK_SIZE = 16;
|
|
using MsgStack = std::array<BridgeMessage, MSG_STACK_SIZE>;
|
|
|
|
|
|
// Ensuring that our HWND looks the same to both 32-bit and 64-bit processes
|
|
struct BridgeHWND
|
|
{
|
|
public:
|
|
void operator=(HWND handle) { m_handle = static_cast<int32>(reinterpret_cast<intptr_t>(handle)); }
|
|
operator HWND() const { return reinterpret_cast<HWND>(static_cast<intptr_t>(m_handle)); }
|
|
protected:
|
|
BridgeAtomic<int32> m_handle;
|
|
};
|
|
|
|
|
|
// Layout of the shared memory chunk
|
|
struct SharedMemLayout
|
|
{
|
|
union
|
|
{
|
|
Vst::AEffect effect; // Native layout from host perspective
|
|
AEffect32 effect32;
|
|
AEffect64 effect64;
|
|
};
|
|
MsgStack ipcMessages;
|
|
AutomationQueue automationQueue;
|
|
Vst::VstTimeInfo timeInfo;
|
|
BridgeHWND hostCommWindow;
|
|
BridgeHWND bridgeCommWindow;
|
|
int32 bridgePluginID;
|
|
BridgeAtomic<int32> tailSize;
|
|
BridgeAtomic<int32> audioThreadToHostMsgID;
|
|
BridgeAtomic<int32> audioThreadToBridgeMsgID;
|
|
};
|
|
static_assert(sizeof(Vst::AEffect) <= sizeof(AEffect64), "Something's going very wrong here.");
|
|
|
|
|
|
// For caching parameter information
|
|
struct ParameterInfo
|
|
{
|
|
Vst::VstParameterProperties props;
|
|
char name[64];
|
|
char label[64];
|
|
char display[64];
|
|
};
|
|
|
|
|
|
#pragma pack(pop)
|
|
|
|
// Common variables that we will find in both the host and plugin side of the bridge (this is not shared memory)
|
|
class BridgeCommon
|
|
{
|
|
public:
|
|
BridgeCommon()
|
|
{
|
|
m_instanceCount++;
|
|
}
|
|
|
|
~BridgeCommon()
|
|
{
|
|
m_instanceCount--;
|
|
}
|
|
|
|
protected:
|
|
enum WindowMessage : UINT
|
|
{
|
|
WM_BRIDGE_KEYFIRST = WM_USER + 4000, // Must be consistent with VSTEditor.cpp!
|
|
WM_BRIDGE_KEYLAST = WM_BRIDGE_KEYFIRST + WM_KEYLAST - WM_KEYFIRST,
|
|
WM_BRIDGE_MESSAGE_TO_BRIDGE,
|
|
WM_BRIDGE_MESSAGE_TO_HOST,
|
|
WM_BRIDGE_DELETE_PLUGIN,
|
|
|
|
WM_BRIDGE_SUCCESS = 1337,
|
|
};
|
|
|
|
static std::vector<BridgeCommon *> m_plugins;
|
|
static HWND m_communicationWindow;
|
|
static int m_instanceCount;
|
|
static thread_local bool m_isAudioThread;
|
|
|
|
// Signals for host <-> bridge communication
|
|
Signal m_sigToHostAudio, m_sigToBridgeAudio;
|
|
Signal m_sigProcessAudio;
|
|
Event m_sigBridgeReady;
|
|
|
|
Event m_otherProcess; // Handle of "other" process (host handle in the bridge and vice versa)
|
|
|
|
// Shared memory segments
|
|
MappedMemory m_queueMem; // AEffect, message, some fixed size VST structures
|
|
MappedMemory m_processMem; // Process message + sample buffer
|
|
MappedMemory m_getChunkMem; // effGetChunk temporary memory
|
|
MappedMemory m_eventMem; // VstEvents memory
|
|
|
|
// Pointer into shared memory
|
|
SharedMemLayout /*volatile*/ *m_sharedMem = nullptr;
|
|
|
|
// Pointer size of the "other" side of the bridge, in bytes
|
|
int32 m_otherPtrSize = 0;
|
|
|
|
int32 m_thisPluginID = 0;
|
|
int32 m_otherPluginID = 0;
|
|
|
|
static void CreateCommunicationWindow(WNDPROC windowProc)
|
|
{
|
|
static constexpr TCHAR windowClassName[] = _T("OpenMPTPluginBridgeCommunication");
|
|
static bool registered = false;
|
|
if(!registered)
|
|
{
|
|
registered = true;
|
|
WNDCLASSEX wndClass;
|
|
wndClass.cbSize = sizeof(WNDCLASSEX);
|
|
wndClass.style = CS_HREDRAW | CS_VREDRAW;
|
|
wndClass.lpfnWndProc = windowProc;
|
|
wndClass.cbClsExtra = 0;
|
|
wndClass.cbWndExtra = 0;
|
|
wndClass.hInstance = GetModuleHandle(nullptr);
|
|
wndClass.hIcon = nullptr;
|
|
wndClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
|
wndClass.hbrBackground = nullptr;
|
|
wndClass.lpszMenuName = nullptr;
|
|
wndClass.lpszClassName = windowClassName;
|
|
wndClass.hIconSm = nullptr;
|
|
RegisterClassEx(&wndClass);
|
|
}
|
|
|
|
m_communicationWindow = CreateWindow(
|
|
windowClassName,
|
|
_T("OpenMPT Plugin Bridge Communication"),
|
|
WS_POPUP,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
1,
|
|
1,
|
|
HWND_MESSAGE,
|
|
nullptr,
|
|
GetModuleHandle(nullptr),
|
|
nullptr);
|
|
}
|
|
|
|
bool CreateSignals(const wchar_t *mapName)
|
|
{
|
|
wchar_t readyName[64];
|
|
wcscpy(readyName, mapName);
|
|
wcscat(readyName, L"rdy");
|
|
return m_sigToHostAudio.Create(mapName, L"sha")
|
|
&& m_sigToBridgeAudio.Create(mapName, L"sba")
|
|
&& m_sigProcessAudio.Create(mapName, L"prc")
|
|
&& m_sigBridgeReady.Create(false, readyName);
|
|
}
|
|
|
|
// Copy a message to shared memory and return relative position.
|
|
int CopyToSharedMemory(const BridgeMessage &msg, MsgStack &stack)
|
|
{
|
|
MPT_ASSERT(msg.header.isUsed);
|
|
for(int i = 0; i < MSG_STACK_SIZE; i++)
|
|
{
|
|
BridgeMessage &targetMsg = stack[i];
|
|
bool expected = false;
|
|
if(targetMsg.header.isUsed.compare_exchange_strong(expected, true))
|
|
{
|
|
std::memcpy(&targetMsg, &msg, std::min(sizeof(BridgeMessage), size_t(msg.header.size)));
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
};
|
|
|
|
|
|
OPENMPT_NAMESPACE_END
|