mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-10 04:30:12 +02:00
Replace all non-group sockets with SocketHandler
This greatly reduces the amount of boilerplate and potential for error.
This commit is contained in:
+34
-84
@@ -211,6 +211,8 @@ class SocketHandler {
|
|||||||
* packets arriving out of order.
|
* packets arriving out of order.
|
||||||
*
|
*
|
||||||
* @see write_object
|
* @see write_object
|
||||||
|
* @see SocketHandler::receive_single
|
||||||
|
* @see SocketHandler::receive_multi
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline void send(const T& object, std::vector<uint8_t>& buffer) {
|
inline void send(const T& object, std::vector<uint8_t>& buffer) {
|
||||||
@@ -244,7 +246,7 @@ class SocketHandler {
|
|||||||
* @relates SocketHandler::send
|
* @relates SocketHandler::send
|
||||||
*
|
*
|
||||||
* @see read_object
|
* @see read_object
|
||||||
* @see SocketHandler::receive
|
* @see SocketHandler::receive_multi
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline T receive_single(std::vector<uint8_t>& buffer) {
|
inline T receive_single(std::vector<uint8_t>& buffer) {
|
||||||
@@ -270,8 +272,10 @@ class SocketHandler {
|
|||||||
* we'd probably want to do some more stuff after sending a reply, calling
|
* we'd probably want to do some more stuff after sending a reply, calling
|
||||||
* `send()` is the responsibility of this function.
|
* `send()` is the responsibility of this function.
|
||||||
*
|
*
|
||||||
* @tparam F A function type in the form of `void(T)` that does something
|
* @tparam F A function type in the form of `void(T, std::vector<uint8_t>&)`
|
||||||
* with the object, and then calls `send()`.
|
* that does something with the object, and then calls `send()`. The
|
||||||
|
* reading/writing buffer is passed along so it can be reused for sending
|
||||||
|
* large amounts of data.
|
||||||
*
|
*
|
||||||
* @relates SocketHandler::send
|
* @relates SocketHandler::send
|
||||||
*
|
*
|
||||||
@@ -279,13 +283,13 @@ class SocketHandler {
|
|||||||
* @see SocketHandler::receive_single
|
* @see SocketHandler::receive_single
|
||||||
*/
|
*/
|
||||||
template <typename T, typename F>
|
template <typename T, typename F>
|
||||||
void receive(F callback) {
|
void receive_multi(F callback) {
|
||||||
std::vector<uint8_t> buffer{};
|
std::vector<uint8_t> buffer{};
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
auto object = receive_single<T>(buffer);
|
auto object = receive_single<T>(buffer);
|
||||||
|
|
||||||
callback(std::move(object));
|
callback(std::move(object), buffer);
|
||||||
} catch (const boost::system::system_error&) {
|
} catch (const boost::system::system_error&) {
|
||||||
// This happens when the sockets got closed because the plugin
|
// This happens when the sockets got closed because the plugin
|
||||||
// is being shut down
|
// is being shut down
|
||||||
@@ -804,35 +808,33 @@ class Sockets {
|
|||||||
vst_host_callback(io_context,
|
vst_host_callback(io_context,
|
||||||
(base_dir / "vst_host_callback.sock").string(),
|
(base_dir / "vst_host_callback.sock").string(),
|
||||||
listen),
|
listen),
|
||||||
host_vst_parameters(io_context),
|
host_vst_parameters(io_context,
|
||||||
host_vst_process_replacing(io_context),
|
(base_dir / "host_vst_parameters.sock").string(),
|
||||||
host_vst_control(io_context),
|
listen),
|
||||||
host_vst_parameters_endpoint(
|
host_vst_process_replacing(
|
||||||
(base_dir / "host_vst_parameters.sock").string()),
|
io_context,
|
||||||
host_vst_process_replacing_endpoint(
|
(base_dir / "host_vst_process_replacing.sock").string(),
|
||||||
(base_dir / "host_vst_process_replacing.sock").string()),
|
listen),
|
||||||
host_vst_control_endpoint(
|
host_vst_control(io_context,
|
||||||
(base_dir / "host_vst_control.sock").string()) {
|
(base_dir / "host_vst_control.sock").string(),
|
||||||
if (listen) {
|
listen) {}
|
||||||
boost::filesystem::create_directories(base_dir);
|
|
||||||
|
|
||||||
acceptors = Acceptors{
|
|
||||||
.host_vst_parameters{io_context, host_vst_parameters_endpoint},
|
|
||||||
.host_vst_process_replacing{
|
|
||||||
io_context, host_vst_process_replacing_endpoint},
|
|
||||||
.host_vst_control{io_context, host_vst_control_endpoint},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleans up the directory containing the socket endpoints when yabridge
|
* Cleans up the directory containing the socket endpoints when yabridge
|
||||||
* shuts down if it still exists.
|
* shuts down if it still exists.
|
||||||
*/
|
*/
|
||||||
~Sockets() {
|
~Sockets() {
|
||||||
|
// Manually close all sockets so we break out of any blocking operations
|
||||||
|
// that may still be active
|
||||||
|
host_vst_dispatch.close();
|
||||||
|
host_vst_dispatch_midi_events.close();
|
||||||
|
vst_host_callback.close();
|
||||||
|
host_vst_parameters.close();
|
||||||
|
host_vst_process_replacing.close();
|
||||||
|
host_vst_control.close();
|
||||||
|
|
||||||
// Only clean if we're the ones who have created these files, although
|
// Only clean if we're the ones who have created these files, although
|
||||||
// it should not cause any harm to also do this on the Wine side
|
// it should not cause any harm to also do this on the Wine side
|
||||||
if (acceptors) {
|
|
||||||
try {
|
try {
|
||||||
boost::filesystem::remove_all(base_dir);
|
boost::filesystem::remove_all(base_dir);
|
||||||
} catch (const boost::filesystem::filesystem_error&) {
|
} catch (const boost::filesystem::filesystem_error&) {
|
||||||
@@ -842,25 +844,6 @@ class Sockets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manually close all sockets so we break out of any blocking operations
|
|
||||||
// that may still be active
|
|
||||||
host_vst_dispatch.close();
|
|
||||||
host_vst_dispatch_midi_events.close();
|
|
||||||
vst_host_callback.close();
|
|
||||||
|
|
||||||
// These shutdowns can fail when the socket has already been closed, but
|
|
||||||
// that's not an issue in our case
|
|
||||||
constexpr auto shutdown_type =
|
|
||||||
boost::asio::local::stream_protocol::socket::shutdown_both;
|
|
||||||
boost::system::error_code err;
|
|
||||||
host_vst_parameters.shutdown(shutdown_type, err);
|
|
||||||
host_vst_process_replacing.shutdown(shutdown_type, err);
|
|
||||||
host_vst_control.shutdown(shutdown_type, err);
|
|
||||||
host_vst_parameters.close();
|
|
||||||
host_vst_process_replacing.close();
|
|
||||||
host_vst_control.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Depending on the value of the `listen` argument passed to the
|
* Depending on the value of the `listen` argument passed to the
|
||||||
* constructor, either accept connections made to the sockets on the Linux
|
* constructor, either accept connections made to the sockets on the Linux
|
||||||
@@ -870,17 +853,9 @@ class Sockets {
|
|||||||
host_vst_dispatch.connect();
|
host_vst_dispatch.connect();
|
||||||
host_vst_dispatch_midi_events.connect();
|
host_vst_dispatch_midi_events.connect();
|
||||||
vst_host_callback.connect();
|
vst_host_callback.connect();
|
||||||
if (acceptors) {
|
host_vst_parameters.connect();
|
||||||
acceptors->host_vst_parameters.accept(host_vst_parameters);
|
host_vst_process_replacing.connect();
|
||||||
acceptors->host_vst_process_replacing.accept(
|
host_vst_control.connect();
|
||||||
host_vst_process_replacing);
|
|
||||||
acceptors->host_vst_control.accept(host_vst_control);
|
|
||||||
} else {
|
|
||||||
host_vst_parameters.connect(host_vst_parameters_endpoint);
|
|
||||||
host_vst_process_replacing.connect(
|
|
||||||
host_vst_process_replacing_endpoint);
|
|
||||||
host_vst_control.connect(host_vst_control_endpoint);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -915,44 +890,19 @@ class Sockets {
|
|||||||
* Used for both `getParameter` and `setParameter` since they mostly
|
* Used for both `getParameter` and `setParameter` since they mostly
|
||||||
* overlap.
|
* overlap.
|
||||||
*/
|
*/
|
||||||
boost::asio::local::stream_protocol::socket host_vst_parameters;
|
SocketHandler host_vst_parameters;
|
||||||
/**
|
/**
|
||||||
* Used for processing audio usign the `process()`, `processReplacing()` and
|
* Used for processing audio usign the `process()`, `processReplacing()` and
|
||||||
* `processDoubleReplacing()` functions.
|
* `processDoubleReplacing()` functions.
|
||||||
*/
|
*/
|
||||||
boost::asio::local::stream_protocol::socket host_vst_process_replacing;
|
SocketHandler host_vst_process_replacing;
|
||||||
/**
|
/**
|
||||||
* A control socket that sends data that is not suitable for the other
|
* A control socket that sends data that is not suitable for the other
|
||||||
* sockets. At the moment this is only used to, on startup, send the Windows
|
* sockets. At the moment this is only used to, on startup, send the Windows
|
||||||
* VST plugin's `AEffect` object to the native VST plugin, and to then send
|
* VST plugin's `AEffect` object to the native VST plugin, and to then send
|
||||||
* the configuration (from `config`) back to the Wine host.
|
* the configuration (from `config`) back to the Wine host.
|
||||||
*/
|
*/
|
||||||
boost::asio::local::stream_protocol::socket host_vst_control;
|
SocketHandler host_vst_control;
|
||||||
|
|
||||||
private:
|
|
||||||
const boost::asio::local::stream_protocol::endpoint
|
|
||||||
host_vst_parameters_endpoint;
|
|
||||||
const boost::asio::local::stream_protocol::endpoint
|
|
||||||
host_vst_process_replacing_endpoint;
|
|
||||||
const boost::asio::local::stream_protocol::endpoint
|
|
||||||
host_vst_control_endpoint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All of our socket acceptors. We have to create these before launching the
|
|
||||||
* Wine process.
|
|
||||||
*/
|
|
||||||
struct Acceptors {
|
|
||||||
boost::asio::local::stream_protocol::acceptor host_vst_parameters;
|
|
||||||
boost::asio::local::stream_protocol::acceptor
|
|
||||||
host_vst_process_replacing;
|
|
||||||
boost::asio::local::stream_protocol::acceptor host_vst_control;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the `listen` constructor argument was set to `true`, when we'll
|
|
||||||
* prepare a set of socket acceptors that listen on the socket endpoints.
|
|
||||||
*/
|
|
||||||
std::optional<Acceptors> acceptors;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -153,13 +153,13 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback)
|
|||||||
// over the `dispatcher()` socket. This would happen whenever the plugin
|
// over the `dispatcher()` socket. This would happen whenever the plugin
|
||||||
// calls `audioMasterIOChanged()` and after the host calls `effOpen()`.
|
// calls `audioMasterIOChanged()` and after the host calls `effOpen()`.
|
||||||
const auto initialization_data =
|
const auto initialization_data =
|
||||||
read_object<EventResult>(sockets.host_vst_control);
|
sockets.host_vst_control.receive_single<EventResult>();
|
||||||
const auto initialized_plugin =
|
const auto initialized_plugin =
|
||||||
std::get<AEffect>(initialization_data.payload);
|
std::get<AEffect>(initialization_data.payload);
|
||||||
|
|
||||||
// After receiving the `AEffect` values we'll want to send the configuration
|
// After receiving the `AEffect` values we'll want to send the configuration
|
||||||
// back to complete the startup process
|
// back to complete the startup process
|
||||||
write_object(sockets.host_vst_control, config);
|
sockets.host_vst_control.send(config);
|
||||||
|
|
||||||
update_aeffect(plugin, initialized_plugin);
|
update_aeffect(plugin, initialized_plugin);
|
||||||
}
|
}
|
||||||
@@ -502,11 +502,12 @@ void PluginBridge::do_process(T** inputs, T** outputs, int sample_frames) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AudioBuffers request{input_buffers, sample_frames};
|
const AudioBuffers request{input_buffers, sample_frames};
|
||||||
write_object(sockets.host_vst_process_replacing, request, process_buffer);
|
sockets.host_vst_process_replacing.send(request, process_buffer);
|
||||||
|
|
||||||
// Write the results back to the `outputs` arrays
|
// Write the results back to the `outputs` arrays
|
||||||
const auto response = read_object<AudioBuffers>(
|
const auto response =
|
||||||
sockets.host_vst_process_replacing, process_buffer);
|
sockets.host_vst_process_replacing.receive_single<AudioBuffers>(
|
||||||
|
process_buffer);
|
||||||
const auto& response_buffers =
|
const auto& response_buffers =
|
||||||
std::get<std::vector<std::vector<T>>>(response.buffers);
|
std::get<std::vector<std::vector<T>>>(response.buffers);
|
||||||
|
|
||||||
@@ -555,8 +556,10 @@ float PluginBridge::get_parameter(AEffect* /*plugin*/, int index) {
|
|||||||
// called at the same time since they share the same socket
|
// called at the same time since they share the same socket
|
||||||
{
|
{
|
||||||
std::lock_guard lock(parameters_mutex);
|
std::lock_guard lock(parameters_mutex);
|
||||||
write_object(sockets.host_vst_parameters, request);
|
sockets.host_vst_parameters.send(request);
|
||||||
response = read_object<ParameterResult>(sockets.host_vst_parameters);
|
|
||||||
|
response =
|
||||||
|
sockets.host_vst_parameters.receive_single<ParameterResult>();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log_get_parameter_response(*response.value);
|
logger.log_get_parameter_response(*response.value);
|
||||||
@@ -572,9 +575,10 @@ void PluginBridge::set_parameter(AEffect* /*plugin*/, int index, float value) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard lock(parameters_mutex);
|
std::lock_guard lock(parameters_mutex);
|
||||||
write_object(sockets.host_vst_parameters, request);
|
sockets.host_vst_parameters.send(request);
|
||||||
|
|
||||||
response = read_object<ParameterResult>(sockets.host_vst_parameters);
|
response =
|
||||||
|
sockets.host_vst_parameters.receive_single<ParameterResult>();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log_set_parameter_response();
|
logger.log_set_parameter_response();
|
||||||
|
|||||||
@@ -121,14 +121,12 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
|
|||||||
// of this object will be sent over the `dispatcher()` socket. This would be
|
// of this object will be sent over the `dispatcher()` socket. This would be
|
||||||
// done after the host calls `effOpen()`, and when the plugin calls
|
// done after the host calls `effOpen()`, and when the plugin calls
|
||||||
// `audioMasterIOChanged()`.
|
// `audioMasterIOChanged()`.
|
||||||
write_object(sockets.host_vst_control,
|
sockets.host_vst_control.send(EventResult{
|
||||||
EventResult{.return_value = 0,
|
.return_value = 0, .payload = *plugin, .value_payload = std::nullopt});
|
||||||
.payload = *plugin,
|
|
||||||
.value_payload = std::nullopt});
|
|
||||||
|
|
||||||
// After sending the AEffect struct we'll receive this instance's
|
// After sending the AEffect struct we'll receive this instance's
|
||||||
// configuration as a response
|
// configuration as a response
|
||||||
config = read_object<Configuration>(sockets.host_vst_control);
|
config = sockets.host_vst_control.receive_single<Configuration>();
|
||||||
|
|
||||||
// This works functionally identically to the `handle_dispatch()` function,
|
// This works functionally identically to the `handle_dispatch()` function,
|
||||||
// but this socket will only handle MIDI events and it will handle them
|
// but this socket will only handle MIDI events and it will handle them
|
||||||
@@ -182,34 +180,27 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
|
|||||||
});
|
});
|
||||||
|
|
||||||
parameters_handler = Win32Thread([&]() {
|
parameters_handler = Win32Thread([&]() {
|
||||||
while (true) {
|
sockets.host_vst_parameters.receive_multi<Parameter>(
|
||||||
try {
|
[&](Parameter request, std::vector<uint8_t>& buffer) {
|
||||||
// Both `getParameter` and `setParameter` functions are passed
|
// Both `getParameter` and `setParameter` functions are passed
|
||||||
// through on this socket since they have a lot of overlap. The
|
// through on this socket since they have a lot of overlap. The
|
||||||
// presence of the `value` field tells us which one we're
|
// presence of the `value` field tells us which one we're
|
||||||
// dealing with.
|
// dealing with.
|
||||||
auto request =
|
|
||||||
read_object<Parameter>(sockets.host_vst_parameters);
|
|
||||||
if (request.value) {
|
if (request.value) {
|
||||||
// `setParameter`
|
// `setParameter`
|
||||||
plugin->setParameter(plugin, request.index, *request.value);
|
plugin->setParameter(plugin, request.index, *request.value);
|
||||||
|
|
||||||
ParameterResult response{std::nullopt};
|
ParameterResult response{std::nullopt};
|
||||||
write_object(sockets.host_vst_parameters, response);
|
sockets.host_vst_parameters.send(response, buffer);
|
||||||
} else {
|
} else {
|
||||||
// `getParameter`
|
// `getParameter`
|
||||||
float value = plugin->getParameter(plugin, request.index);
|
float value = plugin->getParameter(plugin, request.index);
|
||||||
|
|
||||||
ParameterResult response{value};
|
ParameterResult response{value};
|
||||||
write_object(sockets.host_vst_parameters, response);
|
sockets.host_vst_parameters.send(response, buffer);
|
||||||
}
|
|
||||||
} catch (const boost::system::system_error&) {
|
|
||||||
// The plugin has cut off communications, so we can shut down
|
|
||||||
// this host application
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
process_replacing_handler = Win32Thread([&]() {
|
process_replacing_handler = Win32Thread([&]() {
|
||||||
// These are used as scratch buffers to prevent unnecessary allocations.
|
// These are used as scratch buffers to prevent unnecessary allocations.
|
||||||
@@ -221,10 +212,8 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
|
|||||||
std::vector<std::vector<double>> output_buffers_double_precision(
|
std::vector<std::vector<double>> output_buffers_double_precision(
|
||||||
plugin->numOutputs);
|
plugin->numOutputs);
|
||||||
|
|
||||||
while (true) {
|
sockets.host_vst_process_replacing.receive_multi<AudioBuffers>(
|
||||||
try {
|
[&](AudioBuffers request, std::vector<uint8_t>& buffer) {
|
||||||
auto request = read_object<AudioBuffers>(
|
|
||||||
sockets.host_vst_process_replacing, process_buffer);
|
|
||||||
// Let the plugin process the MIDI events that were received
|
// Let the plugin process the MIDI events that were received
|
||||||
// since the last buffer, and then clean up those events. This
|
// since the last buffer, and then clean up those events. This
|
||||||
// approach should not be needed but Kontakt only stores
|
// approach should not be needed but Kontakt only stores
|
||||||
@@ -287,8 +276,8 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
|
|||||||
AudioBuffers response{
|
AudioBuffers response{
|
||||||
output_buffers_single_precision,
|
output_buffers_single_precision,
|
||||||
request.sample_frames};
|
request.sample_frames};
|
||||||
write_object(sockets.host_vst_process_replacing,
|
sockets.host_vst_process_replacing.send(response,
|
||||||
response, process_buffer);
|
buffer);
|
||||||
},
|
},
|
||||||
[&](std::vector<std::vector<double>>& input_buffers) {
|
[&](std::vector<std::vector<double>>& input_buffers) {
|
||||||
// Exactly the same as the above, but for double
|
// Exactly the same as the above, but for double
|
||||||
@@ -314,18 +303,13 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context,
|
|||||||
AudioBuffers response{
|
AudioBuffers response{
|
||||||
output_buffers_double_precision,
|
output_buffers_double_precision,
|
||||||
request.sample_frames};
|
request.sample_frames};
|
||||||
write_object(sockets.host_vst_process_replacing,
|
sockets.host_vst_process_replacing.send(response,
|
||||||
response, process_buffer);
|
buffer);
|
||||||
}},
|
}},
|
||||||
request.buffers);
|
request.buffers);
|
||||||
|
|
||||||
next_audio_buffer_midi_events.clear();
|
next_audio_buffer_midi_events.clear();
|
||||||
} catch (const boost::system::system_error&) {
|
});
|
||||||
// The plugin has cut off communications, so we can shut down
|
|
||||||
// this host application
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,12 +211,6 @@ class Vst2Bridge {
|
|||||||
*/
|
*/
|
||||||
Sockets<Win32Thread> sockets;
|
Sockets<Win32Thread> sockets;
|
||||||
|
|
||||||
/**
|
|
||||||
* A scratch buffer for sending and receiving data during `process` and
|
|
||||||
* `processReplacing` calls.
|
|
||||||
*/
|
|
||||||
std::vector<uint8_t> process_buffer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The MIDI events that have been received **and processed** since the last
|
* The MIDI events that have been received **and processed** since the last
|
||||||
* call to `processReplacing()`. 99% of plugins make a copy of the MIDI
|
* call to `processReplacing()`. 99% of plugins make a copy of the MIDI
|
||||||
|
|||||||
Reference in New Issue
Block a user