// yabridge: a Wine VST bridge // Copyright (C) 2020 Robbert van der Helm // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "vst2.h" #include #include #include #include #include "../../common/communication/vst2.h" /** * A function pointer to what should be the entry point of a VST plugin. */ using VstEntryPoint = AEffect*(VST_CALL_CONV*)(audioMasterCallback); /** * This ugly global is needed so we can get the instance of a `Vst2Bridge` class * from an `AEffect` when it performs a host callback during its initialization. */ Vst2Bridge* current_bridge_instance = nullptr; /** * Needed for the rare event that two plugins are getting initialized at the * same time. */ std::mutex current_bridge_instance_mutex; /** * Opcodes that should always be handled on the main thread because they may * involve GUI operations. */ const std::set unsafe_opcodes{effOpen, effClose, effEditGetRect, effEditOpen, effEditClose, effEditIdle, effEditTop}; intptr_t VST_CALL_CONV host_callback_proxy(AEffect*, int, int, intptr_t, void*, float); /** * 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 * to avoid using globals since we need free function pointers to interface with * the VST C API. */ Vst2Bridge& get_bridge_instance(const AEffect* plugin) { // This is needed during the initialization of the plugin since we can only // add our own pointer after it's done initializing if (current_bridge_instance) { return *current_bridge_instance; } return *static_cast(plugin->ptr1); } Vst2Bridge::Vst2Bridge(MainContext& main_context, std::string plugin_dll_path, std::string endpoint_base_dir) : HostBridge(plugin_dll_path), main_context(main_context), plugin_handle(LoadLibrary(plugin_dll_path.c_str()), FreeLibrary), sockets(main_context.context, endpoint_base_dir, false) { // Got to love these C APIs if (!plugin_handle) { throw std::runtime_error("Could not load the Windows .dll file at '" + plugin_dll_path + "'"); } // VST plugin entry point functions should be called `VSTPluginMain`, but // there are some older deprecated names that legacy plugins may still use VstEntryPoint vst_entry_point = nullptr; for (auto name : {"VSTPluginMain", "main_plugin", "main"}) { vst_entry_point = reinterpret_cast(reinterpret_cast( GetProcAddress(plugin_handle.get(), name))); if (vst_entry_point) { break; } } if (!vst_entry_point) { throw std::runtime_error( "Could not find a valid VST entry point for '" + plugin_dll_path + "'."); } sockets.connect(); // Initialize after communication has been set up // We'll try to do the same `get_bridge_isntance` trick as in // `plugin/plugin.cpp`, but since the plugin will probably call the host // callback while it's initializing we sadly have to use a global here. { std::lock_guard lock(current_bridge_instance_mutex); current_bridge_instance = this; plugin = vst_entry_point(host_callback_proxy); if (!plugin) { throw std::runtime_error("VST plugin at '" + plugin_dll_path + "' failed to initialize."); } // We only needed this little hack during initialization current_bridge_instance = nullptr; plugin->ptr1 = this; } // Send the plugin's information to the Linux VST plugin. Any other updates // of this object will be sent over the `dispatcher()` socket. This would be // done after the host calls `effOpen()`, and when the plugin calls // `audioMasterIOChanged()`. sockets.host_vst_control.send(EventResult{ .return_value = 0, .payload = *plugin, .value_payload = std::nullopt}); // After sending the AEffect struct we'll receive this instance's // configuration as a response config = sockets.host_vst_control.receive_single(); parameters_handler = Win32Thread([&]() { sockets.host_vst_parameters.receive_multi( [&](Parameter request, std::vector& buffer) { // 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. if (request.value) { // `setParameter` plugin->setParameter(plugin, request.index, *request.value); ParameterResult response{std::nullopt}; sockets.host_vst_parameters.send(response, buffer); } else { // `getParameter` float value = plugin->getParameter(plugin, request.index); ParameterResult response{value}; sockets.host_vst_parameters.send(response, buffer); } }); }); 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); sockets.host_vst_process_replacing.receive_multi( [&](AudioBuffers request, std::vector& 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); // HACK: Workaround for a bug in SWAM Cello where it would call // `audioMasterGetTime()` once for every sample. The first // value returned by this function during an audio // processing cycle will be reused for the rest of the // cycle. if (config.cache_time_info) { time_info.reset(); } // 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}; sockets.host_vst_process_replacing.send(response, 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}; sockets.host_vst_process_replacing.send(response, buffer); }}, request.buffers); next_audio_buffer_midi_events.clear(); }); }); } void Vst2Bridge::run() { sockets.host_vst_dispatch.receive_events( std::nullopt, [&](Event& event, bool /*on_main_thread*/) { if (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 to those events, 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 { return passthrough_event( plugin, [&](AEffect* plugin, int opcode, int index, intptr_t value, void* data, float option) -> intptr_t { // HACK: Ardour 6.3 will call `effEditIdle` before // `effEditOpen`, which causes some plugins to // crash. This has been fixed as of // https://github.com/Ardour/ardour/commit/f7cb1b0b481eeda755bdf8eb9fc5f90a81d2aa01. // We should keep this in until Ardour 6.3 is no // longer in distro repositories. if (opcode == effEditIdle && !editor) { std::cerr << "WARNING: The host is calling " "`effEditIdle()` while the " "plugin's editor is closed, " "filtering the request (is " "this Ardour?). This bug should " "be reported to the host." << std::endl; return 0; } // Certain functions will most definitely involve the // GUI or the Win32 message loop. These functions have // to be performed on the thread that is running the IO // context, since this is also where the plugins were // instantiated and where the Win32 message loop is // handled. if (unsafe_opcodes.contains(opcode)) { std::promise dispatch_result; boost::asio::dispatch(main_context.context, [&]() { const intptr_t result = dispatch_wrapper( plugin, opcode, index, value, data, option); dispatch_result.set_value(result); }); return dispatch_result.get_future().get(); } else { return dispatch_wrapper(plugin, opcode, index, value, data, option); } }, event); } }); } intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin, int opcode, int index, intptr_t value, void* data, float option) { // We have to intercept GUI open calls since we can't use // the X11 window handle passed by the host switch (opcode) { case effEditOpen: { // Create a Win32 window through Wine, embed it into the window // provided by the host, and let the plugin embed itself into // the Wine window const auto x11_handle = reinterpret_cast(data); // Win32 window classes have to be unique for the whole application. // When hosting multiple plugins in a group process, all plugins // should get a unique window class const std::string window_class = "yabridge plugin " + sockets.base_dir.string(); Editor& editor_instance = editor.emplace(config, window_class, x11_handle, plugin); return plugin->dispatcher(plugin, opcode, index, value, editor_instance.get_win32_handle(), option); } break; case effEditClose: { const intptr_t return_value = plugin->dispatcher(plugin, opcode, index, value, data, option); // Cleanup is handled through RAII editor.reset(); return return_value; } break; default: return plugin->dispatcher(plugin, opcode, index, value, data, option); break; } } class HostCallbackDataConverter : DefaultDataConverter { public: HostCallbackDataConverter(AEffect* plugin, std::optional& time_info) : plugin(plugin), time_info(time_info) {} EventPayload read(const int opcode, const int index, const intptr_t value, const void* data) const override { switch (opcode) { case audioMasterGetTime: return WantsVstTimeInfo{}; break; case audioMasterIOChanged: // This is a helpful event that indicates that the VST // plugin's `AEffect` struct has changed. Writing these // results back is done inside of `passthrough_event()`. return AEffect(*plugin); break; case audioMasterProcessEvents: return DynamicVstEvents(*static_cast(data)); break; // We detect whether an opcode should return a string by // checking whether there's a zeroed out buffer behind the void // pointer. This works for any host, but not all plugins zero // out their buffers. case audioMasterGetVendorString: case audioMasterGetProductString: return WantsString{}; break; default: return DefaultDataConverter::read(opcode, index, value, data); break; } } std::optional read_value( const int opcode, const intptr_t value) const override { return DefaultDataConverter::read_value(opcode, value); } void write(const int opcode, void* data, const EventResult& response) const override { switch (opcode) { case audioMasterGetTime: // Write the returned `VstTimeInfo` struct into a field and // make the function return a poitner to it in the function // below. Depending on whether the host supported the // requested time information this operations returns either // a null pointer or a pointer to a `VstTimeInfo` object. if (std::holds_alternative(response.payload)) { time_info = std::nullopt; } else { time_info = std::get(response.payload); } break; default: DefaultDataConverter::write(opcode, data, response); break; } } intptr_t return_value(const int opcode, const intptr_t original) const override { switch (opcode) { case audioMasterGetTime: { // Return a pointer to the `VstTimeInfo` object written in // the function above VstTimeInfo* time_info_pointer = nullptr; if (time_info) { time_info_pointer = &*time_info; } return reinterpret_cast(time_info_pointer); } break; default: return DefaultDataConverter::return_value(opcode, original); break; } } void write_value(const int opcode, intptr_t value, const EventResult& response) const override { return DefaultDataConverter::write_value(opcode, value, response); } private: AEffect* plugin; std::optional& time_info; }; intptr_t Vst2Bridge::host_callback(AEffect* effect, int opcode, int index, intptr_t value, void* data, float option) { // HACK: Workaround for a bug in SWAM Cello where it would call // `audioMasterGetTime()` once for every sample. When this option is // enabled `time_info` should be reset in the process function. The // `time_info` value is assigned inside of // `HostCallbackDataConverter::write()`. if (config.cache_time_info && time_info) { return reinterpret_cast(&*time_info); } HostCallbackDataConverter converter(effect, time_info); return sockets.vst_host_callback.send_event(converter, std::nullopt, opcode, index, value, data, option); } intptr_t VST_CALL_CONV host_callback_proxy(AEffect* effect, int opcode, int index, intptr_t value, void* data, float option) { return get_bridge_instance(effect).host_callback(effect, opcode, index, value, data, option); }