diff --git a/src/plugin/host-bridge.cpp b/src/plugin/host-bridge.cpp index 1bc91d22..eb04843b 100644 --- a/src/plugin/host-bridge.cpp +++ b/src/plugin/host-bridge.cpp @@ -49,14 +49,29 @@ fs::path find_wine_vst_host(); fs::path generate_endpoint_name(); bp::environment set_wineprefix(); +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 + * is sadly needed as a workaround to avoid using globals since we need free + * function pointers to interface with the VST C API. + */ +HostBridge& get_bridge_instance(const AEffect& plugin) { + return *static_cast(plugin.ptr3); +} + // TODO: When adding debug information, print both the path to the VST host and // the chosen wineprefix -HostBridge::HostBridge(AEffect* plugin, audioMasterCallback host_callback) +HostBridge::HostBridge(audioMasterCallback host_callback) : io_context(), socket_endpoint(generate_endpoint_name().string()), socket_acceptor(io_context, socket_endpoint), host_vst_dispatch(io_context), vst_host_callback(io_context), + vst_host_aeffect(io_context), host_callback_function(host_callback), vst_host(find_wine_vst_host(), // The Wine VST host needs to know which plugin to load and @@ -68,10 +83,28 @@ HostBridge::HostBridge(AEffect* plugin, audioMasterCallback host_callback) // in the Wine VST host socket_acceptor.accept(host_vst_dispatch); socket_acceptor.accept(vst_host_callback); + socket_acceptor.accept(vst_host_aeffect); - // TODO: REmove - // After accepting the sockets - removeme = std::thread([&]() { return host_callback_loop(plugin); }); + // Set up all pointers for our `AEffect` struct. We will fill this with data + // from the VST plugin loaded in Wine at the end of this constructor. + plugin.ptr3 = this; + plugin.dispatcher = dispatch_proxy; + plugin.process = process_proxy; + plugin.setParameter = setParameter_proxy; + plugin.getParameter = getParameter_proxy; + // TODO: Add processReplacing + + // TODO: Replace manual thread creation with an async_read loop + // Start accepting host callbacks after we've set up our sockets and basic + // `AEffect` struct. + removeme = std::thread([&]() { return host_callback_loop(); }); + + // Read the plugin's information from the Wine process. This can only be + // done after we started accepting host callbacks as the plugin might do + // this during initialization. + // XXX: If the plugin has crashed then this read should fail instead of + // blocking indefinitely, check if this is the case + plugin = read_object(vst_host_aeffect, plugin); } /** @@ -223,9 +256,9 @@ fs::path generate_endpoint_name() { // 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 HostBridge::host_callback_loop(AEffect* plugin) { +void HostBridge::host_callback_loop() { while (true) { - passthrough_event(vst_host_callback, plugin, host_callback_function); + passthrough_event(vst_host_callback, &plugin, host_callback_function); } } @@ -252,3 +285,32 @@ bp::environment set_wineprefix() { return env; } + +// The below functions are proxy functions for the methods defined in +// `Bridge.cpp` + +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_proxy(AEffect* plugin, + float** inputs, + float** outputs, + int32_t sample_frames) { + return get_bridge_instance(*plugin).process(plugin, inputs, outputs, + sample_frames); +} + +void setParameter_proxy(AEffect* plugin, int32_t index, float value) { + return get_bridge_instance(*plugin).set_parameter(plugin, index, value); +} + +float getParameter_proxy(AEffect* plugin, int32_t index) { + return get_bridge_instance(*plugin).get_parameter(plugin, index); +} diff --git a/src/plugin/host-bridge.h b/src/plugin/host-bridge.h index b25d97fe..562295aa 100644 --- a/src/plugin/host-bridge.h +++ b/src/plugin/host-bridge.h @@ -35,17 +35,13 @@ class HostBridge { * Initializes the Wine VST bridge. This sets up the sockets for event * handling. * - * TODO: Figure out whether shared memory gives us better throughput and/or - * lower overhead than using a Unix domain socket would. - * * @param host_callback The callback function passed to the VST plugin by * the host. * * @throw std::runtime_error Thrown when the VST host could not be found, or * if it could not locate and load a VST .dll file. */ - // TODO: The plugin struct should be created here, not passed in - HostBridge(AEffect* plugin, audioMasterCallback host_callback); + HostBridge(audioMasterCallback host_callback); // The four below functions are the handlers from the VST2 API. They are // called through proxy functions in `plugin.cpp`. @@ -68,7 +64,14 @@ class HostBridge { float get_parameter(AEffect* plugin, int32_t index); // TODO: Remove debug loop - void host_callback_loop(AEffect* plugin); + void host_callback_loop(); + + /** + * This AEffect struct will be populated using the data passed by the Wine + * VST host during initialization and then passed as a pointer to the Linux + * native VST host from the Linux VST plugin's entry point. + */ + AEffect plugin; private: boost::asio::io_context io_context; @@ -82,6 +85,13 @@ class HostBridge { boost::asio::local::stream_protocol::socket host_vst_dispatch; boost::asio::local::stream_protocol::socket vst_host_callback; + /** + * This socket only handles updates of the `AEffect` struct instead of + * passing through function calls. It's also used during initialization to + * pass the Wine plugin's information to the host. + */ + boost::asio::local::stream_protocol::socket vst_host_aeffect; + /** * The callback function passed by the host to the VST plugin instance. */ diff --git a/src/plugin/plugin.cpp b/src/plugin/plugin.cpp index 8a3ca391..b6f7c97b 100644 --- a/src/plugin/plugin.cpp +++ b/src/plugin/plugin.cpp @@ -38,20 +38,6 @@ VST_EXPORT AEffect* main_plugin(audioMasterCallback audioMaster) { } } -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 - * is sadly needed as a workaround to avoid using globals since we need free - * function pointers to interface with the VST C API. - */ -HostBridge& get_bridge_instance(const AEffect& plugin) { - return *static_cast(plugin.ptr3); -} - /** * The main VST plugin entry point. We first set up a bridge that connects to a * Wine process that hosts the Windows VST plugin. We then create and return a @@ -63,25 +49,14 @@ HostBridge& get_bridge_instance(const AEffect& plugin) { */ VST_EXPORT AEffect* VSTPluginMain(audioMasterCallback host_callback) { try { - // TODO: Create the plugin instance in the bridge based on the received - // parameters. We can then also use a smart pointer so we only - // have to manually delete the bridge instance. - AEffect* plugin = new AEffect(); - HostBridge* bridge = new HostBridge(plugin, host_callback); - plugin->ptr3 = bridge; + // This is the only place where we have to use manual memory management. + // The bridge's destructor is called when the `effClose` opcode is + // received. + HostBridge* bridge = new HostBridge(host_callback); - plugin->dispatcher = dispatch_proxy; - plugin->process = process_proxy; - plugin->setParameter = setParameter_proxy; - plugin->getParameter = getParameter_proxy; - // // XXX: processReplacing? + // TODO: Debug print information about the loaded plugin - // TODO: Add more and actual data - plugin->magic = kEffectMagic; - plugin->numParams = 69; - plugin->uniqueID = 69420; - - return plugin; + return &bridge->plugin; } catch (const std::exception& error) { std::cerr << "Error during initialization:" << std::endl; std::cerr << error.what() << std::endl; @@ -89,32 +64,3 @@ VST_EXPORT AEffect* VSTPluginMain(audioMasterCallback host_callback) { return nullptr; } } - -// The below functions are proxy functions for the methods defined in -// `Bridge.cpp` - -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_proxy(AEffect* plugin, - float** inputs, - float** outputs, - int32_t sample_frames) { - return get_bridge_instance(*plugin).process(plugin, inputs, outputs, - sample_frames); -} - -void setParameter_proxy(AEffect* plugin, int32_t index, float value) { - return get_bridge_instance(*plugin).set_parameter(plugin, index, value); -} - -float getParameter_proxy(AEffect* plugin, int32_t index) { - return get_bridge_instance(*plugin).get_parameter(plugin, index); -} diff --git a/src/wine-host/plugin-bridge.cpp b/src/wine-host/plugin-bridge.cpp index a0334a70..cdeaa7ce 100644 --- a/src/wine-host/plugin-bridge.cpp +++ b/src/wine-host/plugin-bridge.cpp @@ -56,7 +56,8 @@ PluginBridge::PluginBridge(std::string plugin_dll_path, io_context(), socket_endpoint(socket_endpoint_path), host_vst_dispatch(io_context), - vst_host_callback(io_context) { + vst_host_callback(io_context), + vst_host_aeffect(io_context) { // Got to love these C APIs if (plugin_handle == nullptr) { throw std::runtime_error("Could not load a shared library at '" + @@ -85,6 +86,7 @@ PluginBridge::PluginBridge(std::string plugin_dll_path, // in the Linus plugin host_vst_dispatch.connect(socket_endpoint); vst_host_callback.connect(socket_endpoint); + vst_host_aeffect.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 @@ -97,6 +99,12 @@ PluginBridge::PluginBridge(std::string plugin_dll_path, "' failed to initialize."); } + // Send the plugin's information to the Linux VST plugin + // TODO: This is now done only once at startup, do plugins update their + // parameters? In that case we should be detecting updates and pass + // them along accordingly. + write_object(vst_host_aeffect, *plugin); + // We only needed this little hack during initialization current_bridge_isntance = nullptr; plugin->ptr1 = this; diff --git a/src/wine-host/plugin-bridge.h b/src/wine-host/plugin-bridge.h index 1092f578..f2493a4e 100644 --- a/src/wine-host/plugin-bridge.h +++ b/src/wine-host/plugin-bridge.h @@ -75,4 +75,11 @@ class PluginBridge { // plugin (through the Wine VST host). boost::asio::local::stream_protocol::socket host_vst_dispatch; boost::asio::local::stream_protocol::socket vst_host_callback; + + /** + * This socket only handles updates of the `AEffect` struct instead of + * passing through function calls. It's also used during initialization to + * pass the Wine plugin's information to the host. + */ + boost::asio::local::stream_protocol::socket vst_host_aeffect; };