mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-10 04:30:12 +02:00
Fully implement CLAP audio processing
This commit is contained in:
@@ -9,6 +9,7 @@ Yabridge currently tracks CLAP 1.1.1. The implementation status for CLAP's core
|
|||||||
| core feature | status |
|
| core feature | status |
|
||||||
| ----------------------------------------- | ------------------------------------------------------ |
|
| ----------------------------------------- | ------------------------------------------------------ |
|
||||||
| Core plugin and host functionality | :warning: Everything but actual audio processing works |
|
| Core plugin and host functionality | :warning: Everything but actual audio processing works |
|
||||||
|
| Audio processing | :heavy_check_mark: |
|
||||||
| Events | :heavy_check_mark: |
|
| Events | :heavy_check_mark: |
|
||||||
| Streams | :heavy_check_mark: |
|
| Streams | :heavy_check_mark: |
|
||||||
| `clap.plugin-factory` | :heavy_check_mark: |
|
| `clap.plugin-factory` | :heavy_check_mark: |
|
||||||
|
|||||||
@@ -223,11 +223,59 @@ clap_plugin_proxy::plugin_reset(const struct clap_plugin* plugin) {
|
|||||||
clap_process_status CLAP_ABI
|
clap_process_status CLAP_ABI
|
||||||
clap_plugin_proxy::plugin_process(const struct clap_plugin* plugin,
|
clap_plugin_proxy::plugin_process(const struct clap_plugin* plugin,
|
||||||
const clap_process_t* process) {
|
const clap_process_t* process) {
|
||||||
assert(plugin && plugin->plugin_data);
|
assert(plugin && plugin->plugin_data && process);
|
||||||
auto self = static_cast<const clap_plugin_proxy*>(plugin->plugin_data);
|
auto self = static_cast<clap_plugin_proxy*>(plugin->plugin_data);
|
||||||
|
|
||||||
// TODO: Implement
|
// We'll synchronize the scheduling priority of the audio thread on the Wine
|
||||||
return CLAP_PROCESS_ERROR;
|
// plugin host with that of the host's audio thread every once in a while
|
||||||
|
std::optional<int> 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<clap::plugin::Process>(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
|
const void* CLAP_ABI
|
||||||
|
|||||||
@@ -266,6 +266,13 @@ class clap_plugin_proxy {
|
|||||||
size_t instance_id_;
|
size_t instance_id_;
|
||||||
clap::plugin::Descriptor descriptor_;
|
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
|
* 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
|
* and the Wine plugin host. Copying audio is the most significant source of
|
||||||
@@ -277,6 +284,29 @@ class clap_plugin_proxy {
|
|||||||
*/
|
*/
|
||||||
std::optional<AudioShmBuffer> process_buffers_;
|
std::optional<AudioShmBuffer> 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<clap::plugin::Process>` 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
|
* 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
|
* copied. We'll use the host data pointer instead of placing this vtable at
|
||||||
|
|||||||
+15
-15
@@ -125,21 +125,21 @@ class ClapPluginBridge : PluginBridge<ClapSockets<std::jthread>> {
|
|||||||
object, std::pair<ClapLogger&, bool>(logger_, true));
|
object, std::pair<ClapLogger&, bool>(logger_, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * Send an audio thread control message to a specific plugin instance,
|
* Send an audio thread control message to a specific plugin instance,
|
||||||
// * receiving the results into an existing object. This is similar to the
|
* receiving the results into an existing object. This is similar to the
|
||||||
// * `send_audio_thread_message()` above, but this lets us avoid
|
* `send_audio_thread_message()` above, but this lets us avoid
|
||||||
// allocations
|
allocations
|
||||||
// * in response objects that contain heap data.
|
* in response objects that contain heap data.
|
||||||
// */
|
*/
|
||||||
// template <typename T>
|
template <typename T>
|
||||||
// typename T::Response& receive_audio_thread_message_into(
|
typename T::Response& receive_audio_thread_message_into(
|
||||||
// const T& object,
|
const T& object,
|
||||||
// typename T::Response& response_object) {
|
typename T::Response& response_object) {
|
||||||
// return sockets_.receive_audio_processor_message_into(
|
return sockets_.receive_audio_thread_control_message_into(
|
||||||
// object, response_object,
|
object, response_object,
|
||||||
// std::pair<ClapLogger&, bool>(logger_, true));
|
std::pair<ClapLogger&, bool>(logger_, true));
|
||||||
// }
|
}
|
||||||
|
|
||||||
// TODO: Do we need this for CLAP? If we do, update the docstring
|
// TODO: Do we need this for CLAP? If we do, update the docstring
|
||||||
// /**
|
// /**
|
||||||
|
|||||||
Reference in New Issue
Block a user