From 0f58f3409b097f52fd179e853573a2d91dc988dd Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 23 Sep 2022 20:51:44 +0200 Subject: [PATCH] Fully implement the CLAP params extension --- src/common/logging/clap.cpp | 8 +- src/common/serialization/clap.h | 23 +++- src/common/serialization/clap/README.md | 2 +- src/common/serialization/clap/host.h | 2 + src/common/serialization/clap/plugin.h | 2 + .../bridges/clap-impls/plugin-proxy.cpp | 128 +++++++++++++++++- src/plugin/bridges/clap-impls/plugin-proxy.h | 23 ++++ src/plugin/bridges/clap.cpp | 42 ++++++ .../bridges/clap-impls/host-proxy.cpp | 40 ++++++ src/wine-host/bridges/clap-impls/host-proxy.h | 9 ++ src/wine-host/bridges/clap.cpp | 93 ++++++++++++- src/wine-host/bridges/clap.h | 1 + 12 files changed, 362 insertions(+), 11 deletions(-) diff --git a/src/common/logging/clap.cpp b/src/common/logging/clap.cpp index 52a45b28..9cae78c7 100644 --- a/src/common/logging/clap.cpp +++ b/src/common/logging/clap.cpp @@ -85,7 +85,9 @@ bool ClapLogger::log_request(bool is_host_plugin, {std::pair(supported_extensions.supports_audio_ports, CLAP_EXT_AUDIO_PORTS), std::pair(supported_extensions.supports_note_ports, - CLAP_EXT_NOTE_PORTS)}) { + CLAP_EXT_NOTE_PORTS), + std::pair(supported_extensions.supports_params, + CLAP_EXT_PARAMS)}) { if (!supported) { continue; } @@ -382,7 +384,9 @@ void ClapLogger::log_response(bool is_host_plugin, {std::pair(supported_extensions.supports_audio_ports, CLAP_EXT_AUDIO_PORTS), std::pair(supported_extensions.supports_note_ports, - CLAP_EXT_NOTE_PORTS)}) { + CLAP_EXT_NOTE_PORTS), + std::pair(supported_extensions.supports_params, + CLAP_EXT_PARAMS)}) { if (!supported) { continue; } diff --git a/src/common/serialization/clap.h b/src/common/serialization/clap.h index ce29f0e5..ba4eca49 100644 --- a/src/common/serialization/clap.h +++ b/src/common/serialization/clap.h @@ -53,7 +53,16 @@ using ClapMainThreadControlRequest = clap::ext::audio_ports::plugin::Count, clap::ext::audio_ports::plugin::Get, clap::ext::note_ports::plugin::Count, - clap::ext::note_ports::plugin::Get>; + clap::ext::note_ports::plugin::Get, + clap::ext::params::plugin::Count, + clap::ext::params::plugin::GetInfo, + clap::ext::params::plugin::GetValue, + clap::ext::params::plugin::ValueToText, + clap::ext::params::plugin::TextToValue + // Flush may be called from the audio thread and it may not be + // called at the same time as process, so that's handled using + // the audio thread handler + >; template void serialize(S& s, ClapMainThreadControlRequest& payload) { @@ -86,7 +95,8 @@ struct ClapAudioThreadControlRequest { using Payload = std::variant; + clap::plugin::Reset, + clap::ext::params::plugin::Flush>; Payload payload; @@ -131,7 +141,6 @@ struct ClapAudioThreadControlRequest { * the information we want or the operation we want to perform. A request of * type `ClapMainThreadCallbackRequest(T)` should send back a `T::Response`. */ -// TODO: Placeholder using ClapMainThreadCallbackRequest = std::variant; + clap::ext::note_ports::host::Rescan, + clap::ext::params::host::Rescan, + clap::ext::params::host::Clear, + // This doesn't need to be done on the main thread, but we + // don't have an alternative per-plugin instance socket + // available so this is probably fine + clap::ext::params::host::RequestFlush>; template void serialize(S& s, ClapMainThreadCallbackRequest& payload) { diff --git a/src/common/serialization/clap/README.md b/src/common/serialization/clap/README.md index d4073b67..dfb8542f 100644 --- a/src/common/serialization/clap/README.md +++ b/src/common/serialization/clap/README.md @@ -22,7 +22,7 @@ Yabridge currently tracks CLAP 1.1.1. The implementation status for CLAP's core | `clap.log` | :x: Not supported yet | | `clap.note-name` | :x: Not supported yet | | `clap.note-ports` | :heavy_check_mark: | -| `clap.params` | :x: Not supported yet | +| `clap.params` | :heavy_check_mark: | | `clap.posix-fd-support` | :x: Not supported yet | | `clap.render` | :x: Not supported yet | | `clap.state` | :x: Not supported yet | diff --git a/src/common/serialization/clap/host.h b/src/common/serialization/clap/host.h index 9a451961..fa769c1a 100644 --- a/src/common/serialization/clap/host.h +++ b/src/common/serialization/clap/host.h @@ -84,11 +84,13 @@ struct SupportedHostExtensions { // method bool supports_audio_ports = false; bool supports_note_ports = false; + bool supports_params = false; template void serialize(S& s) { s.value1b(supports_audio_ports); s.value1b(supports_note_ports); + s.value1b(supports_params); } }; diff --git a/src/common/serialization/clap/plugin.h b/src/common/serialization/clap/plugin.h index 75b1e302..4f29bd6e 100644 --- a/src/common/serialization/clap/plugin.h +++ b/src/common/serialization/clap/plugin.h @@ -117,11 +117,13 @@ struct SupportedPluginExtensions { // method bool supports_audio_ports = false; bool supports_note_ports = false; + bool supports_params = false; template void serialize(S& s) { s.value1b(supports_audio_ports); s.value1b(supports_note_ports); + s.value1b(supports_params); } }; diff --git a/src/plugin/bridges/clap-impls/plugin-proxy.cpp b/src/plugin/bridges/clap-impls/plugin-proxy.cpp index f0386e92..e544b326 100644 --- a/src/plugin/bridges/clap-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/clap-impls/plugin-proxy.cpp @@ -22,7 +22,9 @@ ClapHostExtensions::ClapHostExtensions(const clap_host& host) noexcept : audio_ports(static_cast( host.get_extension(&host, CLAP_EXT_AUDIO_PORTS))), note_ports(static_cast( - host.get_extension(&host, CLAP_EXT_NOTE_PORTS))) {} + host.get_extension(&host, CLAP_EXT_NOTE_PORTS))), + params(static_cast( + host.get_extension(&host, CLAP_EXT_PARAMS))) {} ClapHostExtensions::ClapHostExtensions() noexcept {} @@ -30,7 +32,8 @@ clap::host::SupportedHostExtensions ClapHostExtensions::supported() const noexcept { return clap::host::SupportedHostExtensions{ .supports_audio_ports = audio_ports != nullptr, - .supports_note_ports = note_ports != nullptr}; + .supports_note_ports = note_ports != nullptr, + .supports_params = params != nullptr}; } clap_plugin_proxy::clap_plugin_proxy(ClapPluginBridge& bridge, @@ -63,6 +66,14 @@ clap_plugin_proxy::clap_plugin_proxy(ClapPluginBridge& bridge, .count = ext_note_ports_count, .get = ext_note_ports_get, }), + ext_params_vtable(clap_plugin_params_t{ + .count = ext_params_count, + .get_info = ext_params_get_info, + .get_value = ext_params_get_value, + .value_to_text = ext_params_value_to_text, + .text_to_value = ext_params_text_to_value, + .flush = ext_params_flush, + }), // These function objects are relatively large, and we probably won't be // getting that many of them pending_callbacks_(128) {} @@ -196,6 +207,9 @@ clap_plugin_proxy::plugin_get_extension(const struct clap_plugin* plugin, } else if (self->supported_extensions_.supports_note_ports && strcmp(id, CLAP_EXT_NOTE_PORTS) == 0) { extension_ptr = &self->ext_note_ports_vtable; + } else if (self->supported_extensions_.supports_params && + strcmp(id, CLAP_EXT_PARAMS) == 0) { + extension_ptr = &self->ext_params_vtable; } self->bridge_.logger_.log_extension_query("clap_plugin::get_extension", @@ -284,3 +298,113 @@ clap_plugin_proxy::ext_note_ports_get(const clap_plugin_t* plugin, return false; } } + +uint32_t CLAP_ABI +clap_plugin_proxy::ext_params_count(const clap_plugin_t* plugin) { + assert(plugin && plugin->plugin_data); + auto self = static_cast(plugin->plugin_data); + + return self->bridge_.send_main_thread_message( + clap::ext::params::plugin::Count{.instance_id = self->instance_id()}); +} + +bool CLAP_ABI +clap_plugin_proxy::ext_params_get_info(const clap_plugin_t* plugin, + uint32_t param_index, + clap_param_info_t* param_info) { + assert(plugin && plugin->plugin_data && param_info); + auto self = static_cast(plugin->plugin_data); + + const clap::ext::params::plugin::GetInfoResponse response = + self->bridge_.send_main_thread_message( + clap::ext::params::plugin::GetInfo{ + .instance_id = self->instance_id(), + .param_index = param_index}); + if (response.result) { + response.result->reconstruct(*param_info); + + return true; + } else { + return false; + } +} + +bool CLAP_ABI +clap_plugin_proxy::ext_params_get_value(const clap_plugin_t* plugin, + clap_id param_id, + double* value) { + assert(plugin && plugin->plugin_data && value); + auto self = static_cast(plugin->plugin_data); + + const clap::ext::params::plugin::GetValueResponse response = + self->bridge_.send_main_thread_message( + clap::ext::params::plugin::GetValue{ + .instance_id = self->instance_id(), .param_id = param_id}); + if (response.result) { + *value = *response.result; + + return true; + } else { + return false; + } +} + +bool CLAP_ABI +clap_plugin_proxy::ext_params_value_to_text(const clap_plugin_t* plugin, + clap_id param_id, + double value, + char* display, + uint32_t size) { + assert(plugin && plugin->plugin_data && display); + auto self = static_cast(plugin->plugin_data); + + const clap::ext::params::plugin::ValueToTextResponse response = + self->bridge_.send_main_thread_message( + clap::ext::params::plugin::ValueToText{ + .instance_id = self->instance_id(), + .param_id = param_id, + .value = value}); + if (response.result) { + strlcpy_buffer(display, *response.result, size); + + return true; + } else { + return false; + } +} + +bool CLAP_ABI +clap_plugin_proxy::ext_params_text_to_value(const clap_plugin_t* plugin, + clap_id param_id, + const char* display, + double* value) { + assert(plugin && plugin->plugin_data && display && value); + auto self = static_cast(plugin->plugin_data); + + const clap::ext::params::plugin::TextToValueResponse response = + self->bridge_.send_main_thread_message( + clap::ext::params::plugin::TextToValue{ + .instance_id = self->instance_id(), + .param_id = param_id, + .display = display}); + if (response.result) { + *value = *response.result; + + return true; + } else { + return false; + } +} + +void CLAP_ABI +clap_plugin_proxy::ext_params_flush(const clap_plugin_t* plugin, + const clap_input_events_t* in, + const clap_output_events_t* out) { + assert(plugin && plugin->plugin_data); + auto self = static_cast(plugin->plugin_data); + + // This may also be called on the audio thread and it is never called during + // process, so always using the audio thread here is safe + self->bridge_.send_audio_thread_message( + clap::ext::params::plugin::Flush{.instance_id = self->instance_id()}); +} diff --git a/src/plugin/bridges/clap-impls/plugin-proxy.h b/src/plugin/bridges/clap-impls/plugin-proxy.h index 3547513f..25c4fb75 100644 --- a/src/plugin/bridges/clap-impls/plugin-proxy.h +++ b/src/plugin/bridges/clap-impls/plugin-proxy.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -58,6 +59,7 @@ struct ClapHostExtensions { const clap_host_audio_ports_t* audio_ports = nullptr; const clap_host_note_ports_t* note_ports = nullptr; + const clap_host_params_t* params = nullptr; }; /** @@ -139,6 +141,26 @@ class clap_plugin_proxy { bool is_input, clap_note_port_info_t* info); + static uint32_t CLAP_ABI ext_params_count(const clap_plugin_t* plugin); + static bool CLAP_ABI ext_params_get_info(const clap_plugin_t* plugin, + uint32_t param_index, + clap_param_info_t* param_info); + static bool CLAP_ABI ext_params_get_value(const clap_plugin_t* plugin, + clap_id param_id, + double* value); + static bool CLAP_ABI ext_params_value_to_text(const clap_plugin_t* plugin, + clap_id param_id, + double value, + char* display, + uint32_t size); + static bool CLAP_ABI ext_params_text_to_value(const clap_plugin_t* plugin, + clap_id param_id, + const char* display, + double* value); + static void CLAP_ABI ext_params_flush(const clap_plugin_t* plugin, + const clap_input_events_t* in, + const clap_output_events_t* out); + /** * Asynchronously run a function on the host's main thread, returning the * result as a future. @@ -207,6 +229,7 @@ class clap_plugin_proxy { // `clap_plugin::init()`. const clap_plugin_audio_ports ext_audio_ports_vtable; const clap_plugin_note_ports ext_note_ports_vtable; + const clap_plugin_params ext_params_vtable; /** * The extensions supported by the bridged plugin. Set after a successful diff --git a/src/plugin/bridges/clap.cpp b/src/plugin/bridges/clap.cpp index d65e6fe7..a2e1294e 100644 --- a/src/plugin/bridges/clap.cpp +++ b/src/plugin/bridges/clap.cpp @@ -148,6 +148,48 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path) return Ack{}; }, + [&](const clap::ext::params::host::Rescan& request) + -> clap::ext::params::host::Rescan::Response { + const auto& [plugin_proxy, _] = + get_proxy(request.owner_instance_id); + + plugin_proxy + .run_on_main_thread( + [&, host = plugin_proxy.host_, + params = plugin_proxy.host_extensions_.params]() { + params->rescan(host, request.flags); + }) + .wait(); + + return Ack{}; + }, + [&](const clap::ext::params::host::Clear& request) + -> clap::ext::params::host::Clear::Response { + const auto& [plugin_proxy, _] = + get_proxy(request.owner_instance_id); + + plugin_proxy + .run_on_main_thread( + [&, host = plugin_proxy.host_, + params = plugin_proxy.host_extensions_.params]() { + params->clear(host, request.param_id, + request.flags); + }) + .wait(); + + return Ack{}; + }, + [&](const clap::ext::params::host::RequestFlush& request) + -> clap::ext::params::host::RequestFlush::Response { + const auto& [plugin_proxy, _] = + get_proxy(request.owner_instance_id); + + // This doesn't need to be called from the main thread + plugin_proxy.host_extensions_.params->request_flush( + plugin_proxy.host_); + + return Ack{}; + }, }); }); } diff --git a/src/wine-host/bridges/clap-impls/host-proxy.cpp b/src/wine-host/bridges/clap-impls/host-proxy.cpp index e745f1ef..bf878215 100644 --- a/src/wine-host/bridges/clap-impls/host-proxy.cpp +++ b/src/wine-host/bridges/clap-impls/host-proxy.cpp @@ -51,6 +51,11 @@ clap_host_proxy::clap_host_proxy(ClapBridge& bridge, ext_note_ports_vtable(clap_host_note_ports_t{ .supported_dialects = ext_note_ports_supported_dialects, .rescan = ext_note_ports_rescan, + }), + ext_params_vtable(clap_host_params_t{ + .rescan = ext_params_rescan, + .clear = ext_params_clear, + .request_flush = ext_params_request_flush, }) {} const void* CLAP_ABI @@ -66,6 +71,9 @@ clap_host_proxy::host_get_extension(const struct clap_host* host, } else if (self->supported_extensions_.supports_note_ports && strcmp(extension_id, CLAP_EXT_NOTE_PORTS) == 0) { extension_ptr = &self->ext_note_ports_vtable; + } else if (self->supported_extensions_.supports_params && + strcmp(extension_id, CLAP_EXT_PARAMS) == 0) { + extension_ptr = &self->ext_params_vtable; } self->bridge_.logger_.log_extension_query("clap_host::get_extension", @@ -161,3 +169,35 @@ void CLAP_ABI clap_host_proxy::ext_note_ports_rescan(const clap_host_t* host, self->bridge_.send_main_thread_message(clap::ext::note_ports::host::Rescan{ .owner_instance_id = self->owner_instance_id(), .flags = flags}); } + +void CLAP_ABI +clap_host_proxy::ext_params_rescan(const clap_host_t* host, + clap_param_rescan_flags flags) { + assert(host && host->host_data); + auto self = static_cast(host->host_data); + + self->bridge_.send_main_thread_message(clap::ext::params::host::Rescan{ + .owner_instance_id = self->owner_instance_id(), .flags = flags}); +} + +void CLAP_ABI clap_host_proxy::ext_params_clear(const clap_host_t* host, + clap_id param_id, + clap_param_clear_flags flags) { + assert(host && host->host_data); + auto self = static_cast(host->host_data); + + self->bridge_.send_main_thread_message(clap::ext::params::host::Clear{ + .owner_instance_id = self->owner_instance_id(), + .param_id = param_id, + .flags = flags}); +} + +void CLAP_ABI +clap_host_proxy::ext_params_request_flush(const clap_host_t* host) { + assert(host && host->host_data); + auto self = static_cast(host->host_data); + + self->bridge_.send_main_thread_message( + clap::ext::params::host::RequestFlush{.owner_instance_id = + self->owner_instance_id()}); +} diff --git a/src/wine-host/bridges/clap-impls/host-proxy.h b/src/wine-host/bridges/clap-impls/host-proxy.h index 026f107c..833d1b2e 100644 --- a/src/wine-host/bridges/clap-impls/host-proxy.h +++ b/src/wine-host/bridges/clap-impls/host-proxy.h @@ -20,6 +20,7 @@ #include #include +#include #include #include "../../common/serialization/clap/plugin-factory.h" @@ -75,6 +76,13 @@ class clap_host_proxy { static void CLAP_ABI ext_note_ports_rescan(const clap_host_t* host, uint32_t flags); + static void CLAP_ABI ext_params_rescan(const clap_host_t* host, + clap_param_rescan_flags flags); + static void CLAP_ABI ext_params_clear(const clap_host_t* host, + clap_id param_id, + clap_param_clear_flags flags); + static void CLAP_ABI ext_params_request_flush(const clap_host_t* host); + /** * The extensions supported by the host, set just before calling * `clap_plugin::init()` on the bridged plugin. We'll allow the plugin to @@ -99,6 +107,7 @@ class clap_host_proxy { // called `clap_plugin::init()`. const clap_host_audio_ports ext_audio_ports_vtable; const clap_host_note_ports ext_note_ports_vtable; + const clap_host_params ext_params_vtable; /** * Keeps track of whether there are pending host callbacks. Used to prevent diff --git a/src/wine-host/bridges/clap.cpp b/src/wine-host/bridges/clap.cpp index f4930a32..d8c914c2 100644 --- a/src/wine-host/bridges/clap.cpp +++ b/src/wine-host/bridges/clap.cpp @@ -30,7 +30,9 @@ ClapPluginExtensions::ClapPluginExtensions(const clap_plugin& plugin) noexcept : audio_ports(static_cast( plugin.get_extension(&plugin, CLAP_EXT_AUDIO_PORTS))), note_ports(static_cast( - plugin.get_extension(&plugin, CLAP_EXT_NOTE_PORTS))) {} + plugin.get_extension(&plugin, CLAP_EXT_NOTE_PORTS))), + params(static_cast( + plugin.get_extension(&plugin, CLAP_EXT_PARAMS))) {} ClapPluginExtensions::ClapPluginExtensions() noexcept {} @@ -38,7 +40,8 @@ clap::plugin::SupportedPluginExtensions ClapPluginExtensions::supported() const noexcept { return clap::plugin::SupportedPluginExtensions{ .supports_audio_ports = audio_ports != nullptr, - .supports_note_ports = note_ports != nullptr}; + .supports_note_ports = note_ports != nullptr, + .supports_params = params != nullptr}; } ClapPluginInstance::ClapPluginInstance( @@ -362,6 +365,81 @@ void ClapBridge::run() { .result = std::nullopt}; } }, + [&](const clap::ext::params::plugin::Count& request) + -> clap::ext::params::plugin::Count::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + // We'll ignore the main thread requirement for simple array + // lookups to avoid the synchronisation costs in code code paths + return instance.extensions.params->count(instance.plugin.get()); + }, + [&](const clap::ext::params::plugin::GetInfo& request) + -> clap::ext::params::plugin::GetInfo::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + // We'll ignore the main thread requirement for simple array + // lookups to avoid the synchronisation costs in code code paths + clap_param_info_t param_info{}; + if (instance.extensions.params->get_info(instance.plugin.get(), + request.param_index, + ¶m_info)) { + return clap::ext::params::plugin::GetInfoResponse{ + .result = param_info}; + } else { + return clap::ext::params::plugin::GetInfoResponse{ + .result = std::nullopt}; + } + }, + [&](const clap::ext::params::plugin::GetValue& request) + -> clap::ext::params::plugin::GetValue::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + // We'll ignore the main thread requirement for simple array + // lookups to avoid the synchronisation costs in code code paths + double value; + if (instance.extensions.params->get_value( + instance.plugin.get(), request.param_id, &value)) { + return clap::ext::params::plugin::GetValueResponse{ + .result = value}; + } else { + return clap::ext::params::plugin::GetValueResponse{ + .result = std::nullopt}; + } + }, + [&](const clap::ext::params::plugin::ValueToText& request) + -> clap::ext::params::plugin::ValueToText::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + // We'll ignore the main thread requirement for simple array + // lookups to avoid the synchronisation costs in code code paths + std::array display{0}; + if (instance.extensions.params->value_to_text( + instance.plugin.get(), request.param_id, request.value, + display.data(), display.size())) { + return clap::ext::params::plugin::ValueToTextResponse{ + .result = display.data()}; + } else { + return clap::ext::params::plugin::ValueToTextResponse{ + .result = std::nullopt}; + } + }, + [&](const clap::ext::params::plugin::TextToValue& request) + -> clap::ext::params::plugin::TextToValue::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + // We'll ignore the main thread requirement for simple array + // lookups to avoid the synchronisation costs in code code paths + double value; + if (instance.extensions.params->text_to_value( + instance.plugin.get(), request.param_id, + request.display.c_str(), &value)) { + return clap::ext::params::plugin::TextToValueResponse{ + .result = value}; + } else { + return clap::ext::params::plugin::TextToValueResponse{ + .result = std::nullopt}; + } + }, }); } @@ -570,6 +648,17 @@ void ClapBridge::register_plugin_instance( return Ack{}; }, + [&](const clap::ext::params::plugin::Flush& request) + -> clap::ext::params::plugin::Flush::Response { + const auto& [instance, _] = + get_instance(request.instance_id); + + // TODO: Implement this + // instance.extensions.params->flush(instance.plugin.get(), + // in, out); + + return clap::ext::params::plugin::FlushResponse{}; + }, }); }); diff --git a/src/wine-host/bridges/clap.h b/src/wine-host/bridges/clap.h index 579ef8f6..ed55db75 100644 --- a/src/wine-host/bridges/clap.h +++ b/src/wine-host/bridges/clap.h @@ -68,6 +68,7 @@ struct ClapPluginExtensions { const clap_plugin_audio_ports_t* audio_ports = nullptr; const clap_plugin_note_ports_t* note_ports = nullptr; + const clap_plugin_params_t* params = nullptr; }; /**