diff --git a/src/common/serialization/vst3/event-list.cpp b/src/common/serialization/vst3/event-list.cpp index afea7227..aec4ea2e 100644 --- a/src/common/serialization/vst3/event-list.cpp +++ b/src/common/serialization/vst3/event-list.cpp @@ -174,19 +174,24 @@ Steinberg::Vst::Event YaEvent::get() const { return event; } -YaEventList::YaEventList(){FUNKNOWN_CTOR} - -YaEventList::YaEventList(Steinberg::Vst::IEventList& event_list) { +YaEventList::YaEventList() { FUNKNOWN_CTOR +} +void YaEventList::clear() { + events.clear(); +} + +void YaEventList::repopulate(Steinberg::Vst::IEventList& event_list) { + // Copy over all events. Everything gets converted to `YaEvent`s. We sadly + // can't construct these in place because we don't know the event type yet. + events.clear(); events.reserve(event_list.getEventCount()); - - // Copy over all events. Everything gets converted to `YaEvent`s. - Steinberg::Vst::Event event; for (int i = 0; i < event_list.getEventCount(); i++) { // We're skipping the `kResultOk` assertions here + Steinberg::Vst::Event event; event_list.getEvent(i, event); - events.push_back(event); + events.emplace_back(event); } } diff --git a/src/common/serialization/vst3/event-list.h b/src/common/serialization/vst3/event-list.h index 930f6f19..17b639cb 100644 --- a/src/common/serialization/vst3/event-list.h +++ b/src/common/serialization/vst3/event-list.h @@ -213,15 +213,23 @@ struct YaEvent { class YaEventList : public Steinberg::Vst::IEventList { public: /** - * Default constructor with an empty event list. The plugin can use this to - * output data. + * We only provide a default constructor here, because we need to fill the + * existing object with new events every processing cycle to avoid + * reallocating a new object every time. */ YaEventList(); /** - * Read data from an existing `IEventList` object. + * Remove all events. Used when a null pointer gets passed to the input + * events field, and so the plugin can output its own events if the host + * supports this. */ - YaEventList(Steinberg::Vst::IEventList& event_list); + void clear(); + + /** + * Read data from an `IEventList` object into this existing object. + */ + void repopulate(Steinberg::Vst::IEventList& event_list); ~YaEventList(); diff --git a/src/common/serialization/vst3/param-value-queue.cpp b/src/common/serialization/vst3/param-value-queue.cpp index 14c7b5aa..de814ca5 100644 --- a/src/common/serialization/vst3/param-value-queue.cpp +++ b/src/common/serialization/vst3/param-value-queue.cpp @@ -16,19 +16,22 @@ #include "param-value-queue.h" -YaParamValueQueue::YaParamValueQueue(){FUNKNOWN_CTOR} - -YaParamValueQueue::YaParamValueQueue(Steinberg::Vst::ParamID parameter_id) - : parameter_id(parameter_id){FUNKNOWN_CTOR} - - // clang-format /really/ doesn't like these macros - YaParamValueQueue::YaParamValueQueue(Steinberg::Vst::IParamValueQueue & - original_queue) - : parameter_id(original_queue.getParameterId()), - queue(original_queue.getPointCount()) { +YaParamValueQueue::YaParamValueQueue() { FUNKNOWN_CTOR +} + +void YaParamValueQueue::clear_for_parameter( + Steinberg::Vst::ParamID parameter_id) { + this->parameter_id = parameter_id; + queue.clear(); +} + +void YaParamValueQueue::repopulate( + Steinberg::Vst::IParamValueQueue& original_queue) { + parameter_id = original_queue.getParameterId(); // Copy over all points to our vector + queue.resize(original_queue.getPointCount()); for (int i = 0; i < original_queue.getPointCount(); i++) { // We're skipping the assertions here and just assume that the function // returns `kResultOk` diff --git a/src/common/serialization/vst3/param-value-queue.h b/src/common/serialization/vst3/param-value-queue.h index db4c47c1..65d6f25c 100644 --- a/src/common/serialization/vst3/param-value-queue.h +++ b/src/common/serialization/vst3/param-value-queue.h @@ -32,20 +32,22 @@ class YaParamValueQueue : public Steinberg::Vst::IParamValueQueue { public: /** - * Default constructor with an empty queue. + * We only provide a default constructor here, because we need to fill the + * existing object with new data every processing cycle to avoid + * reallocating a new object every time. */ YaParamValueQueue(); /** - * Create an empty queue for a specific parameter. Used in - * `YaParameterChanges::addParameterData`. + * Clear this queue in place so that it can be used to write parameter data + * to. Used in `YaParameterChanges::addParameterData`. */ - YaParamValueQueue(Steinberg::Vst::ParamID parameter_id); + void clear_for_parameter(Steinberg::Vst::ParamID parameter_id); /** - * Read data from an existing `IParamValueQueue` object. + * Read data from an `IParamValueQueue` object into this existing object. */ - YaParamValueQueue(Steinberg::Vst::IParamValueQueue& original_queue); + void repopulate(Steinberg::Vst::IParamValueQueue& original_queue); ~YaParamValueQueue(); diff --git a/src/common/serialization/vst3/parameter-changes.cpp b/src/common/serialization/vst3/parameter-changes.cpp index 608d7c03..df3b4b48 100644 --- a/src/common/serialization/vst3/parameter-changes.cpp +++ b/src/common/serialization/vst3/parameter-changes.cpp @@ -16,17 +16,20 @@ #include "parameter-changes.h" -YaParameterChanges::YaParameterChanges(){FUNKNOWN_CTOR} - -YaParameterChanges::YaParameterChanges( - Steinberg::Vst::IParameterChanges& original_queues) { +YaParameterChanges::YaParameterChanges() { FUNKNOWN_CTOR +} - // Copy over all parameter changne queues. Everything gets converted to - // `YaParamValueQueue`s. - queues.reserve(original_queues.getParameterCount()); +void YaParameterChanges::clear() { + queues.clear(); +} + +void YaParameterChanges::repopulate( + Steinberg::Vst::IParameterChanges& original_queues) { + // Copy over all parameter changne queues + queues.resize(original_queues.getParameterCount()); for (int i = 0; i < original_queues.getParameterCount(); i++) { - queues.push_back(*original_queues.getParameterData(i)); + queues[i].repopulate(*original_queues.getParameterData(i)); } } @@ -75,7 +78,11 @@ Steinberg::Vst::IParamValueQueue* PLUGIN_API YaParameterChanges::addParameterData(const Steinberg::Vst::ParamID& id, int32& index /*out*/) { index = static_cast(queues.size()); - queues.push_back(YaParamValueQueue(id)); + + // Tiny hack, resizing avoids calling the constructor the second time we + // resize the vector to the same size + queues.resize(queues.size() + 1); + queues[index].clear_for_parameter(id); return &queues[index]; } diff --git a/src/common/serialization/vst3/parameter-changes.h b/src/common/serialization/vst3/parameter-changes.h index bbca39bb..edeb4e7c 100644 --- a/src/common/serialization/vst3/parameter-changes.h +++ b/src/common/serialization/vst3/parameter-changes.h @@ -31,15 +31,23 @@ class YaParameterChanges : public Steinberg::Vst::IParameterChanges { public: /** - * Default constructor with an empty parameter changes list. The plugin can - * use this to output data. + * We only provide a default constructor here, because we need to fill the + * existing object with new data every processing cycle to avoid + * reallocating a new object every time. */ YaParameterChanges(); /** - * Read data from an existing `IParameterChanges` object. + * Remove all parameter changes. Used when a null pointer gets passed to the + * input parameters field, and so the plugin can output its own parameter + * changes. */ - YaParameterChanges(Steinberg::Vst::IParameterChanges& original_queues); + void clear(); + + /** + * Read data from an `IParameterChanges` object into this existing object. + */ + void repopulate(Steinberg::Vst::IParameterChanges& original_queues); ~YaParameterChanges(); diff --git a/src/common/serialization/vst3/process-data.cpp b/src/common/serialization/vst3/process-data.cpp index 406b1d6f..213a4609 100644 --- a/src/common/serialization/vst3/process-data.cpp +++ b/src/common/serialization/vst3/process-data.cpp @@ -31,33 +31,44 @@ YaAudioBusBuffers::YaAudioBusBuffers(int32 sample_size, num_channels, std::vector(num_samples, 0.0)))) {} -YaAudioBusBuffers::YaAudioBusBuffers( +void YaAudioBusBuffers::repopulate( int32 sample_size, int32 num_samples, - const Steinberg::Vst::AudioBusBuffers& data) - : silence_flags(data.silenceFlags) { + const Steinberg::Vst::AudioBusBuffers& data) { + silence_flags = data.silenceFlags; + switch (sample_size) { case Steinberg::Vst::kSample64: { - std::vector> vector_buffers(data.numChannels); + if (!std::holds_alternative>>( + buffers)) { + buffers.emplace>>(); + } + + std::vector>& vector_buffers = + std::get>>(buffers); + vector_buffers.resize(data.numChannels); for (int channel = 0; channel < data.numChannels; channel++) { vector_buffers[channel].assign( &data.channelBuffers64[channel][0], &data.channelBuffers64[channel][num_samples]); } - - buffers = std::move(vector_buffers); } break; case Steinberg::Vst::kSample32: // I don't think they'll add any other sample sizes any time soon default: { - std::vector> vector_buffers(data.numChannels); + if (!std::holds_alternative>>( + buffers)) { + buffers.emplace>>(); + } + + std::vector>& vector_buffers = + std::get>>(buffers); + vector_buffers.resize(data.numChannels); for (int channel = 0; channel < data.numChannels; channel++) { vector_buffers[channel].assign( &data.channelBuffers32[channel][0], &data.channelBuffers32[channel][num_samples]); } - - buffers = std::move(vector_buffers); } break; } } @@ -140,39 +151,62 @@ void YaProcessDataResponse::write_back_outputs( YaProcessData::YaProcessData() {} -YaProcessData::YaProcessData(const Steinberg::Vst::ProcessData& process_data) - : process_mode(process_data.processMode), - symbolic_sample_size(process_data.symbolicSampleSize), - num_samples(process_data.numSamples), - outputs_num_channels(process_data.numOutputs), - // Even though `ProcessData::inputParamterChanges` is mandatory, the VST3 - // validator will pass a null pointer here - input_parameter_changes( - process_data.inputParameterChanges - ? YaParameterChanges(*process_data.inputParameterChanges) - : YaParameterChanges()), - output_parameter_changes_supported(process_data.outputParameterChanges), - input_events(process_data.inputEvents ? std::make_optional( - *process_data.inputEvents) - : std::nullopt), - output_events_supported(process_data.outputEvents), - process_context(process_data.processContext - ? std::make_optional( - *process_data.processContext) - : std::nullopt) { +void YaProcessData::repopulate( + const Steinberg::Vst::ProcessData& process_data) { + // 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 + // destroying and creating objects most of the time. + process_mode = process_data.processMode; + 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 + inputs.resize(process_data.numInputs); for (int i = 0; i < process_data.numInputs; i++) { - inputs.emplace_back(symbolic_sample_size, num_samples, - process_data.inputs[i]); + inputs[i].repopulate(symbolic_sample_size, num_samples, + process_data.inputs[i]); } - // Fetch the number of channels for each output so we can recreate these - // buffers in the Wine plugin host + // 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; } + + // Even though `ProcessData::inputParamterChanges` is mandatory, the VST3 + // validator will pass a null pointer here + if (process_data.inputParameterChanges) { + input_parameter_changes.repopulate(*process_data.inputParameterChanges); + } else { + input_parameter_changes.clear(); + } + + output_parameter_changes_supported = process_data.outputParameterChanges; + + if (process_data.inputEvents) { + if (!input_events) { + input_events.emplace(); + } + input_events->repopulate(*process_data.inputEvents); + } else { + input_events.reset(); + } + + output_events_supported = process_data.outputEvents; + + if (process_data.processContext) { + process_context.emplace(*process_data.processContext); + } else { + process_context.reset(); + } } Steinberg::Vst::ProcessData& YaProcessData::get() { + // TODO: Also optimize this to make use of the reused objects + // We'll have to transform out `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 @@ -193,6 +227,13 @@ Steinberg::Vst::ProcessData& YaProcessData::get() { outputs_audio_bus_buffers.push_back(buffers.get()); } + if (output_parameter_changes) { + output_parameter_changes->clear(); + } + if (output_events) { + output_events->clear(); + } + reconstructed_process_data.processMode = process_mode; reconstructed_process_data.symbolicSampleSize = symbolic_sample_size; reconstructed_process_data.numSamples = num_samples; diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h index 794bfecf..8e0a83c0 100644 --- a/src/common/serialization/vst3/process-data.h +++ b/src/common/serialization/vst3/process-data.h @@ -39,14 +39,18 @@ class YaAudioBusBuffers { public: /** - * A default constructor does not make any sense here since the actual data - * is a union, but we need a default constructor for bitsery. + * 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(); /** * Create a new, zero initialize audio bus buffers object. Used to * reconstruct the output buffers during `YaProcessData::get()`. + * + * TODO: Replace with a function similar to `repopulate` that just reassigns + * the existing buffers for the outputs created on the Wine side. */ YaAudioBusBuffers(int32 sample_size, size_t num_samples, @@ -54,15 +58,15 @@ class YaAudioBusBuffers { /** * Copy data from a host provided `AudioBusBuffers` object during a process - * call. Constructed as part of `YaProcessData`. Since `AudioBusBuffers` + * 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. */ - YaAudioBusBuffers(int32 sample_size, - int32 num_samples, - const Steinberg::Vst::AudioBusBuffers& data); + void repopulate(int32 sample_size, + int32 num_samples, + const Steinberg::Vst::AudioBusBuffers& data); /** * Reconstruct the original `AudioBusBuffers` object passed to the @@ -170,14 +174,21 @@ struct YaProcessDataResponse { */ class YaProcessData { public: + /** + * Initialize the process data. We only provide a default constructor here, + * because we need to fill the existing object with new data every + * processing cycle to avoid reallocating a new object every time. + */ YaProcessData(); /** * Copy data from a host provided `ProcessData` object during a process * call. This struct can then be serialized, and `YaProcessData::get()` can - * then be used again to recreate the original `ProcessData` object. + * then be used again to recreate the 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). */ - YaProcessData(const Steinberg::Vst::ProcessData& process_data); + void repopulate(const Steinberg::Vst::ProcessData& process_data); /** * Reconstruct the original `ProcessData` object passed to the constructor diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 3fe48d6a..2f5de79b 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -186,10 +186,8 @@ Vst3PluginProxyImpl::process(Steinberg::Vst::ProcessData& data) { last_audio_thread_priority_synchronization = now; } - // TODO: Document - // TODO: Actually repopulate `process_data` with new data, right now this - // assignment just destroys the old object and creates a new object. - process_data = data; + // We reuse this existing object to avoid allocations + process_data.repopulate(data); ProcessResponse response = bridge.send_audio_processor_message(YaAudioProcessor::Process{