mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-09 20:29:10 +02:00
Move Vst3MessageHandler to common and make generic
CLAP will use the same strategy.
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
#include <bitsery/adapter/buffer.h>
|
#include <bitsery/adapter/buffer.h>
|
||||||
#include <bitsery/bitsery.h>
|
#include <bitsery/bitsery.h>
|
||||||
@@ -900,3 +901,252 @@ class AdHocSocketHandler {
|
|||||||
*/
|
*/
|
||||||
std::atomic_bool sent_first_event_ = false;
|
std::atomic_bool sent_first_event_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An instance of `AdHocSocketHandler` that encapsulates the simple
|
||||||
|
* communication model we use for sending requests and receiving responses. A
|
||||||
|
* request of type `T`, where `T` is in the `{Control,Callback}Request` variatns
|
||||||
|
* for the plugin format, should be answered with an object of type
|
||||||
|
* `T::Response`.
|
||||||
|
*
|
||||||
|
* See the docstrings on `Vst2EventHandler` and `AdHocSocketHandler` for more
|
||||||
|
* information on how this works internally and why it works the way it does.
|
||||||
|
* This is shared for both VST3 and CLAP.
|
||||||
|
*
|
||||||
|
* @tparam Thread The thread implementation to use. On the Linux side this
|
||||||
|
* should be `std::jthread` and on the Wine side this should be `Win32Thread`.
|
||||||
|
* @tparam LoggerImpl The logger instead to use. This should have
|
||||||
|
* `log_request(bool, T)` methods for every T in `Request`, as well as
|
||||||
|
* corresponding `log_response(bool, T::Response)` methods.
|
||||||
|
* @tparam Request Either `ControlRequest` or `CallbackRequest`.
|
||||||
|
*/
|
||||||
|
template <typename Thread, typename LoggerImpl, typename Request>
|
||||||
|
class TypedMessageHandler : public AdHocSocketHandler<Thread> {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Sets up a single main socket for this type of events. The sockets won't
|
||||||
|
* be active until `connect()` gets called.
|
||||||
|
*
|
||||||
|
* @param io_context The IO context the main socket should be bound to. A
|
||||||
|
* new IO context will be created for accepting the additional incoming
|
||||||
|
* connections.
|
||||||
|
* @param endpoint The socket endpoint used for this event handler.
|
||||||
|
* @param listen If `true`, start listening on the sockets. Incoming
|
||||||
|
* connections will be accepted when `connect()` gets called. This should
|
||||||
|
* be set to `true` on the plugin side, and `false` on the Wine host side.
|
||||||
|
*
|
||||||
|
* @see Sockets::connect
|
||||||
|
*/
|
||||||
|
TypedMessageHandler(asio::io_context& io_context,
|
||||||
|
asio::local::stream_protocol::endpoint endpoint,
|
||||||
|
bool listen)
|
||||||
|
: AdHocSocketHandler<Thread>(io_context, endpoint, listen) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize and send an event over a socket and return the appropriate
|
||||||
|
* response.
|
||||||
|
*
|
||||||
|
* As described above, if this function is currently being called from
|
||||||
|
* another thread, then this will create a new socket connection and send
|
||||||
|
* the event there instead.
|
||||||
|
*
|
||||||
|
* @param object The request object to send. Often a marker struct to ask
|
||||||
|
* for a specific object to be returned.
|
||||||
|
* @param logging A pair containing a logger instance and whether or not
|
||||||
|
* this is for sending host -> plugin control messages. If set to false,
|
||||||
|
* then this indicates that this `ClapMessageHandler` is handling plugin
|
||||||
|
* -> host callbacks isntead. Optional since it only has to be set on the
|
||||||
|
* plugin's side.
|
||||||
|
* @param buffer The serialization and receiving buffer to reuse. This is
|
||||||
|
* optional, but it's useful for minimizing allocations in the audio
|
||||||
|
* processing loop.
|
||||||
|
*
|
||||||
|
* @relates ClapMessageHandler::receive_messages
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
typename T::Response send_message(
|
||||||
|
const T& object,
|
||||||
|
std::optional<std::pair<LoggerImpl&, bool>> logging,
|
||||||
|
SerializationBufferBase& buffer) {
|
||||||
|
typename T::Response response_object;
|
||||||
|
receive_into(object, response_object, logging, buffer);
|
||||||
|
|
||||||
|
return response_object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as the above, but with a small default buffer.
|
||||||
|
*
|
||||||
|
* @overload
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
typename T::Response send_message(
|
||||||
|
const T& object,
|
||||||
|
std::optional<std::pair<LoggerImpl&, bool>> logging) {
|
||||||
|
typename T::Response response_object;
|
||||||
|
receive_into(object, response_object, logging);
|
||||||
|
|
||||||
|
return response_object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `ClapMessageHandler::send_message()`, but deserializing the response into
|
||||||
|
* an existing object.
|
||||||
|
*
|
||||||
|
* @param response_object The object to deserialize into.
|
||||||
|
*
|
||||||
|
* @overload ClapMessageHandler::send_message
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
typename T::Response& receive_into(
|
||||||
|
const T& object,
|
||||||
|
typename T::Response& response_object,
|
||||||
|
std::optional<std::pair<LoggerImpl&, bool>> logging,
|
||||||
|
SerializationBufferBase& buffer) {
|
||||||
|
using TResponse = typename T::Response;
|
||||||
|
|
||||||
|
// Since a lot of messages just return a `tresult`, we can't filter out
|
||||||
|
// responses based on the response message type. Instead, we'll just
|
||||||
|
// only print the responses when the request was not filtered out.
|
||||||
|
bool should_log_response = false;
|
||||||
|
if (logging) {
|
||||||
|
auto [logger, is_host_vst] = *logging;
|
||||||
|
should_log_response = logger.log_request(is_host_vst, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A socket only handles a single request at a time as to prevent
|
||||||
|
// messages from arriving out of order. `AdHocSocketHandler::send()`
|
||||||
|
// will either use a long-living primary socket, or if that's currently
|
||||||
|
// in use it will spawn a new socket for us.
|
||||||
|
this->send([&](asio::local::stream_protocol::socket& socket) {
|
||||||
|
write_object(socket, Request(object), buffer);
|
||||||
|
read_object<TResponse>(socket, response_object, buffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (should_log_response) {
|
||||||
|
auto [logger, is_host_vst] = *logging;
|
||||||
|
logger.log_response(!is_host_vst, response_object);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response_object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same function as above, but with a small default buffer.
|
||||||
|
*
|
||||||
|
* @overload
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
typename T::Response& receive_into(
|
||||||
|
const T& object,
|
||||||
|
typename T::Response& response_object,
|
||||||
|
std::optional<std::pair<LoggerImpl&, bool>> logging) {
|
||||||
|
SerializationBuffer<256> buffer{};
|
||||||
|
return receive_into(object, response_object, std::move(logging),
|
||||||
|
buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn a new thread to listen for extra connections to `endpoint`, and
|
||||||
|
* then start a blocking loop that handles messages from the primary
|
||||||
|
* `socket`.
|
||||||
|
*
|
||||||
|
* The specified function receives a `Request` variant object containing an
|
||||||
|
* object of type `T`, and it should then return the corresponding
|
||||||
|
* `T::Response`.
|
||||||
|
*
|
||||||
|
* @param logging A pair containing a logger instance and whether or not
|
||||||
|
* this is for sending host -> plugin control messages. If set to false,
|
||||||
|
* then this indicates that this `ClapMessageHandler` is handling plugin
|
||||||
|
* -> host callbacks isntead. Optional since it only has to be set on the
|
||||||
|
* plugin's side.
|
||||||
|
* @param callback The function used to generate a response out of the
|
||||||
|
* request. See the definition of `F` for more information.
|
||||||
|
*
|
||||||
|
* @tparam F A function type in the form of `T::Response(T)` for every `T`
|
||||||
|
* in `Request`. This way we can directly deserialize into a `T::Response`
|
||||||
|
* on the side that called `receive_into(T, T::Response&)`.
|
||||||
|
* @tparam persistent_buffers If enabled, we'll reuse the buffers used for
|
||||||
|
* sending and receiving serialized data as well as the objects we're
|
||||||
|
* receiving into. This avoids allocations in the audio processing loop
|
||||||
|
* (after the first allocation of course). This is mostly relevant for the
|
||||||
|
* `YaProcessData` object stored inside of `YaAudioProcessor::Process`.
|
||||||
|
* These buffers are thread local and will also never shrink, but that
|
||||||
|
* should not be an issue with the `IAudioProcessor` and `IComponent`
|
||||||
|
* functions. Saving and loading state is handled on the main sockets,
|
||||||
|
* which don't use these persistent buffers.
|
||||||
|
*
|
||||||
|
* @relates ClapMessageHandler::send_event
|
||||||
|
*/
|
||||||
|
template <bool persistent_buffers = false, typename F>
|
||||||
|
void receive_messages(std::optional<std::pair<LoggerImpl&, bool>> logging,
|
||||||
|
F&& callback) {
|
||||||
|
// Reading, processing, and writing back the response for the requests
|
||||||
|
// we receive works in the same way regardless of which socket we're
|
||||||
|
// using
|
||||||
|
const auto process_message =
|
||||||
|
[&](asio::local::stream_protocol::socket& socket) {
|
||||||
|
// The persistent buffer is only used when the
|
||||||
|
// `persistent_buffers` template value is enabled, but we'll
|
||||||
|
// always use the thread local persistent object. Because of
|
||||||
|
// loading and storing state the buffer can grow a lot in size
|
||||||
|
// which is why we might not want to reuse that for tasks that
|
||||||
|
// don't need to be realtime safe, but the object has a fixed
|
||||||
|
// size. Normally reusing this object doesn't make much sense
|
||||||
|
// since it's a variant and it will likely have to be recreated
|
||||||
|
// every time, but on the audio processor side we store the
|
||||||
|
// actual variant within an object and we then use some hackery
|
||||||
|
// to always keep the large process data object in memory.
|
||||||
|
thread_local SerializationBuffer<256> persistent_buffer{};
|
||||||
|
thread_local Request persistent_object;
|
||||||
|
|
||||||
|
auto& request =
|
||||||
|
persistent_buffers
|
||||||
|
? read_object<Request>(socket, persistent_object,
|
||||||
|
persistent_buffer)
|
||||||
|
: read_object<Request>(socket, persistent_object);
|
||||||
|
|
||||||
|
// See the comment in `receive_into()` for more information
|
||||||
|
bool should_log_response = false;
|
||||||
|
if (logging) {
|
||||||
|
should_log_response = std::visit(
|
||||||
|
[&](const auto& object) {
|
||||||
|
auto [logger, is_host_vst] = *logging;
|
||||||
|
return logger.log_request(is_host_vst, object);
|
||||||
|
},
|
||||||
|
// In the case of `AudioProcessorRequest`, we need to
|
||||||
|
// actually fetch the variant field since our object
|
||||||
|
// also contains a persistent object to store process
|
||||||
|
// data into so we can prevent allocations during audio
|
||||||
|
// processing
|
||||||
|
get_request_variant(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do the visiting here using a templated lambda. This way we
|
||||||
|
// always know for sure that the function returns the correct
|
||||||
|
// type, and we can scrap a lot of boilerplate elsewhere.
|
||||||
|
std::visit(
|
||||||
|
[&]<typename T>(T object) {
|
||||||
|
typename T::Response response = callback(object);
|
||||||
|
|
||||||
|
if (should_log_response) {
|
||||||
|
auto [logger, is_host_vst] = *logging;
|
||||||
|
logger.log_response(!is_host_vst, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (persistent_buffers) {
|
||||||
|
write_object(socket, response, persistent_buffer);
|
||||||
|
} else {
|
||||||
|
write_object(socket, response);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// See above
|
||||||
|
get_request_variant(request));
|
||||||
|
};
|
||||||
|
|
||||||
|
this->receive_multi(
|
||||||
|
logging ? std::optional(std::ref(logging->first.logger_))
|
||||||
|
: std::nullopt,
|
||||||
|
process_message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -23,254 +23,6 @@
|
|||||||
#include "../serialization/vst3.h"
|
#include "../serialization/vst3.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
/**
|
|
||||||
* An instance of `AdHocSocketHandler` that encapsulates the simple
|
|
||||||
* communication model we use for sending requests and receiving responses. A
|
|
||||||
* request of type `T`, where `T` is in `{Control,Callback}Request`, should be
|
|
||||||
* answered with an object of type `T::Response`.
|
|
||||||
*
|
|
||||||
* See the docstrings on `Vst2EventHandler` and `AdHocSocketHandler` for more
|
|
||||||
* information on how this works internally and why it works the way it does.
|
|
||||||
*
|
|
||||||
* @note The name of this class is not to be confused with VST3's `IMessage` as
|
|
||||||
* this is very much just general purpose messaging between yabridge's two
|
|
||||||
* components. Of course, this will handle `IMessage` function calls as well.
|
|
||||||
*
|
|
||||||
* @tparam Thread The thread implementation to use. On the Linux side this
|
|
||||||
* should be `std::jthread` and on the Wine side this should be `Win32Thread`.
|
|
||||||
* @tparam Request Either `ControlRequest` or `CallbackRequest`.
|
|
||||||
*/
|
|
||||||
template <typename Thread, typename Request>
|
|
||||||
class Vst3MessageHandler : public AdHocSocketHandler<Thread> {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Sets up a single main socket for this type of events. The sockets won't
|
|
||||||
* be active until `connect()` gets called.
|
|
||||||
*
|
|
||||||
* @param io_context The IO context the main socket should be bound to. A
|
|
||||||
* new IO context will be created for accepting the additional incoming
|
|
||||||
* connections.
|
|
||||||
* @param endpoint The socket endpoint used for this event handler.
|
|
||||||
* @param listen If `true`, start listening on the sockets. Incoming
|
|
||||||
* connections will be accepted when `connect()` gets called. This should
|
|
||||||
* be set to `true` on the plugin side, and `false` on the Wine host side.
|
|
||||||
*
|
|
||||||
* @see Sockets::connect
|
|
||||||
*/
|
|
||||||
Vst3MessageHandler(asio::io_context& io_context,
|
|
||||||
asio::local::stream_protocol::endpoint endpoint,
|
|
||||||
bool listen)
|
|
||||||
: AdHocSocketHandler<Thread>(io_context, endpoint, listen) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialize and send an event over a socket and return the appropriate
|
|
||||||
* response.
|
|
||||||
*
|
|
||||||
* As described above, if this function is currently being called from
|
|
||||||
* another thread, then this will create a new socket connection and send
|
|
||||||
* the event there instead.
|
|
||||||
*
|
|
||||||
* @param object The request object to send. Often a marker struct to ask
|
|
||||||
* for a specific object to be returned.
|
|
||||||
* @param logging A pair containing a logger instance and whether or not
|
|
||||||
* this is for sending host -> plugin control messages. If set to false,
|
|
||||||
* then this indicates that this `Vst3MessageHandler` is handling plugin
|
|
||||||
* -> host callbacks isntead. Optional since it only has to be set on the
|
|
||||||
* plugin's side.
|
|
||||||
* @param buffer The serialization and receiving buffer to reuse. This is
|
|
||||||
* optional, but it's useful for minimizing allocations in the audio
|
|
||||||
* processing loop.
|
|
||||||
*
|
|
||||||
* @relates Vst3MessageHandler::receive_messages
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
typename T::Response send_message(
|
|
||||||
const T& object,
|
|
||||||
std::optional<std::pair<Vst3Logger&, bool>> logging,
|
|
||||||
SerializationBufferBase& buffer) {
|
|
||||||
typename T::Response response_object;
|
|
||||||
receive_into(object, response_object, logging, buffer);
|
|
||||||
|
|
||||||
return response_object;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The same as the above, but with a small default buffer.
|
|
||||||
*
|
|
||||||
* @overload
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
typename T::Response send_message(
|
|
||||||
const T& object,
|
|
||||||
std::optional<std::pair<Vst3Logger&, bool>> logging) {
|
|
||||||
typename T::Response response_object;
|
|
||||||
receive_into(object, response_object, logging);
|
|
||||||
|
|
||||||
return response_object;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `Vst3MessageHandler::send_message()`, but deserializing the response into
|
|
||||||
* an existing object.
|
|
||||||
*
|
|
||||||
* @param response_object The object to deserialize into.
|
|
||||||
*
|
|
||||||
* @overload Vst3MessageHandler::send_message
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
typename T::Response& receive_into(
|
|
||||||
const T& object,
|
|
||||||
typename T::Response& response_object,
|
|
||||||
std::optional<std::pair<Vst3Logger&, bool>> logging,
|
|
||||||
SerializationBufferBase& buffer) {
|
|
||||||
using TResponse = typename T::Response;
|
|
||||||
|
|
||||||
// Since a lot of messages just return a `tresult`, we can't filter out
|
|
||||||
// responses based on the response message type. Instead, we'll just
|
|
||||||
// only print the responses when the request was not filtered out.
|
|
||||||
bool should_log_response = false;
|
|
||||||
if (logging) {
|
|
||||||
auto [logger, is_host_vst] = *logging;
|
|
||||||
should_log_response = logger.log_request(is_host_vst, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A socket only handles a single request at a time as to prevent
|
|
||||||
// messages from arriving out of order. `AdHocSocketHandler::send()`
|
|
||||||
// will either use a long-living primary socket, or if that's currently
|
|
||||||
// in use it will spawn a new socket for us.
|
|
||||||
this->send([&](asio::local::stream_protocol::socket& socket) {
|
|
||||||
write_object(socket, Request(object), buffer);
|
|
||||||
read_object<TResponse>(socket, response_object, buffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (should_log_response) {
|
|
||||||
auto [logger, is_host_vst] = *logging;
|
|
||||||
logger.log_response(!is_host_vst, response_object);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response_object;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The same function as above, but with a small default buffer.
|
|
||||||
*
|
|
||||||
* @overload
|
|
||||||
*/
|
|
||||||
template <typename T>
|
|
||||||
typename T::Response& receive_into(
|
|
||||||
const T& object,
|
|
||||||
typename T::Response& response_object,
|
|
||||||
std::optional<std::pair<Vst3Logger&, bool>> logging) {
|
|
||||||
SerializationBuffer<256> buffer{};
|
|
||||||
return receive_into(object, response_object, std::move(logging),
|
|
||||||
buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spawn a new thread to listen for extra connections to `endpoint`, and
|
|
||||||
* then start a blocking loop that handles messages from the primary
|
|
||||||
* `socket`.
|
|
||||||
*
|
|
||||||
* The specified function receives a `Request` variant object containing an
|
|
||||||
* object of type `T`, and it should then return the corresponding
|
|
||||||
* `T::Response`.
|
|
||||||
*
|
|
||||||
* @param logging A pair containing a logger instance and whether or not
|
|
||||||
* this is for sending host -> plugin control messages. If set to false,
|
|
||||||
* then this indicates that this `Vst3MessageHandler` is handling plugin
|
|
||||||
* -> host callbacks isntead. Optional since it only has to be set on the
|
|
||||||
* plugin's side.
|
|
||||||
* @param callback The function used to generate a response out of the
|
|
||||||
* request. See the definition of `F` for more information.
|
|
||||||
*
|
|
||||||
* @tparam F A function type in the form of `T::Response(T)` for every `T`
|
|
||||||
* in `Request`. This way we can directly deserialize into a `T::Response`
|
|
||||||
* on the side that called `receive_into(T, T::Response&)`.
|
|
||||||
* @tparam persistent_buffers If enabled, we'll reuse the buffers used for
|
|
||||||
* sending and receiving serialized data as well as the objects we're
|
|
||||||
* receiving into. This avoids allocations in the audio processing loop
|
|
||||||
* (after the first allocation of course). This is mostly relevant for the
|
|
||||||
* `YaProcessData` object stored inside of `YaAudioProcessor::Process`.
|
|
||||||
* These buffers are thread local and will also never shrink, but that
|
|
||||||
* should not be an issue with the `IAudioProcessor` and `IComponent`
|
|
||||||
* functions. Saving and loading state is handled on the main sockets,
|
|
||||||
* which don't use these persistent buffers.
|
|
||||||
*
|
|
||||||
* @relates Vst3MessageHandler::send_event
|
|
||||||
*/
|
|
||||||
template <bool persistent_buffers = false, typename F>
|
|
||||||
void receive_messages(std::optional<std::pair<Vst3Logger&, bool>> logging,
|
|
||||||
F&& callback) {
|
|
||||||
// Reading, processing, and writing back the response for the requests
|
|
||||||
// we receive works in the same way regardless of which socket we're
|
|
||||||
// using
|
|
||||||
const auto process_message =
|
|
||||||
[&](asio::local::stream_protocol::socket& socket) {
|
|
||||||
// The persistent buffer is only used when the
|
|
||||||
// `persistent_buffers` template value is enabled, but we'll
|
|
||||||
// always use the thread local persistent object. Because of
|
|
||||||
// loading and storing state the buffer can grow a lot in size
|
|
||||||
// which is why we might not want to reuse that for tasks that
|
|
||||||
// don't need to be realtime safe, but the object has a fixed
|
|
||||||
// size. Normally reusing this object doesn't make much sense
|
|
||||||
// since it's a variant and it will likely have to be recreated
|
|
||||||
// every time, but on the audio processor side we store the
|
|
||||||
// actual variant within an object and we then use some hackery
|
|
||||||
// to always keep the large process data object in memory.
|
|
||||||
thread_local SerializationBuffer<256> persistent_buffer{};
|
|
||||||
thread_local Request persistent_object;
|
|
||||||
|
|
||||||
auto& request =
|
|
||||||
persistent_buffers
|
|
||||||
? read_object<Request>(socket, persistent_object,
|
|
||||||
persistent_buffer)
|
|
||||||
: read_object<Request>(socket, persistent_object);
|
|
||||||
|
|
||||||
// See the comment in `receive_into()` for more information
|
|
||||||
bool should_log_response = false;
|
|
||||||
if (logging) {
|
|
||||||
should_log_response = std::visit(
|
|
||||||
[&](const auto& object) {
|
|
||||||
auto [logger, is_host_vst] = *logging;
|
|
||||||
return logger.log_request(is_host_vst, object);
|
|
||||||
},
|
|
||||||
// In the case of `AudioProcessorRequest`, we need to
|
|
||||||
// actually fetch the variant field since our object
|
|
||||||
// also contains a persistent object to store process
|
|
||||||
// data into so we can prevent allocations during audio
|
|
||||||
// processing
|
|
||||||
get_request_variant(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
// We do the visiting here using a templated lambda. This way we
|
|
||||||
// always know for sure that the function returns the correct
|
|
||||||
// type, and we can scrap a lot of boilerplate elsewhere.
|
|
||||||
std::visit(
|
|
||||||
[&]<typename T>(T object) {
|
|
||||||
typename T::Response response = callback(object);
|
|
||||||
|
|
||||||
if (should_log_response) {
|
|
||||||
auto [logger, is_host_vst] = *logging;
|
|
||||||
logger.log_response(!is_host_vst, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if constexpr (persistent_buffers) {
|
|
||||||
write_object(socket, response, persistent_buffer);
|
|
||||||
} else {
|
|
||||||
write_object(socket, response);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// See above
|
|
||||||
get_request_variant(request));
|
|
||||||
};
|
|
||||||
|
|
||||||
this->receive_multi(
|
|
||||||
logging ? std::optional(std::ref(logging->first.logger_))
|
|
||||||
: std::nullopt,
|
|
||||||
process_message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages all the sockets used for communicating between the plugin and the
|
* Manages all the sockets used for communicating between the plugin and the
|
||||||
* Wine host when hosting a VST3 plugin.
|
* Wine host when hosting a VST3 plugin.
|
||||||
@@ -485,14 +237,14 @@ class Vst3Sockets final : public Sockets {
|
|||||||
* This will be listened on by the Wine plugin host when it calls
|
* This will be listened on by the Wine plugin host when it calls
|
||||||
* `receive_multi()`.
|
* `receive_multi()`.
|
||||||
*/
|
*/
|
||||||
Vst3MessageHandler<Thread, ControlRequest> host_vst_control_;
|
TypedMessageHandler<Thread, Vst3Logger, ControlRequest> host_vst_control_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For sending callbacks from the plugin back to the host. After we have a
|
* For sending callbacks from the plugin back to the host. After we have a
|
||||||
* better idea of what our communication model looks like we'll probably
|
* better idea of what our communication model looks like we'll probably
|
||||||
* want to provide an abstraction similar to `Vst2EventHandler`.
|
* want to provide an abstraction similar to `Vst2EventHandler`.
|
||||||
*/
|
*/
|
||||||
Vst3MessageHandler<Thread, CallbackRequest> vst_host_callback_;
|
TypedMessageHandler<Thread, Vst3Logger, CallbackRequest> vst_host_callback_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
@@ -526,8 +278,9 @@ class Vst3Sockets final : public Sockets {
|
|||||||
* would have one dedicated thread for handling function calls to these
|
* would have one dedicated thread for handling function calls to these
|
||||||
* interfaces, and then another dedicated thread just idling around.
|
* interfaces, and then another dedicated thread just idling around.
|
||||||
*/
|
*/
|
||||||
std::unordered_map<size_t,
|
std::unordered_map<
|
||||||
Vst3MessageHandler<Thread, AudioProcessorRequest>>
|
size_t,
|
||||||
|
TypedMessageHandler<Thread, Vst3Logger, AudioProcessorRequest>>
|
||||||
audio_processor_sockets_;
|
audio_processor_sockets_;
|
||||||
std::mutex audio_processor_sockets_mutex_;
|
std::mutex audio_processor_sockets_mutex_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps around `Logger` to provide VST3 specific logging functionality for
|
* Wraps around `Logger` to provide VST3-specific logging functionality for
|
||||||
* debugging plugins. This way we can have all the complex initialisation be
|
* debugging plugins. This way we can have all the complex initialisation be
|
||||||
* performed in one place.
|
* performed in one place.
|
||||||
*/
|
*/
|
||||||
@@ -52,13 +52,13 @@ class Vst3Logger {
|
|||||||
tresult result,
|
tresult result,
|
||||||
const std::optional<Steinberg::FUID>& uid);
|
const std::optional<Steinberg::FUID>& uid);
|
||||||
|
|
||||||
// For every object we send using `Vst3MessageHandler` we have overloads
|
// For every object we send using the `TypedMessageHandler` we have
|
||||||
// that print information about the request and the response. The boolean
|
// overloads that print information about the request and the response. The
|
||||||
// flag here indicates whether the request was initiated on the host side
|
// boolean flag here indicates whether the request was initiated on the host
|
||||||
// (what we'll call a control message).
|
// side (what we'll call a control message). `log_response()` should only be
|
||||||
// `log_response()` should only be called if the corresponding
|
// called if the corresponding `log_request()` call returned `true`. This
|
||||||
// `log_request()` call returned `true`. This way we can filter out the
|
// way we can filter out the log message for the response together with the
|
||||||
// log message for the response together with the request.
|
// request.
|
||||||
|
|
||||||
bool log_request(bool is_host_vst,
|
bool log_request(bool is_host_vst,
|
||||||
const Vst3PluginFactoryProxy::Construct&);
|
const Vst3PluginFactoryProxy::Construct&);
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ struct HostResponse {
|
|||||||
/**
|
/**
|
||||||
* A reference wrapper similar `std::reference_wrapper<T>` that supports default
|
* A reference wrapper similar `std::reference_wrapper<T>` that supports default
|
||||||
* initializing (which is of course UB, but we need this for serialization) and
|
* initializing (which is of course UB, but we need this for serialization) and
|
||||||
* also forwards the `T::Response` type for use with `Vst3MessageHandler`.
|
* also forwards the `T::Response` type for use with the
|
||||||
|
* `TypedMessageHandler`.
|
||||||
*
|
*
|
||||||
* We use this during audio processing to avoid having to store the actual
|
* We use this during audio processing to avoid having to store the actual
|
||||||
* process data in a temporary object (when we copy it to an
|
* process data in a temporary object (when we copy it to an
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ class Vst3PluginProxyImpl;
|
|||||||
* because a plugin is no longer its own entity, but rather a definition of
|
* because a plugin is no longer its own entity, but rather a definition of
|
||||||
* objects that the host can create and interconnect. This `Vst3PluginBridge`
|
* objects that the host can create and interconnect. This `Vst3PluginBridge`
|
||||||
* will be instantiated when the plugin first gets loaded, and it will survive
|
* will be instantiated when the plugin first gets loaded, and it will survive
|
||||||
* until the last instance of the plugin gets removed. The Wine host process
|
* until the last instance of the plugin is removed. The Wine host process will
|
||||||
* will thus also have the same lifetime, and even with yabridge's 'individual'
|
* thus also have the same lifetime, and even with yabridge's 'individual'
|
||||||
* plugin hosting other instances of the same plugin will be handled by a single
|
* plugin hosting other instances of the same plugin will be handled by a single
|
||||||
* process.
|
* process.
|
||||||
*
|
*
|
||||||
@@ -116,9 +116,12 @@ class Vst3PluginBridge : PluginBridge<Vst3Sockets<std::jthread>> {
|
|||||||
void unregister_plugin_proxy(Vst3PluginProxyImpl& proxy_object);
|
void unregister_plugin_proxy(Vst3PluginProxyImpl& proxy_object);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a control message to the Wine plugin host return the response. This
|
* Send a control message to the Wine plugin host and return the response.
|
||||||
* is a shorthand for `sockets_.host_vst_control_.send_message()` for use in
|
* This is a shorthand for `sockets_.host_vst_control_.send_message()` for
|
||||||
* VST3 interface implementations.
|
* use in VST3 interface implementations. This is mostly used for main
|
||||||
|
* thread messages but outside of the situations where plugins will crash or
|
||||||
|
* misbehave thread guarantees are not always upheld in yabridge's VST3
|
||||||
|
* implementation.
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
typename T::Response send_message(const T& object) {
|
typename T::Response send_message(const T& object) {
|
||||||
|
|||||||
@@ -157,9 +157,8 @@ extern "C" YABRIDGE_EXPORT void yabridge_module_free(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Our VST3 plugin's entry point. When building the plugin factory we'll host
|
* Create and return the plugin factory from a bridge instance. Used by the
|
||||||
* the plugin in our Wine application, retrieve its information and supported
|
* chainloaders.
|
||||||
* classes, and then recreate it here.
|
|
||||||
*/
|
*/
|
||||||
extern "C" YABRIDGE_EXPORT Steinberg::IPluginFactory*
|
extern "C" YABRIDGE_EXPORT Steinberg::IPluginFactory*
|
||||||
yabridge_module_get_plugin_factory(Vst3PluginBridge* instance) {
|
yabridge_module_get_plugin_factory(Vst3PluginBridge* instance) {
|
||||||
|
|||||||
Reference in New Issue
Block a user