Fully implement CLAP audio processing

This commit is contained in:
Robbert van der Helm
2022-10-03 01:30:36 +02:00
parent 80b224fcbd
commit 7ccf54a77c
4 changed files with 98 additions and 19 deletions
+1
View File
@@ -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: |
+52 -4
View File
@@ -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
View File
@@ -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
// /**