Finally implement eff{Set,Get}SpeakerConfiguration

As mentioned in #1. This also indirectly allows yabridge to work under
Renoise.
This commit is contained in:
Robbert van der Helm
2020-05-07 18:05:15 +02:00
parent 92f0d95357
commit 868b0fd357
6 changed files with 130 additions and 5 deletions
+3
View File
@@ -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 - 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 such as Cthulhu and Scaler to output notes and CC for another plugin to work
with. 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 - Added automated development builds for yabridge, available by clicking on the
'Automated builds' badge in the project readme. 'Automated builds' badge in the project readme.
+29 -4
View File
@@ -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<EventPayload> 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*/, virtual void write(const int /*opcode*/,
void* data, void* data,
@@ -76,6 +86,14 @@ class DefaultDataConverter {
response.payload); 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 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 * 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, intptr_t value,
void* data, void* data,
float option) { float option) {
// Encode the right payload type for this event. Check the documentation for // Encode the right payload types for this event. Check the documentation
// `EventPayload` for more information // 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 = const EventPayload payload =
data_converter.read(opcode, index, value, data); data_converter.read(opcode, index, value, data);
const std::optional<EventPayload> value_payload =
data_converter.read_value(opcode, value);
if (logging.has_value()) { if (logging.has_value()) {
auto [logger, is_dispatch] = logging.value(); auto [logger, is_dispatch] = logging.value();
logger.log_event(is_dispatch, opcode, index, value, payload, option); 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 // 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 // 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(opcode, data, response);
data_converter.write_value(opcode, value, response);
return data_converter.return_value(opcode, response.return_value); return data_converter.return_value(opcode, response.return_value);
} }
@@ -308,6 +331,8 @@ auto passthrough_event(AEffect* plugin, F callback) {
return nullptr; return nullptr;
}, },
[&](DynamicSpeakerArrangement& speaker_arrangement)
-> EventResultPayload { return speaker_arrangement; },
[&](WantsChunkBuffer&) -> EventResultPayload { [&](WantsChunkBuffer&) -> EventResultPayload {
// In this case the plugin will have written its data stored in // In this case the plugin will have written its data stored in
// an array to which a pointer is stored in `data`, with the // an array to which a pointer is stored in `data`, with the
+7
View File
@@ -84,3 +84,10 @@ VstSpeakerArrangement& DynamicSpeakerArrangement::as_c_speaker_arrangement() {
return *speaker_arrangement; return *speaker_arrangement;
} }
std::vector<uint8_t>& DynamicSpeakerArrangement::as_raw_data() {
// This will populate the buffer for us with the struct data
as_c_speaker_arrangement();
return speaker_arrangement_buffer;
}
+8 -1
View File
@@ -230,11 +230,18 @@ class alignas(16) DynamicSpeakerArrangement {
const VstSpeakerArrangement& speaker_arrangement); const VstSpeakerArrangement& speaker_arrangement);
/** /**
* Construct a dynamically sized `VstSpeakerArrangement` struct based on * Construct a dynamically sized `VstSpeakerArrangement` object based on
* this object. * this object.
*/ */
VstSpeakerArrangement& as_c_speaker_arrangement(); 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<uint8_t>& as_raw_data();
/** /**
* The flags field from `VstSpeakerArrangement` * The flags field from `VstSpeakerArrangement`
*/ */
+72
View File
@@ -359,6 +359,13 @@ class DispatchDataConverter : DefaultDataConverter {
case effGetMidiKeyName: case effGetMidiKeyName:
return *static_cast<const VstMidiKeyName*>(data); return *static_cast<const VstMidiKeyName*>(data);
break; 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<const VstSpeakerArrangement*>(data));
break;
// Any VST host I've encountered has properly zeroed out these their // Any VST host I've encountered has properly zeroed out these their
// string buffers, but we'll add a list of opcodes that should // string buffers, but we'll add a list of opcodes that should
// return a string just in case `DefaultDataConverter::read()` can't // return a string just in case `DefaultDataConverter::read()` can't
@@ -380,6 +387,26 @@ class DispatchDataConverter : DefaultDataConverter {
} }
} }
std::optional<EventPayload> 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<const VstSpeakerArrangement*>(
reinterpret_cast<void*>(value)));
break;
default:
return DefaultDataConverter::read_value(opcode, value);
break;
}
}
void write(const int opcode, void* data, const EventResult& response) { void write(const int opcode, void* data, const EventResult& response) {
switch (opcode) { switch (opcode) {
case effEditGetRect: { case effEditGetRect: {
@@ -423,6 +450,25 @@ class DispatchDataConverter : DefaultDataConverter {
*static_cast<VstMidiKeyName*>(data) = properties; *static_cast<VstMidiKeyName*>(data) = properties;
} break; } 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<DynamicSpeakerArrangement>(response.payload);
// Reconstruct a dynamically sized `VstSpeakerArrangement`
// object to a buffer, and write back the results to the data
// parameter.
VstSpeakerArrangement* output =
static_cast<VstSpeakerArrangement*>(data);
std::vector<uint8_t> reconstructed_object =
speaker_arrangement.as_raw_data();
std::copy(reconstructed_object.begin(),
reconstructed_object.end(),
reinterpret_cast<uint8_t*>(output));
} break;
default: default:
DefaultDataConverter::write(opcode, data, response); DefaultDataConverter::write(opcode, data, response);
break; break;
@@ -433,6 +479,32 @@ class DispatchDataConverter : DefaultDataConverter {
return DefaultDataConverter::return_value(opcode, original); 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<DynamicSpeakerArrangement>(response.payload);
VstSpeakerArrangement* output =
static_cast<VstSpeakerArrangement*>(
reinterpret_cast<void*>(value));
std::vector<uint8_t> reconstructed_object =
speaker_arrangement.as_raw_data();
std::copy(reconstructed_object.begin(),
reconstructed_object.end(),
reinterpret_cast<uint8_t*>(output));
} break;
default:
return DefaultDataConverter::write_value(opcode, value,
response);
break;
}
}
private: private:
std::vector<uint8_t>& chunk; std::vector<uint8_t>& chunk;
VstRect& rect; VstRect& rect;
+11
View File
@@ -377,6 +377,11 @@ class HostCallbackDataConverter : DefaultDataConverter {
} }
} }
std::optional<EventPayload> 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) { void write(const int opcode, void* data, const EventResult& response) {
switch (opcode) { switch (opcode) {
case audioMasterGetTime: 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: private:
AEffect* plugin; AEffect* plugin;
std::optional<VstTimeInfo>& time_info; std::optional<VstTimeInfo>& time_info;