Allow midi events to be handled during interaction

This commit is contained in:
Robbert van der Helm
2020-03-28 17:00:12 +01:00
parent 629fa72e0c
commit d52989acc5
5 changed files with 52 additions and 16 deletions
+6 -13
View File
@@ -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.
+12 -3
View File
@@ -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&, bool>(logger, true), opcode,
index, value, data, option);
break;
}
// TODO: Maybe reuse buffers here when dealing with chunk data
+8
View File
@@ -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.
+13
View File
@@ -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();
}
+13
View File
@@ -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.
*/