diff --git a/README.md b/README.md index de011790..eb57538e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ There are a few things that should be done before making this public, including: - Document the project setup and the way communication works. - Document what this has been tested on and what does or does not work. - Document wine32 support. +- Add proper debugging support activated using an environment variable. + - Write all stdout and stderr output from the plugin to a temporary file so it + can be inspected when using a host such as Bitwig that hides this by + default. + - Catch exceptions during initialization and print them to stderr. ## Building @@ -18,6 +23,7 @@ the following dependencies: - gcc (tested using GCC 9.2) - A Wine installation with `wiengcc` and the development headers. +- Boost - [msgpack-c](git@github.com:msgpack/msgpack-c.git) The project can then be compiled as follows: diff --git a/meson.build b/meson.build index d341d712..5e5dfe60 100644 --- a/meson.build +++ b/meson.build @@ -20,6 +20,7 @@ endif # about the way these two components work together can be found in the readme # file. +boost_dep = dependency('boost', modules : ['filesystem']) msgpack_dep = dependency('msgpack') include_dir = include_directories('src/include') @@ -40,8 +41,8 @@ shared_library( linux_plugin_src + common_src, native : true, include_directories : include_dir, - dependencies : [msgpack_dep], - link_args : [] + dependencies : [boost_dep, msgpack_dep], + link_args : ['-ldl'] ) executable( diff --git a/src/common/events.h b/src/common/events.h new file mode 100644 index 00000000..06afb2aa --- /dev/null +++ b/src/common/events.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +/** + * An event as dispatched by the VST host. These events will get forwarded to + * the VST host process running under Wine. The fields here mirror those + * arguments sent to the `AEffect::dispatch` function. + */ +struct Event { + int32_t opcode; + int32_t parameter; + // TODO: This is an intptr_t, is this actually a poitner that should be + // dereferenced? + intptr_t value; + float option; + + MSGPACK_DEFINE(opcode, parameter, value, option) +}; + +/** + * AN instance of this should be sent back as a response to an incoming event. + */ +struct EventResult { + /** + * The result that should be returned from the dispatch function. + */ + intptr_t return_value; + /** + * If present, this should get written into the void pointer passed to the + * dispatch function. + */ + std::optional result; + + // TODO: Add missing return value fields; + + MSGPACK_DEFINE(return_value, result) +}; diff --git a/src/plugin/bridge.cpp b/src/plugin/bridge.cpp index 102b4176..10bf923b 100644 --- a/src/plugin/bridge.cpp +++ b/src/plugin/bridge.cpp @@ -1,5 +1,8 @@ #include "bridge.h" +#include +#include +#include #include #include @@ -9,6 +12,22 @@ // implementation details, such as the use of intptr_t isntead of void* // here. +namespace bp = boost::process; +namespace fs = boost::filesystem; + +constexpr auto yabridge_wine_host_name = "yabridge-host.exeTODO"; + +fs::path find_wine_vst_host(); + +Bridge::Bridge() + : vst_stdin(), + vst_stdout(), + vst_host(find_wine_vst_host(), + bp::std_in = vst_stdin, + bp::std_out = vst_stdout) { + // TODO: Wineprefix detection +} + /** * Handle an event sent by the VST host. Most of these opcodes will be passed * through to the winelib VST host. @@ -36,6 +55,8 @@ intptr_t Bridge::dispatch(AEffect* /*plugin*/, switch (opcode) { case effClose: // TODO: Gracefully close the editor? + // XXX: Boost.Process will send SIGKILL to the process for us, is + // there a way to manually send a SIGTERM signal instead? // The VST API does not have an explicit function for releasing // resources, so we'll have to do it here. The actual plugin @@ -67,3 +88,34 @@ float Bridge::get_parameter(AEffect* /*plugin*/, int32_t /*index*/ // TODO: Unimplmemented return 0.0f; } + +/** + * Finds the Wine VST hsot (named `yabridge-host.exe`). For this we will search + * in two places: + * + * 1. Alongside libyabridge.so if the file got symlinked. This is useful + * when developing, as you can simply symlink the the libyabridge.so + * file in the build directory without having to install anything to + * /usr. + * 2. In the regular search path. + * + * @return The a path to the VST host, if found. + * @throw std::runtime_error If the Wine VST host could not be found. + */ +fs::path find_wine_vst_host() { + fs::path host_path = fs::canonical(boost::dll::this_line_location()); + host_path.remove_filename().append(yabridge_wine_host_name); + if (fs::exists(host_path)) { + return host_path; + } + + // Bosot will return an empty path if the file could not be found in the + // search path + fs::path vst_host_path = bp::search_path(yabridge_wine_host_name); + if (fs::is_empty(vst_host_path)) { + throw std::runtime_error("Could not locate '" + + std::string(yabridge_wine_host_name) + "'"); + } + + return vst_host_path; +} diff --git a/src/plugin/bridge.h b/src/plugin/bridge.h index f8d2545e..6ca63f72 100644 --- a/src/plugin/bridge.h +++ b/src/plugin/bridge.h @@ -2,6 +2,8 @@ #include +#include + /** * This handles the communication between the Linux native VST plugin and the * Wine VST host. The functions below should be used as callback functions in an @@ -9,12 +11,24 @@ */ class Bridge { public: - // The below four functions are the handlers from the VST2 API. They are + /** + * Initializes the Wine VST bridge. This sets up the STDIN/STDOUT streams + * for event handling and a shared memory buffer for passing audio. + * + * TODO: Figure out whether shared memory gives us better throughput and/or + * lower overhead than using a Unix domain socket would. + * + * @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. + */ + Bridge(); + + // The four below functions are the handlers from the VST2 API. They are // called through proxy functions in `plugin.cpp`. /** * Handle an event sent by the VST host. Most of these opcodes will be - * passed through to the winelib VST host through a Unix domain socket. + * passed through to the winelib VST host. */ intptr_t dispatch(AEffect* plugin, int32_t opcode, @@ -28,4 +42,9 @@ class Bridge { int32_t sample_frames); void set_parameter(AEffect* plugin, int32_t index, float value); float get_parameter(AEffect* plugin, int32_t index); + + private: + boost::process::opstream vst_stdin; + boost::process::ipstream vst_stdout; + boost::process::child vst_host; };