From c7ea37309da09d8eb370adcdbbbe2edce6344662 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 26 Sep 2022 18:26:42 +0200 Subject: [PATCH] Implement the CLAP latency extension --- src/common/logging/clap.cpp | 15 +++++ src/common/logging/clap.h | 4 ++ src/common/serialization/clap.h | 3 + src/common/serialization/clap/README.md | 2 +- src/common/serialization/clap/ext/latency.h | 67 +++++++++++++++++++ src/common/serialization/clap/host.cpp | 4 +- src/common/serialization/clap/host.h | 4 +- src/common/serialization/clap/plugin.cpp | 4 +- src/common/serialization/clap/plugin.h | 4 +- .../bridges/clap-impls/plugin-proxy.cpp | 18 +++++ src/plugin/bridges/clap-impls/plugin-proxy.h | 5 ++ src/plugin/bridges/clap.cpp | 16 +++++ .../bridges/clap-impls/host-proxy.cpp | 14 ++++ src/wine-host/bridges/clap-impls/host-proxy.h | 4 ++ src/wine-host/bridges/clap.cpp | 11 +++ src/wine-host/bridges/clap.h | 1 + 16 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 src/common/serialization/clap/ext/latency.h diff --git a/src/common/logging/clap.cpp b/src/common/logging/clap.cpp index 0441becc..419b02f0 100644 --- a/src/common/logging/clap.cpp +++ b/src/common/logging/clap.cpp @@ -217,6 +217,13 @@ bool ClapLogger::log_request( }); } +bool ClapLogger::log_request(bool is_host_plugin, + const clap::ext::latency::plugin::Get& request) { + return log_request_base(is_host_plugin, [&](auto& message) { + message << request.instance_id << ": clap_plugin_latency::get()"; + }); +} + bool ClapLogger::log_request(bool is_host_plugin, const clap::plugin::StartProcessing& request) { return log_request_base(is_host_plugin, [&](auto& message) { @@ -343,6 +350,14 @@ bool ClapLogger::log_request( }); } +bool ClapLogger::log_request(bool is_host_plugin, + const clap::ext::latency::host::Changed& request) { + return log_request_base(is_host_plugin, [&](auto& message) { + message << request.owner_instance_id + << ": clap_host_latency::changed()"; + }); +} + bool ClapLogger::log_request(bool is_host_plugin, const clap::ext::tail::host::Changed& request) { return log_request_base(is_host_plugin, [&](auto& message) { diff --git a/src/common/logging/clap.h b/src/common/logging/clap.h index ab195051..c2d357b5 100644 --- a/src/common/logging/clap.h +++ b/src/common/logging/clap.h @@ -96,6 +96,8 @@ class ClapLogger { const clap::ext::params::plugin::ValueToText&); bool log_request(bool is_host_plugin, const clap::ext::params::plugin::TextToValue&); + bool log_request(bool is_host_plugin, + const clap::ext::latency::plugin::Get&); // Audio thread control messages bool log_request(bool is_host_plugin, const clap::plugin::StartProcessing&); @@ -124,6 +126,8 @@ class ClapLogger { const clap::ext::params::host::Clear&); bool log_request(bool is_host_plugin, const clap::ext::params::host::RequestFlush&); + bool log_request(bool is_host_plugin, + const clap::ext::latency::host::Changed&); // Audio thread callbacks bool log_request(bool is_host_plugin, diff --git a/src/common/serialization/clap.h b/src/common/serialization/clap.h index 035554db..8a529cdc 100644 --- a/src/common/serialization/clap.h +++ b/src/common/serialization/clap.h @@ -23,6 +23,7 @@ #include "../bitsery/ext/message-reference.h" #include "../utils.h" #include "clap/ext/audio-ports.h" +#include "clap/ext/latency.h" #include "clap/ext/note-ports.h" #include "clap/ext/params.h" #include "clap/ext/tail.h" @@ -53,6 +54,7 @@ using ClapMainThreadControlRequest = clap::plugin::Deactivate, clap::ext::audio_ports::plugin::Count, clap::ext::audio_ports::plugin::Get, + clap::ext::latency::plugin::Get, clap::ext::note_ports::plugin::Count, clap::ext::note_ports::plugin::Get, clap::ext::params::plugin::Count, @@ -147,6 +149,7 @@ using ClapMainThreadCallbackRequest = std::variant. + +#pragma once + +#include + +#include "../../common.h" + +// Serialization messages for `clap/ext/latency.h` + +namespace clap { +namespace ext { +namespace latency { + +namespace plugin { + +/** + * Message struct for `clap_plugin_latency::get()`. + */ +struct Get { + using Response = PrimitiveResponse; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } +}; + +} // namespace plugin + +namespace host { + +/** + * Message struct for `clap_host_latency::changed()`. + */ +struct Changed { + using Response = Ack; + + native_size_t owner_instance_id; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + } +}; + +} // namespace host + +} // namespace latency +} // namespace ext +} // namespace clap diff --git a/src/common/serialization/clap/host.cpp b/src/common/serialization/clap/host.cpp index 30b5db68..a4bf863b 100644 --- a/src/common/serialization/clap/host.cpp +++ b/src/common/serialization/clap/host.cpp @@ -17,6 +17,7 @@ #include "host.h" #include +#include #include #include #include @@ -31,9 +32,10 @@ Host::Host(const clap_host_t& original) url(original.url ? std::optional(original.url) : std::nullopt), version((assert(original.version), original.version)) {} -std::array, 4> SupportedHostExtensions::list() +std::array, 5> SupportedHostExtensions::list() const noexcept { return {std::pair(supports_audio_ports, CLAP_EXT_AUDIO_PORTS), + std::pair(supports_latency, CLAP_EXT_LATENCY), std::pair(supports_note_ports, CLAP_EXT_NOTE_PORTS), std::pair(supports_params, CLAP_EXT_PARAMS), std::pair(supports_tail, CLAP_EXT_TAIL)}; diff --git a/src/common/serialization/clap/host.h b/src/common/serialization/clap/host.h index 6d1b52e6..4e4996d7 100644 --- a/src/common/serialization/clap/host.h +++ b/src/common/serialization/clap/host.h @@ -82,6 +82,7 @@ struct Host { struct SupportedHostExtensions { // Don't forget to add new extensions to below method bool supports_audio_ports = false; + bool supports_latency = false; bool supports_note_ports = false; bool supports_params = false; bool supports_tail = false; @@ -90,11 +91,12 @@ struct SupportedHostExtensions { * Get a list of `` tuples for the supported * extensions. Used during logging. */ - std::array, 4> list() const noexcept; + std::array, 5> list() const noexcept; template void serialize(S& s) { s.value1b(supports_audio_ports); + s.value1b(supports_latency); s.value1b(supports_note_ports); s.value1b(supports_params); s.value1b(supports_tail); diff --git a/src/common/serialization/clap/plugin.cpp b/src/common/serialization/clap/plugin.cpp index 55919039..72d7d163 100644 --- a/src/common/serialization/clap/plugin.cpp +++ b/src/common/serialization/clap/plugin.cpp @@ -17,6 +17,7 @@ #include "plugin.h" #include +#include #include #include #include @@ -79,9 +80,10 @@ const clap_plugin_descriptor_t* Descriptor::get() const { return &clap_descriptor; } -std::array, 4> SupportedPluginExtensions::list() +std::array, 5> SupportedPluginExtensions::list() const noexcept { return {std::pair(supports_audio_ports, CLAP_EXT_AUDIO_PORTS), + std::pair(supports_latency, CLAP_EXT_LATENCY), std::pair(supports_note_ports, CLAP_EXT_NOTE_PORTS), std::pair(supports_params, CLAP_EXT_PARAMS), std::pair(supports_tail, CLAP_EXT_TAIL)}; diff --git a/src/common/serialization/clap/plugin.h b/src/common/serialization/clap/plugin.h index addd5179..795fba4c 100644 --- a/src/common/serialization/clap/plugin.h +++ b/src/common/serialization/clap/plugin.h @@ -115,6 +115,7 @@ struct Descriptor { struct SupportedPluginExtensions { // Don't forget to add new extensions to below method bool supports_audio_ports = false; + bool supports_latency = false; bool supports_note_ports = false; bool supports_params = false; bool supports_tail = false; @@ -123,11 +124,12 @@ struct SupportedPluginExtensions { * Get a list of `` tuples for the supported * extensions. Used during logging. */ - std::array, 3> list() const noexcept; + std::array, 5> list() const noexcept; template void serialize(S& s) { s.value1b(supports_audio_ports); + s.value1b(supports_latency); s.value1b(supports_note_ports); s.value1b(supports_params); s.value1b(supports_tail); diff --git a/src/plugin/bridges/clap-impls/plugin-proxy.cpp b/src/plugin/bridges/clap-impls/plugin-proxy.cpp index e1e450c0..452c2c39 100644 --- a/src/plugin/bridges/clap-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/clap-impls/plugin-proxy.cpp @@ -21,6 +21,8 @@ ClapHostExtensions::ClapHostExtensions(const clap_host& host) noexcept : audio_ports(static_cast( host.get_extension(&host, CLAP_EXT_AUDIO_PORTS))), + latency(static_cast( + host.get_extension(&host, CLAP_EXT_LATENCY))), note_ports(static_cast( host.get_extension(&host, CLAP_EXT_NOTE_PORTS))), params(static_cast( @@ -34,6 +36,7 @@ clap::host::SupportedHostExtensions ClapHostExtensions::supported() const noexcept { return clap::host::SupportedHostExtensions{ .supports_audio_ports = audio_ports != nullptr, + .supports_latency = latency != nullptr, .supports_note_ports = note_ports != nullptr, .supports_params = params != nullptr, .supports_tail = tail != nullptr}; @@ -65,6 +68,9 @@ clap_plugin_proxy::clap_plugin_proxy(ClapPluginBridge& bridge, .count = ext_audio_ports_count, .get = ext_audio_ports_get, }), + ext_latency_vtable(clap_plugin_latency_t{ + .get = ext_latency_get, + }), ext_note_ports_vtable(clap_plugin_note_ports_t{ .count = ext_note_ports_count, .get = ext_note_ports_get, @@ -210,6 +216,9 @@ clap_plugin_proxy::plugin_get_extension(const struct clap_plugin* plugin, if (self->supported_extensions_.supports_audio_ports && strcmp(id, CLAP_EXT_AUDIO_PORTS) == 0) { extension_ptr = &self->ext_audio_ports_vtable; + } else if (self->supported_extensions_.supports_latency && + strcmp(id, CLAP_EXT_LATENCY) == 0) { + extension_ptr = &self->ext_latency_vtable; } else if (self->supported_extensions_.supports_note_ports && strcmp(id, CLAP_EXT_NOTE_PORTS) == 0) { extension_ptr = &self->ext_note_ports_vtable; @@ -274,6 +283,15 @@ clap_plugin_proxy::ext_audio_ports_get(const clap_plugin_t* plugin, } } +uint32_t CLAP_ABI +clap_plugin_proxy::ext_latency_get(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::latency::plugin::Get{.instance_id = self->instance_id()}); +} + uint32_t CLAP_ABI clap_plugin_proxy::ext_note_ports_count(const clap_plugin_t* plugin, bool is_input) { diff --git a/src/plugin/bridges/clap-impls/plugin-proxy.h b/src/plugin/bridges/clap-impls/plugin-proxy.h index b032556a..cdd99c67 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 @@ -60,6 +61,7 @@ struct ClapHostExtensions { clap::host::SupportedHostExtensions supported() const noexcept; const clap_host_audio_ports_t* audio_ports = nullptr; + const clap_host_latency_t* latency = nullptr; const clap_host_note_ports_t* note_ports = nullptr; const clap_host_params_t* params = nullptr; const clap_host_tail_t* tail = nullptr; @@ -137,6 +139,8 @@ class clap_plugin_proxy { bool is_input, clap_audio_port_info_t* info); + static uint32_t CLAP_ABI ext_latency_get(const clap_plugin_t* plugin); + static uint32_t CLAP_ABI ext_note_ports_count(const clap_plugin_t* plugin, bool is_input); static bool CLAP_ABI ext_note_ports_get(const clap_plugin_t* plugin, @@ -240,6 +244,7 @@ class clap_plugin_proxy { // depends on whether the plugin supported this extension when we called // `clap_plugin::init()`. const clap_plugin_audio_ports_t ext_audio_ports_vtable; + const clap_plugin_latency_t ext_latency_vtable; const clap_plugin_note_ports_t ext_note_ports_vtable; const clap_plugin_params_t ext_params_vtable; const clap_plugin_tail_t ext_tail_vtable; diff --git a/src/plugin/bridges/clap.cpp b/src/plugin/bridges/clap.cpp index 49a21ac3..384501ad 100644 --- a/src/plugin/bridges/clap.cpp +++ b/src/plugin/bridges/clap.cpp @@ -115,6 +115,22 @@ ClapPluginBridge::ClapPluginBridge(const ghc::filesystem::path& plugin_path) return Ack{}; }, + [&](const clap::ext::latency::host::Changed& request) + -> clap::ext::latency::host::Changed::Response { + const auto& [plugin_proxy, _] = + get_proxy(request.owner_instance_id); + + plugin_proxy + .run_on_main_thread( + [&, host = plugin_proxy.host_, + latency = + plugin_proxy.host_extensions_.latency]() { + latency->changed(host); + }) + .wait(); + + return Ack{}; + }, [&](const clap::ext::note_ports::host::SupportedDialects& request) -> clap::ext::note_ports::host::SupportedDialects:: diff --git a/src/wine-host/bridges/clap-impls/host-proxy.cpp b/src/wine-host/bridges/clap-impls/host-proxy.cpp index e7a84607..a118dce8 100644 --- a/src/wine-host/bridges/clap-impls/host-proxy.cpp +++ b/src/wine-host/bridges/clap-impls/host-proxy.cpp @@ -48,6 +48,9 @@ clap_host_proxy::clap_host_proxy(ClapBridge& bridge, .is_rescan_flag_supported = ext_audio_ports_is_rescan_flag_supported, .rescan = ext_audio_ports_rescan, }), + ext_latency_vtable(clap_host_latency_t{ + .changed = ext_latency_changed, + }), ext_note_ports_vtable(clap_host_note_ports_t{ .supported_dialects = ext_note_ports_supported_dialects, .rescan = ext_note_ports_rescan, @@ -71,6 +74,9 @@ clap_host_proxy::host_get_extension(const struct clap_host* host, if (self->supported_extensions_.supports_audio_ports && strcmp(extension_id, CLAP_EXT_AUDIO_PORTS) == 0) { extension_ptr = &self->ext_audio_ports_vtable; + } else if (self->supported_extensions_.supports_latency && + strcmp(extension_id, CLAP_EXT_LATENCY) == 0) { + extension_ptr = &self->ext_latency_vtable; } else if (self->supported_extensions_.supports_note_ports && strcmp(extension_id, CLAP_EXT_NOTE_PORTS) == 0) { extension_ptr = &self->ext_note_ports_vtable; @@ -157,6 +163,14 @@ void CLAP_ABI clap_host_proxy::ext_audio_ports_rescan(const clap_host_t* host, .owner_instance_id = self->owner_instance_id(), .flags = flags}); } +void CLAP_ABI clap_host_proxy::ext_latency_changed(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::latency::host::Changed{ + .owner_instance_id = self->owner_instance_id()}); +} + uint32_t CLAP_ABI clap_host_proxy::ext_note_ports_supported_dialects(const clap_host_t* host) { assert(host && host->host_data); diff --git a/src/wine-host/bridges/clap-impls/host-proxy.h b/src/wine-host/bridges/clap-impls/host-proxy.h index 5a463dee..9656eb9d 100644 --- a/src/wine-host/bridges/clap-impls/host-proxy.h +++ b/src/wine-host/bridges/clap-impls/host-proxy.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -72,6 +73,8 @@ class clap_host_proxy { static void CLAP_ABI ext_audio_ports_rescan(const clap_host_t* host, uint32_t flags); + static void CLAP_ABI ext_latency_changed(const clap_host_t* host); + static uint32_t CLAP_ABI ext_note_ports_supported_dialects(const clap_host_t* host); static void CLAP_ABI ext_note_ports_rescan(const clap_host_t* host, @@ -109,6 +112,7 @@ class clap_host_proxy { // depends on whether the plugin supported this extension when the host // called `clap_plugin::init()`. const clap_host_audio_ports_t ext_audio_ports_vtable; + const clap_host_latency_t ext_latency_vtable; const clap_host_note_ports_t ext_note_ports_vtable; const clap_host_params_t ext_params_vtable; const clap_host_tail_t ext_tail_vtable; diff --git a/src/wine-host/bridges/clap.cpp b/src/wine-host/bridges/clap.cpp index d78b9d2d..76634843 100644 --- a/src/wine-host/bridges/clap.cpp +++ b/src/wine-host/bridges/clap.cpp @@ -29,6 +29,8 @@ namespace fs = ghc::filesystem; ClapPluginExtensions::ClapPluginExtensions(const clap_plugin& plugin) noexcept : audio_ports(static_cast( plugin.get_extension(&plugin, CLAP_EXT_AUDIO_PORTS))), + latency(static_cast( + plugin.get_extension(&plugin, CLAP_EXT_LATENCY))), note_ports(static_cast( plugin.get_extension(&plugin, CLAP_EXT_NOTE_PORTS))), params(static_cast( @@ -42,6 +44,7 @@ clap::plugin::SupportedPluginExtensions ClapPluginExtensions::supported() const noexcept { return clap::plugin::SupportedPluginExtensions{ .supports_audio_ports = audio_ports != nullptr, + .supports_latency = latency != nullptr, .supports_note_ports = note_ports != nullptr, .supports_params = params != nullptr, .supports_tail = tail != nullptr}; @@ -342,6 +345,14 @@ void ClapBridge::run() { .result = std::nullopt}; } }, + [&](clap::ext::latency::plugin::Get& request) + -> clap::ext::latency::plugin::Get::Response { + const auto& [instance, _] = get_instance(request.instance_id); + + // We'll ignore the main thread requirement for simple lookups + // to avoid the synchronisation costs in hot code paths + return instance.extensions.latency->get(instance.plugin.get()); + }, [&](const clap::ext::note_ports::plugin::Count& request) -> clap::ext::note_ports::plugin::Count::Response { const auto& [instance, _] = get_instance(request.instance_id); diff --git a/src/wine-host/bridges/clap.h b/src/wine-host/bridges/clap.h index d07d4e66..17f1ee7f 100644 --- a/src/wine-host/bridges/clap.h +++ b/src/wine-host/bridges/clap.h @@ -67,6 +67,7 @@ struct ClapPluginExtensions { clap::plugin::SupportedPluginExtensions supported() const noexcept; const clap_plugin_audio_ports_t* audio_ports = nullptr; + const clap_plugin_latency_t* latency = nullptr; const clap_plugin_note_ports_t* note_ports = nullptr; const clap_plugin_params_t* params = nullptr; const clap_plugin_tail_t* tail = nullptr;