mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-10 04:30:12 +02:00
Implement the rest of the GUI events
The GUI is still not updating though.
This commit is contained in:
@@ -217,6 +217,7 @@ void passthrough_event(boost::asio::local::stream_protocol::socket& socket,
|
|||||||
return &events.as_c_events();
|
return &events.as_c_events();
|
||||||
},
|
},
|
||||||
[&](WantsChunkBuffer&) -> void* { return string_buffer.data(); },
|
[&](WantsChunkBuffer&) -> void* { return string_buffer.data(); },
|
||||||
|
[&](const WantsVstRect&) -> void* { return string_buffer.data(); },
|
||||||
[&](const WantsVstTimeInfo&) -> void* { return nullptr; },
|
[&](const WantsVstTimeInfo&) -> void* { return nullptr; },
|
||||||
[&](WantsString&) -> void* { return string_buffer.data(); }},
|
[&](WantsString&) -> void* { return string_buffer.data(); }},
|
||||||
event.payload);
|
event.payload);
|
||||||
@@ -264,6 +265,11 @@ void passthrough_event(boost::asio::local::stream_protocol::socket& socket,
|
|||||||
return std::string(*static_cast<char**>(data),
|
return std::string(*static_cast<char**>(data),
|
||||||
return_value);
|
return_value);
|
||||||
},
|
},
|
||||||
|
[&](WantsVstRect&) -> EventResposnePayload {
|
||||||
|
// The plugin has written a pointer to a VstRect struct
|
||||||
|
// into the data poitner
|
||||||
|
return **static_cast<VstRect**>(data);
|
||||||
|
},
|
||||||
[&](WantsVstTimeInfo&) -> EventResposnePayload {
|
[&](WantsVstTimeInfo&) -> EventResposnePayload {
|
||||||
// Not sure why the VST API has twenty different ways of
|
// Not sure why the VST API has twenty different ways of
|
||||||
// returning structs, but in this case the value returned
|
// returning structs, but in this case the value returned
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ void Logger::log_event(bool is_dispatch,
|
|||||||
[&](const WantsChunkBuffer&) {
|
[&](const WantsChunkBuffer&) {
|
||||||
message << "<writable_buffer>";
|
message << "<writable_buffer>";
|
||||||
},
|
},
|
||||||
|
[&](const WantsVstRect&) { message << "<writable_buffer>"; },
|
||||||
[&](const WantsVstTimeInfo&) { message << "<nullptr>"; },
|
[&](const WantsVstTimeInfo&) { message << "<nullptr>"; },
|
||||||
[&](const WantsString&) { message << "<writable_string>"; }},
|
[&](const WantsString&) { message << "<writable_string>"; }},
|
||||||
payload);
|
payload);
|
||||||
@@ -210,6 +211,11 @@ void Logger::log_event_response(bool is_dispatch,
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[&](const AEffect&) { message << ", <AEffect_object>"; },
|
[&](const AEffect&) { message << ", <AEffect_object>"; },
|
||||||
|
[&](const VstRect& rect) {
|
||||||
|
message << ", {l: " << rect.left << ", t: " << rect.top
|
||||||
|
<< ", r: " << rect.right
|
||||||
|
<< ", b: " << rect.bottom << "}";
|
||||||
|
},
|
||||||
[&](const VstTimeInfo&) { message << ", <time_info>"; }},
|
[&](const VstTimeInfo&) { message << ", <time_info>"; }},
|
||||||
payload);
|
payload);
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,14 @@ void serialize(S& s, AEffect& plugin) {
|
|||||||
s.value4b(plugin.version);
|
s.value4b(plugin.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename S>
|
||||||
|
void serialize(S& s, VstRect& rect) {
|
||||||
|
s.value2b(rect.top);
|
||||||
|
s.value2b(rect.left);
|
||||||
|
s.value2b(rect.right);
|
||||||
|
s.value2b(rect.bottom);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename S>
|
template <typename S>
|
||||||
void serialize(S& s, VstTimeInfo& time_info) {
|
void serialize(S& s, VstTimeInfo& time_info) {
|
||||||
s.value8b(time_info.samplePos);
|
s.value8b(time_info.samplePos);
|
||||||
@@ -152,9 +160,15 @@ class alignas(16) DynamicVstEvents {
|
|||||||
*/
|
*/
|
||||||
struct WantsChunkBuffer {};
|
struct WantsChunkBuffer {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker struct to indicate that the event handler will write a pointer to a
|
||||||
|
* `VstRect` struct into the void pointer.
|
||||||
|
*/
|
||||||
|
struct WantsVstRect {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marker struct to indicate that the event handler will return a pointer to a
|
* Marker struct to indicate that the event handler will return a pointer to a
|
||||||
* `VstTiemInfo` struct that should be returned transfered.
|
* `VstTimeInfo` struct that should be returned transfered.
|
||||||
*/
|
*/
|
||||||
struct WantsVstTimeInfo {};
|
struct WantsVstTimeInfo {};
|
||||||
|
|
||||||
@@ -198,6 +212,7 @@ using EventPayload = std::variant<std::nullptr_t,
|
|||||||
AEffect,
|
AEffect,
|
||||||
DynamicVstEvents,
|
DynamicVstEvents,
|
||||||
WantsChunkBuffer,
|
WantsChunkBuffer,
|
||||||
|
WantsVstRect,
|
||||||
WantsVstTimeInfo,
|
WantsVstTimeInfo,
|
||||||
WantsString>;
|
WantsString>;
|
||||||
|
|
||||||
@@ -218,8 +233,8 @@ void serialize(S& s, EventPayload& payload) {
|
|||||||
events.events, max_midi_events,
|
events.events, max_midi_events,
|
||||||
[](S& s, VstEvent& event) { s.container1b(event.dump); });
|
[](S& s, VstEvent& event) { s.container1b(event.dump); });
|
||||||
},
|
},
|
||||||
[](S&, WantsChunkBuffer&) {}, [](S&, WantsVstTimeInfo&) {},
|
[](S&, WantsChunkBuffer&) {}, [](S&, WantsVstRect&) {},
|
||||||
[](S&, WantsString&) {}});
|
[](S&, WantsVstTimeInfo&) {}, [](S&, WantsString&) {}});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -275,7 +290,7 @@ struct Event {
|
|||||||
* - An X11 window pointer for the editor window.
|
* - An X11 window pointer for the editor window.
|
||||||
*/
|
*/
|
||||||
using EventResposnePayload =
|
using EventResposnePayload =
|
||||||
std::variant<std::monostate, std::string, AEffect, VstTimeInfo>;
|
std::variant<std::monostate, std::string, AEffect, VstRect, VstTimeInfo>;
|
||||||
|
|
||||||
template <typename S>
|
template <typename S>
|
||||||
void serialize(S& s, EventResposnePayload& payload) {
|
void serialize(S& s, EventResposnePayload& payload) {
|
||||||
@@ -289,6 +304,7 @@ void serialize(S& s, EventResposnePayload& payload) {
|
|||||||
s.text1b(string, binary_buffer_size);
|
s.text1b(string, binary_buffer_size);
|
||||||
},
|
},
|
||||||
[](S& s, AEffect& effect) { s.object(effect); },
|
[](S& s, AEffect& effect) { s.object(effect); },
|
||||||
|
[](S& s, VstRect& rect) { s.object(rect); },
|
||||||
[](S& s, VstTimeInfo& time_info) { s.object(time_info); }});
|
[](S& s, VstTimeInfo& time_info) { s.object(time_info); }});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+69
-65
@@ -172,8 +172,9 @@ HostBridge::HostBridge(audioMasterCallback host_callback)
|
|||||||
|
|
||||||
class DispatchDataConverter : DefaultDataConverter {
|
class DispatchDataConverter : DefaultDataConverter {
|
||||||
public:
|
public:
|
||||||
DispatchDataConverter(std::vector<uint8_t>& chunk_data)
|
DispatchDataConverter(std::vector<uint8_t>& chunk_data,
|
||||||
: chunk(chunk_data) {}
|
VstRect& editor_rectangle)
|
||||||
|
: chunk(chunk_data), rect(editor_rectangle) {}
|
||||||
|
|
||||||
std::optional<EventPayload> read(const int opcode,
|
std::optional<EventPayload> read(const int opcode,
|
||||||
const intptr_t value,
|
const intptr_t value,
|
||||||
@@ -181,18 +182,8 @@ class DispatchDataConverter : DefaultDataConverter {
|
|||||||
// There are some events that need specific structs that we can't simply
|
// There are some events that need specific structs that we can't simply
|
||||||
// serialize as a string because they might contain null bytes
|
// serialize as a string because they might contain null bytes
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
// TODO: Add GUI support. These events are just disabled for now to
|
|
||||||
// ensure everything else works first.
|
|
||||||
case effEditTop:
|
|
||||||
case effEditIdle:
|
|
||||||
case effEditClose:
|
|
||||||
case effEditGetRect:
|
case effEditGetRect:
|
||||||
std::cerr << "Got opcode "
|
return WantsVstRect();
|
||||||
<< opcode_to_string(true, opcode)
|
|
||||||
.value_or(std::to_string(opcode))
|
|
||||||
<< "), ignoring..." << std::endl;
|
|
||||||
|
|
||||||
return std::nullopt;
|
|
||||||
break;
|
break;
|
||||||
case effEditOpen:
|
case effEditOpen:
|
||||||
// The host will have passed us an X11 window handle in the void
|
// The host will have passed us an X11 window handle in the void
|
||||||
@@ -222,18 +213,31 @@ class DispatchDataConverter : DefaultDataConverter {
|
|||||||
|
|
||||||
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 effGetChunk:
|
case effEditGetRect: {
|
||||||
|
// Write back the (hopefully) updated editor dimensions
|
||||||
|
const auto new_rect = std::get<VstRect>(response.payload);
|
||||||
|
rect = new_rect;
|
||||||
|
|
||||||
|
// TODO: Maybe the host expects this field to be always up to
|
||||||
|
// date, so if the editor resizes itself then the
|
||||||
|
// `VstRect` behind this pointer should change as well
|
||||||
|
// without any additional dispatch calls. If that's the
|
||||||
|
// case, then we can probably reuse
|
||||||
|
// `audioMasterSizeWindow`.
|
||||||
|
*static_cast<VstRect**>(data) = ▭
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case effGetChunk: {
|
||||||
// Write the chunk data to some publically accessible place in
|
// Write the chunk data to some publically accessible place in
|
||||||
// `HostBridge` and write a pointer to that struct to the data
|
// `HostBridge` and write a pointer to that struct to the data
|
||||||
// pointer
|
// pointer
|
||||||
{
|
|
||||||
std::string buffer =
|
|
||||||
std::get<std::string>(response.payload);
|
|
||||||
chunk.assign(buffer.begin(), buffer.end());
|
|
||||||
|
|
||||||
*static_cast<void**>(data) = chunk.data();
|
std::string buffer = std::get<std::string>(response.payload);
|
||||||
}
|
chunk.assign(buffer.begin(), buffer.end());
|
||||||
|
|
||||||
|
*static_cast<void**>(data) = chunk.data();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
DefaultDataConverter::write(opcode, data, response);
|
DefaultDataConverter::write(opcode, data, response);
|
||||||
break;
|
break;
|
||||||
@@ -246,6 +250,7 @@ class DispatchDataConverter : DefaultDataConverter {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<uint8_t>& chunk;
|
std::vector<uint8_t>& chunk;
|
||||||
|
VstRect& rect;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,62 +263,61 @@ intptr_t HostBridge::dispatch(AEffect* /*plugin*/,
|
|||||||
intptr_t value,
|
intptr_t value,
|
||||||
void* data,
|
void* data,
|
||||||
float option) {
|
float option) {
|
||||||
DispatchDataConverter converter(chunk_data);
|
DispatchDataConverter converter(chunk_data, editor_rectangle);
|
||||||
|
|
||||||
// Some events need some extra handling
|
// Some events need some extra handling
|
||||||
// TODO: Handle GUI closing?
|
// TODO: Handle GUI closing?
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
break;
|
break;
|
||||||
case effClose:
|
case effClose: {
|
||||||
// TODO: Gracefully close the editor?
|
// TODO: Gracefully close the editor?
|
||||||
// TODO: Check whether the sockets and the endpoint are closed
|
// TODO: Check whether the sockets and the endpoint are closed
|
||||||
// correctly
|
// correctly
|
||||||
{
|
|
||||||
// Allow the plugin to handle its own shutdown. I've found a few
|
|
||||||
// plugins that work fine except for that they crash during
|
|
||||||
// shutdown. This shouldn't have any negative side effects since
|
|
||||||
// state has already been saved before this and all resources
|
|
||||||
// are cleaned up properly. Still not sure if this is a good way
|
|
||||||
// to handle this.
|
|
||||||
intptr_t return_value = 1;
|
|
||||||
try {
|
|
||||||
return_value = send_event(
|
|
||||||
host_vst_dispatch, dispatch_semaphore, converter,
|
|
||||||
std::pair<Logger&, bool>(logger, true), opcode, index,
|
|
||||||
value, data, option);
|
|
||||||
} catch (const boost::system::system_error& a) {
|
|
||||||
// Thrown when the socket gets closed because the VST plugin
|
|
||||||
// loaded into the Wine process crashed during shutdown
|
|
||||||
logger.log("The plugin crashed during shutdown, ignoring");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boost.Process will send SIGKILL to the Wien host for us when
|
// Allow the plugin to handle its own shutdown. I've found a few
|
||||||
// this class gets destroyed. Because the process is running a
|
// plugins that work fine except for that they crash during
|
||||||
// few threads Wine will say something about a segfault
|
// shutdown. This shouldn't have any negative side effects since
|
||||||
// (probably related to `std::terminate`), but this doesn't seem
|
// state has already been saved before this and all resources are
|
||||||
// to have any negative impact
|
// cleaned up properly. Still not sure if this is a good way to
|
||||||
|
// handle this.
|
||||||
// The `stop()` method will cause the IO context to just drop
|
intptr_t return_value = 1;
|
||||||
// all of its work and immediately and not throw any exceptions
|
try {
|
||||||
// that would have been caused by pipes and sockets being closed
|
return_value =
|
||||||
io_context.stop();
|
send_event(host_vst_dispatch, dispatch_semaphore, converter,
|
||||||
|
std::pair<Logger&, bool>(logger, true), opcode,
|
||||||
// `std::thread`s are not interruptable, and since we're doing
|
index, value, data, option);
|
||||||
// blocking synchronous reads there's no way to interrupt them.
|
} catch (const boost::system::system_error& a) {
|
||||||
// If we don't detach them then the runtime will call
|
// Thrown when the socket gets closed because the VST plugin
|
||||||
// `std::terminate` for us. The workaround here is to simply
|
// loaded into the Wine process crashed during shutdown
|
||||||
// detach the threads and then close all sockets. This will
|
logger.log("The plugin crashed during shutdown, ignoring");
|
||||||
// cause them to throw exceptions which we then catch and
|
|
||||||
// ignore. Please let me know if there's a better way to handle
|
|
||||||
// this.q
|
|
||||||
host_callback_handler.detach();
|
|
||||||
wine_io_handler.detach();
|
|
||||||
|
|
||||||
delete this;
|
|
||||||
|
|
||||||
return return_value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Boost.Process will send SIGKILL to the Wien host for us when this
|
||||||
|
// class gets destroyed. Because the process is running a few
|
||||||
|
// threads Wine will say something about a segfault (probably
|
||||||
|
// related to `std::terminate`), but this doesn't seem to have any
|
||||||
|
// negative impact
|
||||||
|
|
||||||
|
// The `stop()` method will cause the IO context to just drop
|
||||||
|
// all of its work and immediately and not throw any exceptions
|
||||||
|
// that would have been caused by pipes and sockets being closed
|
||||||
|
io_context.stop();
|
||||||
|
|
||||||
|
// `std::thread`s are not interruptable, and since we're doing
|
||||||
|
// blocking synchronous reads there's no way to interrupt them. If
|
||||||
|
// we don't detach them then the runtime will call `std::terminate`
|
||||||
|
// for us. The workaround here is to simply detach the threads and
|
||||||
|
// then close all sockets. This will cause them to throw exceptions
|
||||||
|
// which we then catch and ignore. Please let me know if there's a
|
||||||
|
// better way to handle this.q
|
||||||
|
host_callback_handler.detach();
|
||||||
|
wine_io_handler.detach();
|
||||||
|
|
||||||
|
delete this;
|
||||||
|
|
||||||
|
return return_value;
|
||||||
break;
|
break;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Maybe reuse buffers here when dealing with chunk data
|
// TODO: Maybe reuse buffers here when dealing with chunk data
|
||||||
|
|||||||
@@ -111,6 +111,11 @@ class HostBridge {
|
|||||||
* `effGetChunk` event.
|
* `effGetChunk` event.
|
||||||
*/
|
*/
|
||||||
std::vector<uint8_t> chunk_data;
|
std::vector<uint8_t> chunk_data;
|
||||||
|
/**
|
||||||
|
* The VST host will expect to be returned a pointer to a struct that stores
|
||||||
|
* the dimensions of the editor window.
|
||||||
|
*/
|
||||||
|
VstRect editor_rectangle;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -195,28 +195,41 @@ intptr_t PluginBridge::dispatch_wrapper(AEffect* plugin,
|
|||||||
intptr_t value,
|
intptr_t value,
|
||||||
void* data,
|
void* data,
|
||||||
float option) {
|
float option) {
|
||||||
// TODO: editEffClose
|
|
||||||
// We have to intercept GUI open calls since we can't use
|
// We have to intercept GUI open calls since we can't use
|
||||||
// the X11 window handle passed by the host
|
// the X11 window handle passed by the host
|
||||||
if (opcode == effEditOpen) {
|
switch (opcode) {
|
||||||
const auto win32_handle = editor.open();
|
case effEditOpen: {
|
||||||
|
const auto win32_handle = editor.open();
|
||||||
|
|
||||||
// The plugin will return 0 if it can not open its
|
// The plugin will return 0 if it can not open its
|
||||||
// editor window (or if it does not support it, but in
|
// editor window (or if it does not support it, but in
|
||||||
// that case the DAW should be hiding the option)
|
// that case the DAW should be hiding the option)
|
||||||
const intptr_t return_value = plugin->dispatcher(
|
const intptr_t return_value = plugin->dispatcher(
|
||||||
plugin, opcode, index, value, win32_handle, option);
|
plugin, opcode, index, value, win32_handle, option);
|
||||||
if (return_value == 0) {
|
if (return_value == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto x11_handle = reinterpret_cast<size_t>(data);
|
||||||
|
editor.embed_into(x11_handle);
|
||||||
|
|
||||||
|
return return_value;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case effEditClose: {
|
||||||
|
const intptr_t return_value =
|
||||||
|
plugin->dispatcher(plugin, opcode, index, value, data, option);
|
||||||
|
|
||||||
const auto x11_handle = reinterpret_cast<size_t>(data);
|
editor.close();
|
||||||
editor.embed_into(x11_handle);
|
|
||||||
|
|
||||||
return return_value;
|
return return_value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return plugin->dispatcher(plugin, opcode, index, value, data,
|
||||||
|
option);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return plugin->dispatcher(plugin, opcode, index, value, data, option);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PluginBridge::wait() {
|
void PluginBridge::wait() {
|
||||||
|
|||||||
Reference in New Issue
Block a user