mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-10 04:30:12 +02:00
Allocate shared memory audio buffers for CLAP
This commit is contained in:
@@ -110,7 +110,7 @@ bool CLAP_ABI clap_plugin_proxy::plugin_activate(
|
|||||||
uint32_t min_frames_count,
|
uint32_t min_frames_count,
|
||||||
uint32_t max_frames_count) {
|
uint32_t max_frames_count) {
|
||||||
assert(plugin && plugin->plugin_data);
|
assert(plugin && plugin->plugin_data);
|
||||||
auto self = static_cast<const clap_plugin_proxy*>(plugin->plugin_data);
|
auto self = static_cast<clap_plugin_proxy*>(plugin->plugin_data);
|
||||||
|
|
||||||
const clap::plugin::ActivateResponse response =
|
const clap::plugin::ActivateResponse response =
|
||||||
self->bridge_.send_main_thread_message(
|
self->bridge_.send_main_thread_message(
|
||||||
@@ -119,8 +119,16 @@ bool CLAP_ABI clap_plugin_proxy::plugin_activate(
|
|||||||
.min_frames_count = min_frames_count,
|
.min_frames_count = min_frames_count,
|
||||||
.max_frames_count = max_frames_count});
|
.max_frames_count = max_frames_count});
|
||||||
|
|
||||||
|
// The shared memory audio buffers are allocated here so we can use them
|
||||||
|
// during audio processing
|
||||||
if (response.updated_audio_buffers_config) {
|
if (response.updated_audio_buffers_config) {
|
||||||
// TODO: Set up the shared memory audio buffers
|
if (!self->process_buffers_) {
|
||||||
|
self->process_buffers_.emplace(
|
||||||
|
*response.updated_audio_buffers_config);
|
||||||
|
} else {
|
||||||
|
self->process_buffers_->resize(
|
||||||
|
*response.updated_audio_buffers_config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.result;
|
return response.result;
|
||||||
|
|||||||
@@ -184,6 +184,17 @@ class clap_plugin_proxy {
|
|||||||
size_t instance_id_;
|
size_t instance_id_;
|
||||||
clap::plugin::Descriptor descriptor_;
|
clap::plugin::Descriptor descriptor_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* bridging overhead during audio processing, and this way we can reduce the
|
||||||
|
* amount of copies required to only once for the input audio, and one more
|
||||||
|
* copy when copying the results back to the host.
|
||||||
|
*
|
||||||
|
* This will be set up during `clap_plugin::activate()`.
|
||||||
|
*/
|
||||||
|
std::optional<AudioShmBuffer> process_buffers_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
|||||||
+109
-144
@@ -286,9 +286,10 @@ void ClapBridge::run() {
|
|||||||
plugin, request.sample_rate,
|
plugin, request.sample_rate,
|
||||||
request.min_frames_count, request.max_frames_count);
|
request.min_frames_count, request.max_frames_count);
|
||||||
|
|
||||||
// TODO: Audio buffer setup
|
|
||||||
const std::optional<AudioShmBuffer::Config>
|
const std::optional<AudioShmBuffer::Config>
|
||||||
updated_audio_buffers_config;
|
updated_audio_buffers_config =
|
||||||
|
setup_shared_audio_buffers(request.instance_id,
|
||||||
|
request);
|
||||||
|
|
||||||
return clap::plugin::ActivateResponse{
|
return clap::plugin::ActivateResponse{
|
||||||
.result = result,
|
.result = result,
|
||||||
@@ -393,160 +394,124 @@ size_t ClapBridge::generate_instance_id() noexcept {
|
|||||||
return current_instance_id_.fetch_add(1);
|
return current_instance_id_.fetch_add(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement audio processing
|
std::optional<AudioShmBuffer::Config> ClapBridge::setup_shared_audio_buffers(
|
||||||
// std::optional<AudioShmBuffer::Config> ClapBridge::setup_shared_audio_buffers(
|
size_t instance_id,
|
||||||
// size_t instance_id) {
|
const clap::plugin::Activate& activate_request) {
|
||||||
// const auto& [instance, _] = get_instance(instance_id);
|
const auto& [instance, _] = get_instance(instance_id);
|
||||||
|
|
||||||
// const Steinberg::IPtr<Steinberg::Vst::IComponent> component =
|
const clap_plugin_t* plugin = instance.plugin.get();
|
||||||
// instance.interfaces.component;
|
const clap_plugin_audio_ports_t* audio_ports =
|
||||||
// const Steinberg::IPtr<Steinberg::Vst::IAudioProcessor> audio_processor =
|
instance.extensions.audio_ports;
|
||||||
// instance.interfaces.audio_processor;
|
if (!audio_ports) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
// if (!instance.process_setup || !component || !audio_processor) {
|
// We'll query the plugin for its audio port layouts, and then create
|
||||||
// return std::nullopt;
|
// calculate the offsets in a large memory buffer for the different audio
|
||||||
// }
|
// channels. The offsets for each audio channel are in bytes because CLAP
|
||||||
|
// allows the host to send mixed 32-bit and 64-bit audio if the plugin
|
||||||
|
// advertises supporting 64-bit audio. Because of that we'll allocate enough
|
||||||
|
// space for double precision audio when the port supports it, and then
|
||||||
|
// we'll simply only use the first half of that space if the host sends
|
||||||
|
// 32-bit audio.
|
||||||
|
uint32_t current_offset = 0;
|
||||||
|
auto create_bus_offsets = [&](bool is_input) {
|
||||||
|
const uint32_t num_ports = audio_ports->count(plugin, is_input);
|
||||||
|
|
||||||
// // We'll query the plugin for its audio bus layouts, and then create
|
std::vector<std::vector<uint32_t>> offsets(num_ports);
|
||||||
// // calculate the offsets in a large memory buffer for the different audio
|
for (uint32_t port = 0; port < num_ports; port++) {
|
||||||
// // channels. The offsets for each audio channel are in samples (since
|
clap_audio_port_info_t info{};
|
||||||
// // they'll be used with pointer arithmetic in `AudioShmBuffer`).
|
assert(audio_ports->get(plugin, port, is_input, &info));
|
||||||
// uint32_t current_offset = 0;
|
|
||||||
|
|
||||||
// auto create_bus_offsets = [&, &setup = instance.process_setup](
|
// If the audio port supports 64-bit audio, then we should allocate
|
||||||
// Steinberg::Vst::BusDirection direction) {
|
// enough memory for that
|
||||||
// const auto num_busses =
|
const size_t sample_size =
|
||||||
// component->getBusCount(Steinberg::Vst::kAudio, direction);
|
(info.flags & CLAP_AUDIO_PORT_SUPPORTS_64BITS) != 0
|
||||||
|
? sizeof(double)
|
||||||
|
: sizeof(float);
|
||||||
|
|
||||||
// // This function is also run from `IAudioProcessor::setActive()`.
|
offsets[port].resize(info.channel_count);
|
||||||
// // According to the docs this does not need to be realtime-safe, but we
|
for (size_t channel = 0; channel < info.channel_count; channel++) {
|
||||||
// // should at least still try to not do anything expensive when no work
|
offsets[port][channel] = current_offset;
|
||||||
// // needs to be done.
|
current_offset +=
|
||||||
// llvm::SmallVector<llvm::SmallVector<uint32_t, 32>, 16> bus_offsets(
|
activate_request.max_frames_count * sample_size;
|
||||||
// num_busses);
|
}
|
||||||
// for (int bus = 0; bus < num_busses; bus++) {
|
}
|
||||||
// Steinberg::Vst::SpeakerArrangement speaker_arrangement{};
|
|
||||||
// audio_processor->getBusArrangement(direction, bus,
|
|
||||||
// speaker_arrangement);
|
|
||||||
|
|
||||||
// const size_t num_channels =
|
return offsets;
|
||||||
// std::bitset<sizeof(Steinberg::Vst::SpeakerArrangement) * 8>(
|
};
|
||||||
// speaker_arrangement)
|
|
||||||
// .count();
|
|
||||||
// bus_offsets[bus].resize(num_channels);
|
|
||||||
|
|
||||||
// for (size_t channel = 0; channel < num_channels; channel++) {
|
// Creating the audio buffer offsets for every channel in every bus will
|
||||||
// bus_offsets[bus][channel] = current_offset;
|
// advance `current_offset` to keep pointing to the starting position for
|
||||||
// current_offset += setup->maxSamplesPerBlock;
|
// the next channel
|
||||||
// }
|
const auto input_bus_offsets = create_bus_offsets(true);
|
||||||
// }
|
const auto output_bus_offsets = create_bus_offsets(false);
|
||||||
|
const uint32_t buffer_size = current_offset;
|
||||||
|
|
||||||
// return bus_offsets;
|
// If this function has been called previously and the size did not change,
|
||||||
// };
|
// then we should not do any work
|
||||||
|
if (instance.process_buffers &&
|
||||||
|
instance.process_buffers->config_.size == buffer_size) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
// // Creating the audio buffer offsets for every channel in every bus will
|
// We'll set up these shared memory buffers on the Wine side first, and then
|
||||||
// // advacne `current_offset` to keep pointing to the starting position for
|
// when this request returns we'll do the same thing on the native plugin
|
||||||
// // the next channel
|
// side
|
||||||
// const auto input_bus_offsets =
|
AudioShmBuffer::Config buffer_config{
|
||||||
// create_bus_offsets(Steinberg::Vst::kInput); const auto output_bus_offsets
|
.name = sockets_.base_dir_.filename().string() + "-" +
|
||||||
// = create_bus_offsets(Steinberg::Vst::kOutput);
|
std::to_string(instance_id),
|
||||||
|
.size = buffer_size,
|
||||||
|
.input_offsets = std::move(input_bus_offsets),
|
||||||
|
.output_offsets = std::move(output_bus_offsets)};
|
||||||
|
if (!instance.process_buffers) {
|
||||||
|
instance.process_buffers.emplace(buffer_config);
|
||||||
|
} else {
|
||||||
|
instance.process_buffers->resize(buffer_config);
|
||||||
|
}
|
||||||
|
|
||||||
// // The size of the buffer is in bytes, and it will depend on whether the
|
// After setting up the shared memory buffer, we need to create a vector of
|
||||||
// // host is going to pass 32-bit or 64-bit audio to the plugin
|
// channel audio pointers for every bus. These will then be assigned to the
|
||||||
// const bool double_precision =
|
// `AudioBusBuffers` objects in the `ClapProcess` struct in
|
||||||
// instance.process_setup->symbolicSampleSize ==
|
// `ClapProcess::reconstruct()` before passing the reconstructed process
|
||||||
// Steinberg::Vst::kSample64;
|
// data to `clap_plugin::process()`.
|
||||||
// const uint32_t buffer_size =
|
auto set_port_pointers =
|
||||||
// current_offset * (double_precision ? sizeof(double) : sizeof(float));
|
[&, &process_buffers =
|
||||||
|
instance.process_buffers]<std::invocable<uint32_t, uint32_t> F>(
|
||||||
|
std::vector<std::vector<void*>>& port_pointers,
|
||||||
|
const std::vector<std::vector<uint32_t>>& offsets,
|
||||||
|
F&& get_channel_pointer) {
|
||||||
|
port_pointers.resize(offsets.size());
|
||||||
|
for (size_t port = 0; port < offsets.size(); port++) {
|
||||||
|
port_pointers[port].resize(offsets[port].size());
|
||||||
|
for (size_t channel = 0; channel < offsets[port].size();
|
||||||
|
channel++) {
|
||||||
|
port_pointers[port][channel] =
|
||||||
|
get_channel_pointer(port, channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// // If this function has been called previously and the size did not change,
|
set_port_pointers(instance.process_buffers_input_pointers,
|
||||||
// // then we should not do any work
|
instance.process_buffers->config_.input_offsets,
|
||||||
// if (instance.process_buffers &&
|
[&process_buffers = instance.process_buffers](
|
||||||
// instance.process_buffers->config_.size == buffer_size) {
|
uint32_t port, uint32_t channel) {
|
||||||
// return std::nullopt;
|
// This can be treated as either a `double*` or a
|
||||||
// }
|
// `float*` depending on what the port supports and
|
||||||
|
// what the host gives us
|
||||||
|
return process_buffers->input_channel_ptr<void>(
|
||||||
|
port, channel);
|
||||||
|
});
|
||||||
|
set_port_pointers(instance.process_buffers_output_pointers,
|
||||||
|
instance.process_buffers->config_.output_offsets,
|
||||||
|
[&process_buffers = instance.process_buffers](
|
||||||
|
uint32_t port, uint32_t channel) {
|
||||||
|
return process_buffers->output_channel_ptr<void>(
|
||||||
|
port, channel);
|
||||||
|
});
|
||||||
|
|
||||||
// // Because the above check should be super cheap, we'll now need to convert
|
return buffer_config;
|
||||||
// // the stack allocated SmallVectors to regular heap vectors
|
}
|
||||||
// std::vector<std::vector<uint32_t>> input_bus_offsets_vector;
|
|
||||||
// input_bus_offsets_vector.reserve(input_bus_offsets.size());
|
|
||||||
// for (const auto& channel_offsets : input_bus_offsets) {
|
|
||||||
// input_bus_offsets_vector.push_back(
|
|
||||||
// std::vector(channel_offsets.begin(), channel_offsets.end()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// std::vector<std::vector<uint32_t>> output_bus_offsets_vector;
|
|
||||||
// output_bus_offsets_vector.reserve(output_bus_offsets.size());
|
|
||||||
// for (const auto& channel_offsets : output_bus_offsets) {
|
|
||||||
// output_bus_offsets_vector.push_back(
|
|
||||||
// std::vector(channel_offsets.begin(), channel_offsets.end()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // We'll set up these shared memory buffers on the Wine side first, and then
|
|
||||||
// // when this request returns we'll do the same thing on the native plugin
|
|
||||||
// // side
|
|
||||||
// AudioShmBuffer::Config buffer_config{
|
|
||||||
// .name = sockets_.base_dir_.filename().string() + "-" +
|
|
||||||
// std::to_string(instance_id),
|
|
||||||
// .size = buffer_size,
|
|
||||||
// .input_offsets = std::move(input_bus_offsets_vector),
|
|
||||||
// .output_offsets = std::move(output_bus_offsets_vector)};
|
|
||||||
// if (!instance.process_buffers) {
|
|
||||||
// instance.process_buffers.emplace(buffer_config);
|
|
||||||
// } else {
|
|
||||||
// instance.process_buffers->resize(buffer_config);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // After setting up the shared memory buffer, we need to create a vector of
|
|
||||||
// // channel audio pointers for every bus. These will then be assigned to the
|
|
||||||
// // `AudioBusBuffers` objects in the `ProcessData` struct in
|
|
||||||
// // `YaProcessData::reconstruct()` before passing the reconstructed process
|
|
||||||
// // data to `IAudioProcessor::process()`.
|
|
||||||
// auto set_bus_pointers =
|
|
||||||
// [&]<std::invocable<uint32_t, uint32_t> F>(
|
|
||||||
// std::vector<std::vector<void*>>& bus_pointers,
|
|
||||||
// const std::vector<std::vector<uint32_t>>& bus_offsets,
|
|
||||||
// F&& get_channel_pointer) {
|
|
||||||
// bus_pointers.resize(bus_offsets.size());
|
|
||||||
|
|
||||||
// for (size_t bus = 0; bus < bus_offsets.size(); bus++) {
|
|
||||||
// bus_pointers[bus].resize(bus_offsets[bus].size());
|
|
||||||
|
|
||||||
// for (size_t channel = 0; channel < bus_offsets[bus].size();
|
|
||||||
// channel++) {
|
|
||||||
// bus_pointers[bus][channel] =
|
|
||||||
// get_channel_pointer(bus, channel);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// set_bus_pointers(
|
|
||||||
// instance.process_buffers_input_pointers,
|
|
||||||
// instance.process_buffers->config_.input_offsets,
|
|
||||||
// [&, &instance = instance](uint32_t bus, uint32_t channel) -> void* {
|
|
||||||
// if (double_precision) {
|
|
||||||
// return instance.process_buffers->input_channel_ptr<double>(
|
|
||||||
// bus, channel);
|
|
||||||
// } else {
|
|
||||||
// return instance.process_buffers->input_channel_ptr<float>(
|
|
||||||
// bus, channel);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// set_bus_pointers(
|
|
||||||
// instance.process_buffers_output_pointers,
|
|
||||||
// instance.process_buffers->config_.output_offsets,
|
|
||||||
// [&, &instance = instance](uint32_t bus, uint32_t channel) -> void* {
|
|
||||||
// if (double_precision) {
|
|
||||||
// return instance.process_buffers->output_channel_ptr<double>(
|
|
||||||
// bus, channel);
|
|
||||||
// } else {
|
|
||||||
// return instance.process_buffers->output_channel_ptr<float>(
|
|
||||||
// bus, channel);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return buffer_config;
|
|
||||||
// }
|
|
||||||
|
|
||||||
void ClapBridge::register_plugin_instance(
|
void ClapBridge::register_plugin_instance(
|
||||||
const clap_plugin* plugin,
|
const clap_plugin* plugin,
|
||||||
|
|||||||
@@ -116,22 +116,18 @@ struct ClapPluginInstance {
|
|||||||
std::optional<AudioShmBuffer> process_buffers;
|
std::optional<AudioShmBuffer> process_buffers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pointers to the per-bus input channels in process_buffers so we can pass
|
* Pointers to the per-port input channels in process_buffers so we can pass
|
||||||
* them to the plugin after a call to `YaProcessData::reconstruct()`. These
|
* them to the plugin after a call to `ClapProcess::reconstruct()`. These
|
||||||
* can be either `float*` or `double*`, so we sadly have to use void
|
* can be either `float*` or `double*` depending on the audio port's flags,
|
||||||
* pointers here.
|
* so we're using void pointers here.
|
||||||
*
|
|
||||||
* FIXME: Update docstring for CLAP
|
|
||||||
*/
|
*/
|
||||||
std::vector<std::vector<void*>> process_buffers_input_pointers;
|
std::vector<std::vector<void*>> process_buffers_input_pointers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pointers to the per-bus output channels in process_buffers so we can pass
|
* Pointers to the per-port output channels in process_buffers so we can
|
||||||
* them to the plugin after a call to `YaProcessData::reconstruct()`. These
|
* pass them to the plugin after a call to `ClapProcess::reconstruct()`.
|
||||||
* can be either `float*` or `double*`, so we sadly have to use void
|
* These can be either `float*` or `double*` depending on the audio port's
|
||||||
* pointers here.
|
* flags, so we're using void pointers here.
|
||||||
*
|
|
||||||
* FIXME: Update docstring for CLAP
|
|
||||||
*/
|
*/
|
||||||
std::vector<std::vector<void*>> process_buffers_output_pointers;
|
std::vector<std::vector<void*>> process_buffers_output_pointers;
|
||||||
|
|
||||||
@@ -367,7 +363,8 @@ class ClapBridge : public HostBridge {
|
|||||||
* audio buffers have been set up and the audio buffer size has not changed.
|
* audio buffers have been set up and the audio buffer size has not changed.
|
||||||
*/
|
*/
|
||||||
std::optional<AudioShmBuffer::Config> setup_shared_audio_buffers(
|
std::optional<AudioShmBuffer::Config> setup_shared_audio_buffers(
|
||||||
size_t instance_id);
|
size_t instance_id,
|
||||||
|
const clap::plugin::Activate& activate_request);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a plugin and its host to it to `object_instances_`. The plugin's
|
* Add a plugin and its host to it to `object_instances_`. The plugin's
|
||||||
|
|||||||
@@ -1419,7 +1419,7 @@ std::optional<AudioShmBuffer::Config> Vst3Bridge::setup_shared_audio_buffers(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Creating the audio buffer offsets for every channel in every bus will
|
// Creating the audio buffer offsets for every channel in every bus will
|
||||||
// advacne `current_offset` to keep pointing to the starting position for
|
// advance `current_offset` to keep pointing to the starting position for
|
||||||
// the next channel
|
// the next channel
|
||||||
const auto input_bus_offsets = create_bus_offsets(Steinberg::Vst::kInput);
|
const auto input_bus_offsets = create_bus_offsets(Steinberg::Vst::kInput);
|
||||||
const auto output_bus_offsets = create_bus_offsets(Steinberg::Vst::kOutput);
|
const auto output_bus_offsets = create_bus_offsets(Steinberg::Vst::kOutput);
|
||||||
|
|||||||
Reference in New Issue
Block a user