diff --git a/meson.build b/meson.build index 9570903f..381a26d2 100644 --- a/meson.build +++ b/meson.build @@ -35,6 +35,7 @@ shared_library( [ 'src/common/communication.cpp', 'src/common/logging.cpp', + 'src/common/serialization.cpp', 'src/plugin/host-bridge.cpp', 'src/plugin/plugin.cpp', ], @@ -49,6 +50,7 @@ executable( [ 'src/common/communication.cpp', 'src/common/logging.cpp', + 'src/common/serialization.cpp', 'src/wine-host/plugin-bridge.cpp', 'src/wine-host/vst-host.cpp', ], diff --git a/src/common/communication.cpp b/src/common/communication.cpp index 76a4328b..72aebe1c 100644 --- a/src/common/communication.cpp +++ b/src/common/communication.cpp @@ -31,7 +31,8 @@ intptr_t send_event(boost::asio::local::stream_protocol::socket& socket, // There are some events that need specific structs that we can't simply // serialize as a string because they might contain null bytes if (is_dispatch && opcode == effProcessEvents) { - payload = *static_cast(data); + // Move the elements into a vector for easier serialization + payload = DynamicVstEvents(*static_cast(data)); } else { // TODO: More of these structs diff --git a/src/common/communication.h b/src/common/communication.h index 7fdad390..84a69c6c 100644 --- a/src/common/communication.h +++ b/src/common/communication.h @@ -181,7 +181,9 @@ void passthrough_event(boost::asio::local::stream_protocol::socket& socket, return const_cast(s.c_str()); }, // TODO: Check if the deserialization leaks memory - [&](VstEvents& events) -> void* { return &events; }, + [&](DynamicVstEvents& events) -> void* { + return &events.as_c_events(); + }, [&](NeedsBuffer&) -> void* { return buffer.data(); }}, event.payload); const intptr_t return_value = callback(plugin, event.opcode, event.index, diff --git a/src/common/logging.cpp b/src/common/logging.cpp index 5fe9be28..545ea9fb 100644 --- a/src/common/logging.cpp +++ b/src/common/logging.cpp @@ -161,8 +161,8 @@ void Logger::log_event(bool is_dispatch, overload{ [&](const std::nullptr_t&) { message << ""; }, [&](const std::string& s) { message << "\"" << s << "\""; }, - [&](const VstEvents& events) { - message << "<" << events.numEvents << " midi_events>"; + [&](const DynamicVstEvents& events) { + message << "<" << events.events.size() << " midi_events>"; }, [&](const NeedsBuffer&) { message << ""; }}, payload); diff --git a/src/common/serialization.cpp b/src/common/serialization.cpp new file mode 100644 index 00000000..ac9baddb --- /dev/null +++ b/src/common/serialization.cpp @@ -0,0 +1,35 @@ +// 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 . + +#include "serialization.h" + +#include + +DynamicVstEvents::DynamicVstEvents(const VstEvents& c_events) + : events(c_events.numEvents) { + // Copy from the C-style array into a vector for serialization + for (int i = 0; i < c_events.numEvents; i++) { + events[i] = c_events.events[0][i]; + } +} + +VstEvents& DynamicVstEvents::as_c_events() { + // Populate the vst_events struct with data from the vector + vst_events.numEvents = events.size(); + vst_events.events[0] = events.data(); + + return vst_events; +} diff --git a/src/common/serialization.h b/src/common/serialization.h index 7842c625..da3e685f 100644 --- a/src/common/serialization.h +++ b/src/common/serialization.h @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#pragma once + #include #include #include @@ -25,6 +27,7 @@ #include +// These constants are limits used by bitsery /** * The maximum number of audio channels supported. @@ -34,6 +37,12 @@ constexpr size_t max_audio_channels = 32; * The maximum number of samples in a buffer. */ constexpr size_t max_buffer_size = 16384; +/** + * The maximum number of midi events in a single `VstEvents` struct. + * + * TODO: Can this go higher? + */ +constexpr size_t max_midi_events = 32; /** * The maximum size in bytes of a string or buffer passed through a void pointer * in one of the dispatch functions. This is used to create buffers for plugins @@ -63,6 +72,43 @@ struct overload : Ts... { template overload(Ts...)->overload; +/** + * A wrapper around `VstEvents` that stores the data in a vector instead of a + * C-style array. Needed until bitsery supports C-style arrays + * https://github.com/fraillt/bitsery/issues/28. An advantage of this approach + * is that RAII will handle cleanup for us. + * + * Before serialization the events are read from a C-style array into a vector + * using this class's constructor, and after deserializing the original struct + * can be obtained again usign the `as_c_events()` method. + */ +class DynamicVstEvents { + public: + DynamicVstEvents(){}; + + explicit DynamicVstEvents(const VstEvents& c_events); + + /** + * Construct a `VstEvents` struct from the events vector. This contains a + * pointer to that vector's elements, so the returned object should not + * outlive this struct. + */ + VstEvents& as_c_events(); + + // XXX: The original `VstEvents` stuct hasonly one C-style array of + // `VstEvent`s, but I've seen some implementation that have two. Is + // this only for alignment or does this have an actual use? + std::vector events; + + private: + /** + * A `VstEvents` struct based on the `events` vector. Use the + * `as_c_events()` method to populate and return this after the `events` + * vector has been filled. + */ + VstEvents vst_events; +}; + /** * Marker struct to indicate that that the event requires some buffer to write * its results into. This is to prevent us from having to unnecessarily sending @@ -94,7 +140,7 @@ struct NeedsBuffer {}; * assume that this is the default if none of the above options apply. */ using EventPayload = - std::variant; + std::variant; /** * An event as dispatched by the VST host. These events will get forwarded to @@ -102,8 +148,14 @@ using EventPayload = * arguments sent to the `AEffect::dispatch` function. */ struct Event { - // TODO: Possibly update to account for VstEvents - using buffer_type = ArrayBuffer; + // TODO: Possibly use a vector here sicne we can't know the maximum size for + // certain + using buffer_type = ArrayBuffer; + + // Ensure that the buffer can be aligned correctly and that strings will fit + static_assert(std::tuple_size::value % 16 == 0); + static_assert(std::tuple_size::value >= + max_string_length + 32); int opcode; int index; @@ -136,31 +188,18 @@ struct Event { // I couldn't get this serializer to work seperately without // `EventPayload` in a struct - s.ext(payload, - bitsery::ext::StdVariant{ - [](S&, std::nullptr_t&) {}, - [](S& s, std::string& string) { - s.text1b(string, max_string_length); - }, - [](S& s, VstEvents& events) { - s.value4b(events.numEvents); - - // This will only ever read a single event since - // that's how the `VstEvents` struct is defined, - // hence the assertion. If multiple events can be - // passed at once then `VstEvents` should be - // modified. - // TODO: This is definitely not the case, somehow fix this - assert(events.numEvents <= 1); - s.container( - events.events, [](S& s, VstEvent*(&event_ptr)) { - s.ext(event_ptr, bitsery::ext::PointerOwner(), - [](S& s, VstEvent& event) { - s.container1b(event.dump); - }); - }); - }, - [](S&, NeedsBuffer&) {}}); + s.ext(payload, bitsery::ext::StdVariant{ + [](S&, std::nullptr_t&) {}, + [](S& s, std::string& string) { + s.text1b(string, max_string_length); + }, + [](S& s, DynamicVstEvents& events) { + s.container(events.events, max_midi_events, + [](S& s, VstEvent& event) { + s.container1b(event.dump); + }); + }, + [](S&, NeedsBuffer&) {}}); } };