From 4038e198fe9d866add76f08b888e24d4fe686351 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 27 Oct 2020 17:16:20 +0100 Subject: [PATCH] Replace the Win32Thread proxy functions Now that we can use lambdas instead. --- src/wine-host/bridges/vst2.cpp | 404 ++++++++++++++++----------------- src/wine-host/bridges/vst2.h | 8 - 2 files changed, 192 insertions(+), 220 deletions(-) diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index 648ffe36..ecac52ce 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -50,13 +50,6 @@ const std::set unsafe_opcodes{effOpen, effClose, effEditGetRect, intptr_t VST_CALL_CONV host_callback_proxy(AEffect*, int, int, intptr_t, void*, float); -// We need to use the `CreateThread` WinAPI functions instead of `std::thread` -// to use the correct calling conventions within threads. Otherwise we'll get -// some rare and impossible to debug data races in some particular plugins. -uint32_t WINAPI handle_dispatch_midi_events_proxy(void*); -uint32_t WINAPI handle_parameters_proxy(void*); -uint32_t WINAPI handle_process_replacing_proxy(void*); - /** * Fetch the Vst2Bridge instance stored in one of the two pointers reserved * for the host of the hosted VST plugin. This is sadly needed as a workaround @@ -140,13 +133,200 @@ Vst2Bridge::Vst2Bridge(PluginContext& main_context, // This works functionally identically to the `handle_dispatch()` function, // but this socket will only handle MIDI events and it will handle them // eagerly. This is needed because of Win32 API limitations. - dispatch_midi_events_handler = - Win32Thread(handle_dispatch_midi_events_proxy, this); + dispatch_midi_events_handler = Win32Thread([&]() { + sockets.host_vst_dispatch_midi_events.receive( + std::nullopt, [&](Event& event, bool /*on_main_thread*/) { + if (BOOST_LIKELY(event.opcode == effProcessEvents)) { + // For 99% of the plugins we can just call + // `effProcessReplacing()` and be done with it, but a select + // few plugins (I could only find Kontakt that does this) + // don't actually make copies of the events they receive and + // only store pointers, meaning that they have to live at + // least until the next audio buffer gets processed. We're + // not using `passthrough_events()` here directly because we + // need to store a copy of the `DynamicVstEvents` struct + // before passing the generated `VstEvents` object to the + // plugin. + std::lock_guard lock(next_buffer_midi_events_mutex); - parameters_handler = Win32Thread(handle_parameters_proxy, this); + next_audio_buffer_midi_events.push_back( + std::get(event.payload)); + DynamicVstEvents& events = + next_audio_buffer_midi_events.back(); - process_replacing_handler = - Win32Thread(handle_process_replacing_proxy, this); + // Exact same handling as in `passthrough_event`, apart from + // making a copy of the events first + const intptr_t return_value = plugin->dispatcher( + plugin, event.opcode, event.index, event.value, + &events.as_c_events(), event.option); + + EventResult response{.return_value = return_value, + .payload = nullptr, + .value_payload = std::nullopt}; + + return response; + } else { + using namespace std::placeholders; + + std::cerr << "[Warning] Received non-MIDI " + "event on MIDI processing thread" + << std::endl; + + // Maybe this should just be a hard error instead, since it + // should never happen + return passthrough_event( + plugin, std::bind(&Vst2Bridge::dispatch_wrapper, this, + _1, _2, _3, _4, _5, _6))(event); + } + }); + }); + + parameters_handler = Win32Thread([&]() { + while (true) { + try { + // Both `getParameter` and `setParameter` functions are passed + // through on this socket since they have a lot of overlap. The + // presence of the `value` field tells us which one we're + // dealing with. + auto request = + read_object(sockets.host_vst_parameters); + if (request.value) { + // `setParameter` + plugin->setParameter(plugin, request.index, *request.value); + + ParameterResult response{std::nullopt}; + write_object(sockets.host_vst_parameters, response); + } else { + // `getParameter` + float value = plugin->getParameter(plugin, request.index); + + ParameterResult response{value}; + write_object(sockets.host_vst_parameters, response); + } + } catch (const boost::system::system_error&) { + // The plugin has cut off communications, so we can shut down + // this host application + break; + } + } + }); + + process_replacing_handler = Win32Thread([&]() { + // These are used as scratch buffers to prevent unnecessary allocations. + // Since don't know in advance whether the host will call + // `processReplacing` or `processDoubleReplacing` we'll just create + // both. + std::vector> output_buffers_single_precision( + plugin->numOutputs); + std::vector> output_buffers_double_precision( + plugin->numOutputs); + + while (true) { + try { + auto request = read_object( + sockets.host_vst_process_replacing, process_buffer); + // Let the plugin process the MIDI events that were received + // since the last buffer, and then clean up those events. This + // approach should not be needed but Kontakt only stores + // pointers to rather than copies of the events. + std::lock_guard lock(next_buffer_midi_events_mutex); + + // Since the host should only be calling one of `process()`, + // processReplacing()` or `processDoubleReplacing()`, we can all + // handle them over the same socket. We pick which one to call + // depending on the type of data we got sent and the plugin's + // reported support for these functions. + std::visit( + overload{ + [&](std::vector>& input_buffers) { + // The process functions expect a `float**` for + // their inputs and their outputs + std::vector inputs; + for (auto& buffer : input_buffers) { + inputs.push_back(buffer.data()); + } + + // We reuse the buffers to avoid some unnecessary + // heap allocations, so we need to make sure the + // buffers are large enough since plugins can change + // their output configuration. The type we're using + // here (single precision floats vs double + // precisioon doubles) should be the same as the one + // we're sending in our response. + std::vector outputs; + output_buffers_single_precision.resize( + plugin->numOutputs); + for (auto& buffer : + output_buffers_single_precision) { + buffer.resize(request.sample_frames); + outputs.push_back(buffer.data()); + } + + // Any plugin made in the last fifteen years or so + // should support `processReplacing`. In the off + // chance it does not we can just emulate this + // behavior ourselves. + if (plugin->processReplacing) { + plugin->processReplacing(plugin, inputs.data(), + outputs.data(), + request.sample_frames); + } else { + // If we zero out this buffer then the behavior + // is the same as `processReplacing`` + for (std::vector& buffer : + output_buffers_single_precision) { + std::fill(buffer.begin(), buffer.end(), + 0.0); + } + + plugin->process(plugin, inputs.data(), + outputs.data(), + request.sample_frames); + } + + AudioBuffers response{ + output_buffers_single_precision, + request.sample_frames}; + write_object(sockets.host_vst_process_replacing, + response, process_buffer); + }, + [&](std::vector>& input_buffers) { + // Exactly the same as the above, but for double + // precision audio + std::vector inputs; + for (auto& buffer : input_buffers) { + inputs.push_back(buffer.data()); + } + + std::vector outputs; + output_buffers_double_precision.resize( + plugin->numOutputs); + for (auto& buffer : + output_buffers_double_precision) { + buffer.resize(request.sample_frames); + outputs.push_back(buffer.data()); + } + + plugin->processDoubleReplacing( + plugin, inputs.data(), outputs.data(), + request.sample_frames); + + AudioBuffers response{ + output_buffers_double_precision, + request.sample_frames}; + write_object(sockets.host_vst_process_replacing, + response, process_buffer); + }}, + request.buffers); + + next_audio_buffer_midi_events.clear(); + } catch (const boost::system::system_error&) { + // The plugin has cut off communications, so we can shut down + // this host application + break; + } + } + }); } bool Vst2Bridge::should_skip_message_loop() const { @@ -191,191 +371,6 @@ void Vst2Bridge::handle_dispatch() { }); } -void Vst2Bridge::handle_dispatch_midi_events() { - sockets.host_vst_dispatch_midi_events.receive( - std::nullopt, [&](Event& event, bool /*on_main_thread*/) { - if (BOOST_LIKELY(event.opcode == effProcessEvents)) { - // For 99% of the plugins we can just call - // `effProcessReplacing()` and be done with it, but a select few - // plugins (I could only find Kontakt that does this) don't - // actually make copies of the events they receive and only - // store pointers, meaning that they have to live at least until - // the next audio buffer gets processed. We're not using - // `passthrough_events()` here directly because we need to store - // a copy of the `DynamicVstEvents` struct before passing the - // generated `VstEvents` object to the plugin. - std::lock_guard lock(next_buffer_midi_events_mutex); - - next_audio_buffer_midi_events.push_back( - std::get(event.payload)); - DynamicVstEvents& events = next_audio_buffer_midi_events.back(); - - // Exact same handling as in `passthrough_event`, apart from - // making a copy of the events first - const intptr_t return_value = plugin->dispatcher( - plugin, event.opcode, event.index, event.value, - &events.as_c_events(), event.option); - - EventResult response{.return_value = return_value, - .payload = nullptr, - .value_payload = std::nullopt}; - - return response; - } else { - using namespace std::placeholders; - - std::cerr << "[Warning] Received non-MIDI " - "event on MIDI processing thread" - << std::endl; - - // Maybe this should just be a hard error instead, since it - // should never happen - return passthrough_event( - plugin, std::bind(&Vst2Bridge::dispatch_wrapper, this, _1, - _2, _3, _4, _5, _6))(event); - } - }); -} - -void Vst2Bridge::handle_parameters() { - while (true) { - try { - // Both `getParameter` and `setParameter` functions are passed - // through on this socket since they have a lot of overlap. The - // presence of the `value` field tells us which one we're dealing - // with. - auto request = read_object(sockets.host_vst_parameters); - if (request.value) { - // `setParameter` - plugin->setParameter(plugin, request.index, *request.value); - - ParameterResult response{std::nullopt}; - write_object(sockets.host_vst_parameters, response); - } else { - // `getParameter` - float value = plugin->getParameter(plugin, request.index); - - ParameterResult response{value}; - write_object(sockets.host_vst_parameters, response); - } - } catch (const boost::system::system_error&) { - // The plugin has cut off communications, so we can shut down this - // host application - break; - } - } -} - -void Vst2Bridge::handle_process_replacing() { - // These are used as scratch buffers to prevent unnecessary allocations. - // Since don't know in advance whether the host will call `processReplacing` - // or `processDoubleReplacing` we'll just create both. - std::vector> output_buffers_single_precision( - plugin->numOutputs); - std::vector> output_buffers_double_precision( - plugin->numOutputs); - - while (true) { - try { - auto request = read_object( - sockets.host_vst_process_replacing, process_buffer); - // Let the plugin process the MIDI events that were received since - // the last buffer, and then clean up those events. This approach - // should not be needed but Kontakt only stores pointers to rather - // than copies of the events. - std::lock_guard lock(next_buffer_midi_events_mutex); - - // Since the host should only be calling one of `process()`, - // processReplacing()` or `processDoubleReplacing()`, we can all - // handle them over the same socket. We pick which one to call - // depending on the type of data we got sent and the plugin's - // reported support for these functions. - std::visit( - overload{ - [&](std::vector>& input_buffers) { - // The process functions expect a `float**` for their - // inputs and their outputs - std::vector inputs; - for (auto& buffer : input_buffers) { - inputs.push_back(buffer.data()); - } - - // We reuse the buffers to avoid some unnecessary heap - // allocations, so we need to make sure the buffers are - // large enough since plugins can change their output - // configuration. The type we're using here (single - // precision floats vs double precisioon doubles) should - // be the same as the one we're sending in our response. - std::vector outputs; - output_buffers_single_precision.resize( - plugin->numOutputs); - for (auto& buffer : output_buffers_single_precision) { - buffer.resize(request.sample_frames); - outputs.push_back(buffer.data()); - } - - // Any plugin made in the last fifteen years or so - // should support `processReplacing`. In the off chance - // it does not we can just emulate this behavior - // ourselves. - if (plugin->processReplacing) { - plugin->processReplacing(plugin, inputs.data(), - outputs.data(), - request.sample_frames); - } else { - // If we zero out this buffer then the behavior is - // the same as `processReplacing`` - for (std::vector& buffer : - output_buffers_single_precision) { - std::fill(buffer.begin(), buffer.end(), 0.0); - } - - plugin->process(plugin, inputs.data(), - outputs.data(), - request.sample_frames); - } - - AudioBuffers response{output_buffers_single_precision, - request.sample_frames}; - write_object(sockets.host_vst_process_replacing, - response, process_buffer); - }, - [&](std::vector>& input_buffers) { - // Exactly the same as the above, but for double - // precision audio - std::vector inputs; - for (auto& buffer : input_buffers) { - inputs.push_back(buffer.data()); - } - - std::vector outputs; - output_buffers_double_precision.resize( - plugin->numOutputs); - for (auto& buffer : output_buffers_double_precision) { - buffer.resize(request.sample_frames); - outputs.push_back(buffer.data()); - } - - plugin->processDoubleReplacing(plugin, inputs.data(), - outputs.data(), - request.sample_frames); - - AudioBuffers response{output_buffers_double_precision, - request.sample_frames}; - write_object(sockets.host_vst_process_replacing, - response, process_buffer); - }}, - request.buffers); - - next_audio_buffer_midi_events.clear(); - } catch (const boost::system::system_error&) { - // The plugin has cut off communications, so we can shut down this - // host application - break; - } - } -} - intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin, int opcode, int index, @@ -573,18 +568,3 @@ intptr_t VST_CALL_CONV host_callback_proxy(AEffect* effect, return get_bridge_instance(effect).host_callback(effect, opcode, index, value, data, option); } - -uint32_t WINAPI handle_dispatch_midi_events_proxy(void* instance) { - static_cast(instance)->handle_dispatch_midi_events(); - return 0; -} - -uint32_t WINAPI handle_parameters_proxy(void* instance) { - static_cast(instance)->handle_parameters(); - return 0; -} - -uint32_t WINAPI handle_process_replacing_proxy(void* instance) { - static_cast(instance)->handle_process_replacing(); - return 0; -} diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 7f12527f..55e1cd3d 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -128,14 +128,6 @@ class Vst2Bridge { */ void handle_win32_events(); - // These functions are the entry points for the `*_handler` threads - // defined below. They're defined here because we can't use lambdas with - // WinAPI's `CreateThread` which is needed to support the proper call - // conventions the VST plugins expect. - void handle_dispatch_midi_events(); - void handle_parameters(); - void handle_process_replacing(); - /** * Forward the host callback made by the plugin to the host and return the * results.