diff --git a/CHANGELOG.md b/CHANGELOG.md index fd31fb5a..2135262c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ Versioning](https://semver.org/spec/v2.0.0.html). - Added support for plugins that send MIDI events back to the host. This plugins such as Cthulhu and Scaler to output notes and CC for another plugin to work with. +- Added support for querying and setting detailed information about speaker + configurations for use in advanced surround setups. This indirectly allowed + yabridge to work under **Renoise**. - Added automated development builds for yabridge, available by clicking on the 'Automated builds' badge in the project readme. diff --git a/src/common/events.h b/src/common/events.h index b70598b8..f28f4967 100644 --- a/src/common/events.h +++ b/src/common/events.h @@ -56,7 +56,17 @@ class DefaultDataConverter { } /** - * Write the reponse back to the data pointer. + * Read data from the `value` pointer into a an `EventPayload` value that + * can be serialized and conveys the meaning of the event. This is only used + * for the `effSetSpeakerArrangement` and `effGetSpeakerArrangement` events. + */ + virtual std::optional read_value(const int /*opcode*/, + const intptr_t /*value*/) { + return std::nullopt; + } + + /** + * Write the reponse back to the `data` pointer. */ virtual void write(const int /*opcode*/, void* data, @@ -76,6 +86,14 @@ class DefaultDataConverter { response.payload); } + /** + * Write the reponse back to the `value` pointer. This is only used during + * the `effGetSpeakerArrangement` event. + */ + virtual void write_value(const int /*opcode*/, + intptr_t /*value*/, + const EventResult& /*response*/) {} + /** * This function can override a callback's return value based on the opcode. * This is used in one place to return a pointer to a `VstTime` object @@ -126,17 +144,21 @@ intptr_t send_event(boost::asio::local::stream_protocol::socket& socket, intptr_t value, void* data, float option) { - // Encode the right payload type for this event. Check the documentation for - // `EventPayload` for more information + // Encode the right payload types for this event. Check the documentation + // for `EventPayload` for more information. These types are converted to + // C-style data structures in `passthrough_event()` so they can be passed to + // a plugin or callback function. const EventPayload payload = data_converter.read(opcode, index, value, data); + const std::optional value_payload = + data_converter.read_value(opcode, value); if (logging.has_value()) { auto [logger, is_dispatch] = logging.value(); logger.log_event(is_dispatch, opcode, index, value, payload, option); } - const Event event{opcode, index, value, option, payload}; + const Event event{opcode, index, value, option, payload, value_payload}; // Prevent two threads from writing over the socket at the same time and // messages getting out of order. This is needed because we can't prevent @@ -156,6 +178,7 @@ intptr_t send_event(boost::asio::local::stream_protocol::socket& socket, } data_converter.write(opcode, data, response); + data_converter.write_value(opcode, value, response); return data_converter.return_value(opcode, response.return_value); } @@ -308,6 +331,8 @@ auto passthrough_event(AEffect* plugin, F callback) { return nullptr; }, + [&](DynamicSpeakerArrangement& speaker_arrangement) + -> EventResultPayload { return speaker_arrangement; }, [&](WantsChunkBuffer&) -> EventResultPayload { // In this case the plugin will have written its data stored in // an array to which a pointer is stored in `data`, with the diff --git a/src/common/serialization.cpp b/src/common/serialization.cpp index 64cce827..a5403438 100644 --- a/src/common/serialization.cpp +++ b/src/common/serialization.cpp @@ -84,3 +84,10 @@ VstSpeakerArrangement& DynamicSpeakerArrangement::as_c_speaker_arrangement() { return *speaker_arrangement; } + +std::vector& DynamicSpeakerArrangement::as_raw_data() { + // This will populate the buffer for us with the struct data + as_c_speaker_arrangement(); + + return speaker_arrangement_buffer; +} diff --git a/src/common/serialization.h b/src/common/serialization.h index f103a5be..1384190a 100644 --- a/src/common/serialization.h +++ b/src/common/serialization.h @@ -230,11 +230,18 @@ class alignas(16) DynamicSpeakerArrangement { const VstSpeakerArrangement& speaker_arrangement); /** - * Construct a dynamically sized `VstSpeakerArrangement` struct based on + * Construct a dynamically sized `VstSpeakerArrangement` object based on * this object. */ VstSpeakerArrangement& as_c_speaker_arrangement(); + /** + * Reconstruct the dynamically sized `VstSpeakerArrangement` object and + * return the raw data buffer. Needed to write the results back to the host + * since we can't just reassign the object. + */ + std::vector& as_raw_data(); + /** * The flags field from `VstSpeakerArrangement` */ diff --git a/src/plugin/plugin-bridge.cpp b/src/plugin/plugin-bridge.cpp index 452f804f..f66fefb5 100644 --- a/src/plugin/plugin-bridge.cpp +++ b/src/plugin/plugin-bridge.cpp @@ -359,6 +359,13 @@ class DispatchDataConverter : DefaultDataConverter { case effGetMidiKeyName: return *static_cast(data); break; + case effSetSpeakerArrangement: + case effGetSpeakerArrangement: + // This is the output speaker configuration, the `read_value()` + // method below reads the input speaker configuration + return DynamicSpeakerArrangement( + *static_cast(data)); + break; // Any VST host I've encountered has properly zeroed out these their // string buffers, but we'll add a list of opcodes that should // return a string just in case `DefaultDataConverter::read()` can't @@ -380,6 +387,26 @@ class DispatchDataConverter : DefaultDataConverter { } } + std::optional read_value(const int opcode, + const intptr_t value) { + switch (opcode) { + case effSetSpeakerArrangement: + case effGetSpeakerArrangement: + // These two events are special in that they pass a pointer to + // the output speaker configuration through the `data` + // parameter, but then they also pass a pointer to the input + // speaker configuration through the `value` parameter. This is + // the only event that does this. + return DynamicSpeakerArrangement( + *static_cast( + reinterpret_cast(value))); + break; + default: + return DefaultDataConverter::read_value(opcode, value); + break; + } + } + void write(const int opcode, void* data, const EventResult& response) { switch (opcode) { case effEditGetRect: { @@ -423,6 +450,25 @@ class DispatchDataConverter : DefaultDataConverter { *static_cast(data) = properties; } break; + case effGetSpeakerArrangement: { + // The plugin will have updated the objects passed by the host + // with its preferred output speaker configuration if it + // supports this. The same thing happens for the input speaker + // configuration in `write_value()`. + auto speaker_arrangement = + std::get(response.payload); + + // Reconstruct a dynamically sized `VstSpeakerArrangement` + // object to a buffer, and write back the results to the data + // parameter. + VstSpeakerArrangement* output = + static_cast(data); + std::vector reconstructed_object = + speaker_arrangement.as_raw_data(); + std::copy(reconstructed_object.begin(), + reconstructed_object.end(), + reinterpret_cast(output)); + } break; default: DefaultDataConverter::write(opcode, data, response); break; @@ -433,6 +479,32 @@ class DispatchDataConverter : DefaultDataConverter { return DefaultDataConverter::return_value(opcode, original); } + void write_value(const int opcode, + intptr_t value, + const EventResult& response) { + switch (opcode) { + case effGetSpeakerArrangement: { + // Same as the above, but now for the input speaker + // configuration object under the `value` pointer + auto speaker_arrangement = + std::get(response.payload); + + VstSpeakerArrangement* output = + static_cast( + reinterpret_cast(value)); + std::vector reconstructed_object = + speaker_arrangement.as_raw_data(); + std::copy(reconstructed_object.begin(), + reconstructed_object.end(), + reinterpret_cast(output)); + } break; + default: + return DefaultDataConverter::write_value(opcode, value, + response); + break; + } + } + private: std::vector& chunk; VstRect& rect; diff --git a/src/wine-host/wine-bridge.cpp b/src/wine-host/wine-bridge.cpp index c47f6489..d60e7fa8 100644 --- a/src/wine-host/wine-bridge.cpp +++ b/src/wine-host/wine-bridge.cpp @@ -377,6 +377,11 @@ class HostCallbackDataConverter : DefaultDataConverter { } } + std::optional read_value(const int opcode, + const intptr_t value) { + return DefaultDataConverter::read_value(opcode, value); + } + void write(const int opcode, void* data, const EventResult& response) { switch (opcode) { case audioMasterGetTime: @@ -415,6 +420,12 @@ class HostCallbackDataConverter : DefaultDataConverter { } } + void write_value(const int opcode, + intptr_t value, + const EventResult& response) { + return DefaultDataConverter::write_value(opcode, value, response); + } + private: AEffect* plugin; std::optional& time_info;