diff --git a/src/common/communication.cpp b/src/common/communication.cpp index 7ce87563..947ade34 100644 --- a/src/common/communication.cpp +++ b/src/common/communication.cpp @@ -7,10 +7,21 @@ intptr_t send_event(boost::asio::local::stream_protocol::socket& socket, void* data, float option, std::optional> logging) { - auto payload = - data == nullptr - ? std::nullopt - : std::make_optional(std::string(static_cast(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(data); + if (c_string[0] != 0) { + payload = std::string(c_string); + } else { + payload = std::array(); + } + } + if (logging.has_value()) { auto [logger, is_dispatch] = *logging; logger.log_event(is_dispatch, opcode, index, value, payload, option); diff --git a/src/common/communication.h b/src/common/communication.h index 5f41a2a1..19e7ca46 100644 --- a/src/common/communication.h +++ b/src/common/communication.h @@ -134,6 +134,7 @@ intptr_t send_event(boost::asio::local::stream_protocol::socket& socket, void* data, float option, std::optional> 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 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(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(s.c_str()); + }, + [&](std::array 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>( + event.payload) + ? std::make_optional(static_cast(data)) + : std::nullopt; if (logging.has_value()) { auto [logger, is_dispatch] = *logging; diff --git a/src/common/logging.cpp b/src/common/logging.cpp index 7a720b8f..e14d235d 100644 --- a/src/common/logging.cpp +++ b/src/common/logging.cpp @@ -121,7 +121,7 @@ void Logger::log_event(bool is_dispatch, int opcode, int index, intptr_t value, - std::optional 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 << ""; - } else if (payload->empty()) { - message << ""; - } else { - // Might print binary payload, maybe check for this? - message << "\"" << payload.value() << "\""; - } - message << ")"; + + std::visit( + overload{[&](std::nullptr_t) { message << ""; }, + [&](std::string s) { message << "\"" << s << "\""; }, + [&](std::array) { + message << ""; + }}, + payload); log(message.str()); } diff --git a/src/common/logging.h b/src/common/logging.h index 37a08763..ad2da1fe 100644 --- a/src/common/logging.h +++ b/src/common/logging.h @@ -20,6 +20,8 @@ #include #include +#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 payload, + EventPayload payload, float option); void log_event_response(bool is_dispatch, intptr_t return_value, diff --git a/src/common/serialization.h b/src/common/serialization.h index 1f8d2445..7c1057f0 100644 --- a/src/common/serialization.h +++ b/src/common/serialization.h @@ -1,10 +1,15 @@ #include #include +#include #include #include #include #include +#include + +#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; template using InputAdapter = bitsery::InputBufferAdapter; +// The cannonical overloading template for `std::visitor`, not sure why this +// isn't part of the standard library +template +struct overload : Ts... { + using Ts::operator()...; +}; +template +overload(Ts...)->overload; + +/** + * 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>; + /** * 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; 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 data; + EventPayload payload; template 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& buffer) { + s.container1b(buffer); + }}); } };