diff --git a/README.md b/README.md
index e0771d85..e5857d99 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ imcomplete list of things that still have to be done before this can be used:
- All other optional interfaces
- Fully implemented:
- `GetPluginFactory()` and `IPluginFactory{,2,3}`
- - `IComponent`
+ - `IPluginBase` and `IComponent`
- Update the GitHub Actions workflows.
- Update yabridgectl to handle buth VST2 and VST3 plugins.
- Update all documentation to refer to VST2 and VST3 support separately, and
diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h
index 7d452a02..049d6bac 100644
--- a/src/common/serialization/vst3/base.h
+++ b/src/common/serialization/vst3/base.h
@@ -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
diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h
index 23ce5e95..60b68340 100644
--- a/src/common/serialization/vst3/component.h
+++ b/src/common/serialization/vst3/component.h
@@ -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"
diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h
new file mode 100644
index 00000000..b0e4a600
--- /dev/null
+++ b/src/common/serialization/vst3/process-data.h
@@ -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 .
+
+#pragma once
+
+#include
+
+#include
+#include
+
+#include "base.h"
+
+/**
+ * A serializable wrapper around `AudioBusBuffers` back by `std::vector`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
+ void serialize(S& s) {
+ s.value8b(silence_flags);
+ s.ext(buffers, bitsery::ext::StdVariant{
+ [](S& s, std::vector>& buffers) {
+ s.container(buffers, max_num_speakers,
+ [](S& s, auto& channel) {
+ s.container4b(channel, 1 << 16);
+ });
+ },
+ [](S& s, std::vector>& 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>>
+ 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
+ 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
+ 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 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 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)
+ */
+};