// yabridge: a Wine VST bridge // Copyright (C) 2020-2021 Robbert van der Helm // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "process-data.h" #include "src/common/utils.h" YaAudioBusBuffers::YaAudioBusBuffers() {} YaAudioBusBuffers::YaAudioBusBuffers(int32 sample_size, size_t num_samples, size_t num_channels) : buffers(sample_size == Steinberg::Vst::SymbolicSampleSizes::kSample64 ? decltype(buffers)(std::vector>( num_channels, std::vector(num_samples, 0.0))) : decltype(buffers)(std::vector>( num_channels, std::vector(num_samples, 0.0)))) {} void YaAudioBusBuffers::repopulate( int32 sample_size, int32 num_samples, const Steinberg::Vst::AudioBusBuffers& data) { silence_flags = data.silenceFlags; switch (sample_size) { case Steinberg::Vst::kSample64: { 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]); } } break; case Steinberg::Vst::kSample32: // I don't think they'll add any other sample sizes any time soon default: { 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]); } } break; } } Steinberg::Vst::AudioBusBuffers YaAudioBusBuffers::get() { Steinberg::Vst::AudioBusBuffers reconstructed_buffers; reconstructed_buffers.silenceFlags = silence_flags; std::visit(overload{ [&](std::vector>& buffers) { buffer_pointers.clear(); for (auto& buffer : buffers) { buffer_pointers.push_back(buffer.data()); } reconstructed_buffers.numChannels = static_cast(buffers.size()); reconstructed_buffers.channelBuffers64 = reinterpret_cast(buffer_pointers.data()); }, [&](std::vector>& buffers) { buffer_pointers.clear(); for (auto& buffer : buffers) { buffer_pointers.push_back(buffer.data()); } reconstructed_buffers.numChannels = static_cast(buffers.size()); reconstructed_buffers.channelBuffers32 = reinterpret_cast(buffer_pointers.data()); }, }, buffers); return reconstructed_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( overload{ [&](const std::vector>& buffers) { for (int channel = 0; channel < output_buffers.numChannels; channel++) { std::copy(buffers[channel].begin(), buffers[channel].end(), output_buffers.channelBuffers64[channel]); } }, [&](const std::vector>& buffers) { for (int channel = 0; channel < output_buffers.numChannels; channel++) { std::copy(buffers[channel].begin(), buffers[channel].end(), output_buffers.channelBuffers32[channel]); } }, }, buffers); } void YaProcessDataResponse::write_back_outputs( Steinberg::Vst::ProcessData& process_data) { for (int i = 0; i < process_data.numOutputs; i++) { outputs[i].write_back_outputs(process_data.outputs[i]); } if (output_parameter_changes && process_data.outputParameterChanges) { output_parameter_changes->write_back_outputs( *process_data.outputParameterChanges); } if (output_events && process_data.outputEvents) { output_events->write_back_outputs(*process_data.outputEvents); } } YaProcessData::YaProcessData() {} 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[i].repopulate(symbolic_sample_size, num_samples, process_data.inputs[i]); } // 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 // buffer data themselves. inputs_audio_bus_buffers.clear(); for (auto& buffers : inputs) { inputs_audio_bus_buffers.push_back(buffers.get()); } // 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.clear(); outputs_audio_bus_buffers.clear(); for (auto& num_channels : outputs_num_channels) { YaAudioBusBuffers& buffers = outputs.emplace_back( symbolic_sample_size, num_samples, num_channels); 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; reconstructed_process_data.numInputs = static_cast(inputs.size()); reconstructed_process_data.numOutputs = static_cast(outputs_num_channels.size()); reconstructed_process_data.inputs = inputs_audio_bus_buffers.data(); reconstructed_process_data.outputs = outputs_audio_bus_buffers.data(); reconstructed_process_data.inputParameterChanges = &input_parameter_changes; if (output_parameter_changes_supported) { output_parameter_changes.emplace(); reconstructed_process_data.outputParameterChanges = &*output_parameter_changes; } else { output_parameter_changes.reset(); reconstructed_process_data.outputParameterChanges = nullptr; } if (input_events) { reconstructed_process_data.inputEvents = &*input_events; } else { reconstructed_process_data.inputEvents = nullptr; } if (output_events_supported) { output_events.emplace(); reconstructed_process_data.outputEvents = &*output_events; } else { output_events.reset(); reconstructed_process_data.outputEvents = nullptr; } if (process_context) { reconstructed_process_data.processContext = &*process_context; } else { reconstructed_process_data.processContext = nullptr; } return reconstructed_process_data; } YaProcessDataResponse YaProcessData::move_outputs_to_response() { // 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 for (int i = 0; i < reconstructed_process_data.numOutputs; i++) { outputs[i].silence_flags = reconstructed_process_data.outputs[i].silenceFlags; } return YaProcessDataResponse{ .outputs = std::move(outputs), .output_parameter_changes = std::move(output_parameter_changes), .output_events = std::move(output_events)}; }