// yabridge: a Wine plugin bridge // Copyright (C) 2020-2022 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 "../../utils.h" YaProcessData::YaProcessData() noexcept : // This needs to be zero initialized so we can safely call // `create_response()` on the plugin side reconstructed_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 // destroying and creating objects most of the time. process_mode_ = process_data.processMode; symbolic_sample_size_ = process_data.symbolicSampleSize; num_samples_ = process_data.numSamples; // 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 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(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( bus, channel)); } else { std::copy_n(process_data.inputs[bus].channelBuffers32[channel], process_data.numSamples, shared_audio_buffers.input_channel_ptr( bus, channel)); } } } 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(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 // validator will pass a null pointer here if (process_data.inputParameterChanges) { input_parameter_changes_.repopulate( *process_data.inputParameterChanges); } else { input_parameter_changes_.clear(); } // 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_) { input_events_.emplace(); } input_events_->repopulate(*process_data.inputEvents); } else { input_events_.reset(); } // 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); } else { process_context_.reset(); } } Steinberg::Vst::ProcessData& YaProcessData::reconstruct( std::vector>& input_pointers, std::vector>& 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(inputs_.size()); reconstructed_process_data_.numOutputs = static_cast(outputs_.size()); // 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::setActive()`. // 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(input_pointers[bus].data()); } for (size_t bus = 0; bus < outputs_.size(); bus++) { outputs_[bus].channelBuffers32 = reinterpret_cast(output_pointers[bus].data()); } reconstructed_process_data_.inputs = inputs_.data(); reconstructed_process_data_.outputs = outputs_.data(); reconstructed_process_data_.inputParameterChanges = &input_parameter_changes_; if (output_parameter_changes_) { output_parameter_changes_->clear(); reconstructed_process_data_.outputParameterChanges = &*output_parameter_changes_; } else { reconstructed_process_data_.outputParameterChanges = nullptr; } if (input_events_) { reconstructed_process_data_.inputEvents = &*input_events_; } else { reconstructed_process_data_.inputEvents = nullptr; } if (output_events_) { output_events_->clear(); reconstructed_process_data_.outputEvents = &*output_events_; } else { reconstructed_process_data_.outputEvents = nullptr; } if (process_context_) { reconstructed_process_data_.processContext = &*process_context_; } else { reconstructed_process_data_.processContext = nullptr; } return reconstructed_process_data_; } YaProcessData::Response& YaProcessData::create_response() 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 // fields when sending the response from the Wine side. This lets us avoid // allocations by not having to copy or move the data. On the plugin side we // need to be careful to deserialize into an existing // `YaAudioProcessor::ProcessResponse` object with a response object that // belongs to an actual process data object, because with these changes it's // no longer possible to deserialize those results into a new ad-hoc created // object. response_object_.outputs = &outputs_; response_object_.output_parameter_changes = &output_parameter_changes_; response_object_.output_events = &output_events_; return response_object_; } void YaProcessData::write_back_outputs( Steinberg::Vst::ProcessData& process_data, const AudioShmBuffer& shared_audio_buffers) { assert(static_cast(outputs_.size()) == process_data.numOutputs); 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(bus, channel), process_data.numSamples, process_data.outputs[bus].channelBuffers64[channel]); } else { std::copy_n( shared_audio_buffers.output_channel_ptr(bus, channel), process_data.numSamples, process_data.outputs[bus].channelBuffers32[channel]); } } } 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); } }