diff --git a/src/plugin/plugin.cpp b/src/plugin/plugin.cpp index 0c541b1f..7e2d4f7f 100644 --- a/src/plugin/plugin.cpp +++ b/src/plugin/plugin.cpp @@ -38,10 +38,10 @@ VST_EXPORT AEffect* main_plugin(audioMasterCallback audioMaster) { } } -intptr_t dispatch(AEffect*, int32_t, int32_t, intptr_t, void*, float); -void process(AEffect*, float**, float**, int32_t); -void setParameter(AEffect*, int32_t, float); -float getParameter(AEffect*, int32_t); +intptr_t dispatch_proxy(AEffect*, int32_t, int32_t, intptr_t, void*, float); +void process_proxy(AEffect*, float**, float**, int32_t); +void setParameter_proxy(AEffect*, int32_t, float); +float getParameter_proxy(AEffect*, int32_t); /** * Fetch the bridge instance stored in an unused pointer from a VST plugin. This @@ -68,10 +68,10 @@ VST_EXPORT AEffect* VSTPluginMain(audioMasterCallback /*audioMaster*/) { AEffect* plugin = new AEffect(); plugin->ptr3 = bridge; - plugin->dispatcher = dispatch; - plugin->process = process; - plugin->setParameter = setParameter; - plugin->getParameter = getParameter; + plugin->dispatcher = dispatch_proxy; + plugin->process = process_proxy; + plugin->setParameter = setParameter_proxy; + plugin->getParameter = getParameter_proxy; // // XXX: processReplacing? // TODO: Add more and actual data @@ -91,28 +91,28 @@ VST_EXPORT AEffect* VSTPluginMain(audioMasterCallback /*audioMaster*/) { // The below functions are proxy functions for the methods defined in // `Bridge.cpp` -intptr_t dispatch(AEffect* plugin, - int32_t opcode, - int32_t index, - intptr_t value, - void* data, - float option) { +intptr_t dispatch_proxy(AEffect* plugin, + int32_t opcode, + int32_t index, + intptr_t value, + void* data, + float option) { return get_bridge_instance(*plugin).dispatch(plugin, opcode, index, value, data, option); } -void process(AEffect* plugin, - float** inputs, - float** outputs, - int32_t sample_frames) { +void process_proxy(AEffect* plugin, + float** inputs, + float** outputs, + int32_t sample_frames) { return get_bridge_instance(*plugin).process(plugin, inputs, outputs, sample_frames); } -void setParameter(AEffect* plugin, int32_t index, float value) { +void setParameter_proxy(AEffect* plugin, int32_t index, float value) { return get_bridge_instance(*plugin).set_parameter(plugin, index, value); } -float getParameter(AEffect* plugin, int32_t index) { +float getParameter_proxy(AEffect* plugin, int32_t index) { return get_bridge_instance(*plugin).get_parameter(plugin, index); } diff --git a/src/wine-host/bridge.cpp b/src/wine-host/bridge.cpp index 03545418..fbe711ba 100644 --- a/src/wine-host/bridge.cpp +++ b/src/wine-host/bridge.cpp @@ -1,12 +1,38 @@ #include "bridge.h" +#include "../common/communication.h" + /** * A function pointer to what should be the entry point of a VST plugin. */ using VstEntryPoint = AEffect*(VST_CALL_CONV*)(audioMasterCallback); +/** + * This ugly global is needed so we can get the instance of a `Brdige` class + * from an `AEffect` when it performs a host callback during its initialization. + */ +Bridge* current_bridge_isntance = nullptr; + intptr_t VST_CALL_CONV -host_callback(AEffect*, int32_t, int32_t, intptr_t, void*, float); +host_callback_proxy(AEffect*, int32_t, int32_t, intptr_t, void*, float); + +/** + * Fetch the bridge instance stored in one of the two pointers reserved for the + * host of the hosted VST plugin. This is sadly needed as a workaround to avoid + * using globals since we need free function pointers to interface with the VST + * C API. + */ +Bridge& get_bridge_instance(const AEffect* plugin) { + // This is needed during the initialization of the plugin since we can only + // add our own pointer after it's done initializing + if (current_bridge_isntance != nullptr) { + // This should only be used during initialization + assert(plugin == nullptr || plugin->ptr1 == nullptr); + return *current_bridge_isntance; + } + + return *static_cast(plugin->ptr1); +} Bridge::Bridge(std::string plugin_dll_path, std::string socket_endpoint_path) : plugin_handle(LoadLibrary(plugin_dll_path.c_str()), &FreeLibrary), @@ -37,21 +63,82 @@ Bridge::Bridge(std::string plugin_dll_path, std::string socket_endpoint_path) "'."); } - plugin = vst_entry_point(host_callback); + host_vst_dispatch.connect(socket_endpoint); + + // Initialize after communication has been set up We'll try to do the same + // `get_bridge_isntance` trick as in `plugin/plugin.cpp`, but since the + // plugin will probably call the host callback while it's initializing we + // sadly have to use a global here. + current_bridge_isntance = this; + plugin = vst_entry_point(host_callback_proxy); if (plugin == nullptr) { throw std::runtime_error("VST plugin at '" + plugin_dll_path + "' failed to initialize."); } - host_vst_dispatch.connect(socket_endpoint); + // We only needed this little hack during initialization + current_bridge_isntance = nullptr; + plugin->ptr1 = this; } -// // TODO: Placeholder -// intptr_t VST_CALL_CONV host_callback(AEffect* plugin, -// int32_t opcode, -// int32_t index, -// intptr_t value, -// void* data, -// float option) { -// return 1; -// } +// TODO: Replace blocking loop with async readers or threads for all of the +// sockets. Also extract this functionality somewhere since the host event +// callback needs to do exactly the same thing. +void Bridge::dispatch_loop() { + std::array buffer; + while (true) { + auto event = read_object(host_vst_dispatch); + + // The void pointer argument for the dispatch function is used for + // either: + // - Not at all, in which case it will be a null pointer + // - For passing strings as input to the event + // - For providing a buffer for the event to write results back into + char* payload = nullptr; + if (event.data.has_value()) { + // If the data parameter was an empty string, then we're going to + // pass a larger buffer to the dispatch function instead.. + if (!event.data->empty()) { + payload = const_cast(event.data->c_str()); + } else { + payload = buffer.data(); + } + } + + const intptr_t return_value = + plugin->dispatcher(plugin, event.opcode, event.option, event.index, + payload, event.option); + + // Only write back the value from `payload` if we were passed an empty + // buffer to write into + bool is_updated = event.data.has_value() && event.data->empty(); + + if (is_updated) { + EventResult response{return_value, payload}; + write_object(host_vst_dispatch, response); + } else { + EventResult response{return_value, std::nullopt}; + write_object(host_vst_dispatch, response); + } + } +} + +intptr_t Bridge::host_callback(AEffect* plugin, + int32_t opcode, + int32_t index, + intptr_t value, + void* data, + float option) { + // TODO + return 1; +} + +intptr_t VST_CALL_CONV host_callback_proxy(AEffect* effect, + int32_t opcode, + int32_t index, + intptr_t value, + void* data, + float option) { + return get_bridge_instance(effect).host_callback(effect, opcode, index, + value, data, option); +} diff --git a/src/wine-host/bridge.h b/src/wine-host/bridge.h index 648ae95f..51e6ee49 100644 --- a/src/wine-host/bridge.h +++ b/src/wine-host/bridge.h @@ -45,6 +45,11 @@ class Bridge { */ Bridge(std::string plugin_dll_path, std::string socket_endpoint_path); + intptr_t host_callback(AEffect*, int32_t, int32_t, intptr_t, void*, float); + + // TODO: Remove debug loop + void dispatch_loop(); + // TODO: Set up all callback handlers private: diff --git a/src/wine-host/vst-host.cpp b/src/wine-host/vst-host.cpp index 24159f12..f521c547 100644 --- a/src/wine-host/vst-host.cpp +++ b/src/wine-host/vst-host.cpp @@ -16,25 +16,8 @@ #include -#include "native-includes.h" - -// `native-includes.h` has to be included before any other files as otherwise we -// might get the wrong version of certain libraries -#define WIN32_LEAN_AND_MEAN -#include -#include - -#include "../common/communication.h" #include "bridge.h" -/** - * A function pointer to what should be the entry point of a VST plugin. - */ -using VstEntryPoint = AEffect*(VST_CALL_CONV*)(audioMasterCallback); - -intptr_t VST_CALL_CONV -host_callback(AEffect*, int32_t, int32_t, intptr_t, void*, float); - int main(int argc, char* argv[]) { // We pass the name of the VST plugin .dll file to load and the Unix domain // socket to connect to in plugin/bridge.cpp as the first two arguments of @@ -49,81 +32,15 @@ int main(int argc, char* argv[]) { const std::string plugin_dll_path(argv[1]); const std::string socket_endpoint_path(argv[2]); - // I sadly could not get Boost.DLL to work here, so we'll just load the VST - // plugisn by hand - const auto vst_handle = LoadLibrary(plugin_dll_path.c_str()); + try { + Bridge bridge(plugin_dll_path, socket_endpoint_path); - // TODO: Fall back to the old entry points - const auto vst_entry_point = reinterpret_cast( - reinterpret_cast(GetProcAddress(vst_handle, "VSTPluginMain"))); + // TODO: Remove debug + bridge.dispatch_loop(); + } catch (const std::runtime_error& error) { + std::cerr << "Error while initializing plugin:" << std::endl; + std::cerr << error.what() << std::endl; - // TODO: Check whether this returned a null pointer - AEffect* plugin = vst_entry_point(host_callback); - - // Connect to the sockets for communication once the plugin has finished - // loading - // TODO: The program should terminate gracefully when one of the sockets - // gets closed - // TODO: Remove debug and move most of these things to - // `wine-host/bridge.cpp`, similar to `plugin/bridge.cpp` - - boost::asio::io_context io_context; - boost::asio::local::stream_protocol::endpoint socket_endpoint( - socket_endpoint_path); - - // The naming convention for these sockets is `__`. For - // instance the socket named `host_vst_dispatch` forwards - // `AEffect.dispatch()` calls from the native VST host to the Windows VST - // plugin (through the Wine VST host). - boost::asio::local::stream_protocol::socket host_vst_dispatch(io_context); - host_vst_dispatch.connect(socket_endpoint); - - // TODO: Remove debug, we're just reporting the plugin's name we retrieved - // above - std::array buffer; - while (true) { - auto event = read_object(host_vst_dispatch); - - // The void pointer argument for the dispatch function is used for - // either: - // - Not at all, in which case it will be a null pointer - // - For passing strings as input to the event - // - For providing a buffer for the event to write results back into - char* payload = nullptr; - if (event.data.has_value()) { - // If the data parameter was an empty string, then we're going to - // pass a larger buffer to the dispatch function instead.. - if (!event.data->empty()) { - payload = const_cast(event.data->c_str()); - } else { - payload = buffer.data(); - } - } - - const intptr_t return_value = - plugin->dispatcher(plugin, event.opcode, event.option, event.index, - payload, event.option); - - // Only write back the value from `payload` if we were passed an empty - // buffer to write into - bool is_updated = event.data.has_value() && event.data->empty(); - - if (is_updated) { - EventResult response{return_value, payload}; - write_object(host_vst_dispatch, response); - } else { - EventResult response{return_value, std::nullopt}; - write_object(host_vst_dispatch, response); - } + return 1; } } - -// TODO: Placeholder -intptr_t VST_CALL_CONV host_callback(AEffect* plugin, - int32_t opcode, - int32_t index, - intptr_t value, - void* data, - float option) { - return 1; -}