💥 Reimplement VST3 audio processing

In the same way as 50c25c1cf0 did it for
VST2 plugins. Input and output audio data is now stored in a shared
memory buffer instead of being sent over the sockets. This reduces the
bridging overhead to a minimum since copying data was the most expensive
operation we were doing and we now only need to copy the entire buffer
once per processing cycle.
This commit is contained in:
Robbert van der Helm
2021-06-11 13:56:42 +02:00
parent a7d8063db4
commit dec19dc12a
8 changed files with 444 additions and 363 deletions
+106 -166
View File
@@ -18,113 +18,6 @@
#include "src/common/utils.h"
YaAudioBusBuffers::YaAudioBusBuffers() noexcept {}
void YaAudioBusBuffers::clear(int32 sample_size,
size_t num_samples,
size_t num_channels) {
auto do_clear = [&]<typename T>(T) {
if (!std::holds_alternative<std::vector<std::vector<T>>>(buffers)) {
buffers.emplace<std::vector<std::vector<T>>>();
}
std::vector<std::vector<T>>& vector_buffers =
std::get<std::vector<std::vector<T>>>(buffers);
vector_buffers.resize(num_channels);
for (size_t i = 0; i < vector_buffers.size(); i++) {
vector_buffers[i].resize(num_samples);
}
};
if (sample_size == Steinberg::Vst::SymbolicSampleSizes::kSample64) {
// XXX: Clangd doesn't let you specify template parameters for templated
// lambdas. This argument should get optimized out
do_clear(double());
} else {
do_clear(float());
}
}
void YaAudioBusBuffers::repopulate(
int32 sample_size,
int32 num_samples,
const Steinberg::Vst::AudioBusBuffers& data) {
silence_flags = data.silenceFlags;
auto do_repopuldate = [&]<typename T>(T** original_buffer) {
if (!std::holds_alternative<std::vector<std::vector<T>>>(buffers)) {
buffers.emplace<std::vector<std::vector<T>>>();
}
std::vector<std::vector<T>>& vector_buffers =
std::get<std::vector<std::vector<T>>>(buffers);
vector_buffers.resize(data.numChannels);
for (int channel = 0; channel < data.numChannels; channel++) {
vector_buffers[channel].assign(
&original_buffer[channel][0],
&original_buffer[channel][num_samples]);
}
};
if (sample_size == Steinberg::Vst::kSample64) {
do_repopuldate(data.channelBuffers64);
} else {
// I don't think they'll add any other sample sizes any time soon
do_repopuldate(data.channelBuffers32);
}
}
void YaAudioBusBuffers::reconstruct(
Steinberg::Vst::AudioBusBuffers& reconstructed_buffers) {
// We'll update the `AudioBusBuffers` object in place to point to our new
// data
reconstructed_buffers.silenceFlags = silence_flags;
std::visit(
[&]<typename T>(std::vector<std::vector<T>>& buffers) {
buffer_pointers.resize(buffers.size());
for (size_t i = 0; i < buffers.size(); i++) {
buffer_pointers[i] = buffers[i].data();
}
reconstructed_buffers.numChannels =
static_cast<int32>(buffers.size());
if constexpr (std::is_same_v<T, double>) {
reconstructed_buffers.channelBuffers64 =
reinterpret_cast<T**>(buffer_pointers.data());
} else {
reconstructed_buffers.channelBuffers32 =
reinterpret_cast<T**>(buffer_pointers.data());
}
},
buffers);
}
size_t YaAudioBusBuffers::num_channels() const {
return std::visit([&](const auto& buffers) { return buffers.size(); },
buffers);
}
void YaAudioBusBuffers::write_back_outputs(
Steinberg::Vst::AudioBusBuffers& output_buffers) const {
output_buffers.silenceFlags = silence_flags;
std::visit(
[&]<typename T>(const std::vector<std::vector<T>>& buffers) {
for (int channel = 0; channel < output_buffers.numChannels;
channel++) {
if constexpr (std::is_same_v<T, double>) {
std::copy(buffers[channel].begin(), buffers[channel].end(),
output_buffers.channelBuffers64[channel]);
} else {
std::copy(buffers[channel].begin(), buffers[channel].end(),
output_buffers.channelBuffers32[channel]);
}
}
},
buffers);
}
YaProcessData::YaProcessData() noexcept
// This response object acts as an optimization. It stores pointers to the
// original fields in our objects, so we can both only serialize those
@@ -142,8 +35,8 @@ YaProcessData::YaProcessData() noexcept
// `create_response()` on the plugin side
reconstructed_process_data() {}
void YaProcessData::repopulate(
const Steinberg::Vst::ProcessData& process_data) {
void YaProcessData::repopulate(const Steinberg::Vst::ProcessData& process_data,
AudioShmBuffer& shared_audio_buffers) {
// In this function and in every function we call, we should be careful to
// not use `push_back`/`emplace_back` anywhere. Resizing vectors and
// modifying them in place performs much better because that avoids
@@ -152,19 +45,45 @@ void YaProcessData::repopulate(
symbolic_sample_size = process_data.symbolicSampleSize;
num_samples = process_data.numSamples;
// We'll make sure to not do any allocations here after the first processing
// cycle
// The actual audio is stored in an accompanying `AudioShmBuffer` object, so
// these inputs and outputs objects are only used to serialize metadata
// about the input and output audio bus buffers
inputs.resize(process_data.numInputs);
for (int i = 0; i < process_data.numInputs; i++) {
inputs[i].repopulate(symbolic_sample_size, num_samples,
process_data.inputs[i]);
for (int bus = 0; bus < process_data.numInputs; bus++) {
// NOTE: The host might provide more input channels than what the plugin
// asked for. Carla does this for some reason. We should just
// ignore these.
inputs[bus].numChannels = std::min(
static_cast<int32>(shared_audio_buffers.num_input_channels(bus)),
process_data.inputs[bus].numChannels);
inputs[bus].silenceFlags = process_data.inputs[bus].silenceFlags;
// We copy the actual input audio for every bus to the shared memory
// object
for (int channel = 0; channel < inputs[bus].numChannels; channel++) {
if (process_data.symbolicSampleSize == Steinberg::Vst::kSample64) {
std::copy_n(process_data.inputs[bus].channelBuffers64[channel],
process_data.numSamples,
shared_audio_buffers.input_channel_ptr<double>(
bus, channel));
} else {
std::copy_n(process_data.inputs[bus].channelBuffers32[channel],
process_data.numSamples,
shared_audio_buffers.input_channel_ptr<float>(
bus, channel));
}
}
}
// We only store how many channels ouch output has so we can recreate the
// objects on the Wine side
outputs_num_channels.resize(process_data.numOutputs);
for (int i = 0; i < process_data.numOutputs; i++) {
outputs_num_channels[i] = process_data.outputs[i].numChannels;
outputs.resize(process_data.numOutputs);
for (int bus = 0; bus < process_data.numOutputs; bus++) {
// NOTE: The host might provide more output channels than what the
// plugin asked for. Carla does this for some reason. We should
// just ignore these.
outputs[bus].numChannels = std::min(
static_cast<int32>(shared_audio_buffers.num_output_channels(bus)),
process_data.outputs[bus].numChannels);
outputs[bus].silenceFlags = process_data.outputs[bus].silenceFlags;
}
// Even though `ProcessData::inputParamterChanges` is mandatory, the VST3
@@ -175,7 +94,15 @@ void YaProcessData::repopulate(
input_parameter_changes.clear();
}
output_parameter_changes_supported = process_data.outputParameterChanges;
// The existence of the output parameter changes object indicates whether or
// not the host provides this for the plugin
if (process_data.outputParameterChanges) {
if (!output_parameter_changes) {
output_parameter_changes.emplace();
}
} else {
output_parameter_changes.reset();
}
if (process_data.inputEvents) {
if (!input_events) {
@@ -186,7 +113,14 @@ void YaProcessData::repopulate(
input_events.reset();
}
output_events_supported = process_data.outputEvents;
// Same for the output events
if (process_data.outputEvents) {
if (!output_events) {
output_events.emplace();
}
} else {
output_events.reset();
}
if (process_data.processContext) {
process_context.emplace(*process_data.processContext);
@@ -195,43 +129,40 @@ void YaProcessData::repopulate(
}
}
Steinberg::Vst::ProcessData& YaProcessData::reconstruct() {
Steinberg::Vst::ProcessData& YaProcessData::reconstruct(
std::vector<std::vector<void*>>& input_pointers,
std::vector<std::vector<void*>>& output_pointers) {
reconstructed_process_data.processMode = process_mode;
reconstructed_process_data.symbolicSampleSize = symbolic_sample_size;
reconstructed_process_data.numSamples = num_samples;
reconstructed_process_data.numInputs = static_cast<int32>(inputs.size());
reconstructed_process_data.numOutputs =
static_cast<int32>(outputs_num_channels.size());
reconstructed_process_data.numOutputs = static_cast<int32>(outputs.size());
// We'll have to transform our `YaAudioBusBuffers` objects into an array of
// `AudioBusBuffers` object so the plugin can deal with them. These objects
// contain pointers to those original objects and thus don't store any
// buffer data themselves.
inputs_audio_bus_buffers.resize(inputs.size());
for (size_t i = 0; i < inputs.size(); i++) {
inputs[i].reconstruct(inputs_audio_bus_buffers[i]);
// The actual audio data is contained within a shared memory object, and the
// input and output pointers point to regions in that object. These pointers
// are calculated while handling `IAudioProcessor::setupProcessing()`.
// NOTE: The 32-bit and 64-bit audio pointers are a union, and since this is
// a raw memory buffer we can set either `channelBuffers32` or
// `channelBuffers64` to point at that buffer as long as we do the
// same thing on both the native plugin side and on the Wine plugin
// host
assert(inputs.size() <= input_pointers.size() &&
outputs.size() <= output_pointers.size());
for (size_t bus = 0; bus < inputs.size(); bus++) {
inputs[bus].channelBuffers32 =
reinterpret_cast<float**>(input_pointers[bus].data());
}
for (size_t bus = 0; bus < outputs.size(); bus++) {
outputs[bus].channelBuffers32 =
reinterpret_cast<float**>(output_pointers[bus].data());
}
reconstructed_process_data.inputs = inputs_audio_bus_buffers.data();
reconstructed_process_data.inputs = inputs.data();
reconstructed_process_data.outputs = outputs.data();
// We'll do the same with with the outputs, but we'll first have to
// initialize zeroed out buffers for the plugin to work with since we didn't
// serialize those directly
outputs.resize(outputs_num_channels.size());
outputs_audio_bus_buffers.resize(outputs_num_channels.size());
for (size_t i = 0; i < outputs_num_channels.size(); i++) {
outputs[i].clear(symbolic_sample_size, num_samples,
outputs_num_channels[i]);
outputs[i].reconstruct(outputs_audio_bus_buffers[i]);
}
reconstructed_process_data.outputs = outputs_audio_bus_buffers.data();
reconstructed_process_data.inputParameterChanges = &input_parameter_changes;
if (output_parameter_changes_supported) {
if (!output_parameter_changes) {
output_parameter_changes.emplace();
}
if (output_parameter_changes) {
output_parameter_changes->clear();
reconstructed_process_data.outputParameterChanges =
&*output_parameter_changes;
@@ -245,10 +176,7 @@ Steinberg::Vst::ProcessData& YaProcessData::reconstruct() {
reconstructed_process_data.inputEvents = nullptr;
}
if (output_events_supported) {
if (!output_events) {
output_events.emplace();
}
if (output_events) {
output_events->clear();
reconstructed_process_data.outputEvents = &*output_events;
} else {
@@ -265,27 +193,39 @@ Steinberg::Vst::ProcessData& YaProcessData::reconstruct() {
}
YaProcessData::Response& YaProcessData::create_response() noexcept {
// NOTE: We _have_ to manually copy over the silence flags from the
// `ProcessData` object generated in `get()` here sicne these of
// course are not references or pointers like all other fields, so
// they're not implicitly copied like all of our other fields
//
// On the plugin side this is not necessary, but it also doesn't hurt
for (int i = 0; i < reconstructed_process_data.numOutputs; i++) {
outputs[i].silence_flags =
reconstructed_process_data.outputs[i].silenceFlags;
}
// NOTE: We return an object that only contains references to these original
// fields to avoid any copies or moves
return response_object;
}
void YaProcessData::write_back_outputs(
Steinberg::Vst::ProcessData& process_data) {
Steinberg::Vst::ProcessData& process_data,
const AudioShmBuffer& shared_audio_buffers) {
assert(static_cast<int32>(outputs.size()) == process_data.numOutputs);
for (int i = 0; i < process_data.numOutputs; i++) {
outputs[i].write_back_outputs(process_data.outputs[i]);
for (int bus = 0; bus < process_data.numOutputs; bus++) {
process_data.outputs[bus].silenceFlags = outputs[bus].silenceFlags;
// NOTE: Some hosts, like Carla, provide more output channels than what
// the plugin wants. We'll have already capped
// `outputs[bus].numChannels` to the number of channels requested
// by the plugin during `YaProcessData::repopulate()`.
for (int channel = 0; channel < outputs[bus].numChannels; channel++) {
// We copy the output audio for every bus from the shared memory
// object back to the buffer provided by the host
if (process_data.symbolicSampleSize == Steinberg::Vst::kSample64) {
std::copy_n(
shared_audio_buffers.output_channel_ptr<double>(bus,
channel),
process_data.numSamples,
process_data.outputs[bus].channelBuffers64[channel]);
} else {
std::copy_n(
shared_audio_buffers.output_channel_ptr<float>(bus,
channel),
process_data.numSamples,
process_data.outputs[bus].channelBuffers32[channel]);
}
}
}
if (output_parameter_changes && process_data.outputParameterChanges) {
+78 -166
View File
@@ -20,6 +20,7 @@
#include <pluginterfaces/vst/ivstaudioprocessor.h>
#include "../../audio-shm.h"
#include "../../bitsery/ext/in-place-optional.h"
#include "../../bitsery/ext/in-place-variant.h"
#include "base.h"
@@ -28,114 +29,18 @@
// This header provides serialization wrappers around `ProcessData`
/**
* A serializable wrapper around `AudioBusBuffers` back by `std::vector<T>`s.
* Data can be read from a `AudioBusBuffers` object provided by the host, and
* one the Wine plugin host side we can reconstruct the `AudioBusBuffers` object
* back from this object again.
*
* @see YaProcessData
*/
class alignas(16) YaAudioBusBuffers {
public:
/**
* We only provide a default constructor here, because we need to fill the
* existing object with new audio data every processing cycle to avoid
* reallocating a new object every time.
*/
YaAudioBusBuffers() noexcept;
/**
* Create a new, zero initialize audio bus buffers object. Used to
* reconstruct the output buffers during `YaProcessData::reconstruct()`.
*/
void clear(int32 sample_size, size_t num_samples, size_t num_channels);
/**
* Copy data from a host provided `AudioBusBuffers` object during a process
* call. Used in `YaProcessData::repopulate()`. Since `AudioBusBuffers`
* contains an untagged union for storing single and double precision
* floating point values, the original `ProcessData`'s `symbolicSampleSize`
* field determines which variant of that union to use. Similarly the
* `ProcessData`' `numSamples` field determines the extent of these arrays.
*/
void repopulate(int32 sample_size,
int32 num_samples,
const Steinberg::Vst::AudioBusBuffers& data);
/**
* Reconstruct the original `AudioBusBuffers` object passed to the
* constructor and return it. This is used as part of
* `YaProcessData::reconstruct()`. The object contains pointers to
* `buffers`, so it may not outlive this object.
*
* NOTE: The `silenceFlags` field is of course not a reference, so writing
* to that will not modify `silence_flags`.
*/
void reconstruct(Steinberg::Vst::AudioBusBuffers& reconstructed_buffers);
/**
* Return the number of channels in `buffers`. Only used for debug logs.
*/
size_t num_channels() const;
/**
* Write these buffers and the silence flag back to an `AudioBusBuffers
* object provided by the host.
*/
void write_back_outputs(
Steinberg::Vst::AudioBusBuffers& output_buffers) const;
template <typename S>
void serialize(S& s) {
s.value8b(silence_flags);
s.ext(buffers, bitsery::ext::InPlaceVariant{
[](S& s, std::vector<std::vector<float>>& buffers) {
s.container(buffers, max_num_speakers,
[](S& s, auto& channel) {
s.container4b(channel, 1 << 16);
});
},
[](S& s, std::vector<std::vector<double>>& buffers) {
s.container(buffers, max_num_speakers,
[](S& s, auto& channel) {
s.container8b(channel, 1 << 16);
});
},
});
}
/**
* A bitfield for silent channels copied directly from the input struct.
*
* We could have done some optimizations to avoid unnecessary copying when
* these silence flags are set, but since it's an optional feature we
* shouldn't risk it.
*/
uint64 silence_flags = 0;
private:
/**
* We need these during the reconstruction process to provide a pointer to
* an array of pointers to the actual buffers.
*/
std::vector<void*> buffer_pointers;
/**
* The original implementation uses heap arrays and it stores a
* {float,double} array pointer per channel, with a separate field for the
* number of channels. We'll store this using a vector of vectors.
*/
std::variant<std::vector<std::vector<float>>,
std::vector<std::vector<double>>>
buffers;
};
/**
* A serializable wrapper around `ProcessData`. We'll read all information from
* the host so we can serialize it and provide an equivalent `ProcessData`
* struct to the plugin. Then we can create a `YaProcessData::Response` object
* that contains all output values so we can write those back to the host.
* struct to the Windows VST3 plugin. Then we can create a
* `YaProcessData::Response` object that contains all output values so we can
* write those back to the host.
*
* As an optimization, this no longer stores any actual audio. Instead, both
* `Vst3PluginProxyImpl` and `Vst3Bridge::InstanceInterfaces` contain a shared
* memory object that stores the audio buffers used for the plugin instance.
* This object is then sent alongside it with auxiliary information. This
* prevents a lot of unnecessary copies.
*
* Be sure to double check how `YaProcessData::Response` is used. We do some
* pointer tricks there to avoid copies and moves when serializing the results
@@ -157,21 +62,45 @@ class YaProcessData {
* original `ProcessData` object. This will avoid allocating unless it's
* absolutely necessary (e.g. when we receive more parameter changes than
* we've received in previous calls).
*
* During this process the input audio will be written to
* `shared_audio_buffers`. There's no direct link between this
* `YaProcessData` object and those buffers, but they should be used as a
* pair. This is a bit ugly, but optimizations sadly never made code
* prettier.
*/
void repopulate(const Steinberg::Vst::ProcessData& process_data);
void repopulate(const Steinberg::Vst::ProcessData& process_data,
AudioShmBuffer& shared_audio_buffers);
/**
* Reconstruct the original `ProcessData` object passed to the constructor
* and return it. This is used in the Wine plugin host when processing an
* `IAudioProcessor::process()` call.
*
* Because the actual audio is stored in an `AudioShmBuffer` outside of this
* object, we need to make sure that the `AudioBusBuffers` objects we're
* using point to the correct buffer even after a resize. To make it more
* difficult for us to mess this up, we'll store those bus-channel pointers
* in `Vst3Bridge::InstanceInterfaces` and we'll point the pointers in our
* `inputs` and `outputs` fields directly to those pointers. They will have
* been set up during `IAudioProcessor::setupProcessing()`.
*
* These can be either float or double pointers. Since a pointer is a
* pointer and they're stored using a union the actual type doesn't matter,
* but we'll accept these as void pointers since the stride will be
* different depending on whether the host is going to be sending double or
* single precision audio.
*/
Steinberg::Vst::ProcessData& reconstruct();
Steinberg::Vst::ProcessData& reconstruct(
std::vector<std::vector<void*>>& input_pointers,
std::vector<std::vector<void*>>& output_pointers);
/**
* A serializable wrapper around the output fields of `ProcessData`, so we
* only have to copy the information back that's actually important. These
* fields are pointers to the corresponding fields in `YaProcessData`. On
* the plugin side this information can then be written back to the host.
* The actual output audio is stored in the shared memory object.
*
* HACK: All of this is an optimization to avoid unnecessarily copying or
* moving and reallocating. Directly serializing and deserializing
@@ -183,7 +112,8 @@ class YaProcessData {
struct Response {
// We store raw pointers instead of references so we can default
// initialize this object during deserialization
std::vector<YaAudioBusBuffers>* outputs = nullptr;
boost::container::small_vector_base<Steinberg::Vst::AudioBusBuffers>*
outputs = nullptr;
std::optional<YaParameterChanges>* output_parameter_changes = nullptr;
std::optional<YaEventList>* output_events = nullptr;
@@ -218,20 +148,31 @@ class YaProcessData {
/**
* Write all of this output data back to the host's `ProcessData` object.
* During this process we'll also write the output audio from the
* corresponding shared memory audio buffers back.
*/
void write_back_outputs(Steinberg::Vst::ProcessData& process_data);
void write_back_outputs(Steinberg::Vst::ProcessData& process_data,
const AudioShmBuffer& shared_audio_buffers);
template <typename S>
void serialize(S& s) {
s.value4b(process_mode);
s.value4b(symbolic_sample_size);
s.value4b(num_samples);
// Both of these fields only store metadata. The actual audio is sent
// using an accompanying `AudioShmBuffer` object.
s.container(inputs, max_num_speakers);
s.container4b(outputs_num_channels, max_num_speakers);
s.container(outputs, max_num_speakers);
// The output parameter changes and events will remain empty on the
// plugin side, so by serializing them we merely indicate to the Wine
// plugin host whether the host supports them or not
s.object(input_parameter_changes);
s.value1b(output_parameter_changes_supported);
s.ext(output_parameter_changes, bitsery::ext::InPlaceOptional{});
s.ext(input_events, bitsery::ext::InPlaceOptional{});
s.value1b(output_events_supported);
s.ext(output_events, bitsery::ext::InPlaceOptional{});
s.ext(process_context, bitsery::ext::InPlaceOptional{});
// We of course won't serialize the `reconstructed_process_data` and all
@@ -260,18 +201,20 @@ class YaProcessData {
int32 num_samples;
/**
* In `ProcessData` they use C-style heap arrays, so they have to store the
* number of input/output busses, and then also store pointers to the first
* audio buffer object. We can combine these two into vectors.
* This contains metadata about the input buffers for every bus. During
* `reconstruct()` the channel pointers contained within these objects will
* be set to point to our shared memory surface that holds the actual audio
* data.
*/
std::vector<YaAudioBusBuffers> inputs;
boost::container::small_vector<Steinberg::Vst::AudioBusBuffers, 8> inputs;
/**
* For the outputs we only have to keep track of how many output channels
* each bus has. From this and from `num_samples` we can reconstruct the
* output buffers on the Wine side of the process call.
* This contains metadata about the output buffers for every bus. During
* `reconstruct()` the channel pointers contained within these objects will
* be set to point to our shared memory surface that holds the actual audio
* data.
*/
std::vector<int32> outputs_num_channels;
boost::container::small_vector<Steinberg::Vst::AudioBusBuffers, 8> outputs;
/**
* Incoming parameter changes.
@@ -279,10 +222,10 @@ class YaProcessData {
YaParameterChanges input_parameter_changes;
/**
* Whether the host supports output parameter changes (depending on whether
* `outputParameterChanges` was a null pointer or not).
* If the host supports it, this will allow the plugin to output parameter
* changes. Otherwise we'll also pass a null pointer to the plugin.
*/
bool output_parameter_changes_supported;
std::optional<YaParameterChanges> output_parameter_changes;
/**
* Incoming events.
@@ -290,10 +233,11 @@ class YaProcessData {
std::optional<YaEventList> input_events;
/**
* Whether the host supports output events (depending on whether
* `outputEvents` was a null pointer or not).
* If the host supports it, this will allow the plugin to output events,
* such as note events. Otherwise we'll also pass a null pointer to the
* plugin.
*/
bool output_events_supported;
std::optional<YaEventList> output_events;
/**
* Some more information about the project and transport.
@@ -301,29 +245,6 @@ class YaProcessData {
std::optional<Steinberg::Vst::ProcessContext> process_context;
private:
// These are the same fields as in `YaProcessData::Response`. We'll generate
// these as part of creating `reconstructed_process_data`, and they will be
// referred to in the response object created in `create_response()`
/**
* The outputs. Will be created based on `outputs_num_channels` (which
* determines how many output busses there are and how many channels each
* bus has) and `num_samples`.
*/
std::vector<YaAudioBusBuffers> outputs;
/**
* The output parameter changes. Will be initialized depending on
* `output_parameter_changes_supported`.
*/
std::optional<YaParameterChanges> output_parameter_changes;
/**
* The output events. Will be initialized depending on
* `output_events_supported`.
*/
std::optional<YaEventList> output_events;
// These last few members are used on the Wine plugin host side to
// reconstruct the original `ProcessData` object. Here we also initialize
// these `output*` fields so the Windows VST3 plugin can write to them
@@ -341,23 +262,6 @@ class YaProcessData {
*/
Response response_object;
/**
* Obtained by calling `.get()` on every `YaAudioBusBuffers` object in
* `intputs`. These objects contain pointers to the data in `inputs` and may
* thus not outlive them.
*/
std::vector<Steinberg::Vst::AudioBusBuffers> inputs_audio_bus_buffers;
/**
* Obtained by calling `.get()` on every `YaAudioBusBuffers` object in
* `outputs`. These objects contain pointers to the data in `outputs` and
* may thus not outlive them. These are created in a two step process, since
* we first have to create `outputs` from `outputs_num_channels` before we
* can transform it into a structure the Windows VST3 plugin can work with.
* Hooray for heap arrays.
*/
std::vector<Steinberg::Vst::AudioBusBuffers> outputs_audio_bus_buffers;
/**
* The process data we reconstruct from the other fields during `get()`.
*/
@@ -366,6 +270,14 @@ class YaProcessData {
namespace Steinberg {
namespace Vst {
template <typename S>
void serialize(S& s, Steinberg::Vst::AudioBusBuffers& buffers) {
// We don't don't touch the audio pointers. Those should point to the
// correct positions in the corresponding `AudioShmBuffer` object.
s.value4b(buffers.numChannels);
s.value8b(buffers.silenceFlags);
}
template <typename S>
void serialize(S& s, Steinberg::Vst::ProcessContext& process_context) {
// The docs don't mention that things ever got added to this context (and