From: <sag...@us...> - 2013-11-04 19:30:06
|
Revision: 3080 http://sourceforge.net/p/modplug/code/3080 Author: saga-games Date: 2013-11-04 19:29:54 +0000 (Mon, 04 Nov 2013) Log Message: ----------- [Int] Add experimental plugin bridge code. Not integrated into the main project yet, just here for version control. This code is still rather unstable. Added Paths: ----------- trunk/OpenMPT/pluginBridge/ trunk/OpenMPT/pluginBridge/AEffectWrapper.h trunk/OpenMPT/pluginBridge/Bridge.cpp trunk/OpenMPT/pluginBridge/Bridge.h trunk/OpenMPT/pluginBridge/BridgeCommon.h trunk/OpenMPT/pluginBridge/BridgeWrapper.cpp trunk/OpenMPT/pluginBridge/BridgeWrapper.h trunk/OpenMPT/pluginBridge/PluginBridge.filters trunk/OpenMPT/pluginBridge/PluginBridge.rc trunk/OpenMPT/pluginBridge/PluginBridge.vcxproj trunk/OpenMPT/pluginBridge/PluginBridge.vcxproj.filters Index: trunk/OpenMPT/pluginBridge =================================================================== --- trunk/OpenMPT/pluginBridge 2013-11-04 18:40:53 UTC (rev 3079) +++ trunk/OpenMPT/pluginBridge 2013-11-04 19:29:54 UTC (rev 3080) Property changes on: trunk/OpenMPT/pluginBridge ___________________________________________________________________ Added: svn:ignore ## -0,0 +1,4 ## +*.aps +*.user +Debug +Release Added: tsvn:logminsize ## -0,0 +1 ## +10 \ No newline at end of property Added: trunk/OpenMPT/pluginBridge/AEffectWrapper.h =================================================================== --- trunk/OpenMPT/pluginBridge/AEffectWrapper.h (rev 0) +++ trunk/OpenMPT/pluginBridge/AEffectWrapper.h 2013-11-04 19:29:54 UTC (rev 3080) @@ -0,0 +1,149 @@ +#pragma once + +#include <vector> +#define VST_FORCE_DEPRECATED 0 +#include "../include/vstsdk2.4/pluginterfaces/vst2.x/aeffectx.h" + +#pragma pack(push, 8) + +template<typename ptr_t> +struct AEffectProto +{ + int32_t magic; + ptr_t dispatcher; + ptr_t process; + ptr_t setParameter; + ptr_t getParameter; + + int32_t numPrograms; + int32_t numParams; + int32_t numInputs; + int32_t numOutputs; + + int32_t flags; + + ptr_t resvd1; + ptr_t resvd2; + + int32_t initialDelay; + + int32_t realQualities; + int32_t offQualities; + float ioRatio; + + ptr_t object; + ptr_t user; + + int32_t uniqueID; + int32_t version; + + ptr_t processReplacing; + ptr_t processDoubleReplacing; + char future[56]; + + // Convert native representation to bridge representation. + // Don't overwrite any values managed by the bridge wrapper or host in general. + void FromNative(const AEffect &in) + { + magic = in.magic; + //dispatcher = 0; + //process = in.process ? 1 : 0; + //setParameter = 0; + //getParameter = 0; + + numPrograms = in.numPrograms; + numParams = in.numParams; + numInputs = in.numInputs; + numOutputs = in.numOutputs; + + flags = in.flags; + + //resvd1 = 0; + //resvd2 = 0; + + initialDelay = in.initialDelay; + + realQualities = in.realQualities; + offQualities = in.offQualities; + ioRatio = in.ioRatio; + + //object = 0; + //user = 0; + + uniqueID = in.uniqueID; + version = in.version; + + //processReplacing = in.processReplacing ? 2 : 0; + //processDoubleReplacing = in.processDoubleReplacing ? 3 : 0; + + //memset(future, 0, sizeof(future)); + + if(in.processReplacing == nullptr) + { + flags &= ~effFlagsCanReplacing; + } + if(in.processDoubleReplacing == nullptr) + { + flags &= ~effFlagsCanDoubleReplacing; + } + } +}; + +typedef AEffectProto<int32_t> AEffect32; +typedef AEffectProto<int64_t> AEffect64; + + +#pragma pack(pop) + + +// Translate a VSTEvents struct to bridge format (placed in data vector) +static void TranslateVSTEventsToBridge(std::vector<char> &data, const VstEvents *events, int32_t targetPtrSize) +{ + data.reserve(data.size() + sizeof(int32_t) + sizeof(VstEvent) * events->numEvents); + // Write number of events + PushToVector(data, events->numEvents); + + // Write events + for(VstInt32 i = 0; i < events->numEvents; i++) + { + if(events->events[i]->type == kVstSysExType) + { + // This is going to be messy since the VstMidiSysexEvent event has a different size than other events on 64-bit platforms. + // We are going to write the event using the target process pointer size. + const VstMidiSysexEvent *event = reinterpret_cast<const VstMidiSysexEvent *>(events->events[i]); + PushToVector(data, *events->events[i], sizeof(VstEvent) + sizeof(VstInt32)); // Regular VstEvent struct + dump size + data.resize(data.size() + 3 * targetPtrSize); // Dump pointer + two reserved VstIntPtrs + // Embed SysEx dump as well... + data.insert(data.end(), event->sysexDump, event->sysexDump + event->dumpBytes); + } else + { + PushToVector(data, *events->events[i], events->events[i]->byteSize); + } + } +} + + +// Translate bridge format (void *ptr) back to VSTEvents struct (placed in data vector) +static void TranslateBridgeToVSTEvents(std::vector<char> &data, void *ptr) +{ + int32_t numEvents = *static_cast<const int32_t *>(ptr); + + data.resize(sizeof(VstInt32) + sizeof(VstIntPtr) + sizeof(VstEvent *) * numEvents, 0); + VstEvents *events = reinterpret_cast<VstEvents *>(&data[0]); + events->numEvents = numEvents; + + // Write pointers + char *offset = static_cast<char *>(ptr) + sizeof(int32_t); + for(int32_t i = 0; i < numEvents; i++) + { + events->events[i] = reinterpret_cast<VstEvent *>(offset); + offset += events->events[i]->byteSize; + if(events->events[i]->type == kVstSysExType) + { + VstMidiSysexEvent *event = reinterpret_cast<VstMidiSysexEvent *>(events->events[i]); + event->byteSize = sizeof(VstMidiSysexEvent); // Adjust to target platform + event->sysexDump = offset; + offset += event->dumpBytes; + } + } +} \ No newline at end of file Added: trunk/OpenMPT/pluginBridge/Bridge.cpp =================================================================== --- trunk/OpenMPT/pluginBridge/Bridge.cpp (rev 0) +++ trunk/OpenMPT/pluginBridge/Bridge.cpp 2013-11-04 19:29:54 UTC (rev 3080) @@ -0,0 +1,826 @@ +// TODO +// Host <-> bridge communication of memory size +// Have independent process signal +// Thread safety: Keep multiple messages, when multiple threads are waiting for a signal, we need to figure out which one got its result! +// Translate VstIntPtr size in remaining structs!!! VstFileSelect, VstVariableIo, VstOfflineTask, VstAudioFile, VstWindow, VstFileSelect +// Properly handle sharedMem.otherProcess and ask threads to die properly in every situation +// => sigThreadExit might already be an invalid handle the time it arrives in the thread +// Resizing the mix buffer transfer area +// Bridge should tell host about memory layout, host shouldn't assume things +// Freeze during load: otium, electri-q +// => If a message occours while waiting for an ACK, send a custom SetEvent() handle with the message which should be triggered + +// Low priority: +// Speed up things like consecutive calls to CVstPlugin::GetFormattedProgramName by a custom opcode +// Re-enable DEP in OpenMPT? +// Module files supposedly opened in plugin wrapper => Does DuplicateHandle word on the host side? Otherwise, named events + +#define _CRT_SECURE_NO_WARNINGS +#define NOMINMAX +#define VST_FORCE_DEPRECATED 0 +#define MODPLUG_TRACKER +#define CountOf(x) _countof(x) +#include <Windows.h> +#include <tchar.h> +#include <cstdint> +#include <algorithm> + +#include "Bridge.h" + +#include <iostream> // TODO DEBUG ONLY + + +// 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 intialized PluginBridge object. Right now there's just one, but we might +// have to initialize more than one plugin in a container at some point to get plugins like SideKickv3 to work. +PluginBridge *PluginBridge::latestInstance = nullptr; + + +int _tmain(int argc, TCHAR *argv[]) +{ + if(argc < 8) + { + MessageBox(NULL, _T("This executable is part of OpenMPT. You do not need to run it by yourself."), _T("Plugin Bridge"), 0); + return -1; + } + + PluginBridge pb(argv); + return 0; +} + + +PluginBridge::PluginBridge(TCHAR *argv[]) : window(NULL) +{ + PluginBridge::latestInstance = this; + + if(!CreateMapping(argv[0])) + { + exit(-1); + } + + // Store parent process handle so that we can terminate the bridge process when OpenMPT closes. + sharedMem.otherProcess = OpenProcess(SYNCHRONIZE, FALSE, _ttoi(argv[1])); + + sharedMem.sigToHost.Create(argv[2], argv[3]); + sharedMem.sigToBridge.Create(argv[4], argv[5]); + sharedMem.sigProcess.Create(argv[6], argv[7]); + + sharedMem.sigThreadExit = CreateEvent(NULL, FALSE, FALSE, NULL); + DWORD dummy; // For Win9x + CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&RenderThread, (LPVOID)this, 0, &dummy); + + // Tell the parent process that we've initialized the shared memory and are ready to go. + sharedMem.sigToHost.Confirm(); + + const HANDLE objects[] = { sharedMem.sigToBridge.send, sharedMem.otherProcess }; + DWORD result; + do + { + result = WaitForMultipleObjects(CountOf(objects), objects, FALSE, /*100*/INFINITE); + if(result == WAIT_OBJECT_0) + { + ParseNextMessage(); + } + if(window) + { + //PeekMessage? + MSG msg; + GetMessage(&msg, window, 0, 0); + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } while(result != WAIT_OBJECT_0 + 1); + CloseHandle(sharedMem.otherProcess); +} + + +// Create our shared memory object. Returns true on success. +bool PluginBridge::CreateMapping(const TCHAR *memName) +{ + SECURITY_ATTRIBUTES secAttr; + secAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttr.lpSecurityDescriptor = nullptr; + secAttr.bInheritHandle = TRUE; + + // Create the file mapping object. + CloseMapping(); + sharedMem.mapFile = CreateFileMapping(INVALID_HANDLE_VALUE, &secAttr, PAGE_READWRITE, + 0, sharedMem.queueSize + sharedMem.sampleBufSize, memName); + if(sharedMem.mapFile == NULL) + { + return false; + } + + sharedMem.sharedPtr = static_cast<char *>(MapViewOfFile(sharedMem.mapFile, FILE_MAP_ALL_ACCESS, + 0, 0, sharedMem.queueSize + sharedMem.sampleBufSize)); + if(sharedMem.sharedPtr == nullptr) + { + CloseMapping(); + return false; + } + + // Map a view of the file mapping into the address space of the current process. + sharedMem.effectPtr = reinterpret_cast<AEffect *>(sharedMem.sharedPtr); + sharedMem.sampleBufPtr = sharedMem.sharedPtr + sizeof(AEffect64); + sharedMem.queuePtr = sharedMem.sharedPtr + sharedMem.sampleBufSize; + sharedMem.sampleBufSize -= sizeof(AEffect64); + + return true; +} + + +// Destroy our shared memory object. +void PluginBridge::CloseMapping() +{ + if(sharedMem.mapFile) + { + if(sharedMem.queuePtr) + { + UnmapViewOfFile(sharedMem.queuePtr); + sharedMem.queuePtr = nullptr; + } + CloseHandle(sharedMem.mapFile); + sharedMem.mapFile = NULL; + } +} + + +// Send an arbitrary message to the host. +// Returns a pointer to the message, as processed by the host. +const MsgHeader *PluginBridge::SendToHost(const MsgHeader &msg, MsgHeader *sourceHeader) +{ + if(msg.size > sharedMem.queueSize) + { + // Need to increase shared memory size + static uint32_t pluginCounter = 0; + /*wchar_t name[64]; + wsprintf(name, L"Local\\openmpt-%d-%d", GetCurrentProcessId(), pluginCounter++);*/ + int32_t newSize = msg.size + 1024; + + // TODO + SendToHost(ReallocMsg(0, newSize)); + sharedMem.queueSize = newSize; + CreateMapping(nullptr /*name*/); + } + + sharedMem.writeMutex.lock(); + + if(sharedMem.writeOffset + msg.size > sizeof(MsgQueue) + 512 * 1024 /*sharedMem.queueSize*/) + { + std::cout << "message wraparound" << std::endl; + sharedMem.writeOffset = sizeof(MsgQueue); + } + MsgHeader *addr = reinterpret_cast<MsgHeader *>(static_cast<char *>(sharedMem.queuePtr) + sharedMem.writeOffset); + memcpy(addr, &msg, msg.size); + + if(sourceHeader != nullptr) + { + // Interrupting a command.. + sourceHeader->interruptOffset = sharedMem.writeOffset; + SetEvent(reinterpret_cast<HANDLE>(sourceHeader->interruptSignal)); + } else + { + // Simply sending a new command + MsgQueue *queue = static_cast<MsgQueue *>(sharedMem.queuePtr); + queue->toHost.offset[queue->toHost.writeIndex++] = sharedMem.writeOffset; + if(queue->toHost.writeIndex >= CountOf(queue->toHost.offset)) + { + queue->toHost.writeIndex = 0; + } + sharedMem.sigToHost.Send(); + } + + //sharedMem.writeMutex.unlock(); + + // Wait until we get the result from the host. + const HANDLE objects[] = { sharedMem.sigToHost.ack, sharedMem.otherProcess, sharedMem.sigToBridge.send }; + DWORD result; + do + { + result = WaitForMultipleObjects(CountOf(objects), objects, FALSE, INFINITE); + if(result == WAIT_OBJECT_0 + 2) + { + ParseNextMessage(); + } + } while(result != WAIT_OBJECT_0 && result != WAIT_OBJECT_0 + 1 && result != WAIT_FAILED); + + sharedMem.writeMutex.unlock(); + return (result == WAIT_OBJECT_0) ? addr : nullptr; +} + + +// Copy AEffect to shared memory. +void PluginBridge::UpdateEffectStruct() +{ + if(sharedMem.otherPtrSize == 4) + { + reinterpret_cast<AEffect32 *>(sharedMem.effectPtr)->FromNative(*nativeEffect); + } else if(sharedMem.otherPtrSize == 8) + { + reinterpret_cast<AEffect64 *>(sharedMem.effectPtr)->FromNative(*nativeEffect); + } +} + + +// Receive a message from the host and translate it. +void PluginBridge::ParseNextMessage(MsgHeader *parentMessage) +{ + MsgHeader *msg; + if(parentMessage == nullptr) + { + // Default: Grab next message + MsgQueue *queue = static_cast<MsgQueue *>(sharedMem.queuePtr); + uint32_t index = InterlockedExchangeAdd(&(queue->toBridge.readIndex), 1) % CountOf(queue->toBridge.offset); + msg = reinterpret_cast<MsgHeader *>(static_cast<char *>(sharedMem.queuePtr) + queue->toBridge.offset[index]); + } else + { + // Read interrupt message + msg = reinterpret_cast<MsgHeader *>(static_cast<char *>(sharedMem.queuePtr) + parentMessage->interruptOffset); + } + + switch(msg->type) + { + case MsgHeader::init: + InitBridge(static_cast<InitMsg *>(msg)); + break; + case MsgHeader::close: + CloseBridge(); + break; + case MsgHeader::dispatch: + DispatchToPlugin(static_cast<DispatchMsg *>(msg)); + break; + case MsgHeader::setParameter: + SetParameter(static_cast<ParameterMsg *>(msg)); + break; + case MsgHeader::getParameter: + GetParameter(static_cast<ParameterMsg *>(msg)); + break; + } + sharedMem.sigToBridge.Confirm(); +} + + +// Load the plugin. +void PluginBridge::InitBridge(InitMsg *msg) +{ + msg->result = 0; + msg->mapSize = sharedMem.queueSize; + //msg->hostPtrSize = sizeof(VstIntPtr); + // TODO + msg->samplesOffset = sharedMem.queueSize - sharedMem.sampleBufSize; + + if(msg->headerSize != sizeof(MsgHeader)) + { + wcscpy(msg->str, L"Host message format is not compatible with bridge"); + return; + } + if(msg->version != InitMsg::protocolVersion) + { + swprintf(msg->str, CountOf(msg->str), L"Host protocol version (%u) differs from expected version (%u)", msg->version, InitMsg::protocolVersion); + return; + } + + std::wcout << msg->str << std::endl; + nativeEffect = nullptr; + library = LoadLibraryW(msg->str); + + if(library == nullptr) + { + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msg->str, + CountOf(msg->str), + NULL); + return; + } + + typedef AEffect * (VSTCALLBACK * PVSTPLUGENTRY)(audioMasterCallback); + PVSTPLUGENTRY pMainProc = (PVSTPLUGENTRY)GetProcAddress(library, "VSTPluginMain"); + if(pMainProc == nullptr) + { + pMainProc = (PVSTPLUGENTRY)GetProcAddress(library, "main"); + } + + if(pMainProc != nullptr) + { + try + { + nativeEffect = pMainProc(MasterCallback); + } catch(...) + { + nativeEffect = nullptr; + } + } + + if(nativeEffect == nullptr || nativeEffect->dispatcher == nullptr || nativeEffect->magic != kEffectMagic) + { + FreeLibrary(library); + library = nullptr; + + wcscpy(msg->str, L"File is not a valid plugin"); + return; + } + + nativeEffect->resvd1 = ToVstPtr(this); + + sharedMem.otherPtrSize = msg->hostPtrSize; + msg->result = 1; + + UpdateEffectStruct(); + // Let's identify ourselves... + memcpy(&(sharedMem.effectPtr->resvd1), "OMPT", 4); +} + + +// Unload the plugin. +void PluginBridge::CloseBridge() +{ + CloseMapping(); + SetEvent(sharedMem.sigThreadExit); + exit(0); +} + + +// Re-initialize the shared memory object with a different amount of memory. +void PluginBridge::ReallocateBridge(ReallocMsg *msg) +{ + // TODO + //sharedMem.size = msg->mapSize; + //CreateMapping(msg->str); +} + + +// Host-to-plugin opcode dispatcher +void PluginBridge::DispatchToPlugin(DispatchMsg *msg) +{ + // Various dispatch data - depending on the opcode, one of those might be used. + std::vector<char> extraData; + size_t extraDataSize = 0; + + // Content of ptr is usually stored right after the message header, ptr field indicates size. + void *ptr = (msg->ptr != 0) ? (msg + 1) : nullptr; + + 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 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) + std::cout << "open!" << std::endl; + Sleep(1000); + ptr = reinterpret_cast<void *>(msg->ptr); + if(0) + { + WNDCLASSEX wcex; + + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = DefWindowProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hIcon = NULL; + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wcex.lpszMenuName = NULL; + wcex.lpszClassName = _T("OpenMPTPluginBridge"); + wcex.hIconSm = NULL; + + if(!RegisterClassEx(&wcex)) + { + msg->result = 0; + return; + } + + ptr = window = CreateWindow( + _T("OpenMPTPluginBridge"), + _T("OpenMPT Plugin Bridge"), + WS_VISIBLE | WS_POPUP, + CW_USEDEFAULT, CW_USEDEFAULT, + windowSize.right - windowSize.left, windowSize.bottom- windowSize.top, + /*reinterpret_cast<HWND>(msg->ptr)*/ NULL, + NULL, + wcex.hInstance, + NULL); + //SetParent(window, reinterpret_cast<HWND>(msg->ptr)); + } + break; + + case effEditClose: + DestroyWindow(window); + window = NULL; + break; + + case effGetChunk: + // void** in [ptr] for chunk data address + extraDataSize = sizeof(void *); + break; + + case effProcessEvents: + // VstEvents* in [ptr] + TranslateBridgeToVSTEvents(extraData, ptr); + ptr = &extraData[0]; + break; + + case effOfflineNotify: + // VstAudioFile* in [ptr] + extraData.resize(sizeof(VstAudioFile *) * static_cast<size_t>(msg->value)); + ptr = &extraData[0]; + for(int64_t 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[0]; + for(int64_t i = 0; i < msg->value; i++) + { + // TODO create pointers + } + break; + + case effSetSpeakerArrangement: + case effGetSpeakerArrangement: + // VstSpeakerArrangement* in [value] and [ptr] + msg->value = reinterpret_cast<int64_t>(ptr) + sizeof(VstSpeakerArrangement); + break; + + case effVendorSpecific: + // Let's implement some custom opcodes! + if(msg->index == CCONST('O', 'M', 'P', 'T')) + { + msg->result = 1; + switch(msg->value) + { + case 0: + UpdateEffectStruct(); + break; + default: + msg->result = 0; + } + return; + } + break; + } + + if(extraDataSize != 0) + { + extraData.resize(extraDataSize, 0); + ptr = &extraData[0]; + } + + //std::cout << "about to dispatch " << msg->opcode << " to effect..."; + //std::flush(std::cout); + try + { + msg->result = nativeEffect->dispatcher(nativeEffect, msg->opcode, msg->index, static_cast<VstIntPtr>(msg->value), ptr, msg->opt); + } catch(...) + { + SendToHost(ErrorMsg(L"Exception in dispatch()!")); + } + //std::cout << "done" << std::endl; + + // Post-fix some opcodes + switch(msg->opcode) + { + case effClose: + nativeEffect = nullptr; + FreeLibrary(library); + library = nullptr; + 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; + vst_strncpy(reinterpret_cast<char *>(msg + 1), &extraData[0], static_cast<size_t>(msg->ptr - 1)); + break; + + case effEditGetRect: + // ERect** in [ptr] + { + ERect *rectPtr = *reinterpret_cast<ERect **>(&extraData[0]); + if(rectPtr != nullptr) + { + memcpy(msg + 1, rectPtr, std::min<size_t>(sizeof(ERect), static_cast<size_t>(msg->ptr))); + windowSize = *rectPtr; + } + } + break; + + case effGetChunk: + // void** in [ptr] for chunk data address + { + void *chunkPtr = *reinterpret_cast<void **>(&extraData[0]); + if(chunkPtr != nullptr) + { + memcpy(msg + 1, *reinterpret_cast<void **>(&extraData[0]), std::min(static_cast<size_t>(msg->result), static_cast<size_t>(msg->ptr))); + } + } + break; + } + + UpdateEffectStruct(); // Regularly update the struct +} + + +// Set a plugin parameter. +void PluginBridge::SetParameter(ParameterMsg *msg) +{ + try + { + nativeEffect->setParameter(nativeEffect, msg->index, msg->value); + } catch(...) + { + SendToHost(ErrorMsg(L"Exception in setParameter()!")); + } +} + + +// Get a plugin parameter. +void PluginBridge::GetParameter(ParameterMsg *msg) +{ + try + { + msg->value = nativeEffect->getParameter(nativeEffect, msg->index); + } catch(...) + { + SendToHost(ErrorMsg(L"Exception in getParameter()!")); + } +} + + +// Audio rendering thread +DWORD WINAPI PluginBridge::RenderThread(LPVOID param) +{ + PluginBridge *that = static_cast<PluginBridge *>(param); + + const HANDLE objects[] = { that->sharedMem.sigProcess.send, that->sharedMem.sigThreadExit }; + DWORD result = 0; + do + { + result = WaitForMultipleObjects(CountOf(objects), objects, FALSE, INFINITE); + if(result == WAIT_OBJECT_0) + { + ProcessMsg *msg = static_cast<ProcessMsg *>(that->sharedMem.sampleBufPtr); + switch(msg->processType) + { + case ProcessMsg::process: + that->Process(); + break; + case ProcessMsg::processReplacing: + that->ProcessReplacing(); + break; + case ProcessMsg::processDoubleReplacing: + that->ProcessDoubleReplacing(); + break; + } + that->sharedMem.sigProcess.Confirm(); + } + } while(result != WAIT_OBJECT_0 + 1 && result != WAIT_OBJECT_0 + 2 && result != WAIT_FAILED); + CloseHandle(that->sharedMem.sigThreadExit); + return 0; +} + + +// Process audio. +void PluginBridge::Process() +{ + if(nativeEffect->process) + { + float **inPointers, **outPointers; + int32_t sampleFrames = BuildProcessPointers(inPointers, outPointers); + try + { + nativeEffect->process(nativeEffect, inPointers, outPointers, sampleFrames); + } catch(...) + { + SendToHost(ErrorMsg(L"Exception in process()!")); + } + } +} + + +// Process audio. +void PluginBridge::ProcessReplacing() +{ + if(nativeEffect->processReplacing) + { + float **inPointers, **outPointers; + int32_t sampleFrames = BuildProcessPointers(inPointers, outPointers); + try + { + nativeEffect->processReplacing(nativeEffect, inPointers, outPointers, sampleFrames); + } catch(...) + { + SendToHost(ErrorMsg(L"Exception in processReplacing()!")); + } + } +} + + +// Process audio. +void PluginBridge::ProcessDoubleReplacing() +{ + if(nativeEffect->processDoubleReplacing) + { + double **inPointers, **outPointers; + int32_t sampleFrames = BuildProcessPointers(inPointers, outPointers); + try + { + nativeEffect->processDoubleReplacing(nativeEffect, inPointers, outPointers, sampleFrames); + } catch(...) + { + SendToHost(ErrorMsg(L"Exception in processDoubleReplacing()!")); + } + } +} + + +// Helper function to build the pointer arrays required by the VST process functions. +template<typename buf_t> +int32_t PluginBridge::BuildProcessPointers(buf_t **(&inPointers), buf_t **(&outPointers)) +{ + ProcessMsg *msg = static_cast<ProcessMsg *>(sharedMem.sampleBufPtr); + size_t numPtrs = msg->numInputs + msg->numOutputs; + samplePointers.resize(numPtrs, 0); + buf_t *offset = reinterpret_cast<buf_t *>(msg + 1); + for(size_t i = 0; i < numPtrs; i++) + { + samplePointers[i] = offset; + offset += msg->sampleFrames; + } + inPointers = reinterpret_cast<buf_t **>(&samplePointers[0]); + outPointers = reinterpret_cast<buf_t **>(&samplePointers[msg->numInputs]); + return msg->sampleFrames; +} + + +// Send a message to the host. +VstIntPtr PluginBridge::DispatchToHost(VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt) +{ + std::vector<char> dispatchData(sizeof(DispatchMsg), 0); + int64_t ptrOut = 0; + char *ptrC = static_cast<char *>(ptr); + + switch(opcode) + { + case audioMasterGetTime: + // VstTimeInfo* in [return value] + ptrOut = sizeof(VstTimeInfo); + break; + + case audioMasterProcessEvents: + // VstEvents* in [ptr] + TranslateVSTEventsToBridge(dispatchData, static_cast<VstEvents *>(ptr), sharedMem.otherPtrSize); + ptrOut = dispatchData.size() - sizeof(DispatchMsg); + break; + + case audioMasterIOChanged: + // We need to be sure that the new values are known to the master. + UpdateEffectStruct(); + break; + + case audioMasterSizeWindow: + if(window) + { + SetWindowPos(window, NULL, 0, 0, index, value, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); + } + break; + + case audioMasterGetPreviousPlug: + case audioMasterGetNextPlug: + // Don't even bother, this would explode :) + return 0; + + case audioMasterGetOutputSpeakerArrangement: + case audioMasterGetInputSpeakerArrangement: + // VstSpeakerArrangement* in [return value] + ptrOut = sizeof(VstSpeakerArrangement); + break; + + case audioMasterGetVendorString: + case audioMasterGetProductString: + // Name in [ptr] + ptrOut = 256; + break; + + case audioMasterCanDo: + // Name in [ptr] + { + ptrOut = strlen(ptrC) + 1; + dispatchData.insert(dispatchData.end(), ptrC, ptrC + ptrOut); + } + + case audioMasterGetDirectory: + // Name in [return value] + ptrOut = 256; + break; + + case audioMasterOpenFileSelector: + case audioMasterCloseFileSelector: + // VstFileSelect* in [ptr] + // TODO + ptrOut = sizeof(VstFileSelect); + break; + + default: + if(ptr != nullptr) DebugBreak(); + } + + 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); + } + + //std::cout << "about to dispatch " << opcode << " to host..."; + //std::flush(std::cout); + const DispatchMsg *resultMsg; + { + DispatchMsg *msg = reinterpret_cast<DispatchMsg *>(&dispatchData[0]); + new (msg) DispatchMsg(opcode, index, value, ptrOut, opt, static_cast<int32_t>(dispatchData.size() - sizeof(DispatchMsg))); + resultMsg = static_cast<const DispatchMsg *>(SendToHost(*msg)); + } + //std::cout << "done." << std::endl; + + if(resultMsg == nullptr) + { + return 0; + } + + const char *extraData = reinterpret_cast<const char*>(resultMsg + 1); + // Post-fix some opcodes + switch(opcode) + { + case audioMasterGetTime: + // VstTimeInfo* in [return value] + memcpy(&host2PlugMem.timeInfo, extraData, sizeof(VstTimeInfo)); + return ToVstPtr<VstTimeInfo>(&host2PlugMem.timeInfo); + + case audioMasterGetOutputSpeakerArrangement: + case audioMasterGetInputSpeakerArrangement: + // VstSpeakerArrangement* in [return value] + memcpy(&host2PlugMem.speakerArrangement, extraData, sizeof(VstSpeakerArrangement)); + return ToVstPtr<VstSpeakerArrangement>(&host2PlugMem.speakerArrangement); + + case audioMasterGetVendorString: + case audioMasterGetProductString: + // Name in [ptr] + strcpy(ptrC, extraData); + break; + + case audioMasterGetDirectory: + // Name in [return value] + vst_strncpy(host2PlugMem.name, extraData, CountOf(host2PlugMem.name) - 1); + return ToVstPtr<char>(host2PlugMem.name); + + case audioMasterOpenFileSelector: + case audioMasterCloseFileSelector: + // VstFileSelect* in [ptr] + // TODO + break; + } + + return static_cast<VstIntPtr>(resultMsg->result); +} + + +// Helper function for sending messages to the host. +VstIntPtr VSTCALLBACK PluginBridge::MasterCallback(AEffect *effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt) +{ + PluginBridge *instance = (effect != nullptr && effect->resvd1 != 0) ? FromVstPtr<PluginBridge>(effect->resvd1) : PluginBridge::latestInstance; + return instance->DispatchToHost(opcode, index, value, ptr, opt); +} Added: trunk/OpenMPT/pluginBridge/Bridge.h =================================================================== --- trunk/OpenMPT/pluginBridge/Bridge.h (rev 0) +++ trunk/OpenMPT/pluginBridge/Bridge.h 2013-11-04 19:29:54 UTC (rev 3080) @@ -0,0 +1,59 @@ +#pragma once + +#include <vector> +#include "BridgeCommon.h" + +class PluginBridge +{ +protected: + static PluginBridge *latestInstance; + + // Plugin + AEffect *nativeEffect; + HINSTANCE library; + ERect windowSize; + HWND window; + + // Static memory for host-to-plugin pointers + union + { + VstSpeakerArrangement speakerArrangement; + VstTimeInfo timeInfo; + char name[256]; + } host2PlugMem; + + // Shared memory + SharedMem sharedMem; + + // Pointers to sample data + std::vector<void *> samplePointers; + +public: + PluginBridge(TCHAR *argv[]); + +protected: + bool CreateMapping(const TCHAR *memName); + void CloseMapping(); + const MsgHeader *SendToHost(const MsgHeader &msg, MsgHeader *sourceHeader = nullptr); + + void UpdateEffectStruct(); + + void ParseNextMessage(MsgHeader *parentMessage = nullptr); + void InitBridge(InitMsg *msg); + void CloseBridge(); + void ReallocateBridge(ReallocMsg *msg); + void DispatchToPlugin(DispatchMsg *msg); + void SetParameter(ParameterMsg *msg); + void GetParameter(ParameterMsg *msg); + void Process(); + void ProcessReplacing(); + void ProcessDoubleReplacing(); + VstIntPtr DispatchToHost(VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt); + + template<typename buf_t> + int32_t BuildProcessPointers(buf_t **(&inPointers), buf_t **(&outPointers)); + + static DWORD WINAPI RenderThread(LPVOID param); + + static VstIntPtr VSTCALLBACK MasterCallback(AEffect *effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt); +}; \ No newline at end of file Added: trunk/OpenMPT/pluginBridge/BridgeCommon.h =================================================================== --- trunk/OpenMPT/pluginBridge/BridgeCommon.h (rev 0) +++ trunk/OpenMPT/pluginBridge/BridgeCommon.h 2013-11-04 19:29:54 UTC (rev 3080) @@ -0,0 +1,258 @@ +#pragma once + +// 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)) +{ +#ifdef HAS_TYPE_TRAITS + static_assert(std::is_pointer<T>::value == false, "Won't push pointers to data vectors."); +#endif // HAS_TYPE_TRAITS + const char *objC = reinterpret_cast<const char *>(&obj); + data.insert(data.end(), objC, objC + writeSize); +} + +#include "AEffectWrapper.h" +#include "../common/mutex.h" + +// Bridge communication data + +#pragma pack(push, 8) + +// Host-to-bridge message to initiate a process call. +struct ProcessMsg +{ + enum ProcessType + { + process = 0, + processReplacing, + processDoubleReplacing, + }; + + int32_t processType; + int32_t numInputs; + int32_t numOutputs; + int32_t sampleFrames; + // Input and output buffers follow + + ProcessMsg(ProcessMsg::ProcessType processType, int32_t numInputs, int32_t numOutputs, int32_t sampleFrames) : + processType(processType), numInputs(numInputs), numOutputs(numOutputs), sampleFrames(sampleFrames) { } +}; + + +template<size_t queueSize> +struct MsgQueueProto +{ + uint32_t readIndex; + uint32_t writeIndex; + uint32_t offset[queueSize]; + + MsgQueueProto() : readIndex(0), writeIndex(0) { } +}; + + +struct MsgQueue +{ + MsgQueueProto<1024> toHost; + MsgQueueProto<1024> toBridge; +}; + + +// General message header +struct MsgHeader +{ + enum BridgeMessageType + { + // Management messages, host to bridge + init = 0, + close, + // Management messages, bridge to host + reallocate, + errorMsg, + + // VST messages, common + dispatch, + // VST messages, host to bridge + setParameter, + getParameter, + }; + + uint32_t headerSize; // sizeof(MsgHeader), only here because the struct would be padded anyway, and as extra verification during the intialization step. + uint32_t size; // Size of complete message, including this header + uint32_t type; // See BridgeMessageType + uint32_t ackSignal; // Signal to send when processing of this message is done + uint32_t interruptSignal; // Signal to send when this thread needs to send back a new message while handling this message + uint32_t interruptOffset; // Offset of message in buffer if interruptSignal has been fired (set by interrupting thread). + int64_t result; // Result of this message (exact meaning depends on message type, not every message has a result) + + MsgHeader(uint32_t type, uint32_t size) : headerSize(sizeof(MsgHeader)), size(size), type(type), ackSignal(0), interruptSignal(0), interruptOffset(0), result(0) { } +}; + + +// Host-to-bridge initialization message +struct InitMsg : public MsgHeader +{ + enum ProtocolVersion + { + protocolVersion = 1, + }; + + int32_t version; // In: Protocol version used by host + int32_t mapSize; // Out: Current map size + int32_t samplesOffset; // Out: Offset into shared memory of sample buffers + int32_t hostPtrSize; // In: Size of VstIntPtr in host + wchar_t str[_MAX_PATH]; // In: Plugin file to load. Out: Error message if result != 0. + + InitMsg(const wchar_t *pluginPath) : MsgHeader(MsgHeader::init, sizeof(InitMsg)), + version(protocolVersion), hostPtrSize(sizeof(VstIntPtr)) + { + wcsncpy(str, pluginPath, CountOf(str) - 1); + str[CountOf(str) - 1] = 0; + } +}; + + +// Bridge-to-host memory reallocation message +struct ReallocMsg : public MsgHeader +{ + enum DefaultSizes + { + defaultMapSize = 1024 * 1024, + defaultGlobalsSize = sizeof(AEffect64) + sizeof(ProcessMsg) + 512 * 16 * 4, // 16 channels of floats + }; + + //wchar_t str[64]; // New map name + int32_t handle; // New map handle + uint32_t mapSize; // New map size + + ReallocMsg(int32_t newHandle, uint32_t size) : MsgHeader(MsgHeader::reallocate, sizeof(ReallocMsg)), handle(newHandle), mapSize(size) { } + /*ReallocMsg(wchar_t *newName, uint32_t size) : MsgHeader(MsgHeader::reallocate, sizeof(ReallocMsg)), mapSize(size) + { + wcsncpy(str, newName, CountOf(str) - 1); + str[CountOf(str) - 1] = 0; + }*/ +}; + + +// Host-to-bridge VST dispatch message +struct DispatchMsg : public MsgHeader +{ + int32_t opcode; + int32_t index; + int64_t value; + int64_t ptr; // Usually, this will be the size of ptr. In that case, the data itself is stored after this struct. + float opt; + + DispatchMsg(int32_t opcode, int32_t index, int64_t value, int64_t ptr, float opt, int32_t extraSize) : MsgHeader(MsgHeader::dispatch, sizeof(DispatchMsg) + extraSize), + opcode(opcode), index(index), value(value), ptr(ptr), opt(opt) { } +}; + + +// Host-to-bridge VST setParameter / getParameter message +struct ParameterMsg : public MsgHeader +{ + int32_t index; + float value; + + ParameterMsg(int32_t index, float value) : MsgHeader(MsgHeader::setParameter, sizeof(ParameterMsg)), index(index), value(value) { } + ParameterMsg(int32_t index) : MsgHeader(MsgHeader::getParameter, sizeof(ParameterMsg)), index(index), value(0) { } +}; + + +// Bridge-to-host error message +struct ErrorMsg : public MsgHeader +{ + wchar_t str[64]; + + ErrorMsg(const wchar_t *text) : MsgHeader(MsgHeader::errorMsg, sizeof(ErrorMsg)) + { + wcsncpy(str, text, CountOf(str) - 1); + str[CountOf(str) - 1] = 0; + } +}; + +#pragma pack(pop) + +// Internal data + +struct Signal +{ + HANDLE send, ack; + + Signal() : send(nullptr), ack(nullptr) { } + ~Signal() + { + CloseHandle(send); + CloseHandle(ack); + } + + // Create new signal + bool Create() + { + // We want to share our handles. + SECURITY_ATTRIBUTES secAttr; + secAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttr.lpSecurityDescriptor = nullptr; + secAttr.bInheritHandle = TRUE; + + send = CreateEvent(&secAttr, FALSE, FALSE, NULL); + ack = CreateEvent(&secAttr, FALSE, FALSE, NULL); + return send != nullptr && ack != nullptr; + } + + // Create signal from existing handles + void Create(HANDLE s, HANDLE a) + { + send = s; + ack = a; + } + + // Create signal from existing handles + void Create(const TCHAR *s, const TCHAR *a) + { + send = reinterpret_cast<HANDLE>(_ttoi(s)); + ack = reinterpret_cast<HANDLE>(_ttoi(a)); + } + + void Send() + { + SetEvent(send); + } + + void Confirm() + { + SetEvent(ack); + } +}; + + +#include <map> + +struct SharedMem +{ + Signal defaultSignal; + Signal interruptSignal[8]; + std::map<uint32_t, Signal *> threadMap; // Map thread IDs to signal pairs + + // Signals for host <-> bridge communication + Signal sigToHost, sigToBridge, sigProcess; + + HANDLE otherProcess; // Handle of "other" process (host handle in the bridge and vice versa) + + HANDLE sigThreadExit; // Signal to kill helper thread + + HANDLE mapFile; // Internal handle of memory-mapped file + Util::mutex writeMutex; + char *sharedPtr; // Pointer to beginning of shared memory + void *queuePtr; // Pointer to shared memory queue + AEffect *effectPtr; // Pointer to shared AEffect struct (with host pointer format) + void *sampleBufPtr; // Pointer to shared memory area which contains sample buffers + + uint32_t queueSize; // Size of shared memory queue + uint32_t sampleBufSize; // Size of shared sample memory + uint32_t writeOffset; // Message offset in shared memory + + int32_t otherPtrSize; + + SharedMem() : mapFile(nullptr), queuePtr(nullptr), effectPtr(nullptr), sampleBufPtr(nullptr), queueSize(ReallocMsg::defaultMapSize), + sampleBufSize(ReallocMsg::defaultGlobalsSize), writeOffset(uint32_t(sizeof(MsgQueue))), otherPtrSize(0) { } +}; Added: trunk/OpenMPT/pluginBridge/BridgeWrapper.cpp =================================================================== --- trunk/OpenMPT/pluginBridge/BridgeWrapper.cpp (rev 0) +++ trunk/OpenMPT/pluginBridge/BridgeWrapper.cpp 2013-11-04 19:29:54 UTC (rev 3080) @@ -0,0 +1,597 @@ +#include "stdafx.h" +#include "BridgeWrapper.h" +#include "misc_util.h" +#include "../mptrack/Mptrack.h" +#include "../mptrack/Vstplug.h" +#include <fstream> + + +// Check whether we need to load a 32-bit or 64-bit wrapper. +BridgeWrapper::BinaryType BridgeWrapper::GetPluginBinaryType(const char *pluginPath) +{ + BinaryType type = binUnknown; + std::ifstream file(pluginPath, std::ios::in | std::ios::binary); + if(file.is_open()) + { + IMAGE_DOS_HEADER dosHeader; + IMAGE_NT_HEADERS ntHeader; + file.read(reinterpret_cast<char *>(&dosHeader), sizeof(dosHeader)); + if(dosHeader.e_magic == IMAGE_DOS_SIGNATURE) + { + file.seekg(dosHeader.e_lfanew); + file.read(reinterpret_cast<char *>(&ntHeader), sizeof(ntHeader)); + + ASSERT((ntHeader.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0); + if(ntHeader.FileHeader.Machine == IMAGE_FILE_MACHINE_I386) + type = bin32Bit; + else if(ntHeader.FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) + type = bin64Bit; + } + } + return type; +} + + +BridgeWrapper::BridgeWrapper(const char *pluginPath) +{ + BinaryType binType; + if((binType = GetPluginBinaryType(pluginPath)) == binUnknown) + { + return; + } + + static int32 plugId = 0; + const DWORD procId = GetCurrentProcessId(); + std::string exeName = std::string(theApp.GetAppDirPath()) + (binType == bin64Bit ? "PluginBridge64.exe" : "PluginBridge32.exe"); + std::string mapName = "Local\\openmpt-" + Stringify(procId) + "-" + Stringify(plugId++); + + sharedMem.writeOffset = sizeof(MsgQueue) + 512 * 1024; + sharedMem.otherPtrSize = binType / 8; + + sharedMem.sigToHost.Create(); + sharedMem.sigToBridge.Create(); + sharedMem.sigProcess.Create(); + sharedMem.sigThreadExit = CreateEvent(NULL, FALSE, FALSE, NULL); + + // Parameters: Name for shared memory object, parent process ID, signals + char cmdLine[64]; + sprintf(cmdLine, "%s %d %d %d %d %d %d %d", mapName.c_str(), procId, sharedMem.sigToHost.send, sharedMem.sigToHost.ack, sharedMem.sigToBridge.send, sharedMem.sigToBridge.ack, sharedMem.sigProcess.send, sharedMem.sigProcess.ack); + + STARTUPINFO info; + MemsetZero(info); + info.cb = sizeof(info); + PROCESS_INFORMATION processInfo; + MemsetZero(processInfo); + + bool success = false; + if(CreateProcess(exeName.c_str(), cmdLine, NULL, NULL, TRUE, /*CREATE_NO_WINDOW */0, NULL, NULL, &info, &processInfo)) + { + sharedMem.otherProcess = processInfo.hProcess; + const HANDLE objects[] = { sharedMem.sigToHost.ack, sharedMem.otherProcess }; + DWORD result = WaitForMultipleObjects(CountOf(objects), objects, FALSE, 10000); + if(result == WAIT_OBJECT_0) + { + success = true; + } else if(result == WAIT_OBJECT_0 + 1) + { + // Process died + } + CloseHandle(processInfo.hThread); + } + if(success) + { + sharedMem.mapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, TRUE, mapName.c_str()); + if(sharedMem.mapFile) + { + // TODO De-Dupe this code + sharedMem.sharedPtr = static_cast<char *>(MapViewOfFile(sharedMem.mapFile, FILE_MAP_ALL_ACCESS, 0, 0, sharedMem.queueSize + sharedMem.sampleBufSize)); + sharedMem.effectPtr = reinterpret_cast<AEffect *>(sharedMem.sharedPtr); + sharedMem.sampleBufPtr = sharedMem.sharedPtr + sizeof(AEffect64); + sharedMem.queuePtr = sharedMem.sharedPtr + sharedMem.sampleBufSize; + sharedMem.sampleBufSize -= sizeof(AEffect64); + + // Some opcodes might be dispatched before we get the chance to fill the struct out. + sharedMem.effectPtr->object = this; + + // Initialize bridge + struct InitEffect : public InitMsg + { + AEffect effectCopy; + + InitEffect(const wchar_t *pluginPath) : InitMsg(pluginPath) { } + }; + + const InitEffect *initEffect = static_cast<const InitEffect *>(SendToBridge(InitEffect(mpt::String::Decode(pluginPath, mpt::CharsetLocale).c_str()))); + + if(initEffect == nullptr) + { + Reporting::Error("Could not initialize plugin bridge, it probably crashed."); + } else if(initEffect->result != 1) + { + Reporting::Error(mpt::String::Encode(initEffect->str, mpt::CharsetLocale).c_str()); + } else + { + ASSERT(initEffect->type == MsgHeader::init); + sharedMem.queueSize = initEffect->mapSize; + + sharedMem.effectPtr->object = this; + sharedMem.effectPtr->dispatcher = DispatchToPlugin; + sharedMem.effectPtr->setParameter = SetParameter; + sharedMem.effectPtr->getParameter = GetParameter; + sharedMem.effectPtr->process = Process; + if(sharedMem.effectPtr->flags & effFlagsCanReplacing) sharedMem.effectPtr->processReplacing = ProcessReplacing; + if(sharedMem.effectPtr->flags & effFlagsCanDoubleReplacing) sharedMem.effectPtr->processDoubleReplacing = ProcessDoubleReplacing; + + DWORD dummy; // For Win9x + CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&MessageThread, (LPVOID)this, 0, &dummy); + } + + } + } +} + +BridgeWrapper::~BridgeWrapper() +{ + if(sharedMem.queuePtr != nullptr) + { + SendToBridge(MsgHeader(MsgHeader::close, sizeof(MsgHeader))); + } + + CloseHandle(sharedMem.otherProcess); + SetEvent(sharedMem.sigThreadExit); +} + + +DWORD WINAPI BridgeWrapper::MessageThread(LPVOID param) +{ + BridgeWrapper *that = static_cast<BridgeWrapper *>(param); + + const HANDLE objects[] = { that->sharedMem.sigToHost.send, that->sharedMem.otherProcess, that->sharedMem.sigThreadExit }; + DWORD result = 0; + do + { + result = WaitForMultipleObjects(CountOf(objects), objects, FALSE, INFINITE); + if(result == WAIT_OBJECT_0) + { + that->ParseNextMessage(); + } + } while(result != WAIT_OBJECT_0 + 1 && result != WAIT_OBJECT_0 + 2 && result != WAIT_FAILED); + ASSERT(result != WAIT_FAILED); + CloseHandle(that->sharedMem.sigThreadExit); + return 0; +} + + +// Receive a message from the host and translate it. +void BridgeWrapper::ParseNextMessage(MsgHeader *parentMessage) +{ + MsgHeader *msg; + if(parentMessage == nullptr) + { + // Default: Grab next message + MsgQueue *queue = static_cast<MsgQueue *>(sharedMem.queuePtr); + uint32_t index = InterlockedExchangeAdd(&(queue->toHost.readIndex), 1) % CountOf(queue->toHost.offset); + msg = reinterpret_cast<MsgHeader *>(static_cast<char *>(sharedMem.queuePtr) + queue->toHost.offset[index]); + } else + { + // Read interrupt message + msg = reinterpret_cast<MsgHeader *>(static_cast<char *>(sharedMem.queuePtr) + parentMessage->interruptOffset); + } + + switch(msg->type) + { + case MsgHeader::dispatch: + DispatchToHost(static_cast<DispatchMsg *>(msg)); + break; + + case MsgHeader::errorMsg: + MessageBoxW(theApp.GetMainWnd()->m_hWnd, static_cast<ErrorMsg *>(msg)->str, L"OpenMPT Plugin Bridge", MB_ICONERROR); + break; + } + + if(parentMessage == nullptr) + sharedMem.sigToHost.Confirm(); + else + SetEvent(reinterpret_cast<HANDLE>(parentMessage->interruptSignal)); // TODO +} + + +void BridgeWrapper::DispatchToHost(DispatchMsg *msg) +{ + // Various dispatch data - depending on the opcode, one of those might be used. + std::vector<char> extraData; + size_t extraDataSize = 0; + + void *ptr = msg + 1; // Content of ptr is usually stored right after the message header. + + switch(msg->opcode) + { + case audioMasterProcessEvents: + // VstEvents* in [ptr] + TranslateBridgeToVSTEvents(extraData, ptr); + ptr = &extraData[0]; + break; + + //case audioMasterOpenFileSelector: + //case audioMasterCloseFileSelector: + // TODO: Translate the structs + } + + if(extraDataSize != 0) + { + extraData.resize(extraDataSize, 0); + ptr = &extraData[0]; + } + + msg->result = CVstPluginManager::MasterCallBack(sharedMem.effectPtr, msg->opcode, msg->index, static_cast<VstIntPtr>(msg->value), ptr, msg->opt); + + // Post-fix some opcodes + switch(msg->opcode) + { + case audioMasterGetTime: + // VstTimeInfo* in [return value] + if(msg->result != 0) + { + memcpy(ptr, reinterpret_cast<void *>(msg->result), std::min(sizeof(VstTimeInfo), static_cast<size_t>(msg->ptr))); + } + break; + + case audioMasterGetDirectory: + // char* in [return value] + if(msg->result != 0) + { + char *target = static_cast<char *>(ptr); + strncpy(target, reinterpret_cast<const char *>(msg->result), static_cast<size_t>(msg->ptr - 1)); + target[msg->ptr - 1] = 0; + } + break; + } +} + + +// Send an arbitrary message to the bridge. +// Returns a pointer to the message, as processed by the bridge. +const MsgHeader *BridgeWrapper::SendToBridge(const MsgHeader &msg) +{ + if(msg.size <= sharedMem.queueSize) + { + sharedMem.writeMutex.lock(); + if(sharedMem.writeOffset + msg.size > sharedMem.queueSize) + { + sharedMem.writeOffset = sizeof(MsgQueue) + 512 * 1024; + } + MsgHeader *addr = reinterpret_cast<MsgHeader *>(static_cast<char *>(sharedMem.queuePtr) + sharedMem.writeOffset); + memcpy(addr, &msg, msg.size); + + MsgQueue *queue = static_cast<MsgQueue *>(sharedMem.queuePtr); + queue->toBridge.offset[queue->toBridge.writeIndex++] = sharedMem.writeOffset; + if(queue->toBridge.writeIndex >= CountOf(queue->toBridge.offset)) + { + queue->toBridge.writeIndex = 0; + } + + //sharedMem.writeMutex.unlock(); + + sharedMem.sigToBridge.Send(); + + if(msg.type == MsgHeader::dispatch && static_cast<const DispatchMsg &>(msg).opcode == effEditOpen) + {sharedMem.writeMutex.unlock(); + return addr;} + + // Wait until we get the result from the bridge. + const HANDLE objects[] = { sharedMem.sigToBridge.ack, sharedMem.otherProcess, sharedMem.sigToHost.send }; + DWORD result; + do + { + result = WaitForMultipleObjects(CountOf(objects), objects, FALSE, INFINITE); + if(result == WAIT_OBJECT_0 + 2) + { + ParseNextMessage(); + } + } while(result != WAIT_OBJECT_0 && result != WAIT_OBJECT_0 + 1); + if(result == WAIT_OBJECT_0 + 1) + { + SetEvent(sharedMem.sigThreadExit); + } + sharedMem.writeMutex.unlock(); + return (result == WAIT_OBJECT_0) ? addr : nullptr; + } + return nullptr; +} + +VstIntPtr VSTCALLBACK BridgeWrapper::DispatchToPlugin(AEffect *effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt) +{ + BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object); + + std::vector<char> dispatchData(sizeof(DispatchMsg), 0); + int64_t ptrOut = 0; + bool copyPtrBack = false, ptrIsSize = true; + char *ptrC = static_cast<char *>(ptr); + + switch(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] + ptrOut = 256; + copyPtrBack = true; + break; + + case effSetProgramName: + case effCanDo: + // char* in [ptr] + ptrOut = strlen(ptrC) + 1; + dispatchData.insert(dispatchData.end(), ptrC, ptrC + ptrOut); + break; + + case effEditGetRect: + // ERect** in [ptr] + ptrOut = sizeof(ERect); + copyPtrBack = true; + break; + + case effEditOpen: + // HWND in [ptr] - Note: Window handles are interoperable between 32-bit and 64-bit applications in Windows (http://msdn.microsoft.com/en-us/library/windows/desktop/aa384203%28v=vs.85%29.aspx) + ptrOut = reinterpret_cast<int64_t>(ptr); + ptrIsSize = false; + break; + + case effGetChunk: + // void** in [ptr] for chunk data address + break; + + case effSetChunk: + // void* in [ptr] for chunk data + ptrOut = value; + dispatchData.insert(dispatchData.end(), ptrC, ptrC + value); + break; + + case effProcessEvents: + // VstEvents* in [ptr] + TranslateVSTEventsToBridge(dispatchData, static_cast<VstEvents *>(ptr), that->sharedMem.otherPtrSize); + ptrOut = dispatchData.size() - sizeof(DispatchMsg); + break; + + case effGetInputProperties: + case effGetOutputProperties: + // VstPinProperties* in [ptr] + ptrOut = sizeof(VstPinProperties); + copyPtrBack = true; + break; + + case effOfflineNotify: + // VstAudioFile* in [ptr] + ptrOut = sizeof(VstAudioFile) * value; + // TODO + break; + + case effOfflinePrepare: + case effOfflineRun: + // VstOfflineTask* in [ptr] + ptrOut = sizeof(VstOfflineTask) * value; + // TODO + break; + + case effProcessVarIo: + // VstVariableIo* in [ptr] + ptrOut = sizeof(VstVariableIo); + // TODO + break; + + case effSetSpeakerArrangement: + // VstSpeakerArrangement* in [value] and [ptr] + ptrOut = sizeof(VstSpeakerArrangement) * 2; + PushToVector(dispatchData, *static_cast<VstSpeakerArrangement *>(ptr)); + PushToVector(dispatchData, *FromVstPtr<VstSpeakerArrangement>(value)); + break; + + case effGetParameterProperties: + // VstParameterProperties* in [ptr] + ptrOut = sizeof(VstParameterProperties); + copyPtrBack = true; + break; + + case effGetMidiProgramName: + case effGetCurrentMidiProgram: + // MidiProgramName* in [ptr] + ptrOut = sizeof(MidiProgramName); + copyPtrBack = true; + break; + + case effGetMidiProgramCategory: + // MidiProgramCategory* in [ptr] + ptrOut = sizeof(MidiProgramCategory); + copyPtrBack = true; + break; + + case effGetMidiKeyName: + // MidiKeyName* in [ptr] + ptrOut = sizeof(MidiKeyName); + copyPtrBack = true; + break; + + case effGetSpeakerArrangement: + // VstSpeakerArrangement* in [value] and [ptr] + ptrOut = sizeof(VstSpeakerArrangement) * 2; + copyPtrBack = true; + break; + + case effBeginLoadBank: + case effBeginLoadProgram: + // VstPatchChunkInfo* in [ptr] + ptrOut = sizeof(VstPatchChunkInfo); + break; + + default: + ASSERT(ptr == nullptr); + } + + if(ptrOut != 0 && ptrIsSize) + { + // In case we only reserve space and don't copy stuff over... + dispatchData.resize(sizeof(DispatchMsg) + static_cast<size_t>(ptrOut), 0); + } + + const DispatchMsg *resultMsg; + { + DispatchMsg *msg = reinterpret_cast<DispatchMsg *>(&dispatchData[0]); + new (msg) DispatchMsg(opcode, index, value, ptrOut, opt, static_cast<int32_t>(dispatchData.size() - sizeof(DispatchMsg))); + resultMsg = static_cast<const DispatchMsg *>(that->SendToBridge(*msg)); + } + + if(resultMsg == nullptr) + { + return 0; + } + + const char *extraData = reinterpret_cast<const char *>(resultMsg + 1); + // Post-fix some opcodes + switch(opcode) + { + case effClose: + effect->object = nullptr; + delete that; + break; + + case effGetProgramName: + case effGetParamLabel: + case effGetParamDisplay: + case effGetParamName: + case effString2Parameter: + case effGetProgramNameIndexed: + case effGetEffectName: + case effGetErrorText: + case effGetVendorString: + case effGetProductString: + case effShellGetNextPlugin: + // Name in [ptr] + strcpy(ptrC, extraData); + break; + + case effEditGetRect: + // ERect** in [ptr] + *static_cast<const ERect **>(ptr) = reinterpret_cast<const ERect *>(extraData); + break; + + case effGetChunk: + // void** in [ptr] for chunk data address + { + // Now that we know the chunk size, allocate enough memory for it. + dispatchData.resize(sizeof(DispatchMsg) + static_cast<size_t>(resultMsg->result)); + DispatchMsg *msg = reinterpret_cast<DispatchMsg *>(&dispatchData[0]); + new (msg) DispatchMsg(opcode, index, value, resultMsg->result, opt, static_cast<int32_t>(resultMsg->result)); + resultMsg = static_cast<const DispatchMsg *>(that->SendToBridge(*msg)); + if(resultMsg != nullptr) + { + *static_cast<void **>(ptr) = const_cast<DispatchMsg *>(resultMsg + 1); + } + } + break; + + case effGetSpeakerArrangement: + // VstSpeakerArrangement* in [value] and [ptr] + *static_cast<VstSpeakerArrangement *>(ptr) = *reinterpret_cast<const VstSpeakerArrangement *>(extraData); + *FromVstPtr<VstSpeakerArrangement>(value) = *(reinterpret_cast<const VstSpeakerArrangement *>(extraData) + 1); + break; + + default: + // TODO: Translate VstVariableIo, offline tasks + if(copyPtrBack) + { + memcpy(ptr, extraData, static_cast<size_t>(ptrOut)); + } + } + + return static_cast<VstIntPtr>(resultMsg->result); +} + + +void VSTCALLBACK BridgeWrapper::SetParameter(AEffect *effect, VstInt32 index, float parameter) +{ + BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object); + if(that) + { + that->SendToBridge(ParameterMsg(index, parameter)); + } +} + + +float VSTCALLBACK BridgeWrapper::GetParameter(AEffect *effect, VstInt32 index) +{ + BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object); + if(that) + { + const ParameterMsg *msg = static_cast<const ParameterMsg *>(that->SendToBridge(ParameterMsg(index))); + if(msg != nullptr) return msg->value; + } + return 0.0f; +} + + +template<typename buf_t> +void BridgeWrapper::BuildProcessBuffer(ProcessMsg::ProcessType type, VstInt32 numInputs, VstInt32 numOutputs, buf_t **inputs, buf_t **outputs, VstInt32 sampleFrames) +{ + new (sharedMem.sampleBufPtr) ProcessMsg(type, numInputs, numOutputs, sampleFrames); + + buf_t *ptr = reinterpret_cast<buf_t *>(static_cast<ProcessMsg *>(sharedMem.sampleBufPtr) + 1); + for(VstInt32 i = 0; i < numInputs; i++) + { + memcpy(ptr, inputs[i], sampleFrames * sizeof(buf_t)); + ptr += sampleFrames; + } + // Theoretically, we should memcpy() instead of memset() here in process(), but OpenMPT always clears the output buffer before processing so it doesn't matter. + memset(ptr, 0, numOutputs * sampleFrames * sizeof(buf_t)); + + sharedMem.sigProcess.Send(); + const HANDLE objects[] = { sharedMem.sigProcess.ack, sharedMem.otherProcess }; + WaitForMultipleObjects(CountOf(objects), objects, FALSE, INFINITE); + + for(VstInt32 i = 0; i < numOutputs; i++) + { + //memcpy(outputs[i], ptr, sampleFrames * sizeof(buf_t)); + outputs[i] = ptr; // Exactly what you don't want plugins to do usually (bend your output pointers)... muahahaha! + ptr += sampleFrames; + } +} + + +void VSTCALLBACK BridgeWrapper::Process(AEffect *effect, float **inputs, float **outputs, VstInt32 sampleFrames) +{ + // TODO memory size must probably grow! + BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object); + if(sampleFrames != 0 && that != nullptr) + { + that->BuildProcessBuffer(ProcessMsg::process, effect->numInputs, effect->numOutputs, inputs, outputs, sampleFrames); + } +} + + +void VSTCALLBACK BridgeWrapper::ProcessReplacing(AEffect *effect, float **inputs, float **outputs, VstInt32 sampleFrames) +{ + // TODO memory size must probably grow! + BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object); + if(sampleFrames != 0 && that != nullptr) + { + that->BuildProcessBuffer(ProcessMsg::processReplacing, effect->numInputs, effect->numOutputs, inputs, outputs, sampleFrames); + } +} + + +void VSTCALLBACK BridgeWrapper::ProcessDoubleReplacing(AEffect *effect, double **inputs, double **outputs, VstInt32 sampleFrames) +{ + // TODO memory size must probably grow! + BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object); + if(sampleFrames != 0 && that != nullptr) + { + that->BuildProcessBuffer(ProcessMsg::processDoubleReplacing, effect->numInputs, effect->numOutputs, inputs, outputs, sampleFrames); + } +} Added: trunk/OpenMPT/pluginBridge/BridgeWrapper.h =================================================================== --- trunk/OpenMPT/pluginBridge/BridgeWrapper.h (rev 0) +++ trunk/OpenMPT/pluginBridge/BridgeWrapper.h 2013-11-04 19:29:54 UTC (rev 3080) @@ -0,0 +1,44 @@ +#pragma once + +#include "BridgeCommon.h" + +class BridgeWrapper +{ +protected: + // Shared signals and memory + SharedMem sharedMem; + +public: + enum BinaryType + { + binUnknown = 0, + bin32Bit = 32, + bin64Bit = 64, + }; + +public: + BridgeWrapper(const char *pluginPath); + ~BridgeWrapper(); + + static BinaryType GetPluginBinaryType(const char *pluginPath); + + AEffect *GetEffect() { return sharedMem.mapFile ? sharedMem.effectPtr : nullptr; } + +protected: + + static DWORD WINAPI MessageThread(LPVOID param); + + void ParseNextMessage(MsgHeader *parentMessage = nullptr); + void DispatchToHost(DispatchMsg *msg); + const MsgHeader *SendToBridge(const MsgHeader &msg); + + static VstIntPtr VSTCALLBACK DispatchToPlugin(AEffect *effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *ptr, float opt); + static void VSTCALLBACK SetParameter(AEffect *effect, VstInt32 index, float parameter); + static float VSTCALLBACK GetParameter(AEffect *effect, VstInt32 index); + static void VSTCALLBACK Process(AEffect *effect, float **inputs, float **outputs, VstInt32 sampleFrames); + static void VSTCALLBACK ProcessReplacing(AEffect *effect, float **inputs, float **outputs, VstInt32 sampleFrames); + static void VSTCALLBACK ProcessDoubleReplacing(AEffect *effect, double **inputs, double **outputs, VstInt32 sampleFrames); + + template<typename buf_t> + void BuildProcessBuffer(ProcessMsg::ProcessType type, VstInt32 numInputs, VstInt32 numOutputs, buf_t **inputs, buf_t **outputs, VstInt32 sampleFrames); +}; Added: trunk/OpenMPT/pluginBridge/PluginBridge.filters =================================================================== --- trunk/OpenMPT/pluginBridge/PluginBridge.filters (rev 0) +++ trunk/OpenMPT/pluginBridge/PluginBridge.filters 2013-11-04 19:29:54 UTC (rev 3080) @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Header Files"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions> + </Filter> + <Filter Include="Resource Files"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> + </Filter> + ... [truncated message content] |