Add a partial AudioBusBuffers implementation

This commit is contained in:
Robbert van der Helm
2020-12-15 13:07:25 +01:00
parent 9c97ebb262
commit e7d7317f60
4 changed files with 219 additions and 2 deletions
+1 -1
View File
@@ -28,7 +28,7 @@
// we'll need for all of our interfaces
using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::int64,
Steinberg::uint32, Steinberg::tresult;
Steinberg::uint32, Steinberg::uint64, Steinberg::tresult;
/**
* Both `TUID` (`int8_t[16]`) and `FIDString` (`char*`) are hard to work with
@@ -32,6 +32,7 @@
#include "../common.h"
#include "base.h"
#include "host-application.h"
#include "process-data.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
@@ -0,0 +1,216 @@
// yabridge: a Wine VST bridge
// Copyright (C) 2020 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 <https://www.gnu.org/licenses/>.
#pragma once
#include <variant>
#include <bitsery/ext/std_variant.h>
#include <pluginterfaces/vst/ivstaudioprocessor.h>
#include "base.h"
/**
* 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 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.
*/
YaAudioBusBuffers();
/**
* Create a new, zero initialize audio bus buffers object. Used to
* reconstruct the output buffers during `YaProcessData::get()`.
*/
YaAudioBusBuffers(Steinberg::Vst::SymbolicSampleSizes sample_size,
size_t num_channels,
size_t num_samples);
/**
* Copy data from a host provided `AudioBusBuffers` object during a process
* call. Constructed as part of `YaProcessData`. 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.
*/
YaAudioBusBuffers(Steinberg::Vst::SymbolicSampleSizes sample_size,
const Steinberg::Vst::AudioBusBuffers& data);
/**
* Reconstruct the original `AudioBusBuffers` object passed to the
* constructor and return it. This is used as part of
* `YaProcessData::get()`.
*/
Steinberg::Vst::AudioBusBuffers& get();
template <typename S>
void serialize(S& s) {
s.value8b(silence_flags);
s.ext(buffers, bitsery::ext::StdVariant{
[](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);
});
},
});
}
private:
/**
* The `AudioBusBuffers` object we reconstruct during `get()`.
*/
Steinberg::Vst::AudioBusBuffers reconstructed_buffers;
/**
* A bitfield for silent channels copied directly from the input struct.
*/
uint64 silence_flags;
/**
* 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 the output fields of `ProcessData`. We send
* this back as a response to a process call so we can write those fields back
* to the host. It would be possible to just send `YaProcessData` back and have
* everything be in a single structure, but that would involve a lot of
* unnecessary copying (since, at least in theory, all the input audio buffers,
* events and context data shouldn't have been changed by the plugin).
*
* @see YaProcessData
*/
struct YaProcessDataResponse {
UniversalTResult result;
// TODO: Add the output fields and a function to write these back to a
// `ProcessData&`
template <typename S>
void serialize(S& s) {
s.object(result);
}
};
/**
* 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 `YaProcessDataResponse` object
* that contains all output values so we can write those back to the host.
*/
class YaProcessData {
public:
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.
*/
YaProcessData(const Steinberg::Vst::ProcessData& process_data);
/**
* 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.
*/
Steinberg::Vst::ProcessData& get();
/**
* **Move** all output written by the Windows VST3 plugin to a response
* object that can be used to write those results back to the host.
*/
YaProcessDataResponse move_outputs_to_response();
template <typename S>
void serialize(S& s) {
s.value4b(process_mode);
s.value4b(symbolic_sample_size);
s.value4b(num_samples);
s.container(
inputs, max_num_speakers,
[](S& s, YaAudioBusBuffers& buffers) { s.object(buffers); });
s.container4b(outputs_num_channels, max_num_speakers);
}
private:
/**
* The process data we reconstruct from the other fields during `get()`.
*/
Steinberg::Vst::ProcessData reconstructed_process_data;
/**
* The processing mode copied directly from the input struct.
*/
Steinberg::Vst::ProcessModes process_mode;
/**
* The symbolic sample size (see `Steinberg::Vst::SymbolicSampleSizes`) is
* important. The audio buffers are represented by as a C-style untagged
* union of array of either single or double precision floating point
* arrays. This field determines which of those variants should be used.
*/
Steinberg::Vst::SymbolicSampleSizes symbolic_sample_size;
/**
* The number of samples in each audio buffer.
*/
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.
*/
std::vector<YaAudioBusBuffers> 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.
*/
std::vector<int32> outputs_num_channels;
// TODO: Add these (but since these require interface implementations we'll
// do it in a second round)
/*
IParameterChanges*
inputParameterChanges; ///< incoming parameter changes for this block
IParameterChanges* outputParameterChanges; ///< outgoing parameter changes
///< for this block (optional)
IEventList* inputEvents; ///< incoming events for this block (optional)
IEventList* outputEvents; ///< outgoing events for this block (optional)
ProcessContext*
processContext; ///< processing context (optional, but most welcome)
*/
};