diff --git a/README.md b/README.md index e0334eaa..a65716cc 100644 --- a/README.md +++ b/README.md @@ -136,19 +136,6 @@ window managers will require some slight modifications in meson configure build --buildtype=debug -Duse-winedbg=true ``` -## Known issues - -- Plugins can't receive MIDI events while they have an open dropdown menu. This - is a limitation of the Win32 API which requires all GUI interaction to be done - from a single thread. Dropdowns and similar GUI elements are implemented in - such a way that they will block the thread until the user selects an item. - Most plugins will make the assumption that the GUI thread is the same thread - on which the plugin was created and also that this is also the same thread - from which `dispatch()` calls are being sent. Because of these limitations we - can't just move all GUI interaction to a different thread. A decent solution - for this would be to just create another pair of sockets and threads to - specifically handle the `effProcessEvents` opcode. - ## Rationale I started this project because the alternatives were either unmaintained, not @@ -201,6 +188,12 @@ process works as follows: - Calls from the native VST host to the plugin's `dispatch()` function. These get forwarded to the Windows VST plugin through the Wine VST host. + - Calls from the native VST host to the plugin's `dispatch()` function with + `opcode=effProcessEvents`. These get forwarded to the Windows VST plugin + through the Wine VST host. This has to be handled separately from all other + events because of limitations of the Win32 API. Otherwise the plugin would + not receive any midi events while the GUI is being resized or a dropdown + menu or message box is open. - Host callback calls from the Windows VST plugin loaded into the Wine VST host through the `audioMasterCallback` function. These get forwarded to the native VST host through the plugin. diff --git a/src/plugin/host-bridge.cpp b/src/plugin/host-bridge.cpp index 818993fa..a5ca119a 100644 --- a/src/plugin/host-bridge.cpp +++ b/src/plugin/host-bridge.cpp @@ -81,6 +81,7 @@ HostBridge::HostBridge(audioMasterCallback host_callback) socket_endpoint(generate_endpoint_name().string()), socket_acceptor(io_context, socket_endpoint), host_vst_dispatch(io_context), + host_vst_dispatch_midi_events(io_context), vst_host_callback(io_context), host_vst_parameters(io_context), host_vst_process_replacing(io_context), @@ -127,6 +128,7 @@ HostBridge::HostBridge(audioMasterCallback host_callback) // It's very important that these sockets are connected to in the same // order in the Wine VST host socket_acceptor.accept(host_vst_dispatch); + socket_acceptor.accept(host_vst_dispatch_midi_events); socket_acceptor.accept(vst_host_callback); socket_acceptor.accept(host_vst_parameters); socket_acceptor.accept(host_vst_process_replacing); @@ -281,10 +283,7 @@ intptr_t HostBridge::dispatch(AEffect* /*plugin*/, float option) { DispatchDataConverter converter(chunk_data, editor_rectangle); - // Some events need some extra handling - // TODO: Handle GUI closing? switch (opcode) { - break; case effClose: { // TODO: Gracefully close the editor? // TODO: Check whether the sockets and the endpoint are closed @@ -333,6 +332,16 @@ intptr_t HostBridge::dispatch(AEffect* /*plugin*/, return return_value; }; break; + case effProcessEvents: + // Because of limitations of the Win32 API we have to use a seperate + // thread and socket to pass midi events. Otherwise plugins will + // stop receiving midi data when they have an open dropdowns or + // message box. + return send_event(host_vst_dispatch_midi_events, + dispatch_midi_events_semaphore, converter, + std::pair(logger, true), opcode, + index, value, data, option); + break; } // TODO: Maybe reuse buffers here when dealing with chunk data diff --git a/src/plugin/host-bridge.h b/src/plugin/host-bridge.h index 8087360d..bdcbfbae 100644 --- a/src/plugin/host-bridge.h +++ b/src/plugin/host-bridge.h @@ -139,6 +139,13 @@ class HostBridge { // `AEffect.dispatch()` calls from the native VST host to the Windows VST // plugin (through the Wine VST host). boost::asio::local::stream_protocol::socket host_vst_dispatch; + /** + * Used specifically for the `effProcessEvents` opcode. This is needed + * because the Win32 API is designed to block during certain GUI + * interactions such as resizing a window or opening a dropdown. Without + * this midi input would just stop working at times. + */ + boost::asio::local::stream_protocol::socket host_vst_dispatch_midi_events; boost::asio::local::stream_protocol::socket vst_host_callback; /** * Used for both `getParameter` and `setParameter` since they mostly @@ -168,6 +175,7 @@ class HostBridge { * information. */ std::mutex dispatch_semaphore; + std::mutex dispatch_midi_events_semaphore; /** * The callback function passed by the host to the VST plugin instance. diff --git a/src/wine-host/plugin-bridge.cpp b/src/wine-host/plugin-bridge.cpp index 790a0ac0..ff5cab3e 100644 --- a/src/wine-host/plugin-bridge.cpp +++ b/src/wine-host/plugin-bridge.cpp @@ -62,6 +62,7 @@ PluginBridge::PluginBridge(std::string plugin_dll_path, io_context(), socket_endpoint(socket_endpoint_path), host_vst_dispatch(io_context), + host_vst_dispatch_midi_events(io_context), vst_host_callback(io_context), host_vst_parameters(io_context), host_vst_process_replacing(io_context), @@ -94,6 +95,7 @@ PluginBridge::PluginBridge(std::string plugin_dll_path, // It's very important that these sockets are accepted to in the same order // in the Linus plugin host_vst_dispatch.connect(socket_endpoint); + host_vst_dispatch_midi_events.connect(socket_endpoint); vst_host_callback.connect(socket_endpoint); host_vst_parameters.connect(socket_endpoint); host_vst_process_replacing.connect(socket_endpoint); @@ -118,6 +120,16 @@ PluginBridge::PluginBridge(std::string plugin_dll_path, current_bridge_isntance = nullptr; plugin->ptr1 = this; + // This works functionally identically to the `handle_dispatch()` function + // below, but this socket will only handle midi events. This is needed + // because of Win32 API limitations. + dispatch_midi_events_handler = std::thread([&]() { + while (true) { + passthrough_event(host_vst_dispatch_midi_events, std::nullopt, + plugin, plugin->dispatcher); + } + }); + parameters_handler = std::thread([&]() { while (true) { // Both `getParameter` and `setParameter` functions are passed @@ -191,6 +203,7 @@ void PluginBridge::handle_dispatch() { } catch (const boost::system::system_error&) { // This happens when the sockets got closed because the plugin is being // shut down. In that case we can just let the whole host terminate. + dispatch_midi_events_handler.detach(); parameters_handler.detach(); process_replacing_handler.detach(); } diff --git a/src/wine-host/plugin-bridge.h b/src/wine-host/plugin-bridge.h index bd1d598a..c174f646 100644 --- a/src/wine-host/plugin-bridge.h +++ b/src/wine-host/plugin-bridge.h @@ -104,6 +104,13 @@ class PluginBridge { // `AEffect.dispatch()` calls from the native VST host to the Windows VST // plugin (through the Wine VST host). boost::asio::local::stream_protocol::socket host_vst_dispatch; + /** + * Used specifically for the `effProcessEvents` opcode. This is needed + * because the Win32 API is designed to block during certain GUI + * interactions such as resizing a window or opening a dropdown. Without + * this midi input would just stop working at times. + */ + boost::asio::local::stream_protocol::socket host_vst_dispatch_midi_events; boost::asio::local::stream_protocol::socket vst_host_callback; /** * Used for both `getParameter` and `setParameter` since they mostly @@ -119,6 +126,12 @@ class PluginBridge { */ boost::asio::local::stream_protocol::socket vst_host_aeffect; + /** + * The thread that specifically handles `effProcessEvents` opcodes so the + * plugin can still receive midi during GUI interaction to work around Win32 + * API limitations. + */ + std::thread dispatch_midi_events_handler; /** * The thread that responds to `getParameter` and `setParameter` requests. */