mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 20:10:13 +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 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: |
|
||||
|
||||
@@ -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<const clap_plugin_proxy*>(plugin->plugin_data);
|
||||
assert(plugin && plugin->plugin_data && process);
|
||||
auto self = static_cast<clap_plugin_proxy*>(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<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
|
||||
|
||||
@@ -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<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
|
||||
* 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));
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 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>
|
||||
// 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<ClapLogger&, bool>(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>
|
||||
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<ClapLogger&, bool>(logger_, true));
|
||||
}
|
||||
|
||||
// TODO: Do we need this for CLAP? If we do, update the docstring
|
||||
// /**
|
||||
|
||||
Reference in New Issue
Block a user