From a3aad51e412378b1630dfb0e030c959db0acd2f9 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 7 May 2020 17:15:47 +0200 Subject: [PATCH] Also add a second payload value to event responses This will only be used for `effGetSpeakerArrangement`. --- src/common/events.h | 228 ++++++++++++++++++---------------- src/common/logging.cpp | 8 +- src/common/serialization.h | 13 ++ src/plugin/plugin-bridge.cpp | 2 +- src/wine-host/wine-bridge.cpp | 2 +- 5 files changed, 144 insertions(+), 109 deletions(-) diff --git a/src/common/events.h b/src/common/events.h index 86d3ccea..9189ff06 100644 --- a/src/common/events.h +++ b/src/common/events.h @@ -233,121 +233,137 @@ auto passthrough_event(AEffect* plugin, F callback) { std::array string_buffer; string_buffer[0] = 0; - void* data = std::visit( - overload{ - [&](const std::nullptr_t&) -> void* { return nullptr; }, - [&](const std::string& s) -> void* { - return const_cast(s.c_str()); - }, - [&](const std::vector& buffer) -> void* { - return const_cast(buffer.data()); - }, - [&](native_size_t& window_handle) -> void* { - // This is the X11 window handle that the editor should - // reparent itself to. We have a special wrapper around the - // dispatch function that intercepts `effEditOpen` events - // and creates a Win32 window and then finally embeds the - // X11 window Wine created into this wnidow handle. Make - // sure to convert the window ID first to `size_t` in case - // this is the 32-bit host. - return reinterpret_cast( - static_cast(window_handle)); - }, - [&](const AEffect&) -> void* { return nullptr; }, - [&](DynamicVstEvents& events) -> void* { - return &events.as_c_events(); - }, - [&](DynamicSpeakerArrangement& speaker_arrangement) -> void* { - return &speaker_arrangement.as_c_speaker_arrangement(); - }, - [&](WantsChunkBuffer&) -> void* { - return string_buffer.data(); - }, - [&](VstIOProperties& props) -> void* { return &props; }, - [&](VstMidiKeyName& key_name) -> void* { return &key_name; }, - [&](VstParameterProperties& props) -> void* { return &props; }, - [&](WantsVstRect&) -> void* { return string_buffer.data(); }, - [&](const WantsVstTimeInfo&) -> void* { return nullptr; }, - [&](WantsString&) -> void* { return string_buffer.data(); }}, - event.payload); + auto read_payload_fn = overload{ + [&](const std::nullptr_t&) -> void* { return nullptr; }, + [&](const std::string& s) -> void* { + return const_cast(s.c_str()); + }, + [&](const std::vector& buffer) -> void* { + return const_cast(buffer.data()); + }, + [&](native_size_t& window_handle) -> void* { + // This is the X11 window handle that the editor should reparent + // itself to. We have a special wrapper around the dispatch + // function that intercepts `effEditOpen` events and creates a + // Win32 window and then finally embeds the X11 window Wine + // created into this wnidow handle. Make sure to convert the + // window ID first to `size_t` in case this is the 32-bit host. + return reinterpret_cast( + static_cast(window_handle)); + }, + [&](const AEffect&) -> void* { return nullptr; }, + [&](DynamicVstEvents& events) -> void* { + return &events.as_c_events(); + }, + [&](DynamicSpeakerArrangement& speaker_arrangement) -> void* { + return &speaker_arrangement.as_c_speaker_arrangement(); + }, + [&](WantsChunkBuffer&) -> void* { return string_buffer.data(); }, + [&](VstIOProperties& props) -> void* { return &props; }, + [&](VstMidiKeyName& key_name) -> void* { return &key_name; }, + [&](VstParameterProperties& props) -> void* { return &props; }, + [&](WantsVstRect&) -> void* { return string_buffer.data(); }, + [&](const WantsVstTimeInfo&) -> void* { return nullptr; }, + [&](WantsString&) -> void* { return string_buffer.data(); }}; + + // Almost all events pass data through the `data` argument. There are + // two events, `effSetParameter` and `effGetParameter` that also pass + // data through the value argument. + void* data = std::visit(read_payload_fn, event.payload); + intptr_t value = event.value; + if (event.value_payload.has_value()) { + value = reinterpret_cast( + std::visit(read_payload_fn, event.value_payload.value())); + } const intptr_t return_value = callback( - plugin, event.opcode, event.index, event.value, data, event.option); + plugin, event.opcode, event.index, value, data, event.option); // Only write back data when needed, this depends on the event payload // type - const auto response_data = std::visit( - overload{ - [&](auto) -> EventResposnePayload { return nullptr; }, - [&](const AEffect& updated_plugin) -> EventResposnePayload { - // This is a bit of a special case! Instead of writing some - // return value, we will update values on the native VST - // plugin's `AEffect` object. This is triggered by the - // `audioMasterIOChanged` callback from the hsoted VST - // plugin. + auto write_payload_fn = overload{ + [&](auto) -> EventResposnePayload { return nullptr; }, + [&](const AEffect& updated_plugin) -> EventResposnePayload { + // This is a bit of a special case! Instead of writing some + // return value, we will update values on the native VST + // plugin's `AEffect` object. This is triggered by the + // `audioMasterIOChanged` callback from the hsoted VST plugin. - // These are the same fields written by bitsery in the - // initialization of `PluginBridge`. I can't think of a way - // to reuse the serializer without first having to serialize - // `updated_plugin` first though. - plugin->magic = updated_plugin.magic; - plugin->numPrograms = updated_plugin.numPrograms; - plugin->numParams = updated_plugin.numParams; - plugin->numInputs = updated_plugin.numInputs; - plugin->numOutputs = updated_plugin.numOutputs; - plugin->flags = updated_plugin.flags; - plugin->initialDelay = updated_plugin.initialDelay; - plugin->empty3a = updated_plugin.empty3a; - plugin->empty3b = updated_plugin.empty3b; - plugin->unkown_float = updated_plugin.unkown_float; - plugin->uniqueID = updated_plugin.uniqueID; - plugin->version = updated_plugin.version; + // These are the same fields written by bitsery in the + // initialization of `PluginBridge`. I can't think of a way to + // reuse the serializer without first having to serialize + // `updated_plugin` first though. + plugin->magic = updated_plugin.magic; + plugin->numPrograms = updated_plugin.numPrograms; + plugin->numParams = updated_plugin.numParams; + plugin->numInputs = updated_plugin.numInputs; + plugin->numOutputs = updated_plugin.numOutputs; + plugin->flags = updated_plugin.flags; + plugin->initialDelay = updated_plugin.initialDelay; + plugin->empty3a = updated_plugin.empty3a; + plugin->empty3b = updated_plugin.empty3b; + plugin->unkown_float = updated_plugin.unkown_float; + plugin->uniqueID = updated_plugin.uniqueID; + plugin->version = updated_plugin.version; + return nullptr; + }, + [&](WantsChunkBuffer&) -> EventResposnePayload { + // In this case the plugin will have written its data stored in + // an array to which a pointer is stored in `data`, with the + // return value from the event determines how much data the + // plugin has written + const uint8_t* chunk_data = *static_cast(data); + return std::vector(chunk_data, + chunk_data + return_value); + }, + [&](VstIOProperties& props) -> EventResposnePayload { + return props; + }, + [&](VstMidiKeyName& key_name) -> EventResposnePayload { + return key_name; + }, + [&](VstParameterProperties& props) -> EventResposnePayload { + return props; + }, + [&](WantsVstRect&) -> EventResposnePayload { + // The plugin has written a pointer to a VstRect struct into the + // data poitner + return **static_cast(data); + }, + [&](WantsVstTimeInfo&) -> EventResposnePayload { + // Not sure why the VST API has twenty different ways of + // returning structs, but in this case the value returned from + // the callback function is actually a pointer to a + // `VstTimeInfo` struct! It can also be a null pointer if the + // host doesn't support this. + const auto time_info = + reinterpret_cast(return_value); + if (time_info == nullptr) { return nullptr; - }, - [&](WantsChunkBuffer&) -> EventResposnePayload { - // In this case the plugin will have written its data - // stored in an array to which a pointer is stored in - // `data`, with the return value from the event determines - // how much data the plugin has written - const uint8_t* chunk_data = *static_cast(data); - return std::vector(chunk_data, - chunk_data + return_value); - }, - [&](VstIOProperties& props) -> EventResposnePayload { - return props; - }, - [&](VstMidiKeyName& key_name) -> EventResposnePayload { - return key_name; - }, - [&](VstParameterProperties& props) -> EventResposnePayload { - return props; - }, - [&](WantsVstRect&) -> EventResposnePayload { - // The plugin has written a pointer to a VstRect struct - // into the data poitner - return **static_cast(data); - }, - [&](WantsVstTimeInfo&) -> EventResposnePayload { - // Not sure why the VST API has twenty different ways of - // returning structs, but in this case the value returned - // from the callback function is actually a pointer to a - // `VstTimeInfo` struct! It can also be a null pointer if - // the host doesn't support this. - const auto time_info = - reinterpret_cast(return_value); - if (time_info == nullptr) { - return nullptr; - } else { - return *time_info; - } - }, - [&](WantsString&) -> EventResposnePayload { - return std::string(static_cast(data)); - }}, - event.payload); + } else { + return *time_info; + } + }, + [&](WantsString&) -> EventResposnePayload { + return std::string(static_cast(data)); + }}; - EventResult response{return_value, response_data}; + // As mentioned about, the `effSetSpeakerArrangement` and + // `effGetSpeakerArrangement` events are the only two events that use + // the value argument as a pointer to write data to. Additionally, the + // `effGetSpeakerArrangement` expects the plugin to write its own data + // to this value. Hence why we need to encode the response here + // separately. + const EventResposnePayload response_data = + std::visit(write_payload_fn, event.payload); + std::optional value_response_data = std::nullopt; + if (event.value_payload.has_value()) { + value_response_data = + std::visit(write_payload_fn, event.value_payload.value()); + } + + EventResult response{return_value, response_data, value_response_data}; return response; }; diff --git a/src/common/logging.cpp b/src/common/logging.cpp index 0f3121d5..85dac04f 100644 --- a/src/common/logging.cpp +++ b/src/common/logging.cpp @@ -161,6 +161,7 @@ void Logger::log_event(bool is_dispatch, message << "(index = " << index << ", value = " << value << ", option = " << option << ", data = "; + // TODO: Print value payload std::visit( overload{ [&](const std::nullptr_t&) { message << ""; }, @@ -185,7 +186,7 @@ void Logger::log_event(bool is_dispatch, }, [&](const DynamicSpeakerArrangement& speaker_arrangement) { message << "<" << speaker_arrangement.speakers.size() - << " speakers>"; + << " output_speakers>"; }, [&](const WantsChunkBuffer&) { message << ""; @@ -224,6 +225,7 @@ void Logger::log_event_response(bool is_dispatch, message << return_value; + // TODO: Print value payload std::visit( overload{ [&](const std::nullptr_t&) {}, @@ -240,6 +242,10 @@ void Logger::log_event_response(bool is_dispatch, message << ", <" << buffer.size() << " byte chunk>"; }, [&](const AEffect&) { message << ", "; }, + [&](const DynamicSpeakerArrangement& speaker_arrangement) { + message << ", <" << speaker_arrangement.speakers.size() + << " output_speakers>"; + }, [&](const VstIOProperties&) { message << ", "; }, [&](const VstMidiKeyName&) { message << ", "; }, [&](const VstParameterProperties& props) { diff --git a/src/common/serialization.h b/src/common/serialization.h index 43b81f34..99d23703 100644 --- a/src/common/serialization.h +++ b/src/common/serialization.h @@ -426,6 +426,7 @@ using EventResposnePayload = std::variant, AEffect, + DynamicSpeakerArrangement, VstIOProperties, VstMidiKeyName, VstParameterProperties, @@ -444,6 +445,9 @@ void serialize(S& s, EventResposnePayload& payload) { s.container1b(buffer, binary_buffer_size); }, [](S& s, AEffect& effect) { s.object(effect); }, + [&](DynamicSpeakerArrangement& speaker_arrangement) -> void* { + return &speaker_arrangement.as_c_speaker_arrangement(); + }, [](S& s, VstIOProperties& props) { s.object(props); }, [](S& s, VstMidiKeyName& key_name) { s.object(key_name); }, [](S& s, VstParameterProperties& props) { s.object(props); }, @@ -464,13 +468,22 @@ struct EventResult { * into the void pointer, but sometimes an event response should forward * some kind of special struct. */ + // TODO: Fix typo and rename to `EventResultPayload` for consistency EventResposnePayload payload; + /** + * The same as the above value, but for returning values written to the + * `intptr_t` value parameter. This is only used during + * `effGetSpeakerArrangement`. + */ + std::optional value_payload; template void serialize(S& s) { s.value8b(return_value); s.object(payload); + s.ext(value_payload, bitsery::ext::StdOptional(), + [](S& s, auto& v) { s.object(v); }); } }; diff --git a/src/plugin/plugin-bridge.cpp b/src/plugin/plugin-bridge.cpp index 3cb7b1a4..452f804f 100644 --- a/src/plugin/plugin-bridge.cpp +++ b/src/plugin/plugin-bridge.cpp @@ -289,7 +289,7 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback) incoming_midi_events.push_back( std::get(event.payload)); - EventResult response{1, nullptr}; + EventResult response{1, nullptr, std::nullopt}; return response; } else { diff --git a/src/wine-host/wine-bridge.cpp b/src/wine-host/wine-bridge.cpp index 943a3190..c47f6489 100644 --- a/src/wine-host/wine-bridge.cpp +++ b/src/wine-host/wine-bridge.cpp @@ -206,7 +206,7 @@ void WineBridge::handle_dispatch() { plugin, event.opcode, event.index, event.value, &events.as_c_events(), event.option); - EventResult response{return_value, nullptr}; + EventResult response{return_value, nullptr, std::nullopt}; return response; } else {