Pass through the plugin's AEffect struct

This commit is contained in:
Robbert van der Helm
2020-03-05 14:52:02 +01:00
parent 8f34947307
commit 4f29a98cea
5 changed files with 106 additions and 73 deletions
+68 -6
View File
@@ -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<HostBridge*>(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);
}
+16 -6
View File
@@ -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.
*/
+6 -60
View File
@@ -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<HostBridge*>(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);
}
+9 -1
View File
@@ -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;
+7
View File
@@ -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;
};