diff --git a/src/common/serialization/clap/README.md b/src/common/serialization/clap/README.md index 7e48127b..6102e0c3 100644 --- a/src/common/serialization/clap/README.md +++ b/src/common/serialization/clap/README.md @@ -9,6 +9,7 @@ Yabridge currently tracks CLAP 1.1.1. The implementation status for CLAP's core | core feature | status | | ----------------------------------------- | ------------------------------------------------------ | | Core plugin and host functionality | :warning: Everything but actual audio processing works | +| Audio processing | :heavy_check_mark: | | Events | :heavy_check_mark: | | Streams | :heavy_check_mark: | | `clap.plugin-factory` | :heavy_check_mark: | diff --git a/src/plugin/bridges/clap-impls/plugin-proxy.cpp b/src/plugin/bridges/clap-impls/plugin-proxy.cpp index 83aff66d..315e0715 100644 --- a/src/plugin/bridges/clap-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/clap-impls/plugin-proxy.cpp @@ -223,11 +223,59 @@ clap_plugin_proxy::plugin_reset(const struct clap_plugin* plugin) { clap_process_status CLAP_ABI clap_plugin_proxy::plugin_process(const struct clap_plugin* plugin, const clap_process_t* process) { - assert(plugin && plugin->plugin_data); - auto self = static_cast(plugin->plugin_data); + assert(plugin && plugin->plugin_data && process); + auto self = static_cast(plugin->plugin_data); - // TODO: Implement - return CLAP_PROCESS_ERROR; + // We'll synchronize the scheduling priority of the audio thread on the Wine + // plugin host with that of the host's audio thread every once in a while + std::optional new_realtime_priority = std::nullopt; + time_t now = time(nullptr); + if (now > self->last_audio_thread_priority_synchronization_ + + audio_thread_priority_synchronization_interval) { + new_realtime_priority = get_realtime_priority(); + self->last_audio_thread_priority_synchronization_ = now; + } + + // We reuse this existing object to avoid allocations. + // `clap::process::Process::repopulate()` will write the input audio to the + // shared audio buffers, so they're not stored within the request object + // itself. + assert(self->process_buffers_); + self->process_request_.instance_id = self->instance_id(); + self->process_request_.process.repopulate(*process, + *self->process_buffers_); + self->process_request_.new_realtime_priority = new_realtime_priority; + + // HACK: This is a bit ugly. This `clap::process::Process::Response` object + // actually contains pointers to the corresponding `YaProcessData` + // fields in this object, so we can only send back the fields that are + // actually relevant. This is necessary to avoid allocating copies or + // moves on the Wine side. This `create_response()` function creates a + // response object that points to the fields in + // `process_request_.data`, so when we deserialize into + // `process_response_` we end up actually writing to the actual + // `process_request_.data` object. Thus we can also call + // `process_request_.data.write_back_outputs()` later. + // + // `clap::process::Process::Response::serialize()` should make this a + // lot clearer. + self->process_response_.output_data = + self->process_request_.process.create_response(); + + // We'll also receive the response into an existing object so we can also + // avoid heap allocations there + self->bridge_.receive_audio_thread_message_into( + MessageReference(self->process_request_), + self->process_response_); + + // At this point the shared audio buffers should contain the output audio, + // so we'll write that back to the host along with any metadata (which in + // practice are only the silence flags), as well as any output parameter + // changes and events + self->process_request_.process.write_back_outputs(*process, + *self->process_buffers_); + + return self->process_response_.result; } const void* CLAP_ABI diff --git a/src/plugin/bridges/clap-impls/plugin-proxy.h b/src/plugin/bridges/clap-impls/plugin-proxy.h index 12fbbf6d..a96b96f2 100644 --- a/src/plugin/bridges/clap-impls/plugin-proxy.h +++ b/src/plugin/bridges/clap-impls/plugin-proxy.h @@ -266,6 +266,13 @@ class clap_plugin_proxy { size_t instance_id_; clap::plugin::Descriptor descriptor_; + /** + * We'll periodically synchronize the Wine host's audio thread priority with + * that of the host. Since the overhead from doing so does add up, we'll + * only do this every once in a while. + */ + time_t last_audio_thread_priority_synchronization_ = 0; + /** * A shared memory object to share audio buffers between the native plugin * and the Wine plugin host. Copying audio is the most significant source of @@ -277,6 +284,29 @@ class clap_plugin_proxy { */ std::optional process_buffers_; + /** + * We'll reuse the request objects for the process call so we can keep the + * process data object (which contains vectors and other heap allocated data + * structure) alive. We'll then just fill this object with new data every + * processing cycle to prevent allocations. Then, we pass a + * `MessageReference` to our sockets. This together + * with `bitisery::ext::MessageReference` will let us serialize from and to + * existing objects without having to copy or reallocate them. + * + * To reduce the amount of copying during audio processing we'll write the + * audio data to a shared memory object stored in `process_buffers_` first. + */ + clap::plugin::Process process_request_; + + /** + * The response object we'll get in return when we send the + * `process_request_` object above to the Wine plugin host. This object also + * contains heap data, so we also want to reuse this. This is initialized to + * contain the `process_request_`'s response object so the response can be + * deserialized in place. + */ + clap::plugin::ProcessResponse process_response_; + /** * The vtable for `clap_plugin`, requires that this object is never moved or * copied. We'll use the host data pointer instead of placing this vtable at diff --git a/src/plugin/bridges/clap.h b/src/plugin/bridges/clap.h index b1186f1c..2162b60d 100644 --- a/src/plugin/bridges/clap.h +++ b/src/plugin/bridges/clap.h @@ -125,21 +125,21 @@ class ClapPluginBridge : PluginBridge> { object, std::pair(logger_, true)); } - // /** - // * Send an audio thread control message to a specific plugin instance, - // * receiving the results into an existing object. This is similar to the - // * `send_audio_thread_message()` above, but this lets us avoid - // allocations - // * in response objects that contain heap data. - // */ - // template - // typename T::Response& receive_audio_thread_message_into( - // const T& object, - // typename T::Response& response_object) { - // return sockets_.receive_audio_processor_message_into( - // object, response_object, - // std::pair(logger_, true)); - // } + /** + * Send an audio thread control message to a specific plugin instance, + * receiving the results into an existing object. This is similar to the + * `send_audio_thread_message()` above, but this lets us avoid + allocations + * in response objects that contain heap data. + */ + template + typename T::Response& receive_audio_thread_message_into( + const T& object, + typename T::Response& response_object) { + return sockets_.receive_audio_thread_control_message_into( + object, response_object, + std::pair(logger_, true)); + } // TODO: Do we need this for CLAP? If we do, update the docstring // /**