mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 03:50:11 +02:00
Use std::avriant to encode event payloads
It's a bit more complicated this way, but nwo we can easily add support for the opcode specific structs.
This commit is contained in:
@@ -7,10 +7,21 @@ intptr_t send_event(boost::asio::local::stream_protocol::socket& socket,
|
||||
void* data,
|
||||
float option,
|
||||
std::optional<std::pair<Logger&, bool>> logging) {
|
||||
auto payload =
|
||||
data == nullptr
|
||||
? std::nullopt
|
||||
: std::make_optional(std::string(static_cast<char*>(data)));
|
||||
// Encode the right payload type for this event. Check the documentation for
|
||||
// `EventPayload` for more information.
|
||||
EventPayload payload = nullptr;
|
||||
if (data != nullptr) {
|
||||
// TODO: Specific structs go here
|
||||
|
||||
// Assume buffers are zeroed out, this is probably not the case
|
||||
char* c_string = static_cast<char*>(data);
|
||||
if (c_string[0] != 0) {
|
||||
payload = std::string(c_string);
|
||||
} else {
|
||||
payload = std::array<char, max_string_length>();
|
||||
}
|
||||
}
|
||||
|
||||
if (logging.has_value()) {
|
||||
auto [logger, is_dispatch] = *logging;
|
||||
logger.log_event(is_dispatch, opcode, index, value, payload, option);
|
||||
|
||||
+20
-24
@@ -134,6 +134,7 @@ intptr_t send_event(boost::asio::local::stream_protocol::socket& socket,
|
||||
void* data,
|
||||
float option,
|
||||
std::optional<std::pair<Logger&, bool>> logging);
|
||||
|
||||
/**
|
||||
* Receive an event from a socket and pass it through to some callback function.
|
||||
* This is used for both the host -> plugin 'dispatch' events and the plugin ->
|
||||
@@ -160,35 +161,30 @@ void passthrough_event(boost::asio::local::stream_protocol::socket& socket,
|
||||
if (logging.has_value()) {
|
||||
auto [logger, is_dispatch] = *logging;
|
||||
logger.log_event(is_dispatch, event.opcode, event.index, event.value,
|
||||
event.data, event.option);
|
||||
}
|
||||
|
||||
// The void pointer argument for the dispatch function is used for
|
||||
// either:
|
||||
// - Not at all, in which case it will be a null pointer
|
||||
// - For passing strings as input to the event
|
||||
// - For providing a buffer for the event to write results back into
|
||||
char* payload = nullptr;
|
||||
std::array<char, max_string_length> buffer;
|
||||
if (event.data.has_value()) {
|
||||
// If the data parameter was an empty string, then we're going to
|
||||
// pass a larger buffer to the dispatch function instead. Otherwise
|
||||
// we'll pass the data passed by the host.
|
||||
if (!event.data->empty()) {
|
||||
payload = const_cast<char*>(event.data->c_str());
|
||||
} else {
|
||||
payload = buffer.data();
|
||||
}
|
||||
event.payload, event.option);
|
||||
}
|
||||
|
||||
void* data = std::visit(
|
||||
overload{[&](std::nullptr_t) -> void* { return nullptr; },
|
||||
[&](std::string s) -> void* {
|
||||
return const_cast<char*>(s.c_str());
|
||||
},
|
||||
[&](std::array<char, max_string_length> buffer) -> void* {
|
||||
return buffer.data();
|
||||
}},
|
||||
event.payload);
|
||||
const intptr_t return_value = callback(plugin, event.opcode, event.index,
|
||||
event.value, payload, event.option);
|
||||
event.value, data, event.option);
|
||||
|
||||
// Only write back the value from `payload` if we were passed an empty
|
||||
// buffer to write into
|
||||
bool is_updated = event.data.has_value() && event.data->empty();
|
||||
// Only write back data for empty buffers
|
||||
// XXX: Is it possbile here that we got passed a non empty buffer (i.e.
|
||||
// because it was not zeroed out by the host) for an event that should
|
||||
// report some data back?
|
||||
const auto response_data =
|
||||
is_updated ? std::make_optional(payload) : std::nullopt;
|
||||
std::holds_alternative<std::array<char, max_string_length>>(
|
||||
event.payload)
|
||||
? std::make_optional(static_cast<char*>(data))
|
||||
: std::nullopt;
|
||||
|
||||
if (logging.has_value()) {
|
||||
auto [logger, is_dispatch] = *logging;
|
||||
|
||||
+9
-10
@@ -121,7 +121,7 @@ void Logger::log_event(bool is_dispatch,
|
||||
int opcode,
|
||||
int index,
|
||||
intptr_t value,
|
||||
std::optional<std::string> payload,
|
||||
EventPayload payload,
|
||||
float option) {
|
||||
if (BOOST_UNLIKELY(verbosity >= Verbosity::events)) {
|
||||
std::ostringstream message;
|
||||
@@ -140,15 +140,14 @@ void Logger::log_event(bool is_dispatch,
|
||||
|
||||
message << "(index = " << index << ", value = " << value
|
||||
<< ", option = " << option << ", data = ";
|
||||
if (!payload.has_value()) {
|
||||
message << "<nullptr>";
|
||||
} else if (payload->empty()) {
|
||||
message << "<writable_buffer>";
|
||||
} else {
|
||||
// Might print binary payload, maybe check for this?
|
||||
message << "\"" << payload.value() << "\"";
|
||||
}
|
||||
message << ")";
|
||||
|
||||
std::visit(
|
||||
overload{[&](std::nullptr_t) { message << "<nullptr>"; },
|
||||
[&](std::string s) { message << "\"" << s << "\""; },
|
||||
[&](std::array<char, max_string_length>) {
|
||||
message << "<writeable_buffer>";
|
||||
}},
|
||||
payload);
|
||||
|
||||
log(message.str());
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
|
||||
#include "serialization.h"
|
||||
|
||||
/**
|
||||
* Super basic logging facility meant for debugging malfunctioning VST
|
||||
* plugins. This is also used to redirect the output of the Wine process
|
||||
@@ -93,7 +95,7 @@ class Logger {
|
||||
int opcode,
|
||||
int index,
|
||||
intptr_t value,
|
||||
std::optional<std::string> payload,
|
||||
EventPayload payload,
|
||||
float option);
|
||||
void log_event_response(bool is_dispatch,
|
||||
intptr_t return_value,
|
||||
|
||||
+72
-10
@@ -1,10 +1,15 @@
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/ext/std_optional.h>
|
||||
#include <bitsery/ext/std_variant.h>
|
||||
#include <bitsery/traits/array.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
#include <vestige/aeffectx.h>
|
||||
|
||||
#include <variant>
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* The maximum number of audio channels supported.
|
||||
*/
|
||||
@@ -15,10 +20,10 @@ constexpr size_t max_audio_channels = 32;
|
||||
constexpr size_t max_buffer_size = 16384;
|
||||
/**
|
||||
* The maximum size in bytes of a string or buffer passed through a void pointer
|
||||
* in one of the dispatch functions. This is used as a buffer size and also as a
|
||||
* cutoff for checking if c-style strings behind a `char*` have changed.
|
||||
* in one of the dispatch functions. This is used to create buffers for plugins
|
||||
* to write strings to.
|
||||
*/
|
||||
constexpr size_t max_string_length = 128;
|
||||
constexpr size_t max_string_length = 64;
|
||||
|
||||
/**
|
||||
* A simple constant sized buffer for smaller types that can be allocated on the
|
||||
@@ -33,12 +38,53 @@ using OutputAdapter = bitsery::OutputBufferAdapter<B>;
|
||||
template <typename B>
|
||||
using InputAdapter = bitsery::InputBufferAdapter<B>;
|
||||
|
||||
// The cannonical overloading template for `std::visitor`, not sure why this
|
||||
// isn't part of the standard library
|
||||
template <class... Ts>
|
||||
struct overload : Ts... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
template <class... Ts>
|
||||
overload(Ts...)->overload<Ts...>;
|
||||
|
||||
/**
|
||||
* VST events are passed a void pointer that can contain a variety of different
|
||||
* data types depending on the event's opcode. This is typically either:
|
||||
*
|
||||
* - A null pointer, used for simple events.
|
||||
* - A char pointer to a null terminated string, used for passing strings to the
|
||||
* plugin such as when renaming presets. Bitsery handles the serialization for
|
||||
* us.
|
||||
*
|
||||
* NOTE: Bitsery does not support null terminated C-strings without a known
|
||||
* size. We can replace `std::string` with `char*` once it does for
|
||||
* clarity's sake.
|
||||
*
|
||||
* - Specific data structures from `aeffextx.h`. For instance an event with the
|
||||
* opcode `effProcessEvents` comes with a struct containing a list of midi
|
||||
* events.
|
||||
*
|
||||
* TODO: A lot of these are still missing, beginning with `VstEvents`.
|
||||
*
|
||||
* - Some empty buffer for the plugin to write its own data to, for instance for
|
||||
* a plugin to report its name or the label for a certain parameter. We'll
|
||||
* assume that this is the default if none of the above options apply.
|
||||
*
|
||||
* TODO: As a simple optimization we of course wouldn't have to send an entire
|
||||
* empty array here, this should be replaced by some kind of marker
|
||||
* struct. This would require some minor modifications in
|
||||
* `passthrough_event()`.
|
||||
*/
|
||||
using EventPayload = std::
|
||||
variant<std::nullptr_t, std::string, std::array<char, max_string_length>>;
|
||||
|
||||
/**
|
||||
* An event as dispatched by the VST host. These events will get forwarded to
|
||||
* the VST host process running under Wine. The fields here mirror those
|
||||
* arguments sent to the `AEffect::dispatch` function.
|
||||
*/
|
||||
struct Event {
|
||||
// TODO: Possibly update to account for VstEvents
|
||||
using buffer_type = ArrayBuffer<max_string_length + 32>;
|
||||
|
||||
int opcode;
|
||||
@@ -49,12 +95,16 @@ struct Event {
|
||||
intptr_t value;
|
||||
float option;
|
||||
/**
|
||||
* The event dispatch function has a void pointer parameter that's used to
|
||||
* either send string messages to the event (e.g. for `effCanDo`) or to
|
||||
* write a string back into. This value will contain an (empty) string if
|
||||
* the void* parameter for the dispatch function was not a null pointer.
|
||||
* The event dispatch function has a void pointer parameter that's often
|
||||
* used to either pass additional data for the event or to provide a buffer
|
||||
* for the plugin to write a string into.
|
||||
*
|
||||
* The `VstEvents` struct passed for the `effProcessEvents` event contains
|
||||
* an array of pointers. This requires some special handling which is why we
|
||||
* have to use an `std::variant` instead of a simple string buffer. Luckily
|
||||
* Bitsery can do all the hard work for us.
|
||||
*/
|
||||
std::optional<std::string> data;
|
||||
EventPayload payload;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
@@ -65,8 +115,20 @@ struct Event {
|
||||
// bit large pointers.
|
||||
s.value8b(value);
|
||||
s.value4b(option);
|
||||
s.ext(data, bitsery::ext::StdOptional(),
|
||||
[](S& s, auto& v) { s.text1b(v, max_string_length); });
|
||||
|
||||
// I couldn't get this serializer to work seperately without
|
||||
// `EventPayload` in a struct
|
||||
s.ext(payload,
|
||||
bitsery::ext::StdVariant{
|
||||
// TODO: Some of these oerlaods might not be necessary, check
|
||||
// if this is the case
|
||||
[](S&, std::nullptr_t&) {},
|
||||
[](S& s, std::string& string) {
|
||||
s.text1b(string, max_string_length);
|
||||
},
|
||||
[](S& s, std::array<char, max_string_length>& buffer) {
|
||||
s.container1b(buffer);
|
||||
}});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user