diff --git a/src/wine-host/bridges/clap.cpp b/src/wine-host/bridges/clap.cpp new file mode 100644 index 00000000..8e7183a2 --- /dev/null +++ b/src/wine-host/bridges/clap.cpp @@ -0,0 +1,344 @@ +// yabridge: a Wine plugin bridge +// Copyright (C) 2020-2022 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 "clap.h" + +#include + +// Generated inside of the build directory +#include + +// TODO: Query extensions in the initializer list +ClapPluginExtensions::ClapPluginExtensions(const clap_plugin& plugin) noexcept { +} + +ClapPluginInstance::ClapPluginInstance(const clap_plugin* plugin) noexcept + : plugin((assert(plugin), plugin), plugin->destroy), extensions(*plugin) {} + +ClapBridge::ClapBridge(MainContext& main_context, + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + std::string plugin_dll_path, + std::string endpoint_base_dir, + pid_t parent_pid) + : HostBridge(main_context, plugin_dll_path, parent_pid), + logger_(generic_logger_), + sockets_(main_context.context_, endpoint_base_dir, false) { + // TODO: Load the CLAP module + // std::string error; + // module_ = CLAP::Hosting::Win32Module::create(plugin_dll_path, error); + // if (!module_) { + // throw std::runtime_error("Could not load the CLAP module for '" + + // plugin_dll_path + "': " + error); + // } + + sockets_.connect(); + + // Fetch this instance's configuration from the plugin to finish the setup + // process + config_ = sockets_.plugin_host_main_thread_callback_.send_message( + WantsConfiguration{.host_version = yabridge_git_version}, std::nullopt); + + // Allow this plugin to configure the main context's tick rate + main_context.update_timer_interval(config_.event_loop_interval()); +} + +bool ClapBridge::inhibits_event_loop() noexcept { + std::shared_lock lock(object_instances_mutex_); + + for (const auto& [instance_id, instance] : object_instances_) { + if (!instance.is_initialized) { + return true; + } + } + + return false; +} + +void ClapBridge::run() { + set_realtime_priority(true); + + // TODO: Listen on the socket + // sockets_.host_plugin_control_.receive_messages( + // std::nullopt, + // overload{ + // [&](const ClapPluginFactoryProxy::Construct&) + // -> ClapPluginFactoryProxy::Construct::Response { + // return ClapPluginFactoryProxy::ConstructArgs( + // module_->getFactory().get()); + // }, + // }); +} + +// TODO: Implement this +// bool ClapBridge::maybe_resize_editor(size_t instance_id, +// const Steinberg::ViewRect& new_size) { +// const auto& [instance, _] = get_instance(instance_id); + +// if (instance.editor) { +// instance.editor->resize(new_size.getWidth(), new_size.getHeight()); +// return true; +// } else { +// return false; +// } +// } + +void ClapBridge::close_sockets() { + sockets_.close(); +} + +size_t ClapBridge::generate_instance_id() noexcept { + return current_instance_id_.fetch_add(1); +} + +std::pair> +ClapBridge::get_instance(size_t instance_id) noexcept { + std::shared_lock lock(object_instances_mutex_); + + return std::pair>( + object_instances_.at(instance_id), std::move(lock)); +} + +// TODO: Implement audio processing +// std::optional ClapBridge::setup_shared_audio_buffers( +// size_t instance_id) { +// const auto& [instance, _] = get_instance(instance_id); + +// const Steinberg::IPtr component = +// instance.interfaces.component; +// const Steinberg::IPtr audio_processor = +// instance.interfaces.audio_processor; + +// if (!instance.process_setup || !component || !audio_processor) { +// return std::nullopt; +// } + +// // We'll query the plugin for its audio bus layouts, and then create +// // calculate the offsets in a large memory buffer for the different audio +// // channels. The offsets for each audio channel are in samples (since +// // they'll be used with pointer arithmetic in `AudioShmBuffer`). +// uint32_t current_offset = 0; + +// auto create_bus_offsets = [&, &setup = instance.process_setup]( +// Steinberg::Vst::BusDirection direction) { +// const auto num_busses = +// component->getBusCount(Steinberg::Vst::kAudio, direction); + +// // This function is also run from `IAudioProcessor::setActive()`. +// // According to the docs this does not need to be realtime-safe, but we +// // should at least still try to not do anything expensive when no work +// // needs to be done. +// llvm::SmallVector, 16> bus_offsets( +// num_busses); +// for (int bus = 0; bus < num_busses; bus++) { +// Steinberg::Vst::SpeakerArrangement speaker_arrangement{}; +// audio_processor->getBusArrangement(direction, bus, +// speaker_arrangement); + +// const size_t num_channels = +// std::bitset( +// speaker_arrangement) +// .count(); +// bus_offsets[bus].resize(num_channels); + +// for (size_t channel = 0; channel < num_channels; channel++) { +// bus_offsets[bus][channel] = current_offset; +// current_offset += setup->maxSamplesPerBlock; +// } +// } + +// return bus_offsets; +// }; + +// // Creating the audio buffer offsets for every channel in every bus will +// // advacne `current_offset` to keep pointing to the starting position for +// // the next channel +// const auto input_bus_offsets = +// create_bus_offsets(Steinberg::Vst::kInput); const auto output_bus_offsets +// = create_bus_offsets(Steinberg::Vst::kOutput); + +// // The size of the buffer is in bytes, and it will depend on whether the +// // host is going to pass 32-bit or 64-bit audio to the plugin +// const bool double_precision = +// instance.process_setup->symbolicSampleSize == +// Steinberg::Vst::kSample64; +// const uint32_t buffer_size = +// current_offset * (double_precision ? sizeof(double) : sizeof(float)); + +// // If this function has been called previously and the size did not change, +// // then we should not do any work +// if (instance.process_buffers && +// instance.process_buffers->config_.size == buffer_size) { +// return std::nullopt; +// } + +// // Because the above check should be super cheap, we'll now need to convert +// // the stack allocated SmallVectors to regular heap vectors +// std::vector> input_bus_offsets_vector; +// input_bus_offsets_vector.reserve(input_bus_offsets.size()); +// for (const auto& channel_offsets : input_bus_offsets) { +// input_bus_offsets_vector.push_back( +// std::vector(channel_offsets.begin(), channel_offsets.end())); +// } + +// std::vector> output_bus_offsets_vector; +// output_bus_offsets_vector.reserve(output_bus_offsets.size()); +// for (const auto& channel_offsets : output_bus_offsets) { +// output_bus_offsets_vector.push_back( +// std::vector(channel_offsets.begin(), channel_offsets.end())); +// } + +// // We'll set up these shared memory buffers on the Wine side first, and then +// // when this request returns we'll do the same thing on the native plugin +// // side +// AudioShmBuffer::Config buffer_config{ +// .name = sockets_.base_dir_.filename().string() + "-" + +// std::to_string(instance_id), +// .size = buffer_size, +// .input_offsets = std::move(input_bus_offsets_vector), +// .output_offsets = std::move(output_bus_offsets_vector)}; +// if (!instance.process_buffers) { +// instance.process_buffers.emplace(buffer_config); +// } else { +// instance.process_buffers->resize(buffer_config); +// } + +// // After setting up the shared memory buffer, we need to create a vector of +// // channel audio pointers for every bus. These will then be assigned to the +// // `AudioBusBuffers` objects in the `ProcessData` struct in +// // `YaProcessData::reconstruct()` before passing the reconstructed process +// // data to `IAudioProcessor::process()`. +// auto set_bus_pointers = +// [&] F>( +// std::vector>& bus_pointers, +// const std::vector>& bus_offsets, +// F&& get_channel_pointer) { +// bus_pointers.resize(bus_offsets.size()); + +// for (size_t bus = 0; bus < bus_offsets.size(); bus++) { +// bus_pointers[bus].resize(bus_offsets[bus].size()); + +// for (size_t channel = 0; channel < bus_offsets[bus].size(); +// channel++) { +// bus_pointers[bus][channel] = +// get_channel_pointer(bus, channel); +// } +// } +// }; + +// set_bus_pointers( +// instance.process_buffers_input_pointers, +// instance.process_buffers->config_.input_offsets, +// [&, &instance = instance](uint32_t bus, uint32_t channel) -> void* { +// if (double_precision) { +// return instance.process_buffers->input_channel_ptr( +// bus, channel); +// } else { +// return instance.process_buffers->input_channel_ptr( +// bus, channel); +// } +// }); +// set_bus_pointers( +// instance.process_buffers_output_pointers, +// instance.process_buffers->config_.output_offsets, +// [&, &instance = instance](uint32_t bus, uint32_t channel) -> void* { +// if (double_precision) { +// return instance.process_buffers->output_channel_ptr( +// bus, channel); +// } else { +// return instance.process_buffers->output_channel_ptr( +// bus, channel); +// } +// }); + +// return buffer_config; +// } + +size_t ClapBridge::register_plugin_instance(const clap_plugin* plugin) { + std::unique_lock lock(object_instances_mutex_); + + if (!plugin) { + throw std::invalid_argument("The plugin pointer cannot be null"); + } + + const size_t instance_id = generate_instance_id(); + object_instances_.emplace(instance_id, plugin); + + // Every plugin instance gets its own audio thread + std::promise socket_listening_latch; + object_instances_.at(instance_id) + .audio_thread_handler = Win32Thread([&, instance_id]() { + set_realtime_priority(true); + + // XXX: Like with VST2 worker threads, when using plugin groups the + // thread names from different plugins will clash. Not a huge + // deal probably, since duplicate thread names are still more + // useful than no thread names. + const std::string thread_name = "audio-" + std::to_string(instance_id); + pthread_setname_np(pthread_self(), thread_name.c_str()); + + // TODO: Listen on the socket + // sockets_.add_audio_processor_and_listen( + // instance_id, socket_listening_latch, + // overload{ + // [&](YaAudioProcessor::SetBusArrangements& request) + // -> YaAudioProcessor::SetBusArrangements::Response { + // const auto& [instance, _] = + // get_instance(request.instance_id); + + // // HACK: WA Production Imperfect CLAP somehow requires + // // `inputs` to be a valid pointer, even if there + // // are no inputs. + // Steinberg::Vst::SpeakerArrangement empty_arrangement = + // 0b00000000; + + // return instance.interfaces.audio_processor + // ->setBusArrangements( + // request.num_ins > 0 ? request.inputs.data() + // : &empty_arrangement, + // request.num_ins, + // request.num_outs > 0 ? request.outputs.data() + // : &empty_arrangement, + // request.num_outs); + // }, + // }); + }); + + // Wait for the new socket to be listening on before continuing. Otherwise + // the native plugin may try to connect to it before our thread is up and + // running. + socket_listening_latch.get_future().wait(); + + return instance_id; +} + +void ClapBridge::unregister_object_instance(size_t instance_id) { + sockets_.remove_audio_thread(instance_id); + + // Remove the instance from within the main IO context so + // removing it doesn't interfere with the Win32 message loop + // XXX: I don't think we have to wait for the object to be + // deleted most of the time, but I can imagine a situation + // where the plugin does a host callback triggered by a + // Win32 timer in between where the above closure is being + // executed and when the actual host application context on + // the plugin side gets deallocated. + main_context_ + .run_in_context([&, instance_id]() -> void { + std::unique_lock lock(object_instances_mutex_); + object_instances_.erase(instance_id); + }) + .wait(); +} diff --git a/src/wine-host/bridges/clap.h b/src/wine-host/bridges/clap.h new file mode 100644 index 00000000..8d8b9d39 --- /dev/null +++ b/src/wine-host/bridges/clap.h @@ -0,0 +1,430 @@ +// yabridge: a Wine plugin bridge +// Copyright (C) 2020-2022 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 . + +#pragma once + +#include +#include +#include +#include + +#include + +#include "../../common/audio-shm.h" +#include "../../common/communication/clap.h" +#include "../../common/configuration.h" +#include "../../common/mutual-recursion.h" +#include "../editor.h" +#include "common.h" + +/** + * Pointers to all of a CLAP plugin's extension structs. These will be null if + * the plugin doesn't support the extensions. + * + * @relates ClapPluginInstance + */ +struct ClapPluginExtensions { + /** + * Query all of the plugin's extensions. This can only be done after the + * call to init. + * + * TODO: Return the supported extensions along with the init call. + */ + ClapPluginExtensions(const clap_plugin& plugin) noexcept; + + // TODO: Supported extensions +}; + +/** + * A CLAP plugin instance. This is created when the plugin is created from the + * plugin factory. Dropping this object will also destroy the plugin instance, + * but it will still need to be manually unregistered from the `ClapBridge`'s + * object instance map. The extensions object is queried after the host calls + * the init function. Before that time all extension pointers will be null + * pointers. + */ +struct ClapPluginInstance { + /** + * Bind a CLAP plugin pointer to this plugin instance object. This can only + * be done once per plugin pointer. The pointer must be non-null. + */ + ClapPluginInstance(const clap_plugin* plugin) noexcept; + + public: + /** + * A dedicated thread for handling incoming audio thread function calls. + */ + Win32Thread audio_thread_handler; + + // TODO: Proxies for host extension objects + + /** + * A shared memory object we'll write the input audio buffers to on the + * native plugin side. We'll then let the plugin write its outputs here on + * the Wine side. The buffer will be configured during + * `clap_plugin::activate()`. At that point we'll build the configuration + * for the object here, on the Wine side, and then we'll initialize the + * buffers using that configuration. This same configuration is then used on + * the native plugin side to connect to this same shared memory object for + * the matching plugin instance. + */ + std::optional process_buffers; + + /** + * Pointers to the per-bus input channels in process_buffers so we can pass + * them to the plugin after a call to `YaProcessData::reconstruct()`. These + * can be either `float*` or `double*`, so we sadly have to use void + * pointers here. + * + * FIXME: Update docstring for CLAP + */ + std::vector> process_buffers_input_pointers; + + /** + * Pointers to the per-bus output channels in process_buffers so we can pass + * them to the plugin after a call to `YaProcessData::reconstruct()`. These + * can be either `float*` or `double*`, so we sadly have to use void + * pointers here. + * + * FIXME: Update docstring for CLAP + */ + std::vector> process_buffers_output_pointers; + + /** + * This instance's editor, if it has an open editor. Embedding here works + * exactly the same as how it works for VST2 plugins. + */ + std::optional editor; + + /** + * The plugin object. The plugin gets destroyed together with this struct. + */ + std::unique_ptr plugin; + + /** + * Contains the plugin's supported extensions. Initialized after the host + * calls `clap_plugin::init()`. + */ + ClapPluginExtensions extensions; + + /** + * Whether `clap_plugin::init()` has already been called for this object + * instance. Some VST2 and VST3 plugins would have memory errors if the + * Win32 message loop is run in between creating the plugin and initializing + * it, so we're also preventing this for CLAP as a precaution. + */ + bool is_initialized = false; + + // TODO: Add this when we add support for audio processing + // /** + // * The plugin's current process setup, containing information about the + // * buffer sizes, sample rate, and processing mode. Used for setting up + // * shared memory audio buffers, and to know whether the plugin instance + // is + // * currently in offline processing mode or not. The latter is needed as a + // * HACK for IK Multimedia's T-RackS 5 because those plugins will deadlock + // if + // * they don't process audio from the GUI thread while doing offline + // * processing. + // */ + // std::optional process_setup; +}; + +/** + * This hosts a Windows CLAP plugin, forwards messages sent by the Linux CLAP + * plugin and provides host callback function for the plugin to talk back. + */ +class ClapBridge : public HostBridge { + public: + /** + * Initializes the Windows CLAP plugin and set up communication with the + * native Linux CLAP plugin. + * + * @param main_context The main IO context for this application. Most events + * will be dispatched to this context, and the event handling loop should + * also be run from this context. + * @param plugin_dll_path A (Unix style) path to the Windows .clap file to + * load. In yabridgectl we'll create symlinks to these using a `.clap-win` + * file extension as CLAP uses the same file extension on Windows and + * Linux. + * @param endpoint_base_dir The base directory used for the socket + * endpoints. See `Sockets` for more information. + * @param parent_pid The process ID of the native plugin host this bridge is + * supposed to communicate with. Used as part of our watchdog to prevent + * dangling Wine processes. + * + * @note The object has to be constructed from the same thread that calls + * `main_context.run()`. + * + * @throw std::runtime_error Thrown when the CLAP plugin could not be + * loaded, or if communication could not be set up. + */ + ClapBridge(MainContext& main_context, + std::string plugin_dll_path, + std::string endpoint_base_dir, + pid_t parent_pid); + + /** + * This returns `true` if `clap_plugin::init()` has not yet been called for + * any of the registered plugins. Some VST2 and VST3 plugins have memory + * errors if the Win32 message loop is pumped before init is called, so + * we'll just keep the same behaviour for CLAP just in case. + */ + bool inhibits_event_loop() noexcept override; + + /** + * Here we'll listen for and handle incoming control messages until the + * sockets get closed. + */ + void run() override; + + // TODO: Editor resizing + // /** + // * If the plugin instance has an editor, resize the wrapper window to + // match + // * the new size. This is called from `IPlugFrame::resizeView()` to make + // sure + // * we do the resize before the request gets sent to the host. + // */ + // bool maybe_resize_editor(size_t instance_id, + // const Steinberg::ViewRect& new_size); + + protected: + void close_sockets() override; + + public: + /** + * Send a callback message to the host return the response. This is a + * shorthand for `sockets.plugin_host_callback_.send_message` for use in + * CLAP interface implementations. + */ + template + typename T::Response send_message(const T& object) { + return sockets_.plugin_host_main_thread_callback_.send_message( + object, std::nullopt); + } + + /** + * When called from the GUI thread, spawn a new thread and call + * `send_message()` from there, and then handle functions passed by calls to + * `do_mutual_recursion_on_gui_thread()` and + * `do_mutual_recursion_on_off_thread()` on this thread until we get a + * response back. See the function in `Vst3Bridge` for a much more in-depth + * explanatio nof why this is neede.d + * + * TODO: Is this needed for CLAP? + */ + template + typename T::Response send_mutually_recursive_message(const T& object) { + if (main_context_.is_gui_thread()) { + return mutual_recursion_.fork( + [&]() { return send_message(object); }); + } else { + // TODO: Remove if this isn't needed + logger_.log_trace([]() { + return "'ClapBridge::send_mutually_recursive_message()' called " + "from a non-GUI thread, sending the message directly"; + }); + send_message(object); + + // return audio_thread_mutual_recursion_.fork( + // [&]() { return send_message(object); }); + } + } + + /** + * Crazy functions ask for crazy naming. This is the other part of + * `send_mutually_recursive_message()`, for executing mutually recursive + * functions on the GUI thread. If another thread is currently calling that + * function (from the UI thread), then we'll execute `fn` from the UI thread + * using the IO context started in the above function. Otherwise `f` will be + * run on the UI thread through `main_context_` as usual. + * + * @see ClapBridge::send_mutually_recursive_message + */ + template + std::invoke_result_t do_mutual_recursion_on_gui_thread(F&& fn) { + // If the above function is currently being called from some thread, + // then we'll call `fn` from that same thread. Otherwise we'll just + // submit it to the main IO context. + if (const auto result = + mutual_recursion_.maybe_handle(std::forward(fn))) { + return *result; + } else { + return main_context_.run_in_context(std::forward(fn)).get(); + } + } + + // TODO: Check if this is needed, remove if it isn't + + // /** + // * The same as the above function, but we'll just execute the function on + // * this thread when the mutual recursion context is not active. + // * + // * @see ClapBridge::do_mutual_recursion_on_gui_thread + // */ + // template + // std::invoke_result_t do_mutual_recursion_on_off_thread(F&& fn) { + // if (const auto result = audio_thread_mutual_recursion_.maybe_handle( + // std::forward(fn))) { + // return *result; + // } else { + // return mutual_recursion_.handle(std::forward(fn)); + // } + // } + + /** + * A logger instance we'll use to log about failed + * `clap_host::get_extension()` calls, so they can be hidden on verbosity + * level 0. + * + * This only has to be used instead of directly writing to `std::cerr` when + * the message should be hidden on lower verbosity levels. + * + * TODO: Actually use this + */ + ClapLogger logger_; + + private: + /** + * Generate a nique instance identifier using an atomic fetch-and-add. This + * is used to be able to refer to specific plugin instances in the messages. + */ + size_t generate_instance_id() noexcept; + + /** + * Fetch the plugin instance along with a lock valid for the instance's + * lifetime. This is mostly just to save some boilerplate everywhere. Use + * C++17's structured binding as syntactic sugar to not have to deal with + * the lock handle. + */ + std::pair> + get_instance(size_t instance_id) noexcept; + + /** + * Sets up the shared memory audio buffers for a plugin instance plugin + * instance and return the configuration so the native plugin can connect to + * it as well. + * + * This returns a nullopt when the buffer size parameters passed to + * `clap_plugin::activate()` have not yet been set in the + * `ClapPluginInstance`. + * + * A nullopt will also be returned if this is called again after shared + * audio buffers have been set up and the audio buffer size has not changed. + */ + std::optional setup_shared_audio_buffers( + size_t instance_id); + + /** + * Assign a unique identifier to an object and add it to + * `object_instances_`. This will also set up an audio thread socket + * listener for the plugin instance. + */ + size_t register_plugin_instance(const clap_plugin* plugin); + + /** + * Remove an object from `object_instances_`. Will also tear down the + * instance's audio thread. + */ + void unregister_object_instance(size_t instance_id); + + /** + * The configuration for this instance of yabridge based on the path to the + * `.so` (or well `.clap`) file that got loaded by the host. This + * configuration gets loaded on the plugin side, and then sent over to the + * Wine host as part of the startup process. + */ + Configuration config_; + + // TODO: Grab the entry point and factory + // std::shared_ptr module_; + + /** + * All sockets used for communicating with this specific plugin. + * + * NOTE: This is defined **after** the threads on purpose. This way the + * sockets will be closed first, and we can then safely wait for the + * threads to exit. + */ + ClapSockets sockets_; + + /** + * Used to assign a unique identifier to created plugin instances so they + * can be referred to later. + * + * @related generate_instance_id + */ + std::atomic_size_t current_instance_id_; + + /** + * These are all the objects we have created through the Windows CLAP + * plugins' plugin factory. The keys in all of these maps are the unique + * identifiers we generated for them so we can identify specific instances. + * During the proxy object's destructor (on the plugin side), we'll get a + * request to remove the corresponding plugin object from this map. This + * will cause all pointers to it to get dropped and the object to be cleaned + * up. + */ + std::unordered_map object_instances_; + /** + * In theory all object handling is safe iff the host also doesn't do + * anything weird even without locks. The only time a data race can occur is + * when the host removes or inserts a plugin while also interacting with + * other plugins on different threads. Since the lock should never be + * contested, we should also not get a measurable performance penalty from + * making double sure nothing can go wrong. + * + * TODO: At some point replace this with a multiple reader single writer + * lock based by a spinlock. Because this lock is rarely contested + * `get_instance()` never yields to the scheduler during audio + * processing, but it's still something we should avoid at all costs. + */ + std::shared_mutex object_instances_mutex_; + + /** + * Used in `send_mutually_recursive_message()` to be able to execute + * functions from that same calling thread (through + * `do_mutual_recursion_on_gui_thread()` and + * `do_mutual_recursion_on_off_thread()`) while we're waiting for a + * response. + */ + MutualRecursionHelper mutual_recursion_; + + // TODO: Check if this is needed, remove it if it isn't + // /** + // * The same thing as above, but just for the pair of + // * `IEditController::setParamNormalized()` and + // * `IComponentHandler::performEdit()`, when + // * `IComponentHandler::performEdit()` is called from an audio thread. + // * + // * HACK: This is sadly needed to work around an interaction between a bug + // in + // * JUCE with a bug in Ardour/Mixbus. JUCE calls + // * `IComponentHandler::performEdit()` from the audio thread instead + // of + // * using the output parameters, and Ardour/Mixbus immediately call + // * `IEditController::setParamNormalized()` with the same value + // after + // * the plugin calls `IComponentHandler::performEdit()`. Both of + // these + // * functions need to be run on the same thread (because of + // recursive + // * mutexes), but they may not interfere with the GUI thread if + // * `IComponentHandler::performEdit()` wasn't called from there. + // */ + // MutualRecursionHelper audio_thread_mutual_recursion_; +}; diff --git a/src/wine-host/host.cpp b/src/wine-host/host.cpp index 2213ef91..ff58fcb7 100644 --- a/src/wine-host/host.cpp +++ b/src/wine-host/host.cpp @@ -22,6 +22,9 @@ #include #include "../common/utils.h" +#ifdef WITH_CLAP +#include "bridges/clap.h" +#endif #include "bridges/group.h" #include "bridges/vst2.h" #ifdef WITH_VST3 @@ -138,6 +141,19 @@ int YABRIDGE_EXPORT std::unique_ptr bridge; try { switch (plugin_type) { + case PluginType::clap: +#ifdef WITH_CLAP + bridge = std::make_unique( + main_context, plugin_location, socket_endpoint_path, + parent_pid); +#else + std::cerr + << "This version of yabridge has not been compiled " + "with CLAP support" + << std::endl; + return 1; +#endif + break; case PluginType::vst2: bridge = std::make_unique( main_context, plugin_location, socket_endpoint_path, diff --git a/src/wine-host/meson.build b/src/wine-host/meson.build index 9ff0d195..a124e36b 100644 --- a/src/wine-host/meson.build +++ b/src/wine-host/meson.build @@ -16,6 +16,9 @@ if is_64bit_system wine_threads_dep, xcb_64bit_dep, ] + if with_clap + host_64bit_deps += [clap_dep] + endif if with_vst3 host_64bit_deps += [ vst3_sdk_hosting_wine_64bit_dep, @@ -41,6 +44,9 @@ if with_bitbridge wine_threads_dep, xcb_32bit_dep, ] + if with_clap + host_32bit_deps += [clap_dep] + endif if with_vst3 host_32bit_deps += [ vst3_sdk_hosting_wine_32bit_dep, @@ -71,6 +77,13 @@ host_sources = files( 'xdnd-proxy.cpp', ) +if with_clap + host_sources += files( + '../common/logging/clap.cpp', + 'bridges/clap.cpp', + ) +endif + if with_vst3 host_sources += files( '../common/logging/vst3.cpp', @@ -135,5 +148,5 @@ if with_vst3 'bridges/vst3-impls/host-context-proxy.cpp', 'bridges/vst3-impls/plug-frame-proxy.cpp', 'bridges/vst3.cpp', -) + ) endif