Files
yabridge/src/common/serialization/vst3/process-data.h
T
2020-12-16 17:14:42 +01:00

342 lines
12 KiB
C++

// 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_optional.h>
#include <bitsery/ext/std_variant.h>
#include <pluginterfaces/vst/ivstaudioprocessor.h>
#include "base.h"
#include "event-list.h"
#include "parameter-changes.h"
// 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 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(int32 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. Similarly the
* `ProcessData`' `numSamples` field determines the extent of these arrays.
*/
YaAudioBusBuffers(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::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;
/**
* 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;
/**
* 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;
/**
* 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 {
// These fields are directly moved from a `YaProcessData` object. See the
// docstrings there for more information
std::vector<YaAudioBusBuffers> outputs;
std::optional<YaParameterChanges> output_parameter_changes;
std::optional<YaEventList> output_events;
// TODO: Add function to write these back to the host's `ProcessData`
template <typename S>
void serialize(S& s) {
s.container(outputs, max_num_speakers);
s.ext(output_parameter_changes, bitsery::ext::StdOptional{});
s.ext(output_events, bitsery::ext::StdOptional{});
}
};
/**
* 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. This
* should of course be done after making a call to the `IAudioProcessor`'s
* `process()` function with the object obtained using `get()`.
*/
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.container4b(outputs_num_channels, max_num_speakers);
s.object(input_parameter_changes);
s.value1b(output_parameter_changes_supported);
s.ext(input_events, bitsery::ext::StdOptional{});
s.value1b(output_events_supported);
s.ext(process_context, bitsery::ext::StdOptional{});
// We of course won't serialize the `reconstructed_process_data` and all
// of the `output*` fields defined below it
}
private:
// These fields are input and context data read from the original
// `ProcessData` object
/**
* The processing mode copied directly from the input struct.
*/
int32 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.
*/
int32 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;
/**
* Incoming parameter changes.
*/
YaParameterChanges input_parameter_changes;
/**
* Whether the host supports output parameter changes (depending on whether
* `outputParameterChanges` was a null pointer or not).
*/
bool output_parameter_changes_supported;
/**
* Incoming events.
*/
std::optional<YaEventList> input_events;
/**
* Whether the host supports output events (depending on whether
* `outputEvents` was a null pointer or not).
*/
bool output_events_supported;
/**
* Some more information about the project and transport.
*/
std::optional<Steinberg::Vst::ProcessContext> process_context;
// 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
// though a regular `ProcessData` object. Finally we can wrap these output
// fields back into a `YaProcessDataResponse` using
// `move_outputs_to_response()`. so they can be serialized and written back
// to the host's `ProcessData` object.
/**
* The process data we reconstruct from the other fields during `get()`.
*/
Steinberg::Vst::ProcessData reconstructed_process_data;
// These are the same fields as in `YaProcessDataResponse`. We'll generate
// these as part of creating `reconstructed_process_data`, and they will be
// moved into a response object during `move_outputs_to_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;
};
namespace Steinberg {
namespace Vst {
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
// that some fields thus may not exist for all hosts), so we'll just
// directly serialize everything. If it does end up being the case that new
// fields were added here we should serialize based on the bits set in the
// flags bitfield.
s.value4b(process_context.state);
s.value8b(process_context.sampleRate);
s.value8b(process_context.projectTimeSamples);
s.value8b(process_context.systemTime);
s.value8b(process_context.continousTimeSamples);
s.value8b(process_context.projectTimeMusic);
s.value8b(process_context.barPositionMusic);
s.value8b(process_context.cycleStartMusic);
s.value8b(process_context.cycleEndMusic);
s.value8b(process_context.tempo);
s.value4b(process_context.timeSigNumerator);
s.value4b(process_context.timeSigDenominator);
s.object(process_context.chord);
s.value4b(process_context.smpteOffsetSubframes);
s.value4b(process_context.smpteOffsetSubframes);
s.object(process_context.frameRate);
s.value4b(process_context.samplesToNextClock);
}
template <typename S>
void serialize(S& s, Steinberg::Vst::Chord& chord) {
s.value1b(chord.keyNote);
s.value1b(chord.rootNote);
s.value2b(chord.chordMask);
}
template <typename S>
void serialize(S& s, Steinberg::Vst::FrameRate& frame_rate) {
s.value1b(frame_rate.framesPerSecond);
s.value1b(frame_rate.flags);
}
} // namespace Vst
} // namespace Steinberg