1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291 |
- /*
- * BridgeWrapper.cpp
- * -----------------
- * Purpose: VST plugin bridge wrapper (host side)
- * Notes : (currently none)
- * Authors: OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #include "stdafx.h"
- #ifdef MPT_WITH_VST
- #include "BridgeWrapper.h"
- #include "../soundlib/plugins/PluginManager.h"
- #include "../mptrack/Mainfrm.h"
- #include "../mptrack/Mptrack.h"
- #include "../mptrack/Vstplug.h"
- #include "../mptrack/ExceptionHandler.h"
- #include "../common/mptFileIO.h"
- #include "../common/mptStringBuffer.h"
- #include "../common/misc_util.h"
- using namespace Vst;
- OPENMPT_NAMESPACE_BEGIN
- std::vector<BridgeCommon *> BridgeCommon::m_plugins;
- HWND BridgeCommon::m_communicationWindow = nullptr;
- int BridgeCommon::m_instanceCount = 0;
- thread_local bool BridgeCommon::m_isAudioThread = false;
- std::size_t GetPluginArchPointerSize(PluginArch arch)
- {
- std::size_t result = 0;
- switch(arch)
- {
- case PluginArch_x86:
- result = 4;
- break;
- case PluginArch_amd64:
- result = 8;
- break;
- case PluginArch_arm:
- result = 4;
- break;
- case PluginArch_arm64:
- result = 8;
- break;
- default:
- result = 0;
- break;
- }
- return result;
- }
- ComponentPluginBridge::ComponentPluginBridge(PluginArch arch, Generation generation)
- : ComponentBase(ComponentTypeBundled)
- , arch(arch)
- , generation(generation)
- {
- }
- bool ComponentPluginBridge::DoInitialize()
- {
- mpt::PathString archName;
- switch(arch)
- {
- case PluginArch_x86:
- if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::Architecture::x86) == mpt::OS::Windows::EmulationLevel::NA)
- {
- return false;
- }
- archName = P_("x86");
- break;
- case PluginArch_amd64:
- if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::Architecture::amd64) == mpt::OS::Windows::EmulationLevel::NA)
- {
- return false;
- }
- archName = P_("amd64");
- break;
- case PluginArch_arm:
- if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::Architecture::arm) == mpt::OS::Windows::EmulationLevel::NA)
- {
- return false;
- }
- archName = P_("arm");
- break;
- case PluginArch_arm64:
- if(mpt::OS::Windows::HostCanRun(mpt::OS::Windows::GetHostArchitecture(), mpt::OS::Windows::Architecture::arm64) == mpt::OS::Windows::EmulationLevel::NA)
- {
- return false;
- }
- archName = P_("arm64");
- break;
- default:
- break;
- }
- if(archName.empty())
- {
- return false;
- }
- exeName = mpt::PathString();
- const mpt::PathString generationSuffix = (generation == Generation::Legacy) ? P_("Legacy") : P_("");
- const mpt::PathString exeNames[] =
- {
- theApp.GetInstallPath() + P_("PluginBridge") + generationSuffix + P_("-") + archName + P_(".exe"), // Local
- theApp.GetInstallBinPath() + archName + P_("\\") + P_("PluginBridge") + generationSuffix + P_(".exe"), // Multi-arch
- theApp.GetInstallBinPath() + archName + P_("\\") + P_("PluginBridge") + generationSuffix + P_("-") + archName + P_(".exe") // Multi-arch transitional
- };
- for(const auto &candidate : exeNames)
- {
- if(candidate.IsFile())
- {
- exeName = candidate;
- break;
- }
- }
- if(exeName.empty())
- {
- availability = AvailabilityMissing;
- return false;
- }
- std::vector<WCHAR> exePath(MAX_PATH);
- while(GetModuleFileNameW(0, exePath.data(), mpt::saturate_cast<DWORD>(exePath.size())) >= exePath.size())
- {
- exePath.resize(exePath.size() * 2);
- }
- uint64 mptVersion = BridgeWrapper::GetFileVersion(exePath.data());
- uint64 bridgeVersion = BridgeWrapper::GetFileVersion(exeName.ToWide().c_str());
- if(bridgeVersion != mptVersion)
- {
- availability = AvailabilityWrongVersion;
- return false;
- }
- availability = AvailabilityOK;
- return true;
- }
- PluginArch BridgeWrapper::GetNativePluginBinaryType()
- {
- PluginArch result = PluginArch_unknown;
- switch(mpt::OS::Windows::GetProcessArchitecture())
- {
- case mpt::OS::Windows::Architecture::x86:
- result = PluginArch_x86;
- break;
- case mpt::OS::Windows::Architecture::amd64:
- result = PluginArch_amd64;
- break;
- case mpt::OS::Windows::Architecture::arm:
- result = PluginArch_arm;
- break;
- case mpt::OS::Windows::Architecture::arm64:
- result = PluginArch_arm64;
- break;
- default:
- result = PluginArch_unknown;
- break;
- }
- return result;
- }
- // Check whether we need to load a 32-bit or 64-bit wrapper.
- PluginArch BridgeWrapper::GetPluginBinaryType(const mpt::PathString &pluginPath)
- {
- PluginArch type = PluginArch_unknown;
- mpt::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));
- MPT_ASSERT((ntHeader.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0);
- switch(ntHeader.FileHeader.Machine)
- {
- case IMAGE_FILE_MACHINE_I386:
- type = PluginArch_x86;
- break;
- case IMAGE_FILE_MACHINE_AMD64:
- type = PluginArch_amd64;
- break;
- #if defined(MPT_WITH_WINDOWS10)
- case IMAGE_FILE_MACHINE_ARM:
- type = PluginArch_arm;
- break;
- case IMAGE_FILE_MACHINE_ARM64:
- type = PluginArch_arm64;
- break;
- #endif // MPT_WITH_WINDOWS10
- default:
- type = PluginArch_unknown;
- break;
- }
- }
- }
- return type;
- }
- uint64 BridgeWrapper::GetFileVersion(const WCHAR *exePath)
- {
- DWORD verHandle = 0;
- DWORD verSize = GetFileVersionInfoSizeW(exePath, &verHandle);
- uint64 result = 0;
- if(verSize == 0)
- {
- return result;
- }
- char *verData = new(std::nothrow) char[verSize];
- if(verData && GetFileVersionInfoW(exePath, verHandle, verSize, verData))
- {
- UINT size = 0;
- void *lpBuffer = nullptr;
- if(VerQueryValue(verData, _T("\\"), &lpBuffer, &size) && size != 0)
- {
- auto *verInfo = static_cast<const VS_FIXEDFILEINFO *>(lpBuffer);
- if(verInfo->dwSignature == 0xfeef04bd)
- {
- result = (uint64(HIWORD(verInfo->dwFileVersionMS)) << 48)
- | (uint64(LOWORD(verInfo->dwFileVersionMS)) << 32)
- | (uint64(HIWORD(verInfo->dwFileVersionLS)) << 16)
- | uint64(LOWORD(verInfo->dwFileVersionLS));
- }
- }
- }
- delete[] verData;
- return result;
- }
- // Create a plugin bridge object
- AEffect *BridgeWrapper::Create(const VSTPluginLib &plugin, bool forceLegacy)
- {
- BridgeWrapper *wrapper = new(std::nothrow) BridgeWrapper();
- BridgeWrapper *sharedInstance = nullptr;
- const Generation wantedGeneration = (plugin.modernBridge && !forceLegacy) ? Generation::Modern : Generation::Legacy;
- // Should we share instances?
- if(plugin.shareBridgeInstance)
- {
- // Well, then find some instance to share with!
- CVstPlugin *vstPlug = dynamic_cast<CVstPlugin *>(plugin.pPluginsList);
- while(vstPlug != nullptr)
- {
- if(vstPlug->isBridged)
- {
- BridgeWrapper *instance = FromIntPtr<BridgeWrapper>(vstPlug->Dispatch(effVendorSpecific, kVendorOpenMPT, kGetWrapperPointer, nullptr, 0.0f));
- if(wantedGeneration == instance->m_Generation)
- {
- sharedInstance = instance;
- break;
- }
- }
- vstPlug = dynamic_cast<CVstPlugin *>(vstPlug->GetNextInstance());
- }
- }
- try
- {
- if(wrapper != nullptr && wrapper->Init(plugin.dllPath, wantedGeneration, sharedInstance) && wrapper->m_queueMem.Good())
- {
- return &wrapper->m_sharedMem->effect;
- }
- delete wrapper;
- return nullptr;
- } catch(BridgeException &)
- {
- delete wrapper;
- throw;
- }
- }
- BridgeWrapper::BridgeWrapper()
- {
- m_thisPluginID = static_cast<int32>(m_plugins.size());
- m_plugins.push_back(this);
- if(m_instanceCount == 1)
- CreateCommunicationWindow(WindowProc);
- }
- BridgeWrapper::~BridgeWrapper()
- {
- if(m_instanceCount == 1)
- DestroyWindow(m_communicationWindow);
- }
- // Initialize and launch bridge
- bool BridgeWrapper::Init(const mpt::PathString &pluginPath, Generation bridgeGeneration, BridgeWrapper *sharedInstace)
- {
- static uint32 plugId = 0;
- plugId++;
- const DWORD procId = GetCurrentProcessId();
- const std::wstring mapName = MPT_WFORMAT("Local\\openmpt-{}-{}")(procId, plugId);
- // Create our shared memory object.
- if(!m_queueMem.Create(mapName.c_str(), sizeof(SharedMemLayout))
- || !CreateSignals(mapName.c_str()))
- {
- throw BridgeException("Could not initialize plugin bridge memory.");
- }
- m_sharedMem = m_queueMem.Data<SharedMemLayout>();
- if(sharedInstace == nullptr)
- {
- // Create a new bridge instance
- const PluginArch arch = GetPluginBinaryType(pluginPath);
- bool available = false;
- ComponentPluginBridge::Availability availability = ComponentPluginBridge::AvailabilityUnknown;
- switch(arch)
- {
- case PluginArch_x86:
- if(available = (bridgeGeneration == Generation::Modern) ? IsComponentAvailable(pluginBridge_x86) : IsComponentAvailable(pluginBridgeLegacy_x86); !available)
- availability = (bridgeGeneration == Generation::Modern) ? pluginBridge_x86->GetAvailability() : pluginBridgeLegacy_x86->GetAvailability();
- break;
- case PluginArch_amd64:
- if(available = (bridgeGeneration == Generation::Modern) ? IsComponentAvailable(pluginBridge_amd64) : IsComponentAvailable(pluginBridgeLegacy_amd64); !available)
- availability = (bridgeGeneration == Generation::Modern) ? pluginBridge_amd64->GetAvailability() : pluginBridgeLegacy_amd64->GetAvailability();
- break;
- #if defined(MPT_WITH_WINDOWS10)
- case PluginArch_arm:
- if(available = (bridgeGeneration == Generation::Modern) ? IsComponentAvailable(pluginBridge_arm) : IsComponentAvailable(pluginBridgeLegacy_arm); !available)
- availability = (bridgeGeneration == Generation::Modern) ? pluginBridge_arm->GetAvailability() : pluginBridgeLegacy_arm->GetAvailability();
- break;
- case PluginArch_arm64:
- if(available = (bridgeGeneration == Generation::Modern) ? IsComponentAvailable(pluginBridge_arm64) : IsComponentAvailable(pluginBridgeLegacy_arm64); !available)
- availability = (bridgeGeneration == Generation::Modern) ? pluginBridge_arm64->GetAvailability() : pluginBridgeLegacy_arm64->GetAvailability();
- break;
- #endif // MPT_WITH_WINDOWS10
- default:
- break;
- }
- if(arch == PluginArch_unknown)
- {
- return false;
- }
- if(!available)
- {
- switch(availability)
- {
- case ComponentPluginBridge::AvailabilityMissing:
- // Silently fail if bridge is missing.
- throw BridgeNotFoundException();
- break;
- case ComponentPluginBridge::AvailabilityWrongVersion:
- throw BridgeException("The plugin bridge version does not match your OpenMPT version.");
- break;
- default:
- throw BridgeNotFoundException();
- break;
- }
- }
- const ComponentPluginBridge *const pluginBridge =
- (arch == PluginArch_x86 && bridgeGeneration == Generation::Modern) ? static_cast<const ComponentPluginBridge *>(pluginBridge_x86.get()) :
- (arch == PluginArch_x86 && bridgeGeneration == Generation::Legacy) ? static_cast<const ComponentPluginBridge *>(pluginBridgeLegacy_x86.get()) :
- (arch == PluginArch_amd64 && bridgeGeneration == Generation::Modern) ? static_cast<const ComponentPluginBridge *>(pluginBridge_amd64.get()) :
- (arch == PluginArch_amd64 && bridgeGeneration == Generation::Legacy) ? static_cast<const ComponentPluginBridge *>(pluginBridgeLegacy_amd64.get()) :
- #if defined(MPT_WITH_WINDOWS10)
- (arch == PluginArch_arm && bridgeGeneration == Generation::Modern) ? static_cast<const ComponentPluginBridge *>(pluginBridge_arm.get()) :
- (arch == PluginArch_arm && bridgeGeneration == Generation::Legacy) ? static_cast<const ComponentPluginBridge *>(pluginBridgeLegacy_arm.get()) :
- (arch == PluginArch_arm64 && bridgeGeneration == Generation::Modern) ? static_cast<const ComponentPluginBridge *>(pluginBridge_arm64.get()) :
- (arch == PluginArch_arm64 && bridgeGeneration == Generation::Legacy) ? static_cast<const ComponentPluginBridge *>(pluginBridgeLegacy_arm64.get()) :
- #endif // MPT_WITH_WINDOWS10
- nullptr;
- if(!pluginBridge)
- {
- return false;
- }
- m_Generation = bridgeGeneration;
- const mpt::PathString exeName = pluginBridge->GetFileName();
- m_otherPtrSize = static_cast<int32>(GetPluginArchPointerSize(arch));
- std::wstring cmdLine = MPT_WFORMAT("{} {}")(mapName, procId);
- STARTUPINFOW info;
- MemsetZero(info);
- info.cb = sizeof(info);
- PROCESS_INFORMATION processInfo;
- MemsetZero(processInfo);
- if(!CreateProcessW(exeName.ToWide().c_str(), cmdLine.data(), NULL, NULL, FALSE, 0, NULL, NULL, &info, &processInfo))
- {
- throw BridgeException("Failed to launch plugin bridge.");
- }
- CloseHandle(processInfo.hThread);
- m_otherProcess = processInfo.hProcess;
- } else
- {
- // Re-use existing bridge instance
- m_otherPtrSize = sharedInstace->m_otherPtrSize;
- m_otherProcess.DuplicateFrom(sharedInstace->m_otherProcess);
- BridgeMessage msg;
- msg.NewInstance(mapName.c_str());
- if(!sharedInstace->SendToBridge(msg))
- {
- // Something went wrong, try a new instance
- return Init(pluginPath, bridgeGeneration, nullptr);
- }
- }
- // Initialize bridge
- m_sharedMem->effect.object = this;
- m_sharedMem->effect.dispatcher = DispatchToPlugin;
- m_sharedMem->effect.setParameter = SetParameter;
- m_sharedMem->effect.getParameter = GetParameter;
- m_sharedMem->effect.process = Process;
- std::memcpy(&(m_sharedMem->effect.reservedForHost2), "OMPT", 4);
- m_sigAutomation.Create(true);
- m_sharedMem->hostCommWindow = m_communicationWindow;
- const HANDLE objects[] = {m_sigBridgeReady, m_otherProcess};
- if(WaitForMultipleObjects(mpt::saturate_cast<DWORD>(std::size(objects)), objects, FALSE, 10000) != WAIT_OBJECT_0)
- {
- throw BridgeException("Could not connect to plugin bridge, it probably crashed.");
- }
- m_otherPluginID = m_sharedMem->bridgePluginID;
- BridgeMessage initMsg;
- initMsg.Init(pluginPath.ToWide().c_str(), MIXBUFFERSIZE, m_thisPluginID, ExceptionHandler::fullMemDump);
- if(!SendToBridge(initMsg))
- {
- throw BridgeException("Could not initialize plugin bridge, it probably crashed.");
- } else if(initMsg.init.result != 1)
- {
- throw BridgeException(mpt::ToCharset(mpt::Charset::UTF8, initMsg.init.str).c_str());
- }
- if(m_sharedMem->effect.flags & effFlagsCanReplacing)
- m_sharedMem->effect.processReplacing = ProcessReplacing;
- if(m_sharedMem->effect.flags & effFlagsCanDoubleReplacing)
- m_sharedMem->effect.processDoubleReplacing = ProcessDoubleReplacing;
- return true;
- }
- // Send an arbitrary message to the bridge.
- // Returns true if the message was processed by the bridge.
- bool BridgeWrapper::SendToBridge(BridgeMessage &sendMsg)
- {
- const bool inAudioThread = m_isAudioThread || CMainFrame::GetMainFrame()->InAudioThread();
- auto &messages = m_sharedMem->ipcMessages;
- const auto msgID = CopyToSharedMemory(sendMsg, messages);
- if(msgID < 0)
- return false;
- BridgeMessage &sharedMsg = messages[msgID];
- if(!inAudioThread)
- {
- if(SendMessage(m_sharedMem->bridgeCommWindow, WM_BRIDGE_MESSAGE_TO_BRIDGE, m_otherPluginID, msgID) == WM_BRIDGE_SUCCESS)
- {
- sharedMsg.CopyTo(sendMsg);
- return true;
- }
- return false;
- }
- // Audio thread: Use signals instead of window messages
- m_sharedMem->audioThreadToBridgeMsgID = msgID;
- m_sigToBridgeAudio.Send();
- // Wait until we get the result from the bridge
- DWORD result;
- const HANDLE objects[] = {m_sigToBridgeAudio.confirm, m_sigToHostAudio.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->audioThreadToHostMsgID);
- m_sigToHostAudio.Confirm();
- }
- } while(result != WAIT_OBJECT_0 + 2 && result != WAIT_FAILED);
- return (result == WAIT_OBJECT_0);
- }
- // Receive a message from the host and translate it.
- void BridgeWrapper::ParseNextMessage(int msgID)
- {
- auto &msg = m_sharedMem->ipcMessages[msgID];
- switch(msg.header.type)
- {
- case MsgHeader::dispatch:
- DispatchToHost(msg.dispatch);
- break;
- case MsgHeader::errorMsg:
- // TODO Showing a message box here will deadlock as the main thread can be in a waiting state
- //throw BridgeErrorException(msg.error.str);
- break;
- }
- }
- void BridgeWrapper::DispatchToHost(DispatchMsg &msg)
- {
- // Various dispatch data - depending on the opcode, one of those might be used.
- std::vector<char> extraData;
- 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 audioMasterProcessEvents:
- // VstEvents* in [ptr]
- TranslateBridgeToVstEvents(extraData, ptr);
- ptr = extraData.data();
- break;
- case audioMasterVendorSpecific:
- if(msg.index != kVendorOpenMPT || msg.value != kUpdateProcessingBuffer)
- {
- break;
- }
- [[fallthrough]];
- case audioMasterIOChanged:
- {
- // If the song is playing, the rendering thread might be active at the moment,
- // so we should keep the current processing memory alive until it is done for sure.
- const CVstPlugin *plug = static_cast<CVstPlugin *>(m_sharedMem->effect.reservedForHost1);
- const bool isPlaying = plug != nullptr && plug->IsResumed();
- if(isPlaying)
- {
- m_oldProcessMem.CopyFrom(m_processMem);
- }
- // Set up new processing file
- m_processMem.Open(static_cast<wchar_t *>(ptr));
- if(isPlaying)
- {
- msg.result = 1;
- return;
- }
- }
- break;
- case audioMasterUpdateDisplay:
- m_cachedProgNames.clear();
- m_cachedParamInfo.clear();
- break;
- case audioMasterOpenFileSelector:
- TranslateBridgeToVstFileSelect(extraData, ptr, static_cast<size_t>(msg.ptr));
- ptr = extraData.data();
- break;
- }
- intptr_t result = CVstPlugin::MasterCallBack(&m_sharedMem->effect, static_cast<VstOpcodeToHost>(msg.opcode), msg.index, static_cast<intptr_t>(msg.value), ptr, msg.opt);
- msg.result = static_cast<int32>(result);
- // Post-fix some opcodes
- switch(msg.opcode)
- {
- case audioMasterGetTime:
- // VstTimeInfo* in [return value]
- if(msg.result != 0)
- {
- m_sharedMem->timeInfo = *FromIntPtr<VstTimeInfo>(result);
- }
- break;
- case audioMasterGetDirectory:
- // char* in [return value]
- if(msg.result != 0)
- {
- char *target = static_cast<char *>(ptr);
- strncpy(target, FromIntPtr<const char>(result), static_cast<size_t>(msg.ptr - 1));
- target[msg.ptr - 1] = 0;
- }
- break;
- case audioMasterOpenFileSelector:
- if(msg.result != 0)
- {
- std::vector<char> fileSelect;
- TranslateVstFileSelectToBridge(fileSelect, *static_cast<const VstFileSelect *>(ptr), m_otherPtrSize);
- std::memcpy(origPtr, fileSelect.data(), std::min(fileSelect.size(), static_cast<size_t>(msg.ptr)));
- // Directly free memory on host side, we don't need it anymore
- CVstPlugin::MasterCallBack(&m_sharedMem->effect, audioMasterCloseFileSelector, msg.index, static_cast<intptr_t>(msg.value), ptr, msg.opt);
- }
- break;
- }
- }
- intptr_t VSTCALLBACK BridgeWrapper::DispatchToPlugin(AEffect *effect, VstOpcodeToPlugin opcode, int32 index, intptr_t value, void *ptr, float opt)
- {
- BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object);
- if(that != nullptr)
- {
- return that->DispatchToPlugin(opcode, index, value, ptr, opt);
- }
- return 0;
- }
- intptr_t BridgeWrapper::DispatchToPlugin(VstOpcodeToPlugin opcode, int32 index, intptr_t value, void *ptr, float opt)
- {
- std::vector<char> dispatchData(sizeof(DispatchMsg), 0);
- int64 ptrOut = 0;
- bool copyPtrBack = false, ptrIsSize = true;
- char *ptrC = static_cast<char *>(ptr);
- switch(opcode)
- {
- case effGetParamLabel:
- case effGetParamDisplay:
- case effGetParamName:
- if(index >= m_cachedParamInfoStart && index < m_cachedParamInfoStart + mpt::saturate_cast<int32>(m_cachedParamInfo.size()))
- {
- if(opcode == effGetParamLabel)
- strcpy(ptrC, m_cachedParamInfo[index - m_cachedParamInfoStart].label);
- else if(opcode == effGetParamDisplay)
- strcpy(ptrC, m_cachedParamInfo[index - m_cachedParamInfoStart].display);
- else if(opcode == effGetParamName)
- strcpy(ptrC, m_cachedParamInfo[index - m_cachedParamInfoStart].name);
- return 1;
- }
- [[fallthrough]];
- case effGetProgramName:
- case effString2Parameter:
- case effGetProgramNameIndexed:
- case effGetEffectName:
- case effGetErrorText:
- case effGetVendorString:
- case effGetProductString:
- case effShellGetNextPlugin:
- // Name in [ptr]
- if(opcode == effGetProgramNameIndexed && !m_cachedProgNames.empty())
- {
- // First check if we have cached this program name
- if(index >= m_cachedProgNameStart && index < m_cachedProgNameStart + mpt::saturate_cast<int32>(m_cachedProgNames.size() / kCachedProgramNameLength))
- {
- strcpy(ptrC, &m_cachedProgNames[(index - m_cachedProgNameStart) * kCachedProgramNameLength]);
- return 1;
- }
- }
- ptrOut = 256;
- copyPtrBack = true;
- break;
- case effSetProgramName:
- m_cachedProgNames.clear();
- [[fallthrough]];
- case effCanDo:
- // char* in [ptr]
- ptrOut = strlen(ptrC) + 1;
- dispatchData.insert(dispatchData.end(), ptrC, ptrC + ptrOut);
- break;
- case effIdle:
- // The plugin bridge will generate these messages by itself
- return 0;
- 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>(ptr);
- ptrIsSize = false;
- m_cachedProgNames.clear();
- m_cachedParamInfo.clear();
- break;
- case effEditIdle:
- // The plugin bridge will generate these messages by itself
- return 0;
- case effGetChunk:
- // void** in [ptr] for chunk data address
- {
- static uint32 chunkId = 0;
- const std::wstring mapName = L"Local\\openmpt-" + mpt::wfmt::val(GetCurrentProcessId()) + L"-chunkdata-" + mpt::wfmt::val(chunkId++);
- ptrOut = (mapName.length() + 1) * sizeof(wchar_t);
- PushToVector(dispatchData, *mapName.c_str(), static_cast<size_t>(ptrOut));
- }
- break;
- case effSetChunk:
- // void* in [ptr] for chunk data
- ptrOut = value;
- dispatchData.insert(dispatchData.end(), ptrC, ptrC + value);
- m_cachedProgNames.clear();
- m_cachedParamInfo.clear();
- break;
- case effProcessEvents:
- // VstEvents* in [ptr]
- // We process in a separate memory segment to save a bridge communication message.
- {
- std::vector<char> events;
- TranslateVstEventsToBridge(events, *static_cast<VstEvents *>(ptr), m_otherPtrSize);
- if(m_eventMem.Size() < events.size())
- {
- // Resize memory
- static uint32 chunkId = 0;
- const std::wstring mapName = L"Local\\openmpt-" + mpt::wfmt::val(GetCurrentProcessId()) + L"-events-" + mpt::wfmt::val(chunkId++);
- ptrOut = (mapName.length() + 1) * sizeof(wchar_t);
- PushToVector(dispatchData, *mapName.c_str(), static_cast<size_t>(ptrOut));
- m_eventMem.Create(mapName.c_str(), static_cast<uint32>(events.size() + 1024));
- opcode = effVendorSpecific;
- index = kVendorOpenMPT;
- value = kUpdateEventMemName;
- }
- std::memcpy(m_eventMem.Data(), events.data(), events.size());
- }
- if(opcode != effVendorSpecific)
- {
- return 1;
- }
- break;
- case effGetInputProperties:
- case effGetOutputProperties:
- // VstPinProperties* in [ptr]
- ptrOut = sizeof(VstPinProperties);
- copyPtrBack = true;
- break;
- case effOfflineNotify:
- // VstAudioFile* in [ptr]
- ptrOut = sizeof(VstAudioFile) * value;
- // TODO
- return 0;
- break;
- case effOfflinePrepare:
- case effOfflineRun:
- // VstOfflineTask* in [ptr]
- ptrOut = sizeof(VstOfflineTask) * value;
- // TODO
- return 0;
- break;
- case effProcessVarIo:
- // VstVariableIo* in [ptr]
- ptrOut = sizeof(VstVariableIo);
- // TODO
- return 0;
- break;
- case effSetSpeakerArrangement:
- // VstSpeakerArrangement* in [value] and [ptr]
- ptrOut = sizeof(VstSpeakerArrangement) * 2;
- PushToVector(dispatchData, *static_cast<VstSpeakerArrangement *>(ptr));
- PushToVector(dispatchData, *FromIntPtr<VstSpeakerArrangement>(value));
- break;
- case effVendorSpecific:
- if(index == kVendorOpenMPT)
- {
- switch(value)
- {
- case kGetWrapperPointer:
- return ToIntPtr<BridgeWrapper>(this);
- case kCloseOldProcessingMemory:
- {
- intptr_t result = m_oldProcessMem.Good();
- m_oldProcessMem.Close();
- return result;
- }
- case kCacheProgramNames:
- {
- int32 *prog = static_cast<int32 *>(ptr);
- m_cachedProgNameStart = prog[0];
- ptrOut = std::max(static_cast<int64>(sizeof(int32) * 2), static_cast<int64>((prog[1] - prog[0]) * kCachedProgramNameLength));
- dispatchData.insert(dispatchData.end(), ptrC, ptrC + 2 * sizeof(int32));
- }
- break;
- case kCacheParameterInfo:
- {
- int32 *param = static_cast<int32 *>(ptr);
- m_cachedParamInfoStart = param[0];
- ptrOut = std::max(static_cast<int64>(sizeof(int32) * 2), static_cast<int64>((param[1] - param[0]) * sizeof(ParameterInfo)));
- dispatchData.insert(dispatchData.end(), ptrC, ptrC + 2 * sizeof(int32));
- }
- break;
- case kBeginGetProgram:
- ptrOut = m_sharedMem->effect.numParams * sizeof(float);
- break;
- case kEndGetProgram:
- m_cachedParamValues.clear();
- return 1;
- }
- }
- break;
- case effGetTailSize:
- return m_sharedMem->tailSize;
- case effGetParameterProperties:
- // VstParameterProperties* in [ptr]
- if(index >= m_cachedParamInfoStart && index < m_cachedParamInfoStart + mpt::saturate_cast<int32>(m_cachedParamInfo.size()))
- {
- *static_cast<VstParameterProperties *>(ptr) = m_cachedParamInfo[index - m_cachedParamInfoStart].props;
- return 1;
- }
- 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 effBeginSetProgram:
- m_isSettingProgram = true;
- break;
- case effEndSetProgram:
- m_isSettingProgram = false;
- if(m_sharedMem->automationQueue.pendingEvents)
- {
- SendAutomationQueue();
- }
- m_cachedProgNames.clear();
- m_cachedParamInfo.clear();
- 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);
- m_cachedProgNames.clear();
- m_cachedParamInfo.clear();
- break;
- default:
- MPT_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);
- }
- 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);
- AuxMem *auxMem = nullptr;
- if(useAuxMem)
- {
- // Extra data doesn't fit in message - use secondary memory
- if(dispatchData.size() > std::numeric_limits<uint32>::max())
- return 0;
- auxMem = GetAuxMemory(mpt::saturate_cast<uint32>(dispatchData.size()));
- if(auxMem == nullptr)
- return 0;
- // First, move message data to shared memory...
- std::memcpy(auxMem->memory.Data(), &dispatchData[sizeof(DispatchMsg)], extraSize);
- // ...Now put the shared memory name in the message instead.
- std::memcpy(&dispatchData[sizeof(DispatchMsg)], auxMem->name, sizeof(auxMem->name));
- }
- try
- {
- if(!SendToBridge(msg) && opcode != effClose)
- {
- return 0;
- }
- } catch(...)
- {
- // Don't do anything for now.
- #if 0
- if(opcode != effClose)
- {
- throw;
- }
- #endif
- }
- const DispatchMsg &resultMsg = msg.dispatch;
- // cppcheck false-positive
- // cppcheck-suppress nullPointerRedundantCheck
- const void *extraData = useAuxMem ? auxMem->memory.Data<const char>() : reinterpret_cast<const char *>(&resultMsg + 1);
- // Post-fix some opcodes
- switch(opcode)
- {
- case effClose:
- m_sharedMem->effect.object = nullptr;
- delete this;
- return 0;
- 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, static_cast<const char *>(extraData));
- break;
- case effEditGetRect:
- // ERect** in [ptr]
- m_editRect = *static_cast<const ERect *>(extraData);
- *static_cast<const ERect **>(ptr) = &m_editRect;
- break;
- case effGetChunk:
- // void** in [ptr] for chunk data address
- if(const wchar_t *str = static_cast<const wchar_t *>(extraData); m_getChunkMem.Open(str))
- *static_cast<void **>(ptr) = m_getChunkMem.Data();
- else
- return 0;
- break;
- case effVendorSpecific:
- if(index == kVendorOpenMPT && resultMsg.result == 1)
- {
- switch(value)
- {
- case kCacheProgramNames:
- m_cachedProgNames.assign(static_cast<const char *>(extraData), static_cast<const char *>(extraData) + ptrOut);
- break;
- case kCacheParameterInfo:
- {
- const ParameterInfo *params = static_cast<const ParameterInfo *>(extraData);
- m_cachedParamInfo.assign(params, params + ptrOut / sizeof(ParameterInfo));
- break;
- }
- case kBeginGetProgram:
- m_cachedParamValues.assign(static_cast<const float *>(extraData), static_cast<const float *>(extraData) + ptrOut / sizeof(float));
- break;
- }
- }
- break;
- case effGetSpeakerArrangement:
- // VstSpeakerArrangement* in [value] and [ptr]
- m_speakers[0] = *static_cast<const VstSpeakerArrangement *>(extraData);
- m_speakers[1] = *(static_cast<const VstSpeakerArrangement *>(extraData) + 1);
- *static_cast<VstSpeakerArrangement *>(ptr) = m_speakers[0];
- *FromIntPtr<VstSpeakerArrangement>(value) = m_speakers[1];
- break;
- default:
- // TODO: Translate VstVariableIo, offline tasks
- if(copyPtrBack)
- {
- std::memcpy(ptr, extraData, static_cast<size_t>(ptrOut));
- }
- }
- if(auxMem != nullptr)
- {
- auxMem->used = false;
- }
- return static_cast<intptr_t>(resultMsg.result);
- }
- // Allocate auxiliary shared memory for too long bridge messages
- BridgeWrapper::AuxMem *BridgeWrapper::GetAuxMemory(uint32 size)
- {
- std::size_t index = std::size(m_auxMems);
- for(int pass = 0; pass < 2; pass++)
- {
- for(std::size_t i = 0; i < std::size(m_auxMems); i++)
- {
- if(m_auxMems[i].size >= size || pass == 1)
- {
- // Good candidate - is it taken yet?
- bool expected = false;
- if(m_auxMems[i].used.compare_exchange_strong(expected, true))
- {
- index = i;
- break;
- }
- }
- }
- if(index != std::size(m_auxMems))
- break;
- }
- if(index == std::size(m_auxMems))
- return nullptr;
- AuxMem &auxMem = m_auxMems[index];
- if(auxMem.size >= size && auxMem.memory.Good())
- {
- // Re-use as-is
- return &auxMem;
- }
- // Create new memory with appropriate size
- static_assert(sizeof(DispatchMsg) + sizeof(auxMem.name) <= sizeof(BridgeMessage), "Check message sizes, this will crash!");
- static unsigned int auxMemCount = 0;
- mpt::String::WriteAutoBuf(auxMem.name) = MPT_WFORMAT("Local\\openmpt-{}-auxmem-{}")(GetCurrentProcessId(), auxMemCount++);
- if(auxMem.memory.Create(auxMem.name, size))
- {
- auxMem.size = size;
- return &auxMem;
- } else
- {
- auxMem.used = false;
- return nullptr;
- }
- }
- // Send any pending automation events
- void BridgeWrapper::SendAutomationQueue()
- {
- m_sigAutomation.Reset();
- BridgeMessage msg;
- msg.Automate();
- if(!SendToBridge(msg))
- {
- // Failed (plugin probably crashed) - auto-fix event count
- m_sharedMem->automationQueue.pendingEvents = 0;
- }
- m_sigAutomation.Trigger();
- }
- void VSTCALLBACK BridgeWrapper::SetParameter(AEffect *effect, int32 index, float parameter)
- {
- BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object);
- if(that)
- {
- try
- {
- that->SetParameter(index, parameter);
- } catch(...)
- {
- // Be quiet about exceptions here
- }
- }
- }
- void BridgeWrapper::SetParameter(int32 index, float parameter)
- {
- const CVstPlugin *plug = static_cast<CVstPlugin *>(m_sharedMem->effect.reservedForHost1);
- AutomationQueue &autoQueue = m_sharedMem->automationQueue;
- if(m_isSettingProgram || (plug && plug->IsResumed()))
- {
- // Queue up messages while rendering to reduce latency introduced by every single bridge call
- uint32 i;
- while((i = autoQueue.pendingEvents.fetch_add(1)) >= std::size(autoQueue.params))
- {
- // Queue full!
- if(i == std::size(autoQueue.params))
- {
- // We're the first to notice that it's full
- SendAutomationQueue();
- } else
- {
- // Wait until queue is emptied by someone else (this branch is very unlikely to happen)
- WaitForSingleObject(m_sigAutomation, INFINITE);
- }
- }
- autoQueue.params[i].index = index;
- autoQueue.params[i].value = parameter;
- return;
- } else if(autoQueue.pendingEvents)
- {
- // Actually, this should never happen as pending events are cleared before processing and at the end of a set program event.
- SendAutomationQueue();
- }
- BridgeMessage msg;
- msg.SetParameter(index, parameter);
- SendToBridge(msg);
- }
- float VSTCALLBACK BridgeWrapper::GetParameter(AEffect *effect, int32 index)
- {
- BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object);
- if(that)
- {
- if(static_cast<size_t>(index) < that->m_cachedParamValues.size())
- return that->m_cachedParamValues[index];
- try
- {
- return that->GetParameter(index);
- } catch(...)
- {
- // Be quiet about exceptions here
- }
- }
- return 0.0f;
- }
- float BridgeWrapper::GetParameter(int32 index)
- {
- BridgeMessage msg;
- msg.GetParameter(index);
- if(SendToBridge(msg))
- {
- return msg.parameter.value;
- }
- return 0.0f;
- }
- void VSTCALLBACK BridgeWrapper::Process(AEffect *effect, float **inputs, float **outputs, int32 sampleFrames)
- {
- 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, int32 sampleFrames)
- {
- 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, int32 sampleFrames)
- {
- BridgeWrapper *that = static_cast<BridgeWrapper *>(effect->object);
- if(sampleFrames != 0 && that != nullptr)
- {
- that->BuildProcessBuffer(ProcessMsg::processDoubleReplacing, effect->numInputs, effect->numOutputs, inputs, outputs, sampleFrames);
- }
- }
- template <typename buf_t>
- void BridgeWrapper::BuildProcessBuffer(ProcessMsg::ProcessType type, int32 numInputs, int32 numOutputs, buf_t **inputs, buf_t **outputs, int32 sampleFrames)
- {
- if(!m_processMem.Good())
- {
- MPT_ASSERT_NOTREACHED();
- return;
- }
- ProcessMsg *processMsg = m_processMem.Data<ProcessMsg>();
- new(processMsg) ProcessMsg{type, numInputs, numOutputs, sampleFrames};
- // Anticipate that many plugins will query the play position in a process call and send it along the process call
- // to save some valuable inter-process calls.
- m_sharedMem->timeInfo = *FromIntPtr<VstTimeInfo>(CVstPlugin::MasterCallBack(&m_sharedMem->effect, audioMasterGetTime, 0, kVstNanosValid | kVstPpqPosValid | kVstTempoValid | kVstBarsValid | kVstCyclePosValid | kVstTimeSigValid | kVstSmpteValid | kVstClockValid, nullptr, 0.0f));
- buf_t *ptr = reinterpret_cast<buf_t *>(processMsg + 1);
- for(int32 i = 0; i < numInputs; i++)
- {
- std::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));
- // In case we called Process() from the GUI thread (panic button or song stop => CSoundFile::SuspendPlugins)
- m_isAudioThread = true;
- m_sigProcessAudio.Send();
- const HANDLE objects[] = {m_sigProcessAudio.confirm, m_sigToHostAudio.send, m_otherProcess};
- DWORD result;
- do
- {
- result = WaitForMultipleObjects(mpt::saturate_cast<DWORD>(std::size(objects)), objects, FALSE, INFINITE);
- if(result == WAIT_OBJECT_0 + 1)
- {
- ParseNextMessage(m_sharedMem->audioThreadToHostMsgID);
- m_sigToHostAudio.Confirm();
- }
- } while(result != WAIT_OBJECT_0 && result != WAIT_OBJECT_0 + 2 && result != WAIT_FAILED);
- m_isAudioThread = false;
- for(int32 i = 0; i < numOutputs; i++)
- {
- //std::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;
- }
- // Did we receive any audioMasterProcessEvents data?
- if(auto *events = m_eventMem.Data<int32>(); events != nullptr && *events != 0)
- {
- std::vector<char> eventCache;
- TranslateBridgeToVstEvents(eventCache, events);
- *events = 0;
- CVstPlugin::MasterCallBack(&m_sharedMem->effect, audioMasterProcessEvents, 0, 0, eventCache.data(), 0.0f);
- }
- }
- LRESULT CALLBACK BridgeWrapper::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
- {
- if(hwnd == m_communicationWindow && wParam < m_plugins.size())
- {
- auto *that = static_cast<BridgeWrapper *>(m_plugins[wParam]);
- if(that != nullptr && uMsg == WM_BRIDGE_MESSAGE_TO_HOST)
- {
- that->ParseNextMessage(static_cast<int>(lParam));
- return WM_BRIDGE_SUCCESS;
- }
- }
- return DefWindowProc(hwnd, uMsg, wParam, lParam);
- }
- #endif // MPT_WITH_VST
- OPENMPT_NAMESPACE_END
|