From cd23fe254b2275071d81bc97a5123d9e91d19e94 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 13:05:47 +0100 Subject: [PATCH 001/456] Add an incomplete list of things needing updates --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index d84a559e..ffbd1919 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,22 @@ communication through [plugin groups](#plugin-groups). Its modern concurrent architecture and focus on transparency allows yabridge to be both fast and highly compatible, while also staying easy to debug and maintain. +## TODO + +This branch is still very far removed from being in a usable state. Below is an +imcomplete list of things that still have to be done before this can be used: + +- Rename `PluginBridge` to `Vst2PluginBridge` and explain that the names are + chosen this way to be easily greppable. +- Actually start implementing VST3 support. +- Make sure all old references to `libyabridge` now refer to `libyabridge-vst2` + or to both `libyabridge-{vst2,vst3}` depending on the situation. +- Update the GitHub Actions workflows. +- Update yabridgectl to handle buth VST2 and VST3 plugins. +- Update all documentation to refer to VST2 and VST3 support separately, and + figure out how to do this in the least confusing way possible. +- Update all the AUR packages. + ![yabridge screenshot](https://raw.githubusercontent.com/robbert-vdh/yabridge/master/screenshot.png) ### Table of contents From c6eb55dc6d5efc4744ca36a45d780439adb62a78 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 13:08:49 +0100 Subject: [PATCH 002/456] Rename libyabridge to libyabridge-vst2 --- README.md | 2 ++ meson.build | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ffbd1919..782c7021 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ imcomplete list of things that still have to be done before this can be used: - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and figure out how to do this in the least confusing way possible. +- Mention that this update will break all existing symlinks and that the old + `libyabridge.so` file should be removed when upgrading. - Update all the AUR packages. ![yabridge screenshot](https://raw.githubusercontent.com/robbert-vdh/yabridge/master/screenshot.png) diff --git a/meson.build b/meson.build index 9e1eddcf..fab572d3 100644 --- a/meson.build +++ b/meson.build @@ -71,13 +71,13 @@ xcb_dep = dependency('xcb') include_dir = include_directories('src/include') -# The application consists of a VST plugin (yabridge) that calls a winelib -# program (yabridge-host) that can host Windows VST plugins. More information -# about the way these two components work together can be found in the readme -# file. +# The application consists of a plugin (`libyabridge-{vst2,vst3}.so`) that calls +# a Winelib application (`yabridge-{host,group}{,-32}.exe`) that can host +# Windows VST plugins. More information about the way these two components work +# together can be found in `docs/architecture.md`. shared_library( - 'yabridge', + 'yabridge-vst2', [ 'src/common/configuration.cpp', 'src/common/logging.cpp', From a428d08eff37a0adf4714ffa978af283e6751116 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 13:17:33 +0100 Subject: [PATCH 003/456] Update yabridgectl for libyabridge-vst2.so --- tools/yabridgectl/README.md | 15 ++++++++++----- tools/yabridgectl/src/actions.rs | 26 +++++++++++++------------- tools/yabridgectl/src/config.rs | 28 ++++++++++++++-------------- tools/yabridgectl/src/files.rs | 4 ++-- tools/yabridgectl/src/main.rs | 6 +++--- tools/yabridgectl/src/utils.rs | 2 +- 6 files changed, 43 insertions(+), 38 deletions(-) diff --git a/tools/yabridgectl/README.md b/tools/yabridgectl/README.md index c41ace1f..0366b119 100644 --- a/tools/yabridgectl/README.md +++ b/tools/yabridgectl/README.md @@ -13,11 +13,11 @@ from anywhere. All of the information below can also be found through ### Yabridge path -Yabridgectl will need to know where it can find `libyabridge.so`. By default it -will search for it in both `~/.local/share/yabridge` (the recommended -installation directory when using the prebuilt binaries) and in `/usr/lib`. You -can use the command below to override this behaviour and to use a custom -installation directory instead. +Yabridgectl will need to know where it can find the `libyabridge-vst*.so` files. +By default it will search for it in both `~/.local/share/yabridge` (the +recommended installation directory when using the prebuilt binaries) and in +`/usr/lib`. You can use the command below to override this behaviour and to use +a custom installation directory instead. ```shell yabridgectl set --path= @@ -77,6 +77,11 @@ yabridgectl sync --force ## Alternatives +TODO: This now only mentions how to do this for VST2 plugins. We should probably +just drop this section altogether since using yabridgectl is so much easier, and +if someone really wants to do this by hand then they'll be able to whip up their +own script based on the instructions in the main readme. + If you want to script your own installation behaviour and don't feel like using yabridgectl, then you could use one of the below bash snippets instead. This approach is slightly less robust and does not perform any problem detection or diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 2de63b7d..b2741cd9 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -96,9 +96,9 @@ pub fn show_status(config: &Config) -> Result<()> { .unwrap_or_else(|| String::from("")) ); println!( - "libyabridge.so: {}", + "libyabridge-vst2.so: {}", config - .libyabridge() + .libyabridge_vst2() .map(|path| format!("'{}'", path.display())) .unwrap_or_else(|_| format!("{}", "".red())) ); @@ -154,9 +154,9 @@ pub struct SyncOptions { /// Set up yabridge for all Windows VST2 plugins in the plugin directories. Will also remove orphan /// `.so` files if the prune option is set. pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { - let libyabridge_path = config.libyabridge()?; - let libyabridge_hash = utils::hash_file(&libyabridge_path)?; - println!("Using '{}'\n", libyabridge_path.display()); + let libyabridge_vst2_path = config.libyabridge_vst2()?; + let libyabridge_vst2_hash = utils::hash_file(&libyabridge_vst2_path)?; + println!("Using '{}'\n", libyabridge_vst2_path.display()); let results = config .index_directories() @@ -188,19 +188,19 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { match (options.force, &config.method) { (false, InstallationMethod::Copy) => { // If the target file is already a real file (not a symlink) and its hash is - // the same as the `libyabridge.so` file we're trying to copy there, then we - // don't have to do anything + // the same as the `libyabridge-vst2.so` file we're trying to copy there, + // then we don't have to do anything if metadata.file_type().is_file() - && utils::hash_file(&target_path)? == libyabridge_hash + && utils::hash_file(&target_path)? == libyabridge_vst2_hash { continue; } } (false, InstallationMethod::Symlink) => { - // If the target file is already a symlink to `libyabridge.so`, then we can - // skip this file + // If the target file is already a symlink to `libyabridge-vst2.so`, then we + // can skip this file if metadata.file_type().is_symlink() - && target_path.read_link()? == libyabridge_path + && target_path.read_link()? == libyabridge_vst2_path { continue; } @@ -217,10 +217,10 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { num_new += 1; match config.method { InstallationMethod::Copy => { - utils::copy(&libyabridge_path, &target_path)?; + utils::copy(&libyabridge_vst2_path, &target_path)?; } InstallationMethod::Symlink => { - utils::symlink(&libyabridge_path, &target_path)?; + utils::symlink(&libyabridge_vst2_path, &target_path)?; } } diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index fbb6b979..b4db96a6 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -35,7 +35,7 @@ pub const CONFIG_FILE_NAME: &str = "config.toml"; const YABRIDGECTL_PREFIX: &str = "yabridgectl"; /// The name of the library file we're searching for. -pub const LIBYABRIDGE_NAME: &str = "libyabridge.so"; +pub const LIBYABRIDGE_VST2_NAME: &str = "libyabridge-vst2.so"; /// The name of the script we're going to run to verify that everything's working correctly. pub const YABRIDGE_HOST_EXE_NAME: &str = "yabridge-host.exe"; /// The name of the XDG base directory prefix for yabridge's own files, relative to @@ -66,11 +66,11 @@ pub struct Config { #[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum InstallationMethod { - /// Create a copy of `libyabridge.so` for every Windows VST2 plugin .dll file found. After + /// Create a copy of `libyabridge-vst2.so` for every Windows VST2 plugin .dll file found. After /// updating yabridge, the user will have to rerun `yabridgectl sync` to copy over the new /// version. Copy, - /// This will create a symlink to `libyabridge.so` for every VST2 .dll file in the plugin + /// This will create a symlink to `libyabridge-vst2.so` for every VST2 .dll file in the plugin /// directories. As explained in the readme, this makes updating easier and remvoes the need to /// modify the `PATH` environment variable. Symlink, @@ -158,19 +158,19 @@ impl Config { .with_context(|| format!("Failed to write config file to '{}'", config_path.display())) } - /// Return the path to `libyabridge.so`, or a descriptive error if it can't be found. If + /// Return the path to `libyabridge-vst2.so`, or a descriptive error if it can't be found. If /// `yabridge_home` is `None`, then we'll search in both `/usr/lib` and /// `$XDG_DATA_HOME/yabridge`. - pub fn libyabridge(&self) -> Result { + pub fn libyabridge_vst2(&self) -> Result { match &self.yabridge_home { Some(directory) => { - let candidate = directory.join(LIBYABRIDGE_NAME); + let candidate = directory.join(LIBYABRIDGE_VST2_NAME); if candidate.exists() { Ok(candidate) } else { Err(anyhow!( "Could not find '{}' in '{}'", - LIBYABRIDGE_NAME, + LIBYABRIDGE_VST2_NAME, directory.display() )) } @@ -179,12 +179,12 @@ impl Config { // Search in the two common installation locations if no path was set explicitely. // We'll also search through `/usr/local/lib` just in case but since we advocate // against isntalling yabridge there we won't list this path in the error message - // when `libyabridge.so` can't be found. + // when `libyabridge-vst2.so` can't be found. let system_path = Path::new("/usr/lib"); let system_path_alt = Path::new("/usr/local/lib"); let user_path = yabridge_directories()?.get_data_home(); for directory in &[system_path, system_path_alt, &user_path] { - let candidate = directory.join(LIBYABRIDGE_NAME); + let candidate = directory.join(LIBYABRIDGE_VST2_NAME); if candidate.exists() { return Ok(candidate); } @@ -193,7 +193,7 @@ impl Config { Err(anyhow!( "Could not find '{}' in either '{}' or '{}'. You can override the default \ search path using 'yabridgectl set --path='.", - LIBYABRIDGE_NAME, + LIBYABRIDGE_VST2_NAME, system_path.display(), user_path.display() )) @@ -202,11 +202,11 @@ impl Config { } /// Return the path to `yabridge-host.exe`, or a descriptive error if it can't be found. This - /// will first search alongside `libyabridge.so` and then search through the search path. + /// will first search alongside `libyabridge-vst2.so` and then search through the search path. pub fn yabridge_host_exe(&self) -> Result { - let libyabridge_path = self.libyabridge()?; + let yabridge_path = self.libyabridge_vst2()?; - let yabridge_host_exe_candidate = libyabridge_path.with_file_name(YABRIDGE_HOST_EXE_NAME); + let yabridge_host_exe_candidate = yabridge_path.with_file_name(YABRIDGE_HOST_EXE_NAME); if yabridge_host_exe_candidate.exists() { return Ok(yabridge_host_exe_candidate); } @@ -228,7 +228,7 @@ impl Config { /// Fetch the XDG base directories for yabridge's own files, converting any error messages if this /// somehow fails into a printable string to reduce boiler plate. This is only used when searching -/// for `libyabridge.so` when no explicit search path has been set. +/// for `libyabridge-{vst2,vst3}.so` when no explicit search path has been set. pub fn yabridge_directories() -> Result { BaseDirectories::with_prefix(YABRIDGE_PREFIX).context("Error while parsing base directories") } diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index e3aac87e..3544ff34 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -47,8 +47,8 @@ pub enum FoundFile { } impl SearchResults { - /// For every found VST2 plugin, find the associated copy or symlink of `libyabridge.so`. The - /// returned hashmap will contain a `None` value for plugins that have not yet been set up. + /// For every found VST2 plugin, find the associated copy or symlink of `libyabridge-vst2.so`. + /// The returned hashmap will contain a `None` value for plugins that have not yet been set up. /// /// These two functions could be combined into a single function, but speed isn't really an /// issue here and it's a bit more organized this way. diff --git a/tools/yabridgectl/src/main.rs b/tools/yabridgectl/src/main.rs index 649ce4e6..b21fc8cd 100644 --- a/tools/yabridgectl/src/main.rs +++ b/tools/yabridgectl/src/main.rs @@ -99,10 +99,10 @@ fn main() -> Result<()> { .arg( Arg::with_name("path") .long("path") - .about("Path to the directory containing 'libyabridge.so'") + .about("Path to the directory containing 'libyabridge-{vst2,vst3}.so'") .long_about( - "Path to the directory containing 'libyabridge.so'. If this is \ - not set, then yabridgectl will look in both '/usr/lib' and \ + "Path to the directory containing 'libyabridge-{vst2,vst3}.so'. If this \ + is not set, then yabridgectl will look in both '/usr/lib' and \ '~/.local/share/yabridge' by default.", ) .validator(validate_path) diff --git a/tools/yabridgectl/src/utils.rs b/tools/yabridgectl/src/utils.rs index 736015a6..3e241a2a 100644 --- a/tools/yabridgectl/src/utils.rs +++ b/tools/yabridgectl/src/utils.rs @@ -179,7 +179,7 @@ pub fn verify_path_setup(config: &Config) -> Result { reboot your system to complete the setup.\n\ \n\ https://github.com/robbert-vdh/yabridge#troubleshooting-common-issues", - config.libyabridge()?.parent().unwrap().display(), + config.libyabridge_vst2()?.parent().unwrap().display(), shell.bright_white(), "PATH".bright_white() )) From 9d40e04a625742d00fcaef82cdf96cbb068519d8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 13:26:00 +0100 Subject: [PATCH 004/456] Update all references to libyabridge.so --- .github/workflows/build.yml | 4 ++-- README.md | 17 ++++++++--------- docs/architecture.md | 2 ++ src/common/configuration.h | 4 ++-- src/plugin/utils.cpp | 4 ++-- src/plugin/utils.h | 31 +++++++++++++++++-------------- src/wine-host/group-host.cpp | 10 +++++----- src/wine-host/individual-host.cpp | 4 ++-- tools/yabridgectl/src/config.rs | 6 +++--- 9 files changed, 43 insertions(+), 39 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad7cf00d..e8e3bca1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,7 +53,7 @@ jobs: - name: Create an archive for the binaries run: | mkdir yabridge - cp build/libyabridge.so build/yabridge-{host,group}{,-32}.exe{,.so} yabridge + cp build/libyabridge-vst2.so build/yabridge-{host,group}{,-32}.exe{,.so} yabridge cp CHANGELOG.md README.md yabridge tar -caf "$ARCHIVE_NAME" yabridge @@ -92,7 +92,7 @@ jobs: - name: Create an archive for the binaries run: | mkdir yabridge - cp build/libyabridge.so build/yabridge-{host,group}{,-32}.exe{,.so} yabridge + cp build/libyabridge-vst2.so build/yabridge-{host,group}{,-32}.exe{,.so} yabridge cp CHANGELOG.md README.md yabridge tar -caf "$ARCHIVE_NAME" yabridge diff --git a/README.md b/README.md index 782c7021..6f76cfdd 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,6 @@ imcomplete list of things that still have to be done before this can be used: - Rename `PluginBridge` to `Vst2PluginBridge` and explain that the names are chosen this way to be easily greppable. - Actually start implementing VST3 support. -- Make sure all old references to `libyabridge` now refer to `libyabridge-vst2` - or to both `libyabridge-{vst2,vst3}` depending on the situation. - Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and @@ -134,24 +132,25 @@ as it makes updating easier and yabridgectl will check for some common mistakes during the installation process. To set up yabridge without using yabridgectl, first download and extract yabridge's files like in the section above. The rest of this section assumes that you have extracted the files to `~/.local/share` -(such that `~/.local/share/yabridge/libyabridge.so` exists), and that you want -to set up yabridge for the VST2 plugin called `~/.wine/drive_c/Program Files/Steinberg/VstPlugins/plugin.dll`. +(such that `~/.local/share/yabridge/libyabridge-vst2.so` exists), and that you +want to set up yabridge for the VST2 plugin called +`~/.wine/drive_c/Program Files/Steinberg/VstPlugins/plugin.dll`. Depending on whether you want to use copy or symlink-based installation method, you can then set up yabridge for that plugin by creating a copy or symlink of -`libyabridge.so` next to `plugin.dll` called `plugin.so`. For the example, you -can use either: +`libyabridge-vst2.so` next to `plugin.dll` called `plugin.so`. For the example, +you can use either: ```shell # For the copy-based installation method -cp ~/.local/share/yabridge/libyabridge.so "$HOME/.wine/drive_c/Program Files/Steinberg/VstPlugins/plugin.so" +cp ~/.local/share/yabridge/libyabridge-vst2.so "$HOME/.wine/drive_c/Program Files/Steinberg/VstPlugins/plugin.so" # For the symlink-based installation method -ln -sf ~/.local/share/yabridge/libyabridge.so "$HOME/.wine/drive_c/Program Files/Steinberg/VstPlugins/plugin.so" +ln -sf ~/.local/share/yabridge/libyabridge-vst2.so "$HOME/.wine/drive_c/Program Files/Steinberg/VstPlugins/plugin.so" ``` The symlink-based installation method will not work with any host that does not individually sandbox its plugins. If you are using the copy-based installation -method, then don't forget to overwrite all copies of `libyabridge.so` you +method, then don't forget to overwrite all copies of `libyabridge-vst2.so` you created this way whenever you update yabridge. ### DAW setup diff --git a/docs/architecture.md b/docs/architecture.md index 78998dec..93a80873 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,5 +1,7 @@ # Architecture +TODO: This document has not yet been updated since adding VST3 support + The project consists of two components: a Linux native VST plugin (`libyabridge.so`) and a VST host that runs under Wine (`yabridge-host.exe`/`yabridge-host.exe.so`, and diff --git a/src/common/configuration.h b/src/common/configuration.h index 74981337..3c827c8c 100644 --- a/src/common/configuration.h +++ b/src/common/configuration.h @@ -33,8 +33,8 @@ * they can share resources. Configuration file loading works as follows: * * 1. `load_config_for(path)` from `src/plugin/utils.h` gets called with a path - * to the copy of or symlink to `libyabridge.so` that the plugin host has - * tried to load. + * to the copy of or symlink to `libyabridge-{vst2,vst3}.so` that the plugin + * host has tried to load. * 2. We start looking for a file named `yabridge.toml` in the same directory as * that `.so` file, iteratively continuing to search one directory higher * until we either find the file or we reach the filesystem root. diff --git a/src/plugin/utils.cpp b/src/plugin/utils.cpp index 01b91d9a..c0b7eba6 100644 --- a/src/plugin/utils.cpp +++ b/src/plugin/utils.cpp @@ -140,7 +140,7 @@ fs::path find_vst_plugin() { // In case this files does not exist and our `.so` file is a symlink, we'll // also repeat this check after resolving that symlink to support links to - // copies of `libyabridge.so` as described in issue #3 + // copies of `libyabridge-vst2.so` as described in issue #3 fs::path alternative_plugin_path = fs::canonical(this_plugin_path); alternative_plugin_path.replace_extension(".dll"); if (fs::exists(alternative_plugin_path)) { @@ -151,7 +151,7 @@ fs::path find_vst_plugin() { // throw when the path could not be found throw std::runtime_error("'" + plugin_path.string() + "' does not exist, make sure to rename " - "'libyabridge.so' to match a " + "'libyabridge-vst2.so' to match a " "VST plugin .dll file."); } diff --git a/src/plugin/utils.h b/src/plugin/utils.h index f11d14e8..93bf5c77 100644 --- a/src/plugin/utils.h +++ b/src/plugin/utils.h @@ -76,10 +76,10 @@ PluginArchitecture find_vst_architecture(boost::filesystem::path); * Finds the Wine VST host (either `yabridge-host.exe` or `yabridge-host.exe` * depending on the plugin). 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. + * 1. Alongside libyabridge-{vst2,vst3}.so if the file got symlinked. This is + * useful when developing, as you can simply symlink the + * `libyabridge-{vst2,vst3}.so` file in the build directory without having + * to install anything to /usr. * 2. In the regular search path, augmented with `~/.local/share/yabridge` to * ease the setup process. * @@ -96,11 +96,14 @@ boost::filesystem::path find_vst_host(PluginArchitecture plugin_arch, /** * Find the VST plugin .dll file that corresponds to this copy of - * `libyabridge.so`. This should be the same as the name of this file but with a - * `.dll` file extension instead of `.so`. In case this file does not exist and - * the `.so` file is a symlink, we'll also repeat this check for the file it - * links to. This is to support the workflow described in issue #3 where you use - * symlinks to copies of `libyabridge.so`. + * `libyabridge-vst2.so`. This should be the same as the name of this file but + * with a `.dll` file extension instead of `.so`. In case this file does not + * exist and the `.so` file is a symlink, we'll also repeat this check for the + * file it links to. This is to support the workflow described in issue #3 where + * you use symlinks to copies of `libyabridge-vst2.so`. + * + * TODO: This should probably be renamed to `find_vst2_plugin()` so we can have + * a separate `find_vst3_plugin()` * * @return The a path to the accompanying VST plugin .dll file. * @throw std::runtime_error If no matching .dll file could be found. @@ -156,7 +159,7 @@ std::vector get_augmented_search_path(); /** * Return a path to this `.so` file. This can be used to find out from where - * this link to or copy of `libyabridge.so` was loaded. + * this link to or copy of `libyabridge-{vst2,vst3}.so` was loaded. */ boost::filesystem::path get_this_file_location(); @@ -180,10 +183,10 @@ std::string join_quoted_strings(std::vector& strings); /** * Load the configuration that belongs to a copy of or symlink to - * `libyabridge.so`. If no configuration file could be found then this will - * return an empty configuration object with default settings. See the docstrong - * on the `Configuration` class for more details on how to choose the config - * file to load. + * `libyabridge-{vst2,vst3}.so`. If no configuration file could be found then + * this will return an empty configuration object with default settings. See the + * docstrong on the `Configuration` class for more details on how to choose the + * config file to load. * * This function will take any optional compile-time features that have not been * enabled into account. diff --git a/src/wine-host/group-host.cpp b/src/wine-host/group-host.cpp index d5fa2aef..33d6c605 100644 --- a/src/wine-host/group-host.cpp +++ b/src/wine-host/group-host.cpp @@ -30,11 +30,11 @@ * This works very similar to the host application defined in * `individual-host.cpp`, but instead of just loading a single plugin this will * act as a daemon that can host multiple 'grouped' plugins. This works by - * allowing the `libyabridge.so` instance to connect this this process over a - * socket to ask this process to host a VST `.dll` file using a provided socket. - * After that initialization step both the regular individual plugin host and - * this group plugin host will function identically on both the plugin and the - * Wine VST host side. + * allowing the `libyabridge-{vst2,vst3}.so` instance to connect this this + * process over a socket to ask this process to host a VST `.dll` file using a + * provided socket. After that initialization step both the regular individual + * plugin host and this group plugin host will function identically on both the + * plugin and the Wine VST host side. * * The explicit calling convention is needed to work around a bug introduced in * Wine 5.7: https://bugs.winehq.org/show_bug.cgi?id=49138 diff --git a/src/wine-host/individual-host.cpp b/src/wine-host/individual-host.cpp index 09874e9e..3beff56b 100644 --- a/src/wine-host/individual-host.cpp +++ b/src/wine-host/individual-host.cpp @@ -26,8 +26,8 @@ /** * This is the default VST host application. It will load the specified VST2 - * plugin, and then connect back to the `libyabridge.so` instance that spawned - * this over the socket. + * plugin, and then connect back to the `libyabridge-{vst2,vst3}.so` instance + * that spawned this over the socket. * * The explicit calling convention is needed to work around a bug introduced in * Wine 5.7: https://bugs.winehq.org/show_bug.cgi?id=49138 diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index b4db96a6..6f993bad 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -49,9 +49,9 @@ pub struct Config { /// The installation method to use. We will default to creating copies since that works /// everywhere. pub method: InstallationMethod, - /// The path to the directory containing `libyabridge.so`. If not set, then yabridgectl will - /// look in `/usr/lib` and `$XDG_DATA_HOME/yabridge` since those are the expected locations for - /// yabridge to be installed in. + /// The path to the directory containing `libyabridge-{vst2,vst3}.so`. If not set, then + /// yabridgectl will look in `/usr/lib` and `$XDG_DATA_HOME/yabridge` since those are the + /// expected locations for yabridge to be installed in. pub yabridge_home: Option, /// Directories to search for Windows VST plugins. We're using an ordered set here out of /// convenience so we can't get duplicates and the config file is always sorted. From 6195caf53e32f6c667af5f823eabcb22b9b9dcf1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 13:58:32 +0100 Subject: [PATCH 005/456] Move src/plugin-bridge to src/bridges/vst2 --- README.md | 2 +- meson.build | 2 +- src/plugin/{plugin-bridge.cpp => bridges/vst2.cpp} | 8 ++++---- src/plugin/{plugin-bridge.h => bridges/vst2.h} | 8 ++++---- src/plugin/plugin.cpp | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) rename src/plugin/{plugin-bridge.cpp => bridges/vst2.cpp} (99%) rename src/plugin/{plugin-bridge.h => bridges/vst2.h} (98%) diff --git a/README.md b/README.md index 6f76cfdd..79ed7bae 100644 --- a/README.md +++ b/README.md @@ -619,7 +619,7 @@ launch winedbg in a seperate detached terminal emulator so it doesn't terminate together with the plugin, and winedbg can be a bit picky about the arguments it accepts. I've already set this up behind a feature flag for use in KDE Plasma. Other desktop environments and window managers will require some slight -modifications in `src/plugin/plugin-bridge.cpp`. To enable this, simply run: +modifications in `src/plugin/host-process.cpp`. To enable this, simply run: ```shell meson configure build --buildtype=debug -Dwith-winedbg=true diff --git a/meson.build b/meson.build index fab572d3..c2621b37 100644 --- a/meson.build +++ b/meson.build @@ -86,7 +86,7 @@ shared_library( 'src/common/utils.cpp', 'src/plugin/host-process.cpp', 'src/plugin/plugin.cpp', - 'src/plugin/plugin-bridge.cpp', + 'src/plugin/bridges/vst2.cpp', 'src/plugin/utils.cpp', version_header, ], diff --git a/src/plugin/plugin-bridge.cpp b/src/plugin/bridges/vst2.cpp similarity index 99% rename from src/plugin/plugin-bridge.cpp rename to src/plugin/bridges/vst2.cpp index 44ca3489..13087311 100644 --- a/src/plugin/plugin-bridge.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -14,15 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "plugin-bridge.h" +#include "vst2.h" // Generated inside of the build directory #include #include -#include "../common/communication.h" -#include "../common/utils.h" -#include "utils.h" +#include "../../common/communication.h" +#include "../../common/utils.h" +#include "../utils.h" namespace bp = boost::process; // I'd rather use std::filesystem instead, but Boost.Process depends on diff --git a/src/plugin/plugin-bridge.h b/src/plugin/bridges/vst2.h similarity index 98% rename from src/plugin/plugin-bridge.h rename to src/plugin/bridges/vst2.h index c9da720f..d46685ba 100644 --- a/src/plugin/plugin-bridge.h +++ b/src/plugin/bridges/vst2.h @@ -23,10 +23,10 @@ #include #include -#include "../common/communication.h" -#include "../common/configuration.h" -#include "../common/logging.h" -#include "host-process.h" +#include "../../common/communication.h" +#include "../../common/configuration.h" +#include "../../common/logging.h" +#include "../host-process.h" /** * This handles the communication between the Linux native VST plugin and the diff --git a/src/plugin/plugin.cpp b/src/plugin/plugin.cpp index ac35d2ca..9060060f 100644 --- a/src/plugin/plugin.cpp +++ b/src/plugin/plugin.cpp @@ -20,7 +20,7 @@ #include #include "../common/logging.h" -#include "plugin-bridge.h" +#include "bridges/vst2.h" #define VST_EXPORT __attribute__((visibility("default"))) From 4291083a464c134e3d1af3977a5f102607c464fc Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 14:03:16 +0100 Subject: [PATCH 006/456] Rename PluginBridge to Vst2PluginBridge --- README.md | 2 -- src/common/communication.h | 2 +- src/common/serialization.h | 2 +- src/plugin/bridges/vst2.cpp | 56 +++++++++++++++++++------------------ src/plugin/bridges/vst2.h | 22 +++++++++------ src/plugin/plugin.cpp | 2 +- 6 files changed, 45 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 79ed7bae..a81d1a29 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,6 @@ highly compatible, while also staying easy to debug and maintain. This branch is still very far removed from being in a usable state. Below is an imcomplete list of things that still have to be done before this can be used: -- Rename `PluginBridge` to `Vst2PluginBridge` and explain that the names are - chosen this way to be easily greppable. - Actually start implementing VST3 support. - Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. diff --git a/src/common/communication.h b/src/common/communication.h index b9332ba3..efcd32e2 100644 --- a/src/common/communication.h +++ b/src/common/communication.h @@ -881,7 +881,7 @@ class Sockets { /** * Generate a unique base directory that can be used as a prefix for all Unix - * domain socket endpoints used in `PluginBridge`/`Vst2Bridge`. This will + * domain socket endpoints used in `Vst2PluginBridge`/`Vst2Bridge`. This will * usually return `/run/user//yabridge--/`. * * Sockets for group hosts are handled separately. See diff --git a/src/common/serialization.h b/src/common/serialization.h index 8f0e9823..03011b3d 100644 --- a/src/common/serialization.h +++ b/src/common/serialization.h @@ -458,7 +458,7 @@ struct Event { * - A (short) string. * - Some binary blob stored as a byte vector. During `effGetChunk` this will * contain some chunk data that should be written to - * `PluginBridge::chunk_data`. + * `Vst2PluginBridge::chunk_data`. * - A specific struct in response to an event such as `audioMasterGetTime` or * `audioMasterIOChanged`. * - An X11 window pointer for the editor window. diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index 13087311..d1518130 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -41,11 +41,11 @@ float get_parameter_proxy(AEffect*, int); * is sadly needed as a workaround to avoid using globals since we need free * function pointers to interface with the VST C API. */ -PluginBridge& get_bridge_instance(const AEffect& plugin) { - return *static_cast(plugin.ptr3); +Vst2PluginBridge& get_bridge_instance(const AEffect& plugin) { + return *static_cast(plugin.ptr3); } -PluginBridge::PluginBridge(audioMasterCallback host_callback) +Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) : config(load_config_for(get_this_file_location())), vst_plugin_path(find_vst_plugin()), // All the fields should be zero initialized because @@ -297,8 +297,8 @@ class DispatchDataConverter : DefaultDataConverter { } break; case effGetChunk: { // Write the chunk data to some publically accessible place in - // `PluginBridge` and write a pointer to that struct to the data - // pointer + // `Vst2PluginBridge` and write a pointer to that struct to the + // data pointer const auto buffer = std::get(response.payload).buffer; chunk.assign(buffer.begin(), buffer.end()); @@ -391,12 +391,12 @@ class DispatchDataConverter : DefaultDataConverter { VstRect& rect; }; -intptr_t PluginBridge::dispatch(AEffect* /*plugin*/, - int opcode, - int index, - intptr_t value, - void* data, - float option) { +intptr_t Vst2PluginBridge::dispatch(AEffect* /*plugin*/, + int opcode, + int index, + intptr_t value, + void* data, + float option) { // HACK: Ardour 5.X has a bug in its VST implementation where it calls the // plugin's dispatcher before the plugin has even finished // initializing. This has been fixed back in 2018, but there has not @@ -483,7 +483,7 @@ intptr_t PluginBridge::dispatch(AEffect* /*plugin*/, } template -void PluginBridge::do_process(T** inputs, T** outputs, int sample_frames) { +void Vst2PluginBridge::do_process(T** inputs, T** outputs, int sample_frames) { // The inputs and outputs arrays should be `[num_inputs][sample_frames]` and // `[num_outputs][sample_frames]` floats large respectfully. std::vector> input_buffers(plugin.numInputs, @@ -541,10 +541,10 @@ void PluginBridge::do_process(T** inputs, T** outputs, int sample_frames) { incoming_midi_events.clear(); } -void PluginBridge::process(AEffect* /*plugin*/, - float** inputs, - float** outputs, - int sample_frames) { +void Vst2PluginBridge::process(AEffect* /*plugin*/, + float** inputs, + float** outputs, + int sample_frames) { // Technically either `Vst2PluginBridge::process()` or // `Vst2PluginBridge::process_replacing()` could actually call the other // function on the plugin depending on what the plugin supports. @@ -553,25 +553,25 @@ void PluginBridge::process(AEffect* /*plugin*/, logger.log_trace(" process() :: end"); } -void PluginBridge::process_replacing(AEffect* /*plugin*/, - float** inputs, - float** outputs, - int sample_frames) { +void Vst2PluginBridge::process_replacing(AEffect* /*plugin*/, + float** inputs, + float** outputs, + int sample_frames) { logger.log_trace(">> processReplacing() :: start"); do_process(inputs, outputs, sample_frames); logger.log_trace(" processReplacing() :: end"); } -void PluginBridge::process_double_replacing(AEffect* /*plugin*/, - double** inputs, - double** outputs, - int sample_frames) { +void Vst2PluginBridge::process_double_replacing(AEffect* /*plugin*/, + double** inputs, + double** outputs, + int sample_frames) { logger.log_trace(">> processDoubleReplacing() :: start"); do_process(inputs, outputs, sample_frames); logger.log_trace(" processDoubleReplacing() :: end"); } -float PluginBridge::get_parameter(AEffect* /*plugin*/, int index) { +float Vst2PluginBridge::get_parameter(AEffect* /*plugin*/, int index) { logger.log_get_parameter(index); const Parameter request{index, std::nullopt}; @@ -592,7 +592,9 @@ float PluginBridge::get_parameter(AEffect* /*plugin*/, int index) { return *response.value; } -void PluginBridge::set_parameter(AEffect* /*plugin*/, int index, float value) { +void Vst2PluginBridge::set_parameter(AEffect* /*plugin*/, + int index, + float value) { logger.log_set_parameter(index, value); const Parameter request{index, value}; @@ -612,7 +614,7 @@ void PluginBridge::set_parameter(AEffect* /*plugin*/, int index, float value) { assert(!response.value); } -void PluginBridge::log_init_message() { +void Vst2PluginBridge::log_init_message() { std::stringstream init_msg; init_msg << "Initializing yabridge version " << yabridge_git_version diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index d46685ba..f92bb27a 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -29,11 +29,15 @@ #include "../host-process.h" /** - * This handles the communication between the Linux native VST plugin and the + * This handles the communication between the Linux native VST2 plugin and the * Wine VST host. The functions below should be used as callback functions in an * `AEffect` object. + * + * The naming scheme of all of these 'bridge' classes is `{,Plugin}Bridge` + * for greppability reasons. The `Plugin` infix is added on the native plugin + * side. */ -class PluginBridge { +class Vst2PluginBridge { public: /** * Initializes the Wine VST bridge. This sets up the sockets for event @@ -45,7 +49,7 @@ class PluginBridge { * @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. */ - PluginBridge(audioMasterCallback host_callback); + Vst2PluginBridge(audioMasterCallback host_callback); // The four below functions are the handlers from the VST2 API. They are // called through proxy functions in `plugin.cpp`. @@ -83,10 +87,10 @@ class PluginBridge { float** outputs, int sample_frames); /** - * The same as `PluginBridge::process_replacing`, but for double precision - * audio. Support for this on both the plugin and host side is pretty rare, - * but REAPER supports it. This reuses the same infrastructure as - * `process_replacing` is using since the host will only call one or the + * The same as `Vst2PluginBridge::process_replacing`, but for double + * precision audio. Support for this on both the plugin and host side is + * pretty rare, but REAPER supports it. This reuses the same infrastructure + * as `process_replacing` is using since the host will only call one or the * other. */ void process_double_replacing(AEffect* plugin, @@ -108,8 +112,8 @@ class PluginBridge { * values in `outputs`. No host will use this last behaviour anymore, but * it's part of the VST2.4 spec so we have to support it. * - * @see PluginBridge::process_replacing - * @see PluginBridge::process_double_replacing + * @see Vst2PluginBridge::process_replacing + * @see Vst2PluginBridge::process_double_replacing */ template void do_process(T** inputs, T** outputs, int sample_frames); diff --git a/src/plugin/plugin.cpp b/src/plugin/plugin.cpp index 9060060f..59284fc4 100644 --- a/src/plugin/plugin.cpp +++ b/src/plugin/plugin.cpp @@ -45,7 +45,7 @@ extern "C" VST_EXPORT AEffect* VSTPluginMain( // 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. - PluginBridge* bridge = new PluginBridge(host_callback); + Vst2PluginBridge* bridge = new Vst2PluginBridge(host_callback); return &bridge->plugin; } catch (const std::exception& error) { From 18571bca5ddf6d01950028fe31b5c155bad77d75 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 15:16:44 +0100 Subject: [PATCH 007/456] Add a dependency for the VST3 SDK --- README.md | 15 +++++++++++++++ meson.build | 10 ++++++++++ meson_options.txt | 7 +++++++ subprojects/.gitignore | 1 + subprojects/vst3.wrap | 7 +++++++ 5 files changed, 40 insertions(+) create mode 100644 subprojects/vst3.wrap diff --git a/README.md b/README.md index a81d1a29..59db5c9c 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ imcomplete list of things that still have to be done before this can be used: - Mention that this update will break all existing symlinks and that the old `libyabridge.so` file should be removed when upgrading. - Update all the AUR packages. +- Add CMake to the AUR package dependencies and to our Docker images ![yabridge screenshot](https://raw.githubusercontent.com/robbert-vdh/yabridge/master/screenshot.png) @@ -44,6 +45,7 @@ imcomplete list of things that still have to be done before this can be used: - [Performance tuning](#performance-tuning) - [Runtime dependencies and known issues](#runtime-dependencies-and-known-issues) - [Building](#building) + - [Building without VST3 support](#building-without-vst3-support) - [32-bit bitbridge](#32-bit-bitbridge) - [Debugging](#debugging) - [Attaching a debugger](#attaching-a-debugger) @@ -515,6 +517,7 @@ the following dependencies: - A Wine installation with `winegcc` and the development headers. The latest commits contain a workaround for a winelib [compilation issue](https://bugs.winehq.org/show_bug.cgi?id=49138) with Wine 5.7+. +- CMake for VST3 support[\*](#building-without-vst3-support) - Boost version 1.66 or higher[\*](#building-ubuntu-18.04) - libxcb @@ -545,6 +548,18 @@ After you've finished building you can follow the instructions under the would need to be installed to compile on Ubuntu 18.04. +### Building without VST3 support + +As mentioned above, building the VST3 SDK requires CMake. We might be able to +easily replace the default build definitions for the SDK with a simple +`meson.build` file in the future, but for now if you wish to build yabridge +without VST3 support then you can disable it as follows: + +```shell +# Disable VST3 support, removing the dependency on CMake +meson configure build -Dwith-vst3=false +``` + ### 32-bit bitbridge It is also possible to compile a host application for yabridge that's compatible diff --git a/meson.build b/meson.build index c2621b37..86c50b5a 100644 --- a/meson.build +++ b/meson.build @@ -37,6 +37,7 @@ compiler_options = [ with_bitbridge = get_option('with-bitbridge') with_static_boost = get_option('with-static-boost') with_winedbg = get_option('with-winedbg') +with_vst3 = get_option('with-vst3') if with_bitbridge compiler_options += '-DWITH_BITBRIDGE' @@ -68,6 +69,15 @@ tomlplusplus_dep = subproject('tomlplusplus', version : '2.1.0').get_variable('t # The built in threads dependency does not know how to handle winegcc wine_threads_dep = declare_dependency(link_args : '-lpthread') xcb_dep = dependency('xcb') +if with_vst3 + cmake = import('cmake') + vst3_sdk = cmake.subproject('vst3') + + # TODO: Assert version '3.7.1', somehow + # TODO: vst3_xxx_dep = vst3_sdk.dependency('xxx') +else + message('VST3 support has been disabled') +endif include_dir = include_directories('src/include') diff --git a/meson_options.txt b/meson_options.txt index bca9126c..319d0d54 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -12,6 +12,13 @@ option( description : 'Enable static linking for Boost. Needed when distributing the binaries to other systems.' ) +option( + 'with-vst3', + type : 'boolean', + value : true, + description : 'Whether to build the VST3 version of yabridge. This requires CMake to be installed.' +) + option( 'with-winedbg', type : 'boolean', diff --git a/subprojects/.gitignore b/subprojects/.gitignore index 21ced76b..e557da5e 100644 --- a/subprojects/.gitignore +++ b/subprojects/.gitignore @@ -2,3 +2,4 @@ # The above pattern doesn't match submodules /tomlplusplus +/vst3 diff --git a/subprojects/vst3.wrap b/subprojects/vst3.wrap new file mode 100644 index 00000000..aed03c6a --- /dev/null +++ b/subprojects/vst3.wrap @@ -0,0 +1,7 @@ +[wrap-git] +url = https://github.com/robbert-vdh/vst3sdk.git +# This is VST3 SDK v3.7.1_build_50 with the documentation and VSTGUI submodules +# removed +revision = 2a1a230d45766532360a2f432083f0795f2b0d93 +clone-recursive = true +depth = 1 From c4dd0b30a4a5d732d660a9bcb2bc6326cdf82dec Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 15:22:46 +0100 Subject: [PATCH 008/456] Mention the new CMake requirement in the changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78db3340..ec06be0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ Versioning](https://semver.org/spec/v2.0.0.html). resutling in very bad performance. See the [compatibility options](https://github.com/robbert-vdh/yabridge#compatibility-options) section of the readme for more information on how to enable this. +- Added the `with-vst3` option to control whether yabridge should be built with + VST3 support. This is enabled by default. ### Changed @@ -27,6 +29,9 @@ Versioning](https://semver.org/spec/v2.0.0.html). be logged. This makes recognizing misbheaving plugins a bit easier. - Symbols in all `libyabridge.so` and all Winelib `.so` files are now hidden by default. +- Building from source now requires CMake since this is required to build the + VST3 SDK. You can disable VST3 support to remove this requirement by setting + the `with-vst3` meson build option to `false`. ## [2.1.0] - 2020-11-20 From af9917b9bed2ebd8f4ebfe87fcc3e970b6e06ee2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 16:01:16 +0100 Subject: [PATCH 009/456] Disable the VST3 examples --- meson.build | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 86c50b5a..b5b2d860 100644 --- a/meson.build +++ b/meson.build @@ -71,9 +71,16 @@ wine_threads_dep = declare_dependency(link_args : '-lpthread') xcb_dep = dependency('xcb') if with_vst3 cmake = import('cmake') - vst3_sdk = cmake.subproject('vst3') + vst3_sdk_options = cmake.subproject_options() + vst3_sdk_options.add_cmake_defines({ + 'SMTG_ADD_VST3_HOSTING_SAMPLES': 'OFF', + 'SMTG_ADD_VST3_PLUGINS_SAMPLES': 'OFF', + 'SMTG_ADD_VSTGUI': 'OFF', + }) + vst3_sdk = cmake.subproject('vst3', options : vst3_sdk_options) # TODO: Assert version '3.7.1', somehow + # TODO: Check C++ standard? # TODO: vst3_xxx_dep = vst3_sdk.dependency('xxx') else message('VST3 support has been disabled') From d168fbcd256d5d85607485deb3c47553a717c90f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 16:16:50 +0100 Subject: [PATCH 010/456] Add VST3 SDK to the list of included dependencies --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 59db5c9c..c2ecfe7d 100644 --- a/README.md +++ b/README.md @@ -526,6 +526,7 @@ The following dependencies are included in the repository as a Meson wrap: - bitsery - function2 - tomlplusplus +- The [VST3 SDK](https://github.com/robbert-vdh/vst3sdk) The project can then be compiled as follows: From f9775dced3fb5f424d534a0ab690b0d9824cd516 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 16:17:56 +0100 Subject: [PATCH 011/456] Add links to all other wrapped dependencies --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c2ecfe7d..09b66402 100644 --- a/README.md +++ b/README.md @@ -523,9 +523,9 @@ the following dependencies: The following dependencies are included in the repository as a Meson wrap: -- bitsery -- function2 -- tomlplusplus +- [bitsery](https://github.com/fraillt/bitsery) +- [function2](https://github.com/Naios/function2) +- [tomlplusplus](https://github.com/marzer/tomlplusplus) - The [VST3 SDK](https://github.com/robbert-vdh/vst3sdk) The project can then be compiled as follows: From 9ece1b916e300ede479d0d423d6949564e684b5e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 17:19:29 +0100 Subject: [PATCH 012/456] Rename plugin.cpp to vst2-plugin.cpp --- meson.build | 2 +- src/plugin/{plugin.cpp => vst2-plugin.cpp} | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) rename src/plugin/{plugin.cpp => vst2-plugin.cpp} (87%) diff --git a/meson.build b/meson.build index b5b2d860..5fec0309 100644 --- a/meson.build +++ b/meson.build @@ -102,7 +102,7 @@ shared_library( 'src/common/communication.cpp', 'src/common/utils.cpp', 'src/plugin/host-process.cpp', - 'src/plugin/plugin.cpp', + 'src/plugin/vst2-plugin.cpp', 'src/plugin/bridges/vst2.cpp', 'src/plugin/utils.cpp', version_header, diff --git a/src/plugin/plugin.cpp b/src/plugin/vst2-plugin.cpp similarity index 87% rename from src/plugin/plugin.cpp rename to src/plugin/vst2-plugin.cpp index 59284fc4..75a860f2 100644 --- a/src/plugin/plugin.cpp +++ b/src/plugin/vst2-plugin.cpp @@ -24,20 +24,20 @@ #define VST_EXPORT __attribute__((visibility("default"))) -// The main entry point for VST plugins should be called `VSTPluginMain``. The +// The main entry point for VST2 plugins should be called `VSTPluginMain``. The // other one exist for legacy reasons since some old hosts might still use them. // There's also another possible legacy entry point just called `main`, but GCC // will refuse to compile a function called `main` that's not a regular C++ main -// function +// function. /** - * 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 + * The main VST2 plugin entry point. We first set up a bridge that connects to a + * Wine process that hosts the Windows VST2 plugin. We then create and return a * VST plugin struct that acts as a passthrough to the bridge. * * To keep this somewhat contained this is the only place where we're doing * manual memory management. Clean up is done when we receive the `effClose` - * opcode from the VST host (i.e. opcode 1).` + * opcode from the VST2 host (i.e. opcode 1).` */ extern "C" VST_EXPORT AEffect* VSTPluginMain( audioMasterCallback host_callback) { From 4be7af245102cc7f79c6d3e68c9ab3cfd6533ad6 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 18:05:29 +0100 Subject: [PATCH 013/456] Add a TODO for replacing the cmake subproject This is not going to work due to the linked issue with Meson. I didn't want to reinvent the wheel by writing a meson build file by hand, but we'll just have to. --- meson.build | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/meson.build b/meson.build index 5fec0309..ef158b98 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project( 'yabridge', 'cpp', version : '2.1.0', - default_options : ['warning_level=3', 'cpp_std=c++2a', 'build.cpp_std=c++2a'] + default_options : ['warning_level=3', 'cpp_std=c++2a', 'build.cpp_std=c++2a'], ) # Meson does not let us set a default cross compiler, which makes sense, but it @@ -60,7 +60,7 @@ boost_filesystem_dep = dependency( 'boost', version : '>=1.66', modules : ['filesystem'], - static : with_static_boost + static : with_static_boost, ) bitsery_dep = subproject('bitsery', version : '5.2.0').get_variable('bitsery_dep') function2_dep = subproject('function2', version : '4.1.0').get_variable('function2_dep') @@ -70,6 +70,8 @@ tomlplusplus_dep = subproject('tomlplusplus', version : '2.1.0').get_variable('t wine_threads_dep = declare_dependency(link_args : '-lpthread') xcb_dep = dependency('xcb') if with_vst3 + # TODO: Assert version '3.7.1', somehow + cmake = import('cmake') vst3_sdk_options = cmake.subproject_options() vst3_sdk_options.add_cmake_defines({ @@ -78,10 +80,11 @@ if with_vst3 'SMTG_ADD_VSTGUI': 'OFF', }) vst3_sdk = cmake.subproject('vst3', options : vst3_sdk_options) - - # TODO: Assert version '3.7.1', somehow - # TODO: Check C++ standard? - # TODO: vst3_xxx_dep = vst3_sdk.dependency('xxx') + # TODO: This is not going to work due to + # https://github.com/mesonbuild/meson/issues/8043 + # We'll have to replace this with a Meson subproject for the time being + # and remove all references to cmake + # vst3_pluginterfaces_dep = vst3_sdk.dependency('pluginterfaces', native : true) else message('VST3 support has been disabled') endif @@ -114,7 +117,7 @@ shared_library( boost_filesystem_dep, bitsery_dep, threads_dep, - tomlplusplus_dep + tomlplusplus_dep, ], cpp_args : compiler_options, link_args : ['-ldl'] @@ -151,10 +154,10 @@ executable( function2_dep, tomlplusplus_dep, wine_threads_dep, - xcb_dep + xcb_dep, ], cpp_args : compiler_options + ['-m64'], - link_args : ['-m64'] + link_args : ['-m64'], ) executable( @@ -169,10 +172,10 @@ executable( function2_dep, tomlplusplus_dep, wine_threads_dep, - xcb_dep + xcb_dep, ], cpp_args : compiler_options + ['-m64'], - link_args : ['-m64'] + link_args : ['-m64'], ) if with_bitbridge @@ -215,10 +218,10 @@ if with_bitbridge function2_dep, tomlplusplus_dep, wine_threads_dep, - xcb_dep + xcb_dep, ], cpp_args : compiler_options + ['-m32'], - link_args : ['-m32'] + link_args : ['-m32'], ) executable( @@ -233,9 +236,9 @@ if with_bitbridge function2_dep, tomlplusplus_dep, wine_threads_dep, - xcb_dep + xcb_dep, ], cpp_args : compiler_options + ['-m32'], - link_args : ['-m32'] + link_args : ['-m32'], ) endif From c6b58c1a643a59c00d0ada8b1f548dc541c4cd95 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 21:51:00 +0100 Subject: [PATCH 014/456] Don't use CMake for the VST3 SDK This can't work yet. --- CHANGELOG.md | 3 --- README.md | 13 ------------- meson.build | 16 +--------------- meson_options.txt | 7 +++++++ 4 files changed, 8 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec06be0e..55695d37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,9 +29,6 @@ Versioning](https://semver.org/spec/v2.0.0.html). be logged. This makes recognizing misbheaving plugins a bit easier. - Symbols in all `libyabridge.so` and all Winelib `.so` files are now hidden by default. -- Building from source now requires CMake since this is required to build the - VST3 SDK. You can disable VST3 support to remove this requirement by setting - the `with-vst3` meson build option to `false`. ## [2.1.0] - 2020-11-20 diff --git a/README.md b/README.md index 09b66402..faf8105e 100644 --- a/README.md +++ b/README.md @@ -517,7 +517,6 @@ the following dependencies: - A Wine installation with `winegcc` and the development headers. The latest commits contain a workaround for a winelib [compilation issue](https://bugs.winehq.org/show_bug.cgi?id=49138) with Wine 5.7+. -- CMake for VST3 support[\*](#building-without-vst3-support) - Boost version 1.66 or higher[\*](#building-ubuntu-18.04) - libxcb @@ -549,18 +548,6 @@ After you've finished building you can follow the instructions under the would need to be installed to compile on Ubuntu 18.04. -### Building without VST3 support - -As mentioned above, building the VST3 SDK requires CMake. We might be able to -easily replace the default build definitions for the SDK with a simple -`meson.build` file in the future, but for now if you wish to build yabridge -without VST3 support then you can disable it as follows: - -```shell -# Disable VST3 support, removing the dependency on CMake -meson configure build -Dwith-vst3=false -``` - ### 32-bit bitbridge It is also possible to compile a host application for yabridge that's compatible diff --git a/meson.build b/meson.build index ef158b98..1b881386 100644 --- a/meson.build +++ b/meson.build @@ -70,21 +70,7 @@ tomlplusplus_dep = subproject('tomlplusplus', version : '2.1.0').get_variable('t wine_threads_dep = declare_dependency(link_args : '-lpthread') xcb_dep = dependency('xcb') if with_vst3 - # TODO: Assert version '3.7.1', somehow - - cmake = import('cmake') - vst3_sdk_options = cmake.subproject_options() - vst3_sdk_options.add_cmake_defines({ - 'SMTG_ADD_VST3_HOSTING_SAMPLES': 'OFF', - 'SMTG_ADD_VST3_PLUGINS_SAMPLES': 'OFF', - 'SMTG_ADD_VSTGUI': 'OFF', - }) - vst3_sdk = cmake.subproject('vst3', options : vst3_sdk_options) - # TODO: This is not going to work due to - # https://github.com/mesonbuild/meson/issues/8043 - # We'll have to replace this with a Meson subproject for the time being - # and remove all references to cmake - # vst3_pluginterfaces_dep = vst3_sdk.dependency('pluginterfaces', native : true) + vst3_pluginterfaces_dep = subproject('vst3', version : '3.7.1').get_variable('pluginterfaces_dep') else message('VST3 support has been disabled') endif diff --git a/meson_options.txt b/meson_options.txt index 319d0d54..c42a9a40 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -12,6 +12,13 @@ option( description : 'Enable static linking for Boost. Needed when distributing the binaries to other systems.' ) +# NOTE: Right now this does not actually require CMake. Because of a limitation +# in Meson we can't use the original build definitions just yet. For the +# time being we have just written our own meson.build to replace the SDK's +# CMake project. Once Meson allows mixing native and cross compiled CMake +# subprojects the commit that added this notice can be reverted. +# +# https://github.com/mesonbuild/meson/issues/8043 option( 'with-vst3', type : 'boolean', From 04dacc0a400ba44136b60e6fdaa0de04323bf383 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 Nov 2020 23:35:43 +0100 Subject: [PATCH 015/456] Add a dependency for the VST3 pluginterfaces --- meson.build | 20 +++++++++++++++++++- subprojects/vst3.wrap | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 1b881386..9cabf1bc 100644 --- a/meson.build +++ b/meson.build @@ -69,8 +69,26 @@ tomlplusplus_dep = subproject('tomlplusplus', version : '2.1.0').get_variable('t # The built in threads dependency does not know how to handle winegcc wine_threads_dep = declare_dependency(link_args : '-lpthread') xcb_dep = dependency('xcb') + if with_vst3 - vst3_pluginterfaces_dep = subproject('vst3', version : '3.7.1').get_variable('pluginterfaces_dep') + # Meson does not allow mixing native and non native dependencies from + # subprojects. The only workaround is to only define the necessary variables + # there, and to then assemble the dependencies here ourselves. + vst3 = subproject('vst3', version : '3.7.1') + vst3_compiler_options = vst3.get_variable('compiler_options') + vst3_include_dir = vst3.get_variable('include_dir') + vst3_pluginterfaces_native = static_library( + 'pluginterfaces_native', + vst3.get_variable('pluginterfaces_sources'), + cpp_args : vst3_compiler_options, + include_directories : vst3_include_dir, + override_options : ['warning_level=0'], + native : true, + ) + vst3_pluginterfaces_native_dep = declare_dependency( + link_with : vst3_pluginterfaces_native, + include_directories : vst3_include_dir, + ) else message('VST3 support has been disabled') endif diff --git a/subprojects/vst3.wrap b/subprojects/vst3.wrap index aed03c6a..408cdb48 100644 --- a/subprojects/vst3.wrap +++ b/subprojects/vst3.wrap @@ -2,6 +2,6 @@ url = https://github.com/robbert-vdh/vst3sdk.git # This is VST3 SDK v3.7.1_build_50 with the documentation and VSTGUI submodules # removed -revision = 2a1a230d45766532360a2f432083f0795f2b0d93 +revision = 26374423ae275c01a774e6a1db44912090a66796 clone-recursive = true depth = 1 From 6317ca145528d3a90f74b193f6fc42a959660e39 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 29 Nov 2020 12:51:26 +0100 Subject: [PATCH 016/456] Bump the VST3 dependency to one that uses -isystem Every included file from the SDK would emit a ton of warnings otherwise. They should really be fixing this on the VST3 side, but that's probably not going to happen. --- subprojects/vst3.wrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/vst3.wrap b/subprojects/vst3.wrap index 408cdb48..b1a24457 100644 --- a/subprojects/vst3.wrap +++ b/subprojects/vst3.wrap @@ -2,6 +2,6 @@ url = https://github.com/robbert-vdh/vst3sdk.git # This is VST3 SDK v3.7.1_build_50 with the documentation and VSTGUI submodules # removed -revision = 26374423ae275c01a774e6a1db44912090a66796 +revision = 410f4e01d311f1608a193594a65b98db86758b95 clone-recursive = true depth = 1 From 9c8b543d5d882db44a906f5ea129d1601e0bc397 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 29 Nov 2020 13:29:01 +0100 Subject: [PATCH 017/456] Split serialization.h into common and VST2 parts --- meson.build | 4 +- src/common/logging.h | 3 +- src/common/serialization/common.h | 90 +++++++++++++++++++ .../vst2.cpp} | 2 +- .../{serialization.h => serialization/vst2.h} | 73 +-------------- 5 files changed, 97 insertions(+), 75 deletions(-) create mode 100644 src/common/serialization/common.h rename src/common/{serialization.cpp => serialization/vst2.cpp} (99%) rename src/common/{serialization.h => serialization/vst2.h} (89%) diff --git a/meson.build b/meson.build index 9cabf1bc..62c857a2 100644 --- a/meson.build +++ b/meson.build @@ -105,7 +105,7 @@ shared_library( [ 'src/common/configuration.cpp', 'src/common/logging.cpp', - 'src/common/serialization.cpp', + 'src/common/serialization/vst2.cpp', 'src/common/communication.cpp', 'src/common/utils.cpp', 'src/plugin/host-process.cpp', @@ -130,7 +130,7 @@ shared_library( host_sources = [ 'src/common/configuration.cpp', 'src/common/logging.cpp', - 'src/common/serialization.cpp', + 'src/common/serialization/vst2.cpp', 'src/common/communication.cpp', 'src/common/utils.cpp', 'src/wine-host/bridges/vst2.cpp', diff --git a/src/common/logging.h b/src/common/logging.h index e01075fd..ef046f94 100644 --- a/src/common/logging.h +++ b/src/common/logging.h @@ -16,10 +16,11 @@ #pragma once +#include #include #include -#include "serialization.h" +#include "serialization/vst2.h" /** * Super basic logging facility meant for debugging malfunctioning VST diff --git a/src/common/serialization/common.h b/src/common/serialization/common.h new file mode 100644 index 00000000..7c9f60bc --- /dev/null +++ b/src/common/serialization/common.h @@ -0,0 +1,90 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include +#include +#include + +// The plugin should always be compiled to a 64-bit version, but the host +// application can also be 32-bit to allow using 32-bit legacy Windows VST in a +// modern Linux VST host. Because of this we have to make sure to always use +// 64-bit integers in places where we would otherwise use `size_t` and +// `intptr_t`. Otherwise the binary serialization would break. The 64 <-> 32 bit +// conversion for the 32-bit host application won't cause any issues for us +// since we can't directly pass pointers between the plugin and the host anyway. + +#ifndef __WINE__ +// Sanity check for the plugin, both the 64 and 32 bit hosts should follow these +// conventions +static_assert(std::is_same_v); +static_assert(std::is_same_v); +#endif +using native_size_t = uint64_t; +using native_intptr_t = int64_t; + +// The cannonical overloading template for `std::visitor`, not sure why this +// isn't part of the standard library +template +struct overload : Ts... { + using Ts::operator()...; +}; +template +overload(Ts...) -> overload; + +/** + * An object containing the startup options for hosting a plugin in a plugin + * group process. These are the exact same options that would have been passed + * to `yabridge-host.exe` were the plugin to be hosted individually. + */ +struct GroupRequest { + std::string plugin_path; + std::string endpoint_base_dir; + + template + void serialize(S& s) { + s.text1b(plugin_path, 4096); + s.text1b(endpoint_base_dir, 4096); + } +}; + +template <> +struct std::hash { + std::size_t operator()(GroupRequest const& params) const noexcept { + std::hash hasher{}; + + return hasher(params.plugin_path) ^ + (hasher(params.endpoint_base_dir) << 1); + } +}; + +/** + * The response sent back after the group host process receives a `GroupRequest` + * object. This only holds the group process's PID because we need to know if + * the group process crashes while it is initializing the plugin to prevent us + * from waiting indefinitely for the socket to be connected to. + */ +struct GroupResponse { + pid_t pid; + + template + void serialize(S& s) { + s.value4b(pid); + } +}; diff --git a/src/common/serialization.cpp b/src/common/serialization/vst2.cpp similarity index 99% rename from src/common/serialization.cpp rename to src/common/serialization/vst2.cpp index a764a7e7..f0219559 100644 --- a/src/common/serialization.cpp +++ b/src/common/serialization/vst2.cpp @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "serialization.h" +#include "vst2.h" DynamicVstEvents::DynamicVstEvents(const VstEvents& c_events) : events(c_events.numEvents) { diff --git a/src/common/serialization.h b/src/common/serialization/vst2.h similarity index 89% rename from src/common/serialization.h rename to src/common/serialization/vst2.h index 03011b3d..ada95336 100644 --- a/src/common/serialization.h +++ b/src/common/serialization/vst2.h @@ -16,18 +16,16 @@ #pragma once -#include -#include #include #include #include -#include #include #include #include -#include "vst24.h" +#include "../vst24.h" +#include "common.h" // These constants are limits used by bitsery @@ -60,32 +58,6 @@ constexpr size_t max_midi_events = max_buffer_size / sizeof(size_t); */ constexpr size_t binary_buffer_size = 50 << 20; -// The plugin should always be compiled to a 64-bit version, but the host -// application can also be 32-bit to allow using 32-bit legacy Windows VST in a -// modern Linux VST host. Because of this we have to make sure to always use -// 64-bit integers in places where we would otherwise use `size_t` and -// `intptr_t`. Otherwise the binary serialization would break. The 64 <-> 32 bit -// conversion for the 32-bit host application won't cause any issues for us -// since we can't directly pass pointers between the plugin and the host anyway. - -#ifndef __WINE__ -// Sanity check for the plugin, both the 64 and 32 bit hosts should follow these -// conventions -static_assert(std::is_same_v); -static_assert(std::is_same_v); -#endif -using native_size_t = uint64_t; -using native_intptr_t = int64_t; - -// The cannonical overloading template for `std::visitor`, not sure why this -// isn't part of the standard library -template -struct overload : Ts... { - using Ts::operator()...; -}; -template -overload(Ts...) -> overload; - /** * Update an `AEffect` object, copying values from `updated_plugin` to `plugin`. * This will copy all flags and regular values, leaving all pointers in `plugin` @@ -601,44 +573,3 @@ struct AudioBuffers { s.value4b(sample_frames); } }; - -/** - * An object containing the startup options for hosting a plugin in a plugin - * group process. These are the exact same options that would have been passed - * to `yabridge-host.exe` were the plugin to be hosted individually. - */ -struct GroupRequest { - std::string plugin_path; - std::string endpoint_base_dir; - - template - void serialize(S& s) { - s.text1b(plugin_path, 4096); - s.text1b(endpoint_base_dir, 4096); - } -}; - -template <> -struct std::hash { - std::size_t operator()(GroupRequest const& params) const noexcept { - std::hash hasher{}; - - return hasher(params.plugin_path) ^ - (hasher(params.endpoint_base_dir) << 1); - } -}; - -/** - * The response sent back after the group host process receives a `GroupRequest` - * object. This only holds the group process's PID because we need to know if - * the group process crashes while it is initializing the plugin to prevent us - * from waiting indefinitely for the socket to be connected to. - */ -struct GroupResponse { - pid_t pid; - - template - void serialize(S& s) { - s.value4b(pid); - } -}; From 28fe0ecd6096569b07eb4d183e245c2c26b91201 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 29 Nov 2020 13:30:26 +0100 Subject: [PATCH 018/456] Add [[maybe_unused]] to our constexpr constants --- src/common/vst24.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/common/vst24.h b/src/common/vst24.h index 3fb38394..1ed5ed44 100644 --- a/src/common/vst24.h +++ b/src/common/vst24.h @@ -28,8 +28,8 @@ * parameter. Finally the plugin returns a string containing the input or output * name. */ -constexpr int effGetInputProperties = 33; -constexpr int effGetOutputProperties = 34; +[[maybe_unused]] constexpr int effGetInputProperties = 33; +[[maybe_unused]] constexpr int effGetOutputProperties = 34; /** * Found on @@ -37,15 +37,15 @@ constexpr int effGetOutputProperties = 34; * Used to assign names to MIDI keys, for some reason uses the `VstMidiKeyName` * struct defined below rather than a simple string. */ -constexpr int effGetMidiKeyName = 66; +[[maybe_unused]] constexpr int effGetMidiKeyName = 66; /** * Events used to tell a plugin to use a specific speaker arrangement (is this * used outside of things like Dolby Atmos?), or to query its preferred speaker * arrangement. Found on the same list as above. */ -constexpr int effSetSpeakerArrangement = 42; -constexpr int effGetSpeakerArrangement = 69; +[[maybe_unused]] constexpr int effSetSpeakerArrangement = 42; +[[maybe_unused]] constexpr int effGetSpeakerArrangement = 69; /** * The struct that's being passed through the data parameter during the From 46bc0301af8c665393dcae0fb26fc279e0f52021 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 29 Nov 2020 13:43:15 +0100 Subject: [PATCH 019/456] Move communication.h to communication/vst2.h --- meson.build | 12 ++++++------ .../{communication.cpp => communication/vst2.cpp} | 4 ++-- src/common/{communication.h => communication/vst2.h} | 2 +- src/common/logging.h | 2 ++ src/plugin/bridges/vst2.cpp | 2 +- src/plugin/bridges/vst2.h | 2 +- src/plugin/host-process.cpp | 2 +- src/plugin/host-process.h | 2 +- src/wine-host/bridges/group.cpp | 4 +++- src/wine-host/bridges/vst2.cpp | 2 +- src/wine-host/bridges/vst2.h | 2 +- 11 files changed, 20 insertions(+), 16 deletions(-) rename src/common/{communication.cpp => communication/vst2.cpp} (98%) rename src/common/{communication.h => communication/vst2.h} (99%) diff --git a/meson.build b/meson.build index 62c857a2..b7d408b9 100644 --- a/meson.build +++ b/meson.build @@ -103,15 +103,15 @@ include_dir = include_directories('src/include') shared_library( 'yabridge-vst2', [ + 'src/common/communication/vst2.cpp', + 'src/common/serialization/vst2.cpp', 'src/common/configuration.cpp', 'src/common/logging.cpp', - 'src/common/serialization/vst2.cpp', - 'src/common/communication.cpp', 'src/common/utils.cpp', - 'src/plugin/host-process.cpp', - 'src/plugin/vst2-plugin.cpp', 'src/plugin/bridges/vst2.cpp', + 'src/plugin/host-process.cpp', 'src/plugin/utils.cpp', + 'src/plugin/vst2-plugin.cpp', version_header, ], native : true, @@ -128,10 +128,10 @@ shared_library( ) host_sources = [ + 'src/common/communication/vst2.cpp', + 'src/common/serialization/vst2.cpp', 'src/common/configuration.cpp', 'src/common/logging.cpp', - 'src/common/serialization/vst2.cpp', - 'src/common/communication.cpp', 'src/common/utils.cpp', 'src/wine-host/bridges/vst2.cpp', 'src/wine-host/editor.cpp', diff --git a/src/common/communication.cpp b/src/common/communication/vst2.cpp similarity index 98% rename from src/common/communication.cpp rename to src/common/communication/vst2.cpp index 1ad60440..845507cc 100644 --- a/src/common/communication.cpp +++ b/src/common/communication/vst2.cpp @@ -14,11 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "communication.h" +#include "vst2.h" #include -#include "utils.h" +#include "../utils.h" namespace fs = boost::filesystem; diff --git a/src/common/communication.h b/src/common/communication/vst2.h similarity index 99% rename from src/common/communication.h rename to src/common/communication/vst2.h index efcd32e2..1bc0e658 100644 --- a/src/common/communication.h +++ b/src/common/communication/vst2.h @@ -30,7 +30,7 @@ #include #include -#include "logging.h" +#include "../logging.h" template using OutputAdapter = bitsery::OutputBufferAdapter; diff --git a/src/common/logging.h b/src/common/logging.h index ef046f94..8ce0a8e8 100644 --- a/src/common/logging.h +++ b/src/common/logging.h @@ -20,6 +20,8 @@ #include #include +// TODO: Split up the plugin API specific logging functions so we don't have to +// include a bunch of stuff we don't need #include "serialization/vst2.h" /** diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index d1518130..4572bab2 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -20,7 +20,7 @@ #include #include -#include "../../common/communication.h" +#include "../../common/communication/vst2.h" #include "../../common/utils.h" #include "../utils.h" diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index f92bb27a..c9660d41 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -23,7 +23,7 @@ #include #include -#include "../../common/communication.h" +#include "../../common/communication/vst2.h" #include "../../common/configuration.h" #include "../../common/logging.h" #include "../host-process.h" diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index 930209f8..04f858f8 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -21,7 +21,7 @@ #include #include -#include "../common/communication.h" +#include "../common/communication/vst2.h" namespace bp = boost::process; namespace fs = boost::filesystem; diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index c21ecf16..585c55a9 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -25,7 +25,7 @@ #include #include -#include "../common/communication.h" +#include "../common/communication/vst2.h" #include "../common/logging.h" #include "utils.h" diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index d81788ca..7d9396a3 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -21,7 +21,9 @@ #include #include -#include "../../common/communication.h" +// TODO: Change this to commucation/common.h after refactoring, and do the same +// thing in other places where we don't need everything from VST2 +#include "../../common/communication/vst2.h" // FIXME: `std::filesystem` is broken in wineg++, at least under Wine 5.8. Any // path operation will thrown an encoding related error diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index 7d11aa32..a52cdb8f 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -21,7 +21,7 @@ #include #include -#include "../../common/communication.h" +#include "../../common/communication/vst2.h" /** * A function pointer to what should be the entry point of a VST plugin. diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index c3e1ed16..4dc46e7d 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -29,7 +29,7 @@ #include #include -#include "../../common/communication.h" +#include "../../common/communication/vst2.h" #include "../../common/configuration.h" #include "../../common/logging.h" #include "../editor.h" From 2fbd14908a8a58c123fcc983cab0ce9167f0a366 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 29 Nov 2020 13:54:33 +0100 Subject: [PATCH 020/456] Split communication/vst2.h into common and VST2 So we can reuse the generic bits for our VST3 implementation. --- meson.build | 1 + src/common/communication/common.cpp | 38 ++++ src/common/communication/common.h | 319 ++++++++++++++++++++++++++++ src/common/communication/vst2.cpp | 37 ---- src/common/communication/vst2.h | 306 +------------------------- src/plugin/host-process.cpp | 2 - src/plugin/host-process.h | 3 + src/wine-host/bridges/group.cpp | 4 +- 8 files changed, 366 insertions(+), 344 deletions(-) create mode 100644 src/common/communication/common.cpp create mode 100644 src/common/communication/common.h diff --git a/meson.build b/meson.build index b7d408b9..544e1dad 100644 --- a/meson.build +++ b/meson.build @@ -103,6 +103,7 @@ include_dir = include_directories('src/include') shared_library( 'yabridge-vst2', [ + 'src/common/communication/common.cpp', 'src/common/communication/vst2.cpp', 'src/common/serialization/vst2.cpp', 'src/common/configuration.cpp', diff --git a/src/common/communication/common.cpp b/src/common/communication/common.cpp new file mode 100644 index 00000000..a4fba588 --- /dev/null +++ b/src/common/communication/common.cpp @@ -0,0 +1,38 @@ +#include "common.h" + +#include + +#include "../utils.h" + +namespace fs = boost::filesystem; + +/** + * Used for generating random identifiers. + */ +constexpr char alphanumeric_characters[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +boost::filesystem::path generate_endpoint_base(const std::string& plugin_name) { + fs::path temp_directory = get_temporary_directory(); + + std::random_device random_device; + std::mt19937 rng(random_device()); + fs::path candidate_endpoint; + do { + std::string random_id; + std::sample( + alphanumeric_characters, + alphanumeric_characters + strlen(alphanumeric_characters) - 1, + std::back_inserter(random_id), 8, rng); + + // We'll get rid of the file descriptors immediately after accepting the + // sockets, so putting them inside of a subdirectory would only leave + // behind an empty directory + std::ostringstream socket_name; + socket_name << "yabridge-" << plugin_name << "-" << random_id; + + candidate_endpoint = temp_directory / socket_name.str(); + } while (fs::exists(candidate_endpoint)); + + return candidate_endpoint; +} diff --git a/src/common/communication/common.h b/src/common/communication/common.h new file mode 100644 index 00000000..1bce69a4 --- /dev/null +++ b/src/common/communication/common.h @@ -0,0 +1,319 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include + +#ifdef __WINE__ +#include "../wine-host/boost-fix.h" +#endif +#include +#include +#include +#include +#include + +template +using OutputAdapter = bitsery::OutputBufferAdapter; + +template +using InputAdapter = bitsery::InputBufferAdapter; + +/** + * Serialize an object using bitsery and write it to a socket. This will write + * both the size of the serialized object and the object itself over the socket. + * + * @param socket The Boost.Asio socket to write to. + * @param object The object to write to the stream. + * @param buffer The buffer to write to. This is useful for sending audio and + * chunk data since that can vary in size by a lot. + * + * @warning This operation is not atomic, and calling this function with the + * same socket from multiple threads at once will cause issues with the + * packets arriving out of order. + * + * @relates read_object + */ +template +inline void write_object(Socket& socket, + const T& object, + std::vector& buffer) { + const size_t size = + bitsery::quickSerialization>>( + buffer, object); + + // Tell the other side how large the object is so it can prepare a buffer + // large enough before sending the data + // NOTE: We're writing these sizes as a 64 bit integers, **not** as pointer + // sized integers. This is to provide compatibility with the 32-bit + // bit bridge. This won't make any function difference aside from the + // 32-bit host application having to convert between 64 and 32 bit + // integers. + boost::asio::write(socket, + boost::asio::buffer(std::array{size})); + const size_t bytes_written = + boost::asio::write(socket, boost::asio::buffer(buffer, size)); + assert(bytes_written == size); +} + +/** + * `write_object()` with a small default buffer for convenience. + * + * @overload + */ +template +inline void write_object(Socket& socket, const T& object) { + std::vector buffer(64); + write_object(socket, object, buffer); +} + +/** + * Deserialize an object by reading it from a socket. This should be used + * together with `write_object`. This will block until the object is available. + * + * @param socket The Boost.Asio socket to read from. + * @param buffer The buffer to read into. This is useful for sending audio and + * chunk data since that can vary in size by a lot. + * + * @return The deserialized object. + * + * @throw std::runtime_error If the conversion to an object was not successful. + * @throw boost::system::system_error If the socket is closed or gets closed + * while reading. + * + * @relates write_object + */ +template +inline T read_object(Socket& socket, std::vector& buffer) { + // See the note above on the use of `uint64_t` instead of `size_t` + std::array message_length; + boost::asio::read(socket, boost::asio::buffer(message_length)); + + // Make sure the buffer is large enough + const size_t size = message_length[0]; + buffer.resize(size); + + // `boost::asio::read/write` will handle all the packet splitting and + // merging for us, since local domain sockets have packet limits somewhere + // in the hundreds of kilobytes + const auto actual_size = + boost::asio::read(socket, boost::asio::buffer(buffer)); + assert(size == actual_size); + + T object; + auto [_, success] = + bitsery::quickDeserialization>>( + {buffer.begin(), size}, object); + + if (BOOST_UNLIKELY(!success)) { + throw std::runtime_error("Deserialization failure in call: " + + std::string(__PRETTY_FUNCTION__)); + } + + return object; +} + +/** + * `read_object()` with a small default buffer for convenience. + * + * @overload + */ +template +inline T read_object(Socket& socket) { + std::vector buffer(64); + return read_object(socket, buffer); +} + +/** + * A single, long-living socket + */ +class SocketHandler { + public: + /** + * Sets up the sockets and start listening on the socket on the listening + * side. The sockets won't be active until `connect()` gets called. + * + * @param io_context The IO context the socket should be bound to. + * @param endpoint The endpoint this socket should connect to or listen on. + * @param listen If `true`, start listening on the sockets. Incoming + * connections will be accepted when `connect()` gets called. This should + * be set to `true` on the plugin side, and `false` on the Wine host side. + * + * @see Sockets::connect + */ + SocketHandler(boost::asio::io_context& io_context, + boost::asio::local::stream_protocol::endpoint endpoint, + bool listen) + : endpoint(endpoint), socket(io_context) { + if (listen) { + boost::filesystem::create_directories( + boost::filesystem::path(endpoint.path()).parent_path()); + acceptor.emplace(io_context, endpoint); + } + } + + /** + * Depending on the value of the `listen` argument passed to the + * constructor, either accept connections made to the sockets on the Linux + * side or connect to the sockets on the Wine side. + */ + void connect() { + if (acceptor) { + acceptor->accept(socket); + } else { + socket.connect(endpoint); + } + } + + /** + * Close the socket. Both sides that are actively listening will be thrown a + * `boost::system_error` when this happens. + */ + void close() { + // The shutdown can fail when the socket is already closed + boost::system::error_code err; + socket.shutdown( + boost::asio::local::stream_protocol::socket::shutdown_both, err); + socket.close(); + } + + /** + * Serialize an object and send it over the socket. + * + * @param object The object to send. + * @param buffer The buffer to use for the serialization. This is used to + * prevent excess allocations when sending audio. + * + * @throw boost::system::system_error If the socket is closed or gets closed + * during sending. + * + * @warning This operation is not atomic, and calling this function with the + * same socket from multiple threads at once will cause issues with the + * packets arriving out of order. + * + * @see write_object + * @see SocketHandler::receive_single + * @see SocketHandler::receive_multi + */ + template + inline void send(const T& object, std::vector& buffer) { + write_object(socket, object, buffer); + } + + /** + * `SocketHandler::send()` with a small default buffer for convenience. + * + * @overload + */ + template + inline void send(const T& object) { + write_object(socket, object); + } + + /** + * Read a serialized object from the socket sent using `send()`. This will + * block until the object is available. + * + * @param buffer The buffer to read into. This is used to prevent excess + * allocations when sending audio. + * + * @return The deserialized object. + * + * @throw std::runtime_error If the conversion to an object was not + * successful. + * @throw boost::system::system_error If the socket is closed or gets closed + * while reading. + * + * @relates SocketHandler::send + * + * @see read_object + * @see SocketHandler::receive_multi + */ + template + inline T receive_single(std::vector& buffer) { + return read_object(socket, buffer); + } + + /** + * `SocketHandler::receive_single()` with a small default buffer for + * convenience. + * + * @overload + */ + template + inline T receive_single() { + return read_object(socket); + } + + /** + * Start a blocking loop to receive objects on this socket. This function + * will return once the socket gets closed. + * + * @param callback A function that gets passed the received object. Since + * we'd probably want to do some more stuff after sending a reply, calling + * `send()` is the responsibility of this function. + * + * @tparam F A function type in the form of `void(T, std::vector&)` + * that does something with the object, and then calls `send()`. The + * reading/writing buffer is passed along so it can be reused for sending + * large amounts of data. + * + * @relates SocketHandler::send + * + * @see read_object + * @see SocketHandler::receive_single + */ + template + void receive_multi(F callback) { + std::vector buffer{}; + while (true) { + try { + auto object = receive_single(buffer); + + callback(std::move(object), buffer); + } catch (const boost::system::system_error&) { + // This happens when the sockets got closed because the plugin + // is being shut down + break; + } + } + } + + private: + boost::asio::local::stream_protocol::endpoint endpoint; + boost::asio::local::stream_protocol::socket socket; + + /** + * Will be used in `connect()` on the listening side to establish the + * connection. + */ + std::optional acceptor; +}; + +/** + * Generate a unique base directory that can be used as a prefix for all Unix + * domain socket endpoints used in `Vst2PluginBridge`/`Vst2Bridge`. This will + * usually return `/run/user//yabridge--/`. + * + * Sockets for group hosts are handled separately. See + * `../plugin/utils.h:generate_group_endpoint` for more information on those. + * + * @param plugin_name The name of the plugin we're generating endpoints for. + * Used as a visual indication of what plugin is using this endpoint. + */ +boost::filesystem::path generate_endpoint_base(const std::string& plugin_name); diff --git a/src/common/communication/vst2.cpp b/src/common/communication/vst2.cpp index 845507cc..da13669d 100644 --- a/src/common/communication/vst2.cpp +++ b/src/common/communication/vst2.cpp @@ -16,18 +16,6 @@ #include "vst2.h" -#include - -#include "../utils.h" - -namespace fs = boost::filesystem; - -/** - * Used for generating random identifiers. - */ -constexpr char alphanumeric_characters[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - EventPayload DefaultDataConverter::read(const int /*opcode*/, const int /*index*/, const intptr_t /*value*/, @@ -80,28 +68,3 @@ intptr_t DefaultDataConverter::return_value(const int /*opcode*/, const intptr_t original) const { return original; } - -boost::filesystem::path generate_endpoint_base(const std::string& plugin_name) { - fs::path temp_directory = get_temporary_directory(); - - std::random_device random_device; - std::mt19937 rng(random_device()); - fs::path candidate_endpoint; - do { - std::string random_id; - std::sample( - alphanumeric_characters, - alphanumeric_characters + strlen(alphanumeric_characters) - 1, - std::back_inserter(random_id), 8, rng); - - // We'll get rid of the file descriptors immediately after accepting the - // sockets, so putting them inside of a subdirectory would only leave - // behind an empty directory - std::ostringstream socket_name; - socket_name << "yabridge-" << plugin_name << "-" << random_id; - - candidate_endpoint = temp_directory / socket_name.str(); - } while (fs::exists(candidate_endpoint)); - - return candidate_endpoint; -} diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index 1bc0e658..ba30b841 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -18,296 +18,8 @@ #include -#include -#include - -#ifdef __WINE__ -#include "../wine-host/boost-fix.h" -#endif -#include -#include -#include -#include -#include - #include "../logging.h" - -template -using OutputAdapter = bitsery::OutputBufferAdapter; - -template -using InputAdapter = bitsery::InputBufferAdapter; - -/** - * Serialize an object using bitsery and write it to a socket. This will write - * both the size of the serialized object and the object itself over the socket. - * - * @param socket The Boost.Asio socket to write to. - * @param object The object to write to the stream. - * @param buffer The buffer to write to. This is useful for sending audio and - * chunk data since that can vary in size by a lot. - * - * @warning This operation is not atomic, and calling this function with the - * same socket from multiple threads at once will cause issues with the - * packets arriving out of order. - * - * @relates read_object - */ -template -inline void write_object(Socket& socket, - const T& object, - std::vector& buffer) { - const size_t size = - bitsery::quickSerialization>>( - buffer, object); - - // Tell the other side how large the object is so it can prepare a buffer - // large enough before sending the data - // NOTE: We're writing these sizes as a 64 bit integers, **not** as pointer - // sized integers. This is to provide compatibility with the 32-bit - // bit bridge. This won't make any function difference aside from the - // 32-bit host application having to convert between 64 and 32 bit - // integers. - boost::asio::write(socket, - boost::asio::buffer(std::array{size})); - const size_t bytes_written = - boost::asio::write(socket, boost::asio::buffer(buffer, size)); - assert(bytes_written == size); -} - -/** - * `write_object()` with a small default buffer for convenience. - * - * @overload - */ -template -inline void write_object(Socket& socket, const T& object) { - std::vector buffer(64); - write_object(socket, object, buffer); -} - -/** - * Deserialize an object by reading it from a socket. This should be used - * together with `write_object`. This will block until the object is available. - * - * @param socket The Boost.Asio socket to read from. - * @param buffer The buffer to read into. This is useful for sending audio and - * chunk data since that can vary in size by a lot. - * - * @return The deserialized object. - * - * @throw std::runtime_error If the conversion to an object was not successful. - * @throw boost::system::system_error If the socket is closed or gets closed - * while reading. - * - * @relates write_object - */ -template -inline T read_object(Socket& socket, std::vector& buffer) { - // See the note above on the use of `uint64_t` instead of `size_t` - std::array message_length; - boost::asio::read(socket, boost::asio::buffer(message_length)); - - // Make sure the buffer is large enough - const size_t size = message_length[0]; - buffer.resize(size); - - // `boost::asio::read/write` will handle all the packet splitting and - // merging for us, since local domain sockets have packet limits somewhere - // in the hundreds of kilobytes - const auto actual_size = - boost::asio::read(socket, boost::asio::buffer(buffer)); - assert(size == actual_size); - - T object; - auto [_, success] = - bitsery::quickDeserialization>>( - {buffer.begin(), size}, object); - - if (BOOST_UNLIKELY(!success)) { - throw std::runtime_error("Deserialization failure in call: " + - std::string(__PRETTY_FUNCTION__)); - } - - return object; -} - -/** - * `read_object()` with a small default buffer for convenience. - * - * @overload - */ -template -inline T read_object(Socket& socket) { - std::vector buffer(64); - return read_object(socket, buffer); -} - -/** - * A single, long-living socket - */ -class SocketHandler { - public: - /** - * Sets up the sockets and start listening on the socket on the listening - * side. The sockets won't be active until `connect()` gets called. - * - * @param io_context The IO context the socket should be bound to. - * @param endpoint The endpoint this socket should connect to or listen on. - * @param listen If `true`, start listening on the sockets. Incoming - * connections will be accepted when `connect()` gets called. This should - * be set to `true` on the plugin side, and `false` on the Wine host side. - * - * @see Sockets::connect - */ - SocketHandler(boost::asio::io_context& io_context, - boost::asio::local::stream_protocol::endpoint endpoint, - bool listen) - : endpoint(endpoint), socket(io_context) { - if (listen) { - boost::filesystem::create_directories( - boost::filesystem::path(endpoint.path()).parent_path()); - acceptor.emplace(io_context, endpoint); - } - } - - /** - * Depending on the value of the `listen` argument passed to the - * constructor, either accept connections made to the sockets on the Linux - * side or connect to the sockets on the Wine side. - */ - void connect() { - if (acceptor) { - acceptor->accept(socket); - } else { - socket.connect(endpoint); - } - } - - /** - * Close the socket. Both sides that are actively listening will be thrown a - * `boost::system_error` when this happens. - */ - void close() { - // The shutdown can fail when the socket is already closed - boost::system::error_code err; - socket.shutdown( - boost::asio::local::stream_protocol::socket::shutdown_both, err); - socket.close(); - } - - /** - * Serialize an object and send it over the socket. - * - * @param object The object to send. - * @param buffer The buffer to use for the serialization. This is used to - * prevent excess allocations when sending audio. - * - * @throw boost::system::system_error If the socket is closed or gets closed - * during sending. - * - * @warning This operation is not atomic, and calling this function with the - * same socket from multiple threads at once will cause issues with the - * packets arriving out of order. - * - * @see write_object - * @see SocketHandler::receive_single - * @see SocketHandler::receive_multi - */ - template - inline void send(const T& object, std::vector& buffer) { - write_object(socket, object, buffer); - } - - /** - * `SocketHandler::send()` with a small default buffer for convenience. - * - * @overload - */ - template - inline void send(const T& object) { - write_object(socket, object); - } - - /** - * Read a serialized object from the socket sent using `send()`. This will - * block until the object is available. - * - * @param buffer The buffer to read into. This is used to prevent excess - * allocations when sending audio. - * - * @return The deserialized object. - * - * @throw std::runtime_error If the conversion to an object was not - * successful. - * @throw boost::system::system_error If the socket is closed or gets closed - * while reading. - * - * @relates SocketHandler::send - * - * @see read_object - * @see SocketHandler::receive_multi - */ - template - inline T receive_single(std::vector& buffer) { - return read_object(socket, buffer); - } - - /** - * `SocketHandler::receive_single()` with a small default buffer for - * convenience. - * - * @overload - */ - template - inline T receive_single() { - return read_object(socket); - } - - /** - * Start a blocking loop to receive objects on this socket. This function - * will return once the socket gets closed. - * - * @param callback A function that gets passed the received object. Since - * we'd probably want to do some more stuff after sending a reply, calling - * `send()` is the responsibility of this function. - * - * @tparam F A function type in the form of `void(T, std::vector&)` - * that does something with the object, and then calls `send()`. The - * reading/writing buffer is passed along so it can be reused for sending - * large amounts of data. - * - * @relates SocketHandler::send - * - * @see read_object - * @see SocketHandler::receive_single - */ - template - void receive_multi(F callback) { - std::vector buffer{}; - while (true) { - try { - auto object = receive_single(buffer); - - callback(std::move(object), buffer); - } catch (const boost::system::system_error&) { - // This happens when the sockets got closed because the plugin - // is being shut down - break; - } - } - } - - private: - boost::asio::local::stream_protocol::endpoint endpoint; - boost::asio::local::stream_protocol::socket socket; - - /** - * Will be used in `connect()` on the listening side to establish the - * connection. - */ - std::optional acceptor; -}; +#include "common.h" /** * Encodes the base behavior for reading from and writing to the `data` argument @@ -391,6 +103,9 @@ class DefaultDataConverter { * sets up asynchronous listeners for the socket endpoint, and then block and * handle events until the main socket is closed. * + * TODO: Factor out the on-demand socket spawning and handling logic so we can + * reuse most of this for the VST3 implementation + * * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ @@ -879,19 +594,6 @@ class Sockets { SocketHandler host_vst_control; }; -/** - * Generate a unique base directory that can be used as a prefix for all Unix - * domain socket endpoints used in `Vst2PluginBridge`/`Vst2Bridge`. This will - * usually return `/run/user//yabridge--/`. - * - * Sockets for group hosts are handled separately. See - * `../plugin/utils.h:generate_group_endpoint` for more information on those. - * - * @param plugin_name The name of the plugin we're generating endpoints for. - * Used as a visual indication of what plugin is using this endpoint. - */ -boost::filesystem::path generate_endpoint_base(const std::string& plugin_name); - /** * Unmarshall an `EventPayload` back to the representation used by VST2, pass * that value to a callback function (either `AEffect::dispatcher()` for host -> diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index 04f858f8..cbde8679 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -21,8 +21,6 @@ #include #include -#include "../common/communication/vst2.h" - namespace bp = boost::process; namespace fs = boost::filesystem; diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index 585c55a9..81e1889b 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -25,6 +25,9 @@ #include #include +// TODO: Those host process implementation now directly uses the Vst2Sockets and +// thus requires `communication/vst2.h`. We should create a simple common +// interface for this instead. #include "../common/communication/vst2.h" #include "../common/logging.h" #include "utils.h" diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index 7d9396a3..4e0c1d4d 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -21,9 +21,7 @@ #include #include -// TODO: Change this to commucation/common.h after refactoring, and do the same -// thing in other places where we don't need everything from VST2 -#include "../../common/communication/vst2.h" +#include "../../common/communication/common.h" // FIXME: `std::filesystem` is broken in wineg++, at least under Wine 5.8. Any // path operation will thrown an encoding related error From bb85d9965785ea7eb8b8f42a03b315b5bc369dd8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 29 Nov 2020 13:58:34 +0100 Subject: [PATCH 021/456] Rename 'Sockets' to 'Vst2Sockets' --- src/common/communication/common.h | 2 +- src/common/communication/vst2.h | 18 +++++++++--------- src/plugin/bridges/vst2.h | 2 +- src/plugin/host-process.cpp | 4 ++-- src/plugin/host-process.h | 6 +++--- src/wine-host/bridges/vst2.h | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 1bce69a4..7ea18191 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -154,7 +154,7 @@ class SocketHandler { * connections will be accepted when `connect()` gets called. This should * be set to `true` on the plugin side, and `false` on the Wine host side. * - * @see Sockets::connect + * @see Vst2Sockets::connect */ SocketHandler(boost::asio::io_context& io_context, boost::asio::local::stream_protocol::endpoint endpoint, diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index ba30b841..82218654 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -93,7 +93,7 @@ class DefaultDataConverter { * slightly differently: * * - We'll keep a single long lived socket connection. This works the exact same - * way as every other socket defined in the `Sockets` class. + * way as every other socket defined in the `Vst2Sockets` class. * - Aside from that the listening side will have a second thread asynchronously * listening for new connections on the socket endpoint. * @@ -124,7 +124,7 @@ class EventHandler { * connections will be accepted when `connect()` gets called. This should * be set to `true` on the plugin side, and `false` on the Wine host side. * - * @see Sockets::connect + * @see Vst2Sockets::connect */ EventHandler(boost::asio::io_context& io_context, boost::asio::local::stream_protocol::endpoint endpoint, @@ -448,7 +448,7 @@ class EventHandler { /** * This acceptor will be used once synchronously on the listening side - * during `Sockets::connect()`. When `EventHandler::receive_events()` is + * during `Vst2Sockets::connect()`. When `EventHandler::receive_events()` is * then called, we'll recreate the acceptor to asynchronously listen for new * incoming socket connections on `endpoint` using. This is important, * because on the case of `vst_host_callback` the acceptor is first accepts @@ -480,7 +480,7 @@ class EventHandler { * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ template -class Sockets { +class Vst2Sockets { public: /** * Sets up the sockets using the specified base directory. The sockets won't @@ -494,11 +494,11 @@ class Sockets { * connections will be accepted when `connect()` gets called. This should * be set to `true` on the plugin side, and `false` on the Wine host side. * - * @see Sockets::connect + * @see Vst2Sockets::connect */ - Sockets(boost::asio::io_context& io_context, - const boost::filesystem::path& endpoint_base_dir, - bool listen) + Vst2Sockets(boost::asio::io_context& io_context, + const boost::filesystem::path& endpoint_base_dir, + bool listen) : base_dir(endpoint_base_dir), host_vst_dispatch(io_context, (base_dir / "host_vst_dispatch.sock").string(), @@ -521,7 +521,7 @@ class Sockets { * Cleans up the directory containing the socket endpoints when yabridge * shuts down if it still exists. */ - ~Sockets() { + ~Vst2Sockets() { // Manually close all sockets so we break out of any blocking operations // that may still be active host_vst_dispatch.close(); diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index c9660d41..33faeb98 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -145,7 +145,7 @@ class Vst2PluginBridge { void log_init_message(); boost::asio::io_context io_context; - Sockets sockets; + Vst2Sockets sockets; /** * The thread that handles host callbacks. diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index cbde8679..b283450b 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -87,7 +87,7 @@ void HostProcess::async_log_pipe_lines(patched_async_pipe& pipe, IndividualHost::IndividualHost(boost::asio::io_context& io_context, Logger& logger, fs::path plugin_path, - const Sockets& sockets) + const Vst2Sockets& sockets) : HostProcess(io_context, logger), plugin_arch(find_vst_architecture(plugin_path)), host_path(find_vst_host(plugin_arch, false)), @@ -134,7 +134,7 @@ void IndividualHost::terminate() { GroupHost::GroupHost(boost::asio::io_context& io_context, Logger& logger, fs::path plugin_path, - Sockets& sockets, + Vst2Sockets& sockets, std::string group_name) : HostProcess(io_context, logger), plugin_arch(find_vst_architecture(plugin_path)), diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index 81e1889b..4130dc90 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -130,7 +130,7 @@ class IndividualHost : public HostProcess { IndividualHost(boost::asio::io_context& io_context, Logger& logger, boost::filesystem::path plugin_path, - const Sockets& sockets); + const Vst2Sockets& sockets); PluginArchitecture architecture() override; boost::filesystem::path path() override; @@ -172,7 +172,7 @@ class GroupHost : public HostProcess { GroupHost(boost::asio::io_context& io_context, Logger& logger, boost::filesystem::path plugin_path, - Sockets& socket_endpoint, + Vst2Sockets& socket_endpoint, std::string group_name); PluginArchitecture architecture() override; @@ -206,7 +206,7 @@ class GroupHost : public HostProcess { * The associated sockets for the plugin we're hosting. This is used to * terminate the plugin. */ - Sockets& sockets; + Vst2Sockets& sockets; /** * A thread that waits for the group host to have started and then ask it to diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 4dc46e7d..983f9a0f 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -59,7 +59,7 @@ class Vst2Bridge { * @param plugin_dll_path A (Unix style) path to the VST plugin .dll file to * load. * @param endpoint_base_dir The base directory used for the socket - * endpoints. See `Sockets` for more information. + * endpoints. See `Vst2Sockets` for more information. * * @note The object has to be constructed from the same thread that calls * `main_context.run()`. @@ -189,7 +189,7 @@ class Vst2Bridge { * sockets will be closed first, and we can then safely wait for the * threads to exit. */ - Sockets sockets; + Vst2Sockets sockets; /** * The MIDI events that have been received **and processed** since the last From c8d76d9c92a39a84b7cbfb0e5a233bf5f909d7ba Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 29 Nov 2020 14:56:13 +0100 Subject: [PATCH 022/456] Add proper dependencies for the entire VST3 SDK --- meson.build | 21 +++++++++++++++++++-- subprojects/vst3.wrap | 2 +- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 544e1dad..fc40675f 100644 --- a/meson.build +++ b/meson.build @@ -77,6 +77,14 @@ if with_vst3 vst3 = subproject('vst3', version : '3.7.1') vst3_compiler_options = vst3.get_variable('compiler_options') vst3_include_dir = vst3.get_variable('include_dir') + vst3_base_native = static_library( + 'base_native', + vst3.get_variable('base_sources'), + cpp_args : vst3_compiler_options + ['-Wno-cpp'], + include_directories : vst3_include_dir, + override_options : ['warning_level=0'], + native : true, + ) vst3_pluginterfaces_native = static_library( 'pluginterfaces_native', vst3.get_variable('pluginterfaces_sources'), @@ -85,8 +93,17 @@ if with_vst3 override_options : ['warning_level=0'], native : true, ) - vst3_pluginterfaces_native_dep = declare_dependency( - link_with : vst3_pluginterfaces_native, + vst3_sdk_native = static_library( + 'sdk_native', + vst3.get_variable('sdk_common_sources') + vst3.get_variable('sdk_sources'), + link_with : [vst3_base_native, vst3_pluginterfaces_native], + cpp_args : vst3_compiler_options + ['-Wno-multichar'], + include_directories : vst3_include_dir, + override_options : ['warning_level=0'], + native : true, + ) + vst3_sdk_native_dep = declare_dependency( + link_with : vst3_sdk_native, include_directories : vst3_include_dir, ) else diff --git a/subprojects/vst3.wrap b/subprojects/vst3.wrap index b1a24457..395c2ce8 100644 --- a/subprojects/vst3.wrap +++ b/subprojects/vst3.wrap @@ -2,6 +2,6 @@ url = https://github.com/robbert-vdh/vst3sdk.git # This is VST3 SDK v3.7.1_build_50 with the documentation and VSTGUI submodules # removed -revision = 410f4e01d311f1608a193594a65b98db86758b95 +revision = d6d6ae0d33ada0c07a508b0e079d962464fa93cd clone-recursive = true depth = 1 From b64c67d2ad000d929416fa0bfe5575859761c581 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 29 Nov 2020 18:38:02 +0100 Subject: [PATCH 023/456] Patch VST3 SDK base to allow winelib compilation --- meson.build | 34 +++++++++++++++++++++++++++ subprojects/vst3.wrap | 2 +- tools/patch-vst3-sdk.sh | 52 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100755 tools/patch-vst3-sdk.sh diff --git a/meson.build b/meson.build index fc40675f..017cbdc5 100644 --- a/meson.build +++ b/meson.build @@ -77,6 +77,8 @@ if with_vst3 vst3 = subproject('vst3', version : '3.7.1') vst3_compiler_options = vst3.get_variable('compiler_options') vst3_include_dir = vst3.get_variable('include_dir') + + # We'll create a dependency for the plugin SDK for our native VST3 plugin vst3_base_native = static_library( 'base_native', vst3.get_variable('base_sources'), @@ -106,6 +108,38 @@ if with_vst3 link_with : vst3_sdk_native, include_directories : vst3_include_dir, ) + + # And another dependency for the host SDK for our Wine host applications + # We need to do some minor hacking to get this to compile with winegcc. Most + # notably some attributes are named differently, and the SDK uses 'Windows.h' + # instead of 'windows.h' like how the file is actually called. + # message(vst3_include_dir) + vst3_sdk_base_dir = vst3.get_variable('sdk_base_dir') + patch_result = run_command('tools/patch-vst3-sdk.sh', vst3_sdk_base_dir) + if patch_result.returncode() == 0 + message(patch_result.stdout()) + else + error('Error while trying to patch the VST3 SDK:\n' + patch_result.stderr()) + endif + + vst3_wine_compiler_options = [ + # Removes some MSVC-isms for us + '-D__MINGW32__', + # We don't need all of this stuff from `Windows.h`, and it only causes more + # issues + '-DNOMINMAX', + '-DNOSERVICE', + '-DNOMCX', + '-DWIN32_LEAN_AND_MEAN', + ] + vst3_base_wine = static_library( + 'base_wine', + vst3.get_variable('base_sources'), + cpp_args : vst3_compiler_options + vst3_wine_compiler_options + [ '-Wno-cpp'], + include_directories : vst3_include_dir, + override_options : ['warning_level=0'], + native : false, + ) else message('VST3 support has been disabled') endif diff --git a/subprojects/vst3.wrap b/subprojects/vst3.wrap index 395c2ce8..19b3e6a6 100644 --- a/subprojects/vst3.wrap +++ b/subprojects/vst3.wrap @@ -2,6 +2,6 @@ url = https://github.com/robbert-vdh/vst3sdk.git # This is VST3 SDK v3.7.1_build_50 with the documentation and VSTGUI submodules # removed -revision = d6d6ae0d33ada0c07a508b0e079d962464fa93cd +revision = e2fbb41f28a4b311f2fc7d28e9b4330eec1802b6 clone-recursive = true depth = 1 diff --git a/tools/patch-vst3-sdk.sh b/tools/patch-vst3-sdk.sh new file mode 100755 index 00000000..02a77f49 --- /dev/null +++ b/tools/patch-vst3-sdk.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# +# Patch the VST3 SDK and replace all MSVC-isms so it can be compiled with +# winegcc. We do it this way instead of modifying the SDK directly so we don't +# have to fork multiple repositories and keep them up to date. +# If anyone knows a better way to get the SDK to compile with Win32 supports +# under winegcc without having to modify it then please let me know, because I'd +# rather not have to do this. +# +# Usage: +# patch-vst3-sdk.sh + +set -euo pipefail + +sdk_directory=$1 +if [[ -z $sdk_directory ]]; then + echo "Usage:" + echo "patch-vst3-sdk.sh " + exit 1 +fi + +# Make sure all imports use the correct casing +find "$sdk_directory" -type f \( -iname '*.h' -or -iname '*.cpp' \) -print0 | + xargs -0 sed -i 's/^#include $/#include /' + +# Use the string manipulation functions from the C standard library +sed -i 's/\bSMTG_OS_WINDOWS\b/0/g;s/\bSMTG_OS_LINUX\b/1/g' "$sdk_directory/base/source/fstring.cpp" +sed -i 's/\bSMTG_OS_WINDOWS\b/0/g;s/\bSMTG_OS_LINUX\b/1/g' "$sdk_directory/pluginterfaces/base/fstrdefs.h" + +# We'll need some careful replacements in the Linux definitions in `fstring.cpp` +# to use `wchar_t` instead of `char16_t`. +sed -i "s/^using ConverterFacet = std::codecvt_utf8_utf16;$/#ifdef __WINE__\\ + using ConverterFacet = std::codecvt_utf8_utf16;\\ +#else\\ + \0\\ +#endif/" "$sdk_directory/base/source/fstring.cpp" +sed -i "s/^using Converter = std::wstring_convert;$/#ifdef __WINE__\\ + using Converter = std::wstring_convert;\\ +#else\\ + \0\\ +#endif/" "$sdk_directory/base/source/fstring.cpp" + +# `Windows.h` expects `wchar_t`, and the above defines will cause us to use +# `char16_t` for string literals. This replacement targets a very specific line, +# so if the SDK gets updated, this fails, and we're getting a ton of `wchar_t` +# related compile errors, that's why. The previous sed call will have replaced +# `SMTG_OS_WINDOWS` with a 0 here. +sed -i 's/^ #if 0$/ #if __WINE__/' "$sdk_directory/pluginterfaces/base/fstrdefs.h" + +# Meson requires this program to output something, or else it will error out +# when trying to encode the empty output +echo "Successfully patched '$sdk_directory' for winegcc compatibility" From 7dc0be40a47e018bfe37751f0f015150a10a03fb Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 29 Nov 2020 18:38:33 +0100 Subject: [PATCH 024/456] Move libyabridge-vst2.so definition to above VST3 --- meson.build | 70 ++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/meson.build b/meson.build index 017cbdc5..2f6cf805 100644 --- a/meson.build +++ b/meson.build @@ -70,6 +70,41 @@ tomlplusplus_dep = subproject('tomlplusplus', version : '2.1.0').get_variable('t wine_threads_dep = declare_dependency(link_args : '-lpthread') xcb_dep = dependency('xcb') +include_dir = include_directories('src/include') + +# The application consists of a plugin (`libyabridge-{vst2,vst3}.so`) that calls +# a Winelib application (`yabridge-{host,group}{,-32}.exe`) that can host +# Windows VST plugins. More information about the way these two components work +# together can be found in `docs/architecture.md`. + +shared_library( + 'yabridge-vst2', + [ + 'src/common/communication/common.cpp', + 'src/common/communication/vst2.cpp', + 'src/common/serialization/vst2.cpp', + 'src/common/configuration.cpp', + 'src/common/logging.cpp', + 'src/common/utils.cpp', + 'src/plugin/bridges/vst2.cpp', + 'src/plugin/host-process.cpp', + 'src/plugin/utils.cpp', + 'src/plugin/vst2-plugin.cpp', + version_header, + ], + native : true, + include_directories : include_dir, + dependencies : [ + boost_dep, + boost_filesystem_dep, + bitsery_dep, + threads_dep, + tomlplusplus_dep, + ], + cpp_args : compiler_options, + link_args : ['-ldl'] +) + if with_vst3 # Meson does not allow mixing native and non native dependencies from # subprojects. The only workaround is to only define the necessary variables @@ -144,41 +179,6 @@ else message('VST3 support has been disabled') endif -include_dir = include_directories('src/include') - -# The application consists of a plugin (`libyabridge-{vst2,vst3}.so`) that calls -# a Winelib application (`yabridge-{host,group}{,-32}.exe`) that can host -# Windows VST plugins. More information about the way these two components work -# together can be found in `docs/architecture.md`. - -shared_library( - 'yabridge-vst2', - [ - 'src/common/communication/common.cpp', - 'src/common/communication/vst2.cpp', - 'src/common/serialization/vst2.cpp', - 'src/common/configuration.cpp', - 'src/common/logging.cpp', - 'src/common/utils.cpp', - 'src/plugin/bridges/vst2.cpp', - 'src/plugin/host-process.cpp', - 'src/plugin/utils.cpp', - 'src/plugin/vst2-plugin.cpp', - version_header, - ], - native : true, - include_directories : include_dir, - dependencies : [ - boost_dep, - boost_filesystem_dep, - bitsery_dep, - threads_dep, - tomlplusplus_dep, - ], - cpp_args : compiler_options, - link_args : ['-ldl'] -) - host_sources = [ 'src/common/communication/vst2.cpp', 'src/common/serialization/vst2.cpp', From e7e1387c301508bcc38da5c0bcd5a50e4c572a1e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 29 Nov 2020 19:03:52 +0100 Subject: [PATCH 025/456] Also patch pluginterfaces for winegcc compilation --- meson.build | 8 ++++++++ tools/patch-vst3-sdk.sh | 36 +++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/meson.build b/meson.build index 2f6cf805..86e04319 100644 --- a/meson.build +++ b/meson.build @@ -175,6 +175,14 @@ if with_vst3 override_options : ['warning_level=0'], native : false, ) + vst3_pluginterfaces_wine = static_library( + 'pluginterfaces_wine', + vst3.get_variable('pluginterfaces_sources'), + cpp_args : vst3_compiler_options + vst3_wine_compiler_options, + include_directories : vst3_include_dir, + override_options : ['warning_level=0'], + native : false, + ) else message('VST3 support has been disabled') endif diff --git a/tools/patch-vst3-sdk.sh b/tools/patch-vst3-sdk.sh index 02a77f49..3434a4cd 100755 --- a/tools/patch-vst3-sdk.sh +++ b/tools/patch-vst3-sdk.sh @@ -26,19 +26,7 @@ find "$sdk_directory" -type f \( -iname '*.h' -or -iname '*.cpp' \) -print0 | # Use the string manipulation functions from the C standard library sed -i 's/\bSMTG_OS_WINDOWS\b/0/g;s/\bSMTG_OS_LINUX\b/1/g' "$sdk_directory/base/source/fstring.cpp" sed -i 's/\bSMTG_OS_WINDOWS\b/0/g;s/\bSMTG_OS_LINUX\b/1/g' "$sdk_directory/pluginterfaces/base/fstrdefs.h" - -# We'll need some careful replacements in the Linux definitions in `fstring.cpp` -# to use `wchar_t` instead of `char16_t`. -sed -i "s/^using ConverterFacet = std::codecvt_utf8_utf16;$/#ifdef __WINE__\\ - using ConverterFacet = std::codecvt_utf8_utf16;\\ -#else\\ - \0\\ -#endif/" "$sdk_directory/base/source/fstring.cpp" -sed -i "s/^using Converter = std::wstring_convert;$/#ifdef __WINE__\\ - using Converter = std::wstring_convert;\\ -#else\\ - \0\\ -#endif/" "$sdk_directory/base/source/fstring.cpp" +sed -i 's/\bSMTG_OS_WINDOWS\b/0/g;s/\bSMTG_OS_LINUX\b/1/g' "$sdk_directory/pluginterfaces/base/ustring.cpp" # `Windows.h` expects `wchar_t`, and the above defines will cause us to use # `char16_t` for string literals. This replacement targets a very specific line, @@ -47,6 +35,28 @@ sed -i "s/^using Converter = std::wstring_convert;$/#i # `SMTG_OS_WINDOWS` with a 0 here. sed -i 's/^ #if 0$/ #if __WINE__/' "$sdk_directory/pluginterfaces/base/fstrdefs.h" +# We'll need some careful replacements in the Linux definitions in `fstring.cpp` +# to use `wchar_t` instead of `char16_t`. +replace_char16() { + local needle=$1 + local filename=$2 + + wchar_version=${needle//char16_t/wchar_t} + sed -i "s/^$needle$/#ifdef __WINE__\\ + $wchar_version\\ + #else\\ + \0\\ + #endif/" "$filename" +} + +replace_char16 "using ConverterFacet = std::codecvt_utf8_utf16;" "$sdk_directory/base/source/fstring.cpp" +replace_char16 "using Converter = std::wstring_convert;" "$sdk_directory/base/source/fstring.cpp" +replace_char16 "using Converter = std::wstring_convert, char16_t>;" "$sdk_directory/pluginterfaces/base/ustring.cpp" + +# The definitions of long doesn't match up between platforms, and the mingw +# version here is trying to do something funky +sed -i 's/\b__MINGW32__\b/__NOPE__/g' "$sdk_directory/pluginterfaces/base/funknown.cpp" + # Meson requires this program to output something, or else it will error out # when trying to encode the empty output echo "Successfully patched '$sdk_directory' for winegcc compatibility" From 6c2616830348714142c404f9f3c9f895d8a1f93d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 29 Nov 2020 19:18:45 +0100 Subject: [PATCH 026/456] Patch the rest of the VST3 SDK for winegcc --- meson.build | 13 +++++++++++++ tools/patch-vst3-sdk.sh | 14 ++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 86e04319..6c85793a 100644 --- a/meson.build +++ b/meson.build @@ -183,6 +183,19 @@ if with_vst3 override_options : ['warning_level=0'], native : false, ) + vst3_sdk_hosting_wine = static_library( + 'sdk_hosting_wine', + vst3.get_variable('sdk_common_sources') + vst3.get_variable('sdk_hosting_sources'), + link_with : [vst3_base_wine, vst3_pluginterfaces_wine], + cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-Wno-multichar'], + include_directories : vst3_include_dir, + override_options : ['warning_level=0'], + native : false, + ) + vst3_sdk_hosting_wine_dep = declare_dependency( + link_with : vst3_sdk_hosting_wine, + include_directories : vst3_include_dir, + ) else message('VST3 support has been disabled') endif diff --git a/tools/patch-vst3-sdk.sh b/tools/patch-vst3-sdk.sh index 3434a4cd..fad11cac 100755 --- a/tools/patch-vst3-sdk.sh +++ b/tools/patch-vst3-sdk.sh @@ -43,10 +43,10 @@ replace_char16() { wchar_version=${needle//char16_t/wchar_t} sed -i "s/^$needle$/#ifdef __WINE__\\ - $wchar_version\\ - #else\\ - \0\\ - #endif/" "$filename" + $wchar_version\\ +#else\\ + \0\\ +#endif/" "$filename" } replace_char16 "using ConverterFacet = std::codecvt_utf8_utf16;" "$sdk_directory/base/source/fstring.cpp" @@ -57,6 +57,12 @@ replace_char16 "using Converter = std::wstring_convert$/#include \\/\\/ patched for yabridge\\ +#include /" "$sdk_directory/public.sdk/source/common/openurl.cpp" + # Meson requires this program to output something, or else it will error out # when trying to encode the empty output echo "Successfully patched '$sdk_directory' for winegcc compatibility" From 5b2221b25175fe41d7b0d11c6b0824ae43639349 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 29 Nov 2020 22:15:01 +0100 Subject: [PATCH 027/456] Add a (nonfunctional) VST3 entry point --- meson.build | 23 ++++++++++++++ src/plugin/vst3-plugin.cpp | 65 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 src/plugin/vst3-plugin.cpp diff --git a/meson.build b/meson.build index 6c85793a..f1811ada 100644 --- a/meson.build +++ b/meson.build @@ -196,6 +196,29 @@ if with_vst3 link_with : vst3_sdk_hosting_wine, include_directories : vst3_include_dir, ) + + # This is the VST3 equivalent of `libyabridge-vst2.so`. The Wine host + # applications can handle both VST2 and VST3 plugins. + shared_library( + 'yabridge-vst3', + [ + 'src/common/logging.cpp', + 'src/plugin/vst3-plugin.cpp', + version_header, + ], + native : true, + include_directories : include_dir, + dependencies : [ + boost_dep, + boost_filesystem_dep, + bitsery_dep, + threads_dep, + tomlplusplus_dep, + vst3_sdk_native_dep, + ], + cpp_args : compiler_options, + link_args : ['-ldl'], + ) else message('VST3 support has been disabled') endif diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp new file mode 100644 index 00000000..67f4df8b --- /dev/null +++ b/src/plugin/vst3-plugin.cpp @@ -0,0 +1,65 @@ +#include + +// TODO: Should you include this implementation file or copy everything over? +#include + +using Steinberg::gPluginFactory; + +// TODO: What should we do here? Also, note to self, don't forget to call these +// on the Wine host side if the host SDK doesn't already do that for us. +bool InitModule() { + return true; +} + +bool DeinitModule() { + return true; +} + +/** + * Our VST3 plugin's entry point. When building the plugin factory we'll host + * the plugin in our Wine application, retrieve its information and supported + * classes, and then recreate it here. + */ +SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { + // TODO: So from this I can imagine that the host is supposed to keep this + // module loaded into memory and reuse it for multiple plugins? How + // should Wine host instances be tied to native plugin instances? + if (!gPluginFactory) { + // TODO: Here we want to: + // 1) Load the plugin on the Wine host + // 2) Create a factory using the plugins PFactoryInfo + // 3) Get all PClassInfo{,2,W} objects from the plugin, register + // those classes. + // + // We should wrap this in our `Vst3PluginBridge` + + // static Steinberg::PFactoryInfo factoryInfo(vendor, url, email, + // flags); gPluginFactory = new Steinberg::CPluginFactory(factoryInfo); + + // + // { + // Steinberg::TUID lcid = cid; + // static Steinberg::PClassInfo componentClass(lcid, cardinality, + // category, name); + // gPluginFactory->registerClass(&componentClass, createMethod); + // } + // { + // Steinberg::TUID lcid = cid; + // static Steinberg::PClassInfo2 componentClass( + // lcid, cardinality, category, name, classFlags, subCategories, + // 0, version, sdkVersion); + // gPluginFactory->registerClass(&componentClass, createMethod); + // } + // { + // TUID lcid = cid; + // static Steinberg::PClassInfoW componentClass( + // lcid, cardinality, category, name, classFlags, subCategories, + // 0, version, sdkVersion); + // gPluginFactory->registerClass(&componentClass, createMethod); + // } + } else { + gPluginFactory->addRef(); + } + + return gPluginFactory; +} From fa719c286d3656c590b7b251e7ba732c8c684d4d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 30 Nov 2020 16:24:47 +0100 Subject: [PATCH 028/456] Create base class for sockets and derive from that Now the host launching procedure can be agnostic of the socket implementation. --- src/common/communication/common.h | 88 ++++++++++++++++++++++++++----- src/common/communication/vst2.h | 57 ++++++-------------- src/plugin/host-process.cpp | 8 +-- src/plugin/host-process.h | 11 ++-- src/wine-host/bridges/vst2.h | 2 +- 5 files changed, 99 insertions(+), 67 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 7ea18191..76fdbce3 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -139,6 +139,79 @@ inline T read_object(Socket& socket) { return read_object(socket, buffer); } +/** + * Generate a unique base directory that can be used as a prefix for all Unix + * domain socket endpoints used in `Vst2PluginBridge`/`Vst2Bridge`. This will + * usually return `/run/user//yabridge--/`. + * + * Sockets for group hosts are handled separately. See + * `../plugin/utils.h:generate_group_endpoint` for more information on those. + * + * @param plugin_name The name of the plugin we're generating endpoints for. + * Used as a visual indication of what plugin is using this endpoint. + */ +boost::filesystem::path generate_endpoint_base(const std::string& plugin_name); + +/** + * Manages all the sockets used for communicating between the plugin and the + * Wine host. Every plugin will get its own directory (the socket endpoint base + * directory), and all socket endpoints are created within this directory. This + * is usually `/run/user//yabridge--/`. + */ +class Sockets { + public: + /** + * Sets up the the base directory for the sockets. Classes inheriting this + * should set up their sockets here. + * + * @param endpoint_base_dir The base directory that will be used for the + * Unix domain sockets. + * + * @see Sockets::connect + */ + Sockets(const boost::filesystem::path& endpoint_base_dir) + : base_dir(endpoint_base_dir) {} + + /** + * Shuts down and closes all sockets and then cleans up the directory + * containing the socket endpoints when yabridge shuts down if it still + * exists. + * + * @note Classes overriding this should call `close()` in their destructor. + */ + virtual ~Sockets() { + try { + boost::filesystem::remove_all(base_dir); + } catch (const boost::filesystem::filesystem_error&) { + // There should not be any filesystem errors since only one side + // removes the files, but if we somehow can't delete the file + // then we can just silently ignore this + } + } + + /** + * Depending on the value of the `listen` argument passed to the + * constructor, either accept connections made to the sockets on the Linux + * side or connect to the sockets on the Wine side + */ + virtual void connect() = 0; + + /** + * Shut down and close all sockets. Called during the destructor and also + * explicitly called when shutting down a plugin in a group host process. + * + * It should be safe to call this function more than once, and it should be + * called in the overridden class's destructor. + */ + virtual void close() = 0; + + /** + * The base directory for our socket endpoints. All `*_endpoint` variables + * below are files within this directory. + */ + const boost::filesystem::path base_dir; +}; + /** * A single, long-living socket */ @@ -154,7 +227,7 @@ class SocketHandler { * connections will be accepted when `connect()` gets called. This should * be set to `true` on the plugin side, and `false` on the Wine host side. * - * @see Vst2Sockets::connect + * @see Sockets::connect */ SocketHandler(boost::asio::io_context& io_context, boost::asio::local::stream_protocol::endpoint endpoint, @@ -304,16 +377,3 @@ class SocketHandler { */ std::optional acceptor; }; - -/** - * Generate a unique base directory that can be used as a prefix for all Unix - * domain socket endpoints used in `Vst2PluginBridge`/`Vst2Bridge`. This will - * usually return `/run/user//yabridge--/`. - * - * Sockets for group hosts are handled separately. See - * `../plugin/utils.h:generate_group_endpoint` for more information on those. - * - * @param plugin_name The name of the plugin we're generating endpoints for. - * Used as a visual indication of what plugin is using this endpoint. - */ -boost::filesystem::path generate_endpoint_base(const std::string& plugin_name); diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index 82218654..9706688c 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -124,7 +124,7 @@ class EventHandler { * connections will be accepted when `connect()` gets called. This should * be set to `true` on the plugin side, and `false` on the Wine host side. * - * @see Vst2Sockets::connect + * @see Sockets::connect */ EventHandler(boost::asio::io_context& io_context, boost::asio::local::stream_protocol::endpoint endpoint, @@ -448,7 +448,7 @@ class EventHandler { /** * This acceptor will be used once synchronously on the listening side - * during `Vst2Sockets::connect()`. When `EventHandler::receive_events()` is + * during `Sockets::connect()`. When `EventHandler::receive_events()` is * then called, we'll recreate the acceptor to asynchronously listen for new * incoming socket connections on `endpoint` using. This is important, * because on the case of `vst_host_callback` the acceptor is first accepts @@ -467,9 +467,7 @@ class EventHandler { /** * Manages all the sockets used for communicating between the plugin and the - * Wine host. Every plugin will get its own directory (the socket endpoint base - * directory), and all socket endpoints are created within this directory. This - * is usually `/run/user//yabridge--/`. + * Wine host when hosting a VST2 plugin. * * On the plugin side this class should be initialized with `listen` set to * `true` before launching the Wine VST host. This will start listening on the @@ -480,7 +478,7 @@ class EventHandler { * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ template -class Vst2Sockets { +class Vst2Sockets : public Sockets { public: /** * Sets up the sockets using the specified base directory. The sockets won't @@ -499,7 +497,7 @@ class Vst2Sockets { Vst2Sockets(boost::asio::io_context& io_context, const boost::filesystem::path& endpoint_base_dir, bool listen) - : base_dir(endpoint_base_dir), + : Sockets(endpoint_base_dir), host_vst_dispatch(io_context, (base_dir / "host_vst_dispatch.sock").string(), listen), @@ -517,36 +515,9 @@ class Vst2Sockets { (base_dir / "host_vst_control.sock").string(), listen) {} - /** - * Cleans up the directory containing the socket endpoints when yabridge - * shuts down if it still exists. - */ - ~Vst2Sockets() { - // Manually close all sockets so we break out of any blocking operations - // that may still be active - host_vst_dispatch.close(); - vst_host_callback.close(); - host_vst_parameters.close(); - host_vst_process_replacing.close(); - host_vst_control.close(); + ~Vst2Sockets() { close(); } - // Only clean if we're the ones who have created these files, although - // it should not cause any harm to also do this on the Wine side - try { - boost::filesystem::remove_all(base_dir); - } catch (const boost::filesystem::filesystem_error&) { - // There should not be any filesystem errors since only one side - // removes the files, but if we somehow can't delete the file - // then we can just silently ignore this - } - } - - /** - * Depending on the value of the `listen` argument passed to the - * constructor, either accept connections made to the sockets on the Linux - * side or connect to the sockets on the Wine side - */ - void connect() { + void connect() override { host_vst_dispatch.connect(); vst_host_callback.connect(); host_vst_parameters.connect(); @@ -554,11 +525,15 @@ class Vst2Sockets { host_vst_control.connect(); } - /** - * The base directory for our socket endpoints. All `*_endpoint` variables - * below are files within this directory. - */ - const boost::filesystem::path base_dir; + void close() override { + // Manually close all sockets so we break out of any blocking operations + // that may still be active + host_vst_dispatch.close(); + vst_host_callback.close(); + host_vst_parameters.close(); + host_vst_process_replacing.close(); + host_vst_control.close(); + } // The naming convention for these sockets is `__`. For // instance the socket named `host_vst_dispatch` forwards diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index b283450b..3af6ef9a 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -87,7 +87,7 @@ void HostProcess::async_log_pipe_lines(patched_async_pipe& pipe, IndividualHost::IndividualHost(boost::asio::io_context& io_context, Logger& logger, fs::path plugin_path, - const Vst2Sockets& sockets) + const Sockets& sockets) : HostProcess(io_context, logger), plugin_arch(find_vst_architecture(plugin_path)), host_path(find_vst_host(plugin_arch, false)), @@ -134,7 +134,7 @@ void IndividualHost::terminate() { GroupHost::GroupHost(boost::asio::io_context& io_context, Logger& logger, fs::path plugin_path, - Vst2Sockets& sockets, + Sockets& sockets, std::string group_name) : HostProcess(io_context, logger), plugin_arch(find_vst_architecture(plugin_path)), @@ -251,8 +251,8 @@ bool GroupHost::running() { void GroupHost::terminate() { // There's no need to manually terminate group host processes as they will // shut down automatically after all plugins have exited. Manually closing - // the dispatch socket will cause the associated plugin to exit. - sockets.host_vst_dispatch.close(); + // the sockets will cause the associated plugin to exit. + sockets.close(); } bool pid_running(pid_t pid) { diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index 4130dc90..8ce79dad 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -25,10 +25,7 @@ #include #include -// TODO: Those host process implementation now directly uses the Vst2Sockets and -// thus requires `communication/vst2.h`. We should create a simple common -// interface for this instead. -#include "../common/communication/vst2.h" +#include "../common/communication/common.h" #include "../common/logging.h" #include "utils.h" @@ -130,7 +127,7 @@ class IndividualHost : public HostProcess { IndividualHost(boost::asio::io_context& io_context, Logger& logger, boost::filesystem::path plugin_path, - const Vst2Sockets& sockets); + const Sockets& sockets); PluginArchitecture architecture() override; boost::filesystem::path path() override; @@ -172,7 +169,7 @@ class GroupHost : public HostProcess { GroupHost(boost::asio::io_context& io_context, Logger& logger, boost::filesystem::path plugin_path, - Vst2Sockets& socket_endpoint, + Sockets& socket_endpoint, std::string group_name); PluginArchitecture architecture() override; @@ -206,7 +203,7 @@ class GroupHost : public HostProcess { * The associated sockets for the plugin we're hosting. This is used to * terminate the plugin. */ - Vst2Sockets& sockets; + Sockets& sockets; /** * A thread that waits for the group host to have started and then ask it to diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 983f9a0f..9ddfe3b0 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -59,7 +59,7 @@ class Vst2Bridge { * @param plugin_dll_path A (Unix style) path to the VST plugin .dll file to * load. * @param endpoint_base_dir The base directory used for the socket - * endpoints. See `Vst2Sockets` for more information. + * endpoints. See `Sockets` for more information. * * @note The object has to be constructed from the same thread that calls * `main_context.run()`. From 555b442f75bc7bc1bf5a95d9ffe51da5d80a7364 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 30 Nov 2020 16:49:51 +0100 Subject: [PATCH 029/456] Add a todo regarding benchmarking ad hoc sockets --- src/common/communication/vst2.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index 9706688c..bdf9f934 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -230,6 +230,10 @@ class EventHandler { // another thread, then we'll spawn a new socket to handle the request. EventResult response; { + // XXX: Maybe at some point we should benchmark how often this + // ad hoc socket spawning mechanism gets used. If some hosts + // for instance consistently and repeatedly trigger this then + // we might be able to do some optimizations there. std::unique_lock lock(write_mutex, std::try_to_lock); if (lock.owns_lock()) { write_object(socket, event); From 5607a643e4cee9ef1d2f8a2fcf1d10f8ded280df Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 30 Nov 2020 18:03:25 +0100 Subject: [PATCH 030/456] Add a generic ad hoc socket listener This is a generalized version of EventHandler. --- src/common/communication/common.h | 313 +++++++++++++++++++++++++++++- 1 file changed, 312 insertions(+), 1 deletion(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 76fdbce3..6d7055a7 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -28,6 +28,8 @@ #include #include +#include "../logging.h" + template using OutputAdapter = bitsery::OutputBufferAdapter; @@ -277,7 +279,8 @@ class SocketHandler { * * @warning This operation is not atomic, and calling this function with the * same socket from multiple threads at once will cause issues with the - * packets arriving out of order. + * packets arriving out of order. The caller is responsible for preventing + * this. * * @see write_object * @see SocketHandler::receive_single @@ -312,6 +315,14 @@ class SocketHandler { * @throw boost::system::system_error If the socket is closed or gets closed * while reading. * + * @note This function can safely be called within the lambda of + * `SocketHandler::receive_multi()`. + * + * @warning This operation is not atomic, and calling this function with the + * same socket from multiple threads at once will cause issues with the + * packets arriving out of order. The caller is responsible for preventing + * this. + * * @relates SocketHandler::send * * @see read_object @@ -377,3 +388,303 @@ class SocketHandler { */ std::optional acceptor; }; + +/** + * There are situations where we can not know in advance how many sockets we + * need. The main example of this are VST2 `dispatcher()` and `audioMaster()` + * calls. These functions can be called from multiple threads at the same time, + * so using a single socket with a mutex to prevent two threads from using the + * socket at the same time would cause issues. Luckily situation does not come + * up that often so to work around it, we'll do two things: + * + * - We'll keep a single long lived socket connection. This works the exact same + * way as every other `SocketHandler` socket. When we want to send data and + * the socket is primary socket is not currently being written to, we'll just + * use that. On the listening side we'll read from this in a loop. + * - On the listening side we also have a second thread asynchronously listening + * for new connections on the socket endpoint. When the sending side wants to + * send data and the primary socket is in use, it will instantiate a new + * connection to same socket endpoint and it will send the data over that + * socket instead. On the listening side the new connection will be accepted, + * and a newly spawned thread will handle incoming connection just like it + * would for the primary socket. + * + * @tparam Thread The thread implementation to use. On the Linux side this + * should be `std::jthread` and on the Wine side this should be `Win32Thread`. + */ +template +class AdHocSocketHandler { + protected: + /** + * Sets up a single primary socket. The sockets won't be active until + * `connect()` gets called. + * + * @param io_context The IO context the primary socket should be bound to. A + * new IO context will be created for accepting the additional incoming + * connections. + * @param endpoint The socket endpoint used for this event handler. + * @param listen If `true`, start listening on the sockets. Incoming + * connections will be accepted when `connect()` gets called. This should + * be set to `true` on the plugin side, and `false` on the Wine host side. + * + * @see Sockets::connect + */ + AdHocSocketHandler(boost::asio::io_context& io_context, + boost::asio::local::stream_protocol::endpoint endpoint, + bool listen) + : io_context(io_context), endpoint(endpoint), socket(io_context) { + if (listen) { + boost::filesystem::create_directories( + boost::filesystem::path(endpoint.path()).parent_path()); + acceptor.emplace(io_context, endpoint); + } + } + + public: + /** + * Depending on the value of the `listen` argument passed to the + * constructor, either accept connections made to the sockets on the Linux + * side or connect to the sockets on the Wine side + */ + void connect() { + if (acceptor) { + acceptor->accept(socket); + + // As mentioned in `acceptor's` docstring, this acceptor will be + // recreated in `receive_multi()` on another context, and + // potentially on the other side of the connection in the case + // where we're handling `vst_host_callback` VST2 events + acceptor.reset(); + boost::filesystem::remove(endpoint.path()); + } else { + socket.connect(endpoint); + } + } + + /** + * Close the socket. Both sides that are actively listening will be thrown a + * `boost::system_error` when this happens. + */ + void close() { + // The shutdown can fail when the socket is already closed + boost::system::error_code err; + socket.shutdown( + boost::asio::local::stream_protocol::socket::shutdown_both, err); + socket.close(); + } + + protected: + /** + * Serialize and send an event over a socket. This is used for both the host + * -> plugin 'dispatch' events and the plugin -> host 'audioMaster' host + * callbacks since they follow the same format. See one of those functions + * for details on the parameters and return value of this function. + * + * As described above, if this function is currently being called from + * another thread, then this will create a new socket connection and send + * the event there instead. + * + * @param callback A function that will be called with a reference to a + * socket. This is either the primary `socket`, or a new ad hock socket if + * this function is currently being called from another thread. + * + * @tparam F A function in the form of + * `void(boost::asio::local::stream_protocol::socket&)`. + */ + template + void send(F callback) { + // XXX: Maybe at some point we should benchmark how often this + // ad hoc socket spawning mechanism gets used. If some hosts + // for instance consistently and repeatedly trigger this then + // we might be able to do some optimizations there. + std::unique_lock lock(write_mutex, std::try_to_lock); + if (lock.owns_lock()) { + callback(socket); + } else { + try { + boost::asio::local::stream_protocol::socket secondary_socket( + io_context); + secondary_socket.connect(endpoint); + + callback(secondary_socket); + } catch (const boost::system::system_error&) { + // So, what do we do when noone is listening on the endpoint + // yet? This can happen with plugin groups when the Wine host + // process does an `audioMaster()` call before the plugin is + // listening. If that happens we'll fall back to a synchronous + // request. This is not very pretty, so if anyone can think of a + // better way to structure all of this while still mainting a + // long living primary socket please let me know. + std::lock_guard lock(write_mutex); + + callback(socket); + } + } + } + + /** + * Spawn a new thread to listen for extra connections to `endpoint`, and + * then a blocking loop that handles incoming data from the primary + * `socket`. + * + * @param logging A pair containing a logger instance and whether or not + * this is for sending `dispatch()` events or host callbacks. Optional + * since it doesn't have to be done on both sides. + * @param primary_callback A function that will do a single read cycle for + * the primary socket socket that should do a single read cycle. This is + * called in a loop so it shouldn't do any looping itself. + * @param secondary_callback A function that will be called when we receive + * an incoming connection on a secondary socket. This would often do the + * same thing as `primary_callback`, but secondary sockets may need some + * different handling. + * + * TODO: Add an overload with a single callback + * + * @tparam F A function type in the form of + * `void(boost::asio::local::stream_protocol::socket&)`. + * @tparam G The same as `F`. + */ + template + void receive_multi(std::optional> logging, + F primary_callback, + G secondary_callback) { + // As described above we'll handle incoming requests for `socket` on + // this thread. We'll also listen for incoming connections on `endpoint` + // on another thread. For any incoming connection we'll spawn a new + // thread to handle the request. When `socket` closes and this loop + // breaks, the listener and any still active threads will be cleaned up + // before this function exits. + boost::asio::io_context secondary_context{}; + + // The previous acceptor has already been shut down by + // `AdHocSocketHandler::connect()` + acceptor.emplace(secondary_context, endpoint); + + // This works the exact same was as `active_plugins` and + // `next_plugin_id` in `GroupBridge` + std::map active_secondary_requests{}; + std::atomic_size_t next_request_id{}; + std::mutex active_secondary_requests_mutex{}; + accept_requests( + *acceptor, logging, + [&](boost::asio::local::stream_protocol::socket secondary_socket) { + const size_t request_id = next_request_id.fetch_add(1); + + // We have to make sure to keep moving these sockets into the + // threads that will handle them + std::lock_guard lock(active_secondary_requests_mutex); + active_secondary_requests[request_id] = Thread( + [&, request_id](boost::asio::local::stream_protocol::socket + secondary_socket) { + secondary_callback(secondary_socket); + + // When we have processed this request, we'll join the + // thread again with the thread that's handling + // `secondary_context` + boost::asio::post(secondary_context, [&, request_id]() { + std::lock_guard lock( + active_secondary_requests_mutex); + + // The join is implicit because we're using + // `std::jthread`/`Win32Thread` + active_secondary_requests.erase(request_id); + }); + }, + std::move(secondary_socket)); + }); + + Thread secondary_requests_handler([&]() { secondary_context.run(); }); + + // Now we'll handle reads on the primary socket in a loop until the + // socket shuts down + while (true) { + try { + primary_callback(socket); + } catch (const boost::system::system_error&) { + // This happens when the sockets got closed because the plugin + // is being shut down + break; + } + } + + // After the primary socket gets terminated (during shutdown) we'll make + // sure all outstanding jobs have been processed and then drop all work + // from the IO context + std::lock_guard lock(active_secondary_requests_mutex); + secondary_context.stop(); + acceptor.reset(); + } + + private: + /** + * Used in `receive_multi()` to asynchronously listen for secondary socket + * connections. After `callback()` returns this function will continue to be + * called until the IO context gets stopped. + * + * @param acceptor The acceptor we will be listening on. + * @param logging A pair containing a logger instance and whether or not + * this is for sending `dispatch()` events or host callbacks. Optional + * since it doesn't have to be done on both sides. + * @param callback A function that handles the new socket connection. + * + * @tparam F A function in the form + * `void(boost::asio::local::stream_protocol::socket)` to handle a new + * incoming connection. + */ + template + void accept_requests( + boost::asio::local::stream_protocol::acceptor& acceptor, + std::optional> logging, + F callback) { + acceptor.async_accept( + [&, logging, callback]( + const boost::system::error_code& error, + boost::asio::local::stream_protocol::socket secondary_socket) { + if (error.failed()) { + // On the Wine side it's expected that the primary socket + // connection will be dropped during shutdown, so we can + // silently ignore any related socket errors on the Wine + // side + if (logging) { + auto [logger, is_dispatch] = *logging; + logger.log("Failure while accepting connections: " + + error.message()); + } + + return; + } + + callback(std::move(secondary_socket)); + + accept_requests(acceptor, logging, callback); + }); + } + + /** + * The main IO context. New sockets created during `send()` will be + * bound to this context. In `receive_multi()` we'll create a new IO context + * since we want to do all listening there on a dedicated thread. + */ + boost::asio::io_context& io_context; + + boost::asio::local::stream_protocol::endpoint endpoint; + boost::asio::local::stream_protocol::socket socket; + + /** + * This acceptor will be used once synchronously on the listening side + * during `Sockets::connect()`. When `AdHocSocketHandler::receive_multi()` + * is then called, we'll recreate the acceptor to asynchronously listen for + * new incoming socket connections on `endpoint` using. This is important, + * because on the case of `Vst2Sockets`'s' `vst_host_callback` the acceptor + * is first accepts an initial socket on the plugin side (like all sockets), + * but all additional incoming connections of course have to be listened for + * on the plugin side. + */ + std::optional acceptor; + + /** + * A mutex that locks the primary `socket`. If this is locked, then any new + * events will be sent over a new socket instead. + */ + std::mutex write_mutex; +}; From 687696ec6b7b786e0cf7ecfbd4732ed1aa7f7a85 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 30 Nov 2020 18:58:24 +0100 Subject: [PATCH 031/456] Reimplement EventHandler on top of AdHocSocketHandler --- src/common/communication/common.h | 13 +- src/common/communication/vst2.h | 242 +++--------------------------- 2 files changed, 25 insertions(+), 230 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 6d7055a7..08349eb2 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -488,25 +488,26 @@ class AdHocSocketHandler { * socket. This is either the primary `socket`, or a new ad hock socket if * this function is currently being called from another thread. * + * @tparam T The return value of F. * @tparam F A function in the form of - * `void(boost::asio::local::stream_protocol::socket&)`. + * `T(boost::asio::local::stream_protocol::socket&)`. */ - template - void send(F callback) { + template + T send(F callback) { // XXX: Maybe at some point we should benchmark how often this // ad hoc socket spawning mechanism gets used. If some hosts // for instance consistently and repeatedly trigger this then // we might be able to do some optimizations there. std::unique_lock lock(write_mutex, std::try_to_lock); if (lock.owns_lock()) { - callback(socket); + return callback(socket); } else { try { boost::asio::local::stream_protocol::socket secondary_socket( io_context); secondary_socket.connect(endpoint); - callback(secondary_socket); + return callback(secondary_socket); } catch (const boost::system::system_error&) { // So, what do we do when noone is listening on the endpoint // yet? This can happen with plugin groups when the Wine host @@ -517,7 +518,7 @@ class AdHocSocketHandler { // long living primary socket please let me know. std::lock_guard lock(write_mutex); - callback(socket); + return callback(socket); } } } diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index bdf9f934..da713612 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -77,10 +77,8 @@ class DefaultDataConverter { }; /** - * So, this is a bit of a mess. The TL;DR is that we want to use a single long - * living socket connection for `dispatch()` and another one for `audioMaster()` - * for performance reasons, but when the socket is already being written to we - * create new connections on demand. + * An instance of `AdHocSocketHandler` that can handle VST2 `dispatcher()` and + * `audioMaster()` events. * * For most of our sockets we can just send out our messages on the writing * side, and do a simple blocking loop on the reading side. The `dispatch()` and @@ -103,14 +101,11 @@ class DefaultDataConverter { * sets up asynchronous listeners for the socket endpoint, and then block and * handle events until the main socket is closed. * - * TODO: Factor out the on-demand socket spawning and handling logic so we can - * reuse most of this for the VST3 implementation - * * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ template -class EventHandler { +class EventHandler : public AdHocSocketHandler { public: /** * Sets up a single main socket for this type of events. The sockets won't @@ -129,45 +124,7 @@ class EventHandler { EventHandler(boost::asio::io_context& io_context, boost::asio::local::stream_protocol::endpoint endpoint, bool listen) - : io_context(io_context), endpoint(endpoint), socket(io_context) { - if (listen) { - boost::filesystem::create_directories( - boost::filesystem::path(endpoint.path()).parent_path()); - acceptor.emplace(io_context, endpoint); - } - } - - /** - * Depending on the value of the `listen` argument passed to the - * constructor, either accept connections made to the sockets on the Linux - * side or connect to the sockets on the Wine side - */ - void connect() { - if (acceptor) { - acceptor->accept(socket); - - // As mentioned in `acceptor's` docstring, this acceptor will be - // recreated in `receive_events()` on another context, and - // potentially on the other side of the connection in the case of - // `vst_host_callback` - acceptor.reset(); - boost::filesystem::remove(endpoint.path()); - } else { - socket.connect(endpoint); - } - } - - /** - * Close the socket. Both sides that are actively listening will be thrown a - * `boost::system_error` when this happens. - */ - void close() { - // The shutdown can fail when the socket is already closed - boost::system::error_code err; - socket.shutdown( - boost::asio::local::stream_protocol::socket::shutdown_both, err); - socket.close(); - } + : AdHocSocketHandler(io_context, endpoint, listen) {} /** * Serialize and send an event over a socket. This is used for both the host @@ -223,45 +180,14 @@ class EventHandler { .value_payload = value_payload}; // A socket only handles a single request at a time as to prevent - // messages from arriving out of order. For throughput reasons we prefer - // to do most communication over a single main socket (`socket`), and - // we'll lock `write_mutex` while doing so. In the event that the mutex - // is already locked and thus the main socket is currently in use by - // another thread, then we'll spawn a new socket to handle the request. - EventResult response; - { - // XXX: Maybe at some point we should benchmark how often this - // ad hoc socket spawning mechanism gets used. If some hosts - // for instance consistently and repeatedly trigger this then - // we might be able to do some optimizations there. - std::unique_lock lock(write_mutex, std::try_to_lock); - if (lock.owns_lock()) { + // messages from arriving out of order. `AdHocSocketHandler::send()` + // will either use a long-living primary socket, or if that's currently + // in use it will spawn a new socket for us. + EventResult response = this->template send( + [&](boost::asio::local::stream_protocol::socket& socket) { write_object(socket, event); - response = read_object(socket); - } else { - try { - boost::asio::local::stream_protocol::socket - secondary_socket(io_context); - secondary_socket.connect(endpoint); - - write_object(secondary_socket, event); - response = read_object(secondary_socket); - } catch (const boost::system::system_error&) { - // So, what do we do when noone is listening on the endpoint - // yet? This can happen with plugin groups when the Wine - // host process does an `audioMaster()` call before the - // plugin is listening. If that happens we'll fall back to a - // synchronous request. This is not very pretty, so if - // anyone can think of a better way to structure all of this - // while still mainting a long living primary socket please - // let me know. - std::lock_guard lock(write_mutex); - - write_object(socket, event); - response = read_object(socket); - } - } - } + return read_object(socket); + }); if (logging) { auto [logger, is_dispatch] = *logging; @@ -278,7 +204,7 @@ class EventHandler { /** * Spawn a new thread to listen for extra connections to `endpoint`, and - * then a blocking loop that handles events from the primary `socket`. + * then start a blocking loop that handles events from the primary `socket`. * * The specified function will be used to create an `EventResult` from an * `Event`. This is almost uses `passthrough_event()`, which converts a @@ -286,10 +212,6 @@ class EventHandler { * `audioMaster()` depending on the context, and then serializes the result * back into an `EventResultPayload`. * - * This function will also be used separately for receiving MIDI data, as - * some plugins will need pointers to received MIDI data to stay alive until - * the next audio buffer gets processed. - * * @param logging A pair containing a logger instance and whether or not * this is for sending `dispatch()` events or host callbacks. Optional * since it doesn't have to be done on both sides. @@ -330,143 +252,15 @@ class EventHandler { write_object(socket, response); }; - // As described above we'll handle incoming requests for `socket` on - // this thread. We'll also listen for incoming connections on `endpoint` - // on another thread. For any incoming connection we'll spawn a new - // thread to handle the request. When `socket` closes and this loop - // breaks, the listener and any still active threads will be cleaned up - // before this function exits. - boost::asio::io_context secondary_context{}; - - // The previous acceptor has already been shut down by - // `EventHandler::connect()` - acceptor.emplace(secondary_context, endpoint); - - // This works the exact same was as `active_plugins` and - // `next_plugin_id` in `GroupBridge` - std::map active_secondary_requests{}; - std::atomic_size_t next_request_id{}; - std::mutex active_secondary_requests_mutex{}; - accept_requests( - *acceptor, logging, - [&](boost::asio::local::stream_protocol::socket secondary_socket) { - const size_t request_id = next_request_id.fetch_add(1); - - // We have to make sure to keep moving these sockets into the - // threads that will handle them - std::lock_guard lock(active_secondary_requests_mutex); - active_secondary_requests[request_id] = Thread( - [&, request_id](boost::asio::local::stream_protocol::socket - secondary_socket) { - process_event(secondary_socket, false); - - // When we have processed this request, we'll join the - // thread again with the thread that's handling - // `secondary_context`. - boost::asio::post(secondary_context, [&, request_id]() { - std::lock_guard lock( - active_secondary_requests_mutex); - - // The join is implicit because we're using - // std::jthread/Win32Thread - active_secondary_requests.erase(request_id); - }); - }, - std::move(secondary_socket)); - }); - - Thread secondary_requests_handler([&]() { secondary_context.run(); }); - - while (true) { - try { + this->receive_multi( + logging, + [&](boost::asio::local::stream_protocol::socket& socket) { process_event(socket, true); - } catch (const boost::system::system_error&) { - // This happens when the sockets got closed because the plugin - // is being shut down - break; - } - } - - // After the main socket gets terminated (during shutdown) we'll make - // sure all outstanding jobs have been processed and then drop all work - // from the IO context - std::lock_guard lock(active_secondary_requests_mutex); - secondary_context.stop(); - acceptor.reset(); - } - - private: - /** - * Used in `receive_events()` to asynchronously listen for secondary socket - * connections. After `callback()` returns this function will continue to be - * called until the IO context gets stopped. - * - * @param acceptor The acceptor we will be listening on. - * @param logging A pair containing a logger instance and whether or not - * this is for sending `dispatch()` events or host callbacks. Optional - * since it doesn't have to be done on both sides. - * @param callback A function that handles the new socket connection. - * - * @tparam F A function in the form - * `void(boost::asio::local::stream_protocol::socket)` to handle a new - * incoming connection. - */ - template - void accept_requests( - boost::asio::local::stream_protocol::acceptor& acceptor, - std::optional> logging, - F callback) { - acceptor.async_accept( - [&, logging, callback]( - const boost::system::error_code& error, - boost::asio::local::stream_protocol::socket secondary_socket) { - if (error.failed()) { - // On the Wine side it's expected that the main socket - // connection will be dropped during shutdown, so we can - // silently ignore any related socket errors on the Wine - // side - if (logging) { - auto [logger, is_dispatch] = *logging; - logger.log("Failure while accepting connections: " + - error.message()); - } - - return; - } - - callback(std::move(secondary_socket)); - - accept_requests(acceptor, logging, callback); + }, + [&](boost::asio::local::stream_protocol::socket& socket) { + process_event(socket, false); }); } - - /** - * The main IO context. New sockets created during `send_event()` will be - * bound to this context. In `receive_events()` we'll create a new IO - * context since we want to do all listening there on a dedicated thread. - */ - boost::asio::io_context& io_context; - - boost::asio::local::stream_protocol::endpoint endpoint; - boost::asio::local::stream_protocol::socket socket; - - /** - * This acceptor will be used once synchronously on the listening side - * during `Sockets::connect()`. When `EventHandler::receive_events()` is - * then called, we'll recreate the acceptor to asynchronously listen for new - * incoming socket connections on `endpoint` using. This is important, - * because on the case of `vst_host_callback` the acceptor is first accepts - * an initial socket on the plugin side (like all sockets), but all - * additional incoming connections of course have to be listened for on the - * plugin side. - */ - std::optional acceptor; - - /** - * A mutex that locks the main `socket`. If this is locked, then any new - * events will be sent over a new socket instead. - */ - std::mutex write_mutex; }; /** From 7fc7a51a4677d8ea6f19dcd411e3c8d3e34602e9 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 30 Nov 2020 21:24:41 +0100 Subject: [PATCH 032/456] Move PluginArchitecture to common Along with the accompanying function to parse a PE32 file. We're going to have to define some common tags for different plugin types. --- meson.build | 1 + src/common/plugins.cpp | 53 +++++++++++++++++++++++++++++++++++++++ src/common/plugins.h | 44 ++++++++++++++++++++++++++++++++ src/plugin/host-process.h | 1 + src/plugin/utils.cpp | 48 ----------------------------------- src/plugin/utils.h | 22 +--------------- 6 files changed, 100 insertions(+), 69 deletions(-) create mode 100644 src/common/plugins.cpp create mode 100644 src/common/plugins.h diff --git a/meson.build b/meson.build index f1811ada..3fcb0a4a 100644 --- a/meson.build +++ b/meson.build @@ -85,6 +85,7 @@ shared_library( 'src/common/serialization/vst2.cpp', 'src/common/configuration.cpp', 'src/common/logging.cpp', + 'src/common/plugins.cpp', 'src/common/utils.cpp', 'src/plugin/bridges/vst2.cpp', 'src/plugin/host-process.cpp', diff --git a/src/common/plugins.cpp b/src/common/plugins.cpp new file mode 100644 index 00000000..449cd21c --- /dev/null +++ b/src/common/plugins.cpp @@ -0,0 +1,53 @@ +#include "plugins.h" + +#include +#include + +namespace fs = boost::filesystem; + +PluginArchitecture find_vst_architecture(fs::path plugin_path) { + std::ifstream file(plugin_path, std::ifstream::binary | std::ifstream::in); + + // The linker will place the offset where the PE signature is placed at the + // end of the MS-DOS stub, at offset 0x3c + uint32_t pe_signature_offset; + file.seekg(0x3c); + file.read(reinterpret_cast(&pe_signature_offset), + sizeof(pe_signature_offset)); + + // The PE32 signature will be followed by a magic number that indicates the + // target architecture of the binary + uint32_t pe_signature; + uint16_t machine_type; + file.seekg(pe_signature_offset); + file.read(reinterpret_cast(&pe_signature), sizeof(pe_signature)); + file.read(reinterpret_cast(&machine_type), sizeof(machine_type)); + + constexpr char expected_pe_signature[4] = {'P', 'E', '\0', '\0'}; + if (pe_signature != + *reinterpret_cast(expected_pe_signature)) { + throw std::runtime_error("'" + plugin_path.string() + + "' is not a valid .dll file"); + } + + // These constants are specified in + // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types + switch (machine_type) { + case 0x014c: // IMAGE_FILE_MACHINE_I386 + return PluginArchitecture::vst_32; + break; + case 0x8664: // IMAGE_FILE_MACHINE_AMD64 + case 0x0000: // IMAGE_FILE_MACHINE_UNKNOWN + return PluginArchitecture::vst_64; + break; + } + + // When compiled without optimizations, GCC 9.3 will warn that the function + // does not return if we put this in a `default:` case instead. + std::ostringstream error_msg; + error_msg << "'" << plugin_path + << "' is neither a x86 nor a x86_64 PE32 file. Actual " + "architecture: 0x" + << std::hex << machine_type; + throw std::runtime_error(error_msg.str()); +} diff --git a/src/common/plugins.h b/src/common/plugins.h new file mode 100644 index 00000000..478b9e75 --- /dev/null +++ b/src/common/plugins.h @@ -0,0 +1,44 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#ifdef __WINE__ +#include "../wine-host/boost-fix.h" +#endif +#include + +// Utilities and tags for plugin types and architectures + +/** + * A tag to differentiate between 32 and 64-bit plugins, used to determine which + * host application to use. + */ +enum class PluginArchitecture { vst_32, vst_64 }; + +/** + * Determine the architecture of a VST plugin (or rather, a .dll file) based on + * it's header values. + * + * See https://docs.microsoft.com/en-us/windows/win32/debug/pe-format for more + * information on the PE32 format. + * + * @param plugin_path The path to the .dll file we're going to check. + * + * @return The detected architecture. + * @throw std::runtime_error If the file is not a .dll file. + */ +PluginArchitecture find_vst_architecture(boost::filesystem::path); diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index 8ce79dad..299dab72 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -27,6 +27,7 @@ #include "../common/communication/common.h" #include "../common/logging.h" +#include "../common/plugins.h" #include "utils.h" /** diff --git a/src/plugin/utils.cpp b/src/plugin/utils.cpp index c0b7eba6..ab14641e 100644 --- a/src/plugin/utils.cpp +++ b/src/plugin/utils.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include // Generated inside of the build directory @@ -55,53 +54,6 @@ std::optional find_wineprefix() { return dosdevices_dir->parent_path(); } -PluginArchitecture find_vst_architecture(fs::path plugin_path) { - std::ifstream file(plugin_path, std::ifstream::binary | std::ifstream::in); - - // The linker will place the offset where the PE signature is placed at the - // end of the MS-DOS stub, at offset 0x3c - uint32_t pe_signature_offset; - file.seekg(0x3c); - file.read(reinterpret_cast(&pe_signature_offset), - sizeof(pe_signature_offset)); - - // The PE32 signature will be followed by a magic number that indicates the - // target architecture of the binary - uint32_t pe_signature; - uint16_t machine_type; - file.seekg(pe_signature_offset); - file.read(reinterpret_cast(&pe_signature), sizeof(pe_signature)); - file.read(reinterpret_cast(&machine_type), sizeof(machine_type)); - - constexpr char expected_pe_signature[4] = {'P', 'E', '\0', '\0'}; - if (pe_signature != - *reinterpret_cast(expected_pe_signature)) { - throw std::runtime_error("'" + plugin_path.string() + - "' is not a valid .dll file"); - } - - // These constants are specified in - // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types - switch (machine_type) { - case 0x014c: // IMAGE_FILE_MACHINE_I386 - return PluginArchitecture::vst_32; - break; - case 0x8664: // IMAGE_FILE_MACHINE_AMD64 - case 0x0000: // IMAGE_FILE_MACHINE_UNKNOWN - return PluginArchitecture::vst_64; - break; - } - - // When compiled without optimizations, GCC 9.3 will warn that the function - // does not return if we put this in a `default:` case instead. - std::ostringstream error_msg; - error_msg << "'" << plugin_path - << "' is neither a x86 nor a x86_64 PE32 file. Actual " - "architecture: 0x" - << std::hex << machine_type; - throw std::runtime_error(error_msg.str()); -} - fs::path find_vst_host(PluginArchitecture plugin_arch, bool use_plugin_groups) { auto host_name = use_plugin_groups ? yabridge_group_host_name : yabridge_individual_host_name; diff --git a/src/plugin/utils.h b/src/plugin/utils.h index 93bf5c77..67d32bf2 100644 --- a/src/plugin/utils.h +++ b/src/plugin/utils.h @@ -16,11 +16,11 @@ #pragma once -#include #include #include #include "../common/configuration.h" +#include "../common/plugins.h" /** * Boost 1.72 was released with a known breaking bug caused by a missing @@ -39,12 +39,6 @@ class patched_async_pipe : public boost::process::async_pipe { typedef typename handle_type::executor_type executor_type; }; -/** - * A tag to differentiate between 32 and 64-bit plugins, used to determine which - * host application to use. - */ -enum class PluginArchitecture { vst_32, vst_64 }; - /** * Create a logger prefix based on the endpoint base directory used for the * sockets for easy identification. This will result in a prefix of the form @@ -58,20 +52,6 @@ enum class PluginArchitecture { vst_32, vst_64 }; std::string create_logger_prefix( const boost::filesystem::path& endpoint_base_dir); -/** - * Determine the architecture of a VST plugin (or rather, a .dll file) based on - * it's header values. - * - * See https://docs.microsoft.com/en-us/windows/win32/debug/pe-format for more - * information on the PE32 format. - * - * @param plugin_path The path to the .dll file we're going to check. - * - * @return The detected architecture. - * @throw std::runtime_error If the file is not a .dll file. - */ -PluginArchitecture find_vst_architecture(boost::filesystem::path); - /** * Finds the Wine VST host (either `yabridge-host.exe` or `yabridge-host.exe` * depending on the plugin). For this we will search in two places: From 47baef3107e92f27bae4c239cb4f89c887ed5ce4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 30 Nov 2020 21:29:57 +0100 Subject: [PATCH 033/456] Rename architecture related functions and structs --- src/common/plugins.cpp | 6 +++--- src/common/plugins.h | 13 ++++++------- src/plugin/bridges/vst2.cpp | 2 +- src/plugin/host-process.cpp | 8 ++++---- src/plugin/host-process.h | 10 +++++----- src/plugin/utils.cpp | 10 +++++----- src/plugin/utils.h | 4 ++-- 7 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/common/plugins.cpp b/src/common/plugins.cpp index 449cd21c..1aae7397 100644 --- a/src/common/plugins.cpp +++ b/src/common/plugins.cpp @@ -5,7 +5,7 @@ namespace fs = boost::filesystem; -PluginArchitecture find_vst_architecture(fs::path plugin_path) { +LibArchitecture find_dll_architecture(fs::path plugin_path) { std::ifstream file(plugin_path, std::ifstream::binary | std::ifstream::in); // The linker will place the offset where the PE signature is placed at the @@ -34,11 +34,11 @@ PluginArchitecture find_vst_architecture(fs::path plugin_path) { // https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types switch (machine_type) { case 0x014c: // IMAGE_FILE_MACHINE_I386 - return PluginArchitecture::vst_32; + return LibArchitecture::dll_32; break; case 0x8664: // IMAGE_FILE_MACHINE_AMD64 case 0x0000: // IMAGE_FILE_MACHINE_UNKNOWN - return PluginArchitecture::vst_64; + return LibArchitecture::dll_64; break; } diff --git a/src/common/plugins.h b/src/common/plugins.h index 478b9e75..2dd310dd 100644 --- a/src/common/plugins.h +++ b/src/common/plugins.h @@ -24,21 +24,20 @@ // Utilities and tags for plugin types and architectures /** - * A tag to differentiate between 32 and 64-bit plugins, used to determine which - * host application to use. + * A tag to differentiate between 32 and 64-bit `.dll` files, used to determine + * which host application to use. */ -enum class PluginArchitecture { vst_32, vst_64 }; +enum class LibArchitecture { dll_32, dll_64 }; /** - * Determine the architecture of a VST plugin (or rather, a .dll file) based on - * it's header values. + * Determine the architecture of a `.dll` file based on the file header. * * See https://docs.microsoft.com/en-us/windows/win32/debug/pe-format for more * information on the PE32 format. * - * @param plugin_path The path to the .dll file we're going to check. + * @param path The path to the .dll file we're going to check. * * @return The detected architecture. * @throw std::runtime_error If the file is not a .dll file. */ -PluginArchitecture find_vst_architecture(boost::filesystem::path); +LibArchitecture find_dll_architecture(boost::filesystem::path); diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index 4572bab2..c4db6042 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -656,7 +656,7 @@ void Vst2PluginBridge::log_init_message() { } else { init_msg << "individually"; } - if (vst_host->architecture() == PluginArchitecture::vst_32) { + if (vst_host->architecture() == LibArchitecture::dll_32) { init_msg << ", 32-bit"; } else { init_msg << ", 64-bit"; diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index 3af6ef9a..94e67563 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -89,7 +89,7 @@ IndividualHost::IndividualHost(boost::asio::io_context& io_context, fs::path plugin_path, const Sockets& sockets) : HostProcess(io_context, logger), - plugin_arch(find_vst_architecture(plugin_path)), + plugin_arch(find_dll_architecture(plugin_path)), host_path(find_vst_host(plugin_arch, false)), host(launch_host(host_path, #ifdef WITH_WINEDBG @@ -114,7 +114,7 @@ IndividualHost::IndividualHost(boost::asio::io_context& io_context, #endif } -PluginArchitecture IndividualHost::architecture() { +LibArchitecture IndividualHost::architecture() { return plugin_arch; } @@ -137,7 +137,7 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, Sockets& sockets, std::string group_name) : HostProcess(io_context, logger), - plugin_arch(find_vst_architecture(plugin_path)), + plugin_arch(find_dll_architecture(plugin_path)), host_path(find_vst_host(plugin_arch, true)), sockets(sockets) { #ifdef WITH_WINEDBG @@ -233,7 +233,7 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, } } -PluginArchitecture GroupHost::architecture() { +LibArchitecture GroupHost::architecture() { return plugin_arch; } diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index 299dab72..ca0dc25e 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -44,7 +44,7 @@ class HostProcess { * Return the architecture of the plugin we are loading, i.e. whether it is * 32-bit or 64-bit. */ - virtual PluginArchitecture architecture() = 0; + virtual LibArchitecture architecture() = 0; /** * Return the full path to the host application in use. The host application @@ -130,13 +130,13 @@ class IndividualHost : public HostProcess { boost::filesystem::path plugin_path, const Sockets& sockets); - PluginArchitecture architecture() override; + LibArchitecture architecture() override; boost::filesystem::path path() override; bool running() override; void terminate() override; private: - PluginArchitecture plugin_arch; + LibArchitecture plugin_arch; boost::filesystem::path host_path; boost::process::child host; }; @@ -173,13 +173,13 @@ class GroupHost : public HostProcess { Sockets& socket_endpoint, std::string group_name); - PluginArchitecture architecture() override; + LibArchitecture architecture() override; boost::filesystem::path path() override; bool running() override; void terminate() override; private: - PluginArchitecture plugin_arch; + LibArchitecture plugin_arch; boost::filesystem::path host_path; /** diff --git a/src/plugin/utils.cpp b/src/plugin/utils.cpp index ab14641e..a9e7d894 100644 --- a/src/plugin/utils.cpp +++ b/src/plugin/utils.cpp @@ -54,10 +54,10 @@ std::optional find_wineprefix() { return dosdevices_dir->parent_path(); } -fs::path find_vst_host(PluginArchitecture plugin_arch, bool use_plugin_groups) { +fs::path find_vst_host(LibArchitecture plugin_arch, bool use_plugin_groups) { auto host_name = use_plugin_groups ? yabridge_group_host_name : yabridge_individual_host_name; - if (plugin_arch == PluginArchitecture::vst_32) { + if (plugin_arch == LibArchitecture::dll_32) { host_name = use_plugin_groups ? yabridge_group_host_name_32bit : yabridge_individual_host_name_32bit; } @@ -110,17 +110,17 @@ fs::path find_vst_plugin() { boost::filesystem::path generate_group_endpoint( const std::string& group_name, const boost::filesystem::path& wine_prefix, - const PluginArchitecture architecture) { + const LibArchitecture architecture) { std::ostringstream socket_name; socket_name << "yabridge-group-" << group_name << "-" << std::to_string( std::hash{}(wine_prefix.string())) << "-"; switch (architecture) { - case PluginArchitecture::vst_32: + case LibArchitecture::dll_32: socket_name << "x32"; break; - case PluginArchitecture::vst_64: + case LibArchitecture::dll_64: socket_name << "x64"; break; } diff --git a/src/plugin/utils.h b/src/plugin/utils.h index 67d32bf2..c68175be 100644 --- a/src/plugin/utils.h +++ b/src/plugin/utils.h @@ -71,7 +71,7 @@ std::string create_logger_prefix( * @return The a path to the VST host, if found. * @throw std::runtime_error If the Wine VST host could not be found. */ -boost::filesystem::path find_vst_host(PluginArchitecture plugin_arch, +boost::filesystem::path find_vst_host(LibArchitecture plugin_arch, bool use_plugin_groups); /** @@ -125,7 +125,7 @@ std::optional find_wineprefix(); boost::filesystem::path generate_group_endpoint( const std::string& group_name, const boost::filesystem::path& wine_prefix, - const PluginArchitecture architecture); + const LibArchitecture architecture); /** * Return the search path as defined in `$PATH`, with `~/.local/share/yabridge` From 67388dc2a6f1236e3c0556052e69d992fbba1e83 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 30 Nov 2020 21:51:28 +0100 Subject: [PATCH 034/456] Add a plugin type tag and conversion functions --- src/common/plugins.cpp | 22 +++++++++++++++++++++- src/common/plugins.h | 18 +++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/common/plugins.cpp b/src/common/plugins.cpp index 1aae7397..b545abc6 100644 --- a/src/common/plugins.cpp +++ b/src/common/plugins.cpp @@ -5,7 +5,7 @@ namespace fs = boost::filesystem; -LibArchitecture find_dll_architecture(fs::path plugin_path) { +LibArchitecture find_dll_architecture(const fs::path& plugin_path) { std::ifstream file(plugin_path, std::ifstream::binary | std::ifstream::in); // The linker will place the offset where the PE signature is placed at the @@ -51,3 +51,23 @@ LibArchitecture find_dll_architecture(fs::path plugin_path) { << std::hex << machine_type; throw std::runtime_error(error_msg.str()); } + +PluginType plugin_type_from_string(const std::string& plugin_type) { + if (plugin_type == "vst2") { + return PluginType::vst2; + } else if (plugin_type == "vst3") { + return PluginType::vst3; + } else { + return PluginType::unknown; + } +} + +std::string plugin_type_to_string(const PluginType& plugin_type) { + if (plugin_type == PluginType::vst2) { + return "vst2"; + } else if (plugin_type == PluginType::vst3) { + return "vst3"; + } else { + return "unknown"; + } +} diff --git a/src/common/plugins.h b/src/common/plugins.h index 2dd310dd..37f69b7a 100644 --- a/src/common/plugins.h +++ b/src/common/plugins.h @@ -29,6 +29,19 @@ */ enum class LibArchitecture { dll_32, dll_64 }; +/** + * A tag to differentiate between different plugin types. + * `plugin_tyep_to_string()` and `plugin_type_from_string()` can be used to + * convert these values to and from strings. The string form is used as a + * command line argument for the individual Wine host applications, and the enum + * form is passed directly in `GroupRequest`. + * + * The `unkonwn` tag is not used directly, but in the event that we do call + * `plugin_type_from_string()` with some invalid value we can use it to + * gracefully show an error message without resorting to exceptions. + */ +enum class PluginType { vst2, vst3, unknown }; + /** * Determine the architecture of a `.dll` file based on the file header. * @@ -40,4 +53,7 @@ enum class LibArchitecture { dll_32, dll_64 }; * @return The detected architecture. * @throw std::runtime_error If the file is not a .dll file. */ -LibArchitecture find_dll_architecture(boost::filesystem::path); +LibArchitecture find_dll_architecture(const boost::filesystem::path&); + +PluginType plugin_type_from_string(const std::string& plugin_type); +std::string plugin_type_to_string(const PluginType& plugin_type); From 1142c908df577cf1f1b541575bf468aa2a597b73 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 30 Nov 2020 22:53:08 +0100 Subject: [PATCH 035/456] Add serialization support for PluginType --- src/common/plugins.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common/plugins.h b/src/common/plugins.h index 37f69b7a..55778726 100644 --- a/src/common/plugins.h +++ b/src/common/plugins.h @@ -42,6 +42,11 @@ enum class LibArchitecture { dll_32, dll_64 }; */ enum class PluginType { vst2, vst3, unknown }; +template +void serialize(S& s, PluginType& plugin_type) { + s.value4b(plugin_type); +} + /** * Determine the architecture of a `.dll` file based on the file header. * From f9bb3822dead4a3f91edeab2f37c616a39a59d8f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 30 Nov 2020 23:04:28 +0100 Subject: [PATCH 036/456] Pass plugin type when calling the host application --- src/common/plugins.cpp | 12 ++++++----- src/common/serialization/common.h | 4 ++++ src/plugin/host-process.cpp | 6 +++++- src/wine-host/bridges/group.cpp | 3 +++ src/wine-host/bridges/vst2.h | 3 +++ src/wine-host/individual-host.cpp | 35 ++++++++++++++++++------------- 6 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/common/plugins.cpp b/src/common/plugins.cpp index b545abc6..abca7b49 100644 --- a/src/common/plugins.cpp +++ b/src/common/plugins.cpp @@ -53,9 +53,9 @@ LibArchitecture find_dll_architecture(const fs::path& plugin_path) { } PluginType plugin_type_from_string(const std::string& plugin_type) { - if (plugin_type == "vst2") { + if (plugin_type == "VST2") { return PluginType::vst2; - } else if (plugin_type == "vst3") { + } else if (plugin_type == "VST3") { return PluginType::vst3; } else { return PluginType::unknown; @@ -63,11 +63,13 @@ PluginType plugin_type_from_string(const std::string& plugin_type) { } std::string plugin_type_to_string(const PluginType& plugin_type) { + // We'll capitalize the acronyms because this is also our human readable + // format if (plugin_type == PluginType::vst2) { - return "vst2"; + return "VST2"; } else if (plugin_type == PluginType::vst3) { - return "vst3"; + return "VST3"; } else { - return "unknown"; + return ""; } } diff --git a/src/common/serialization/common.h b/src/common/serialization/common.h index 7c9f60bc..bd8f4d54 100644 --- a/src/common/serialization/common.h +++ b/src/common/serialization/common.h @@ -22,6 +22,8 @@ #include #include +#include "../plugins.h" + // The plugin should always be compiled to a 64-bit version, but the host // application can also be 32-bit to allow using 32-bit legacy Windows VST in a // modern Linux VST host. Because of this we have to make sure to always use @@ -54,11 +56,13 @@ overload(Ts...) -> overload; * to `yabridge-host.exe` were the plugin to be hosted individually. */ struct GroupRequest { + PluginType plugin_type; std::string plugin_path; std::string endpoint_base_dir; template void serialize(S& s) { + s.object(plugin_type); s.text1b(plugin_path, 4096); s.text1b(endpoint_base_dir, 4096); } diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index 94e67563..1d949d3e 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -92,6 +92,8 @@ IndividualHost::IndividualHost(boost::asio::io_context& io_context, plugin_arch(find_dll_architecture(plugin_path)), host_path(find_vst_host(plugin_arch, false)), host(launch_host(host_path, + // TODO: This should of course not be hardcoded + plugin_type_to_string(PluginType::vst2), #ifdef WITH_WINEDBG plugin_path.filename(), #else @@ -180,7 +182,9 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, write_object( group_socket, - GroupRequest{.plugin_path = plugin_path.string(), + // TODO: The plugin type should of course not be hardcoded + GroupRequest{.plugin_type = PluginType::vst2, + .plugin_path = plugin_path.string(), .endpoint_base_dir = endpoint_base_dir.string()}); const auto response = read_object(group_socket); assert(response.pid > 0); diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index 4e0c1d4d..ca4f757c 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -181,6 +181,9 @@ void GroupBridge::accept_requests() { // yabridge plugin will be able to tell if the plugin has caused // this process to crash during its initialization to prevent // waiting indefinitely on the sockets to be connected to. + // TODO: Do something with the plugin type + // TODO: Maybe try to merge instantiation with `individual_host`? + // Might only make things messier const auto request = read_object(socket); write_object(socket, GroupResponse{boost::this_process::get_id()}); diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 9ddfe3b0..0cb68926 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -66,6 +66,9 @@ class Vst2Bridge { * * @throw std::runtime_error Thrown when the VST plugin could not be loaded, * or if communication could not be set up. + * + * TODO: Make these two arguments `boost::filesystem::path`, also use those + * in `GroupRequest`. */ Vst2Bridge(MainContext& main_context, std::string plugin_dll_path, diff --git a/src/wine-host/individual-host.cpp b/src/wine-host/individual-host.cpp index 3beff56b..7746db1b 100644 --- a/src/wine-host/individual-host.cpp +++ b/src/wine-host/individual-host.cpp @@ -25,9 +25,9 @@ #include "bridges/vst2.h" /** - * This is the default VST host application. It will load the specified VST2 - * plugin, and then connect back to the `libyabridge-{vst2,vst3}.so` instance - * that spawned this over the socket. + * This is the default plugin host application. It will load the specified + * plugin plugin, and then connect back to the `libyabridge-{vst2,vst3}.so` + * instance that spawned this over the socket. * * The explicit calling convention is needed to work around a bug introduced in * Wine 5.7: https://bugs.winehq.org/show_bug.cgi?id=49138 @@ -36,23 +36,28 @@ int __cdecl __attribute__((visibility("default"))) main(int argc, char* argv[]) { set_realtime_priority(); - // We pass the name of the VST plugin .dll file to load and the base - // directory for the Unix domain socket endpoints to connect to as the first - // two arguments of this process in plugin/bridge.cpp. - if (argc < 3) { - std::cerr << "Usage: " + // We pass plugin format, the name of the VST2 plugin .dll file or VST3 + // bundle to load, and the base directory for the Unix domain socket + // endpoints to connect to as the first two arguments of this process in + // `src/plugin/host-process.cpp` + if (argc < 4) { + std::cerr + << "Usage: " #ifdef __i386__ - << yabridge_individual_host_name_32bit + << yabridge_individual_host_name_32bit #else - << yabridge_individual_host_name + << yabridge_individual_host_name #endif - << " " << std::endl; + << " " + << std::endl; return 1; } - const std::string plugin_dll_path(argv[1]); - const std::string socket_endpoint_path(argv[2]); + // TODO: Do something with the plugin type + const PluginType plugin_type = plugin_type_from_string(argv[1]); + const std::string plugin_location(argv[2]); + const std::string socket_endpoint_path(argv[3]); std::cout << "Initializing yabridge host version " << yabridge_git_version #ifdef __i386__ @@ -68,7 +73,7 @@ main(int argc, char* argv[]) { MainContext main_context{}; std::unique_ptr bridge; try { - bridge = std::make_unique(main_context, plugin_dll_path, + bridge = std::make_unique(main_context, plugin_location, socket_endpoint_path); } catch (const std::runtime_error& error) { std::cerr << "Error while initializing Wine VST host:" << std::endl; @@ -77,7 +82,7 @@ main(int argc, char* argv[]) { return 1; } - std::cout << "Finished initializing '" << plugin_dll_path << "'" + std::cout << "Finished initializing '" << plugin_location << "'" << std::endl; // We'll listen for `dispatcher()` calls on a different thread, but the From 1c5a9423d2496d0c71912c81172b1bc219549ef5 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 30 Nov 2020 23:10:50 +0100 Subject: [PATCH 037/456] Print the plugin type on initialization --- meson.build | 1 + src/plugin/bridges/vst2.cpp | 1 + src/wine-host/bridges/group.cpp | 4 +++- src/wine-host/individual-host.cpp | 5 +++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 3fcb0a4a..2fa69ec7 100644 --- a/meson.build +++ b/meson.build @@ -229,6 +229,7 @@ host_sources = [ 'src/common/serialization/vst2.cpp', 'src/common/configuration.cpp', 'src/common/logging.cpp', + 'src/common/plugins.cpp', 'src/common/utils.cpp', 'src/wine-host/bridges/vst2.cpp', 'src/wine-host/editor.cpp', diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index c4db6042..b539b91f 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -617,6 +617,7 @@ void Vst2PluginBridge::set_parameter(AEffect* /*plugin*/, void Vst2PluginBridge::log_init_message() { std::stringstream init_msg; + // TODO: This should also list the plugin type init_msg << "Initializing yabridge version " << yabridge_git_version << std::endl; init_msg << "host: '" << vst_host->path().string() << "'" diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index ca4f757c..fd7f6d2f 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -190,7 +190,9 @@ void GroupBridge::accept_requests() { // The plugin has to be initiated on the IO context's thread because // this has to be done on the same thread that's handling messages, // and all window messages have to be handled from the same thread. - logger.log("Received request to host '" + request.plugin_path + + logger.log("Received request to host " + + plugin_type_to_string(request.plugin_type) + + " plugin at '" + request.plugin_path + "' using socket endpoint base directory '" + request.endpoint_base_dir + "'"); try { diff --git a/src/wine-host/individual-host.cpp b/src/wine-host/individual-host.cpp index 7746db1b..a31fca60 100644 --- a/src/wine-host/individual-host.cpp +++ b/src/wine-host/individual-host.cpp @@ -55,6 +55,9 @@ main(int argc, char* argv[]) { } // TODO: Do something with the plugin type + // TODO: On the Wine side of things, we should only allow hosting VST3 + // plugins when the Meson build option is enabled (because, well, + // otherwise we'd get compile errors) const PluginType plugin_type = plugin_type_from_string(argv[1]); const std::string plugin_location(argv[2]); const std::string socket_endpoint_path(argv[3]); @@ -64,6 +67,8 @@ main(int argc, char* argv[]) { << " (32-bit compatibility mode)" #endif << std::endl; + std::cout << "Preparing to load " << plugin_type_to_string(plugin_type) + << " plugin at '" << plugin_location << "'" << std::endl; // As explained in `Vst2Bridge`, the plugin has to be initialized in the // same thread as the one that calls `io_context.run()`. This setup is From e21d3e020fb5909ec135d19cf2f01a8a8d6f8f93 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 30 Nov 2020 23:15:42 +0100 Subject: [PATCH 038/456] Rename GroupRequest to HostRequest We'll also use this to encode information in when launching `yabridge-host.exe` for individually hosted plugins. --- src/common/plugins.h | 2 +- src/common/serialization/common.h | 16 ++++++++-------- src/plugin/host-process.cpp | 8 ++++---- src/wine-host/bridges/group.cpp | 4 ++-- src/wine-host/bridges/vst2.h | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/common/plugins.h b/src/common/plugins.h index 55778726..86d4c5be 100644 --- a/src/common/plugins.h +++ b/src/common/plugins.h @@ -34,7 +34,7 @@ enum class LibArchitecture { dll_32, dll_64 }; * `plugin_tyep_to_string()` and `plugin_type_from_string()` can be used to * convert these values to and from strings. The string form is used as a * command line argument for the individual Wine host applications, and the enum - * form is passed directly in `GroupRequest`. + * form is passed directly in `HostRequest`. * * The `unkonwn` tag is not used directly, but in the event that we do call * `plugin_type_from_string()` with some invalid value we can use it to diff --git a/src/common/serialization/common.h b/src/common/serialization/common.h index bd8f4d54..5b77082c 100644 --- a/src/common/serialization/common.h +++ b/src/common/serialization/common.h @@ -51,11 +51,11 @@ template overload(Ts...) -> overload; /** - * An object containing the startup options for hosting a plugin in a plugin - * group process. These are the exact same options that would have been passed - * to `yabridge-host.exe` were the plugin to be hosted individually. + * An object containing the startup options for hosting a plugin. These options + * are passed to `yabridge-host.exe` as command line arguments, and they are + * used directly by group host processes. */ -struct GroupRequest { +struct HostRequest { PluginType plugin_type; std::string plugin_path; std::string endpoint_base_dir; @@ -69,8 +69,8 @@ struct GroupRequest { }; template <> -struct std::hash { - std::size_t operator()(GroupRequest const& params) const noexcept { +struct std::hash { + std::size_t operator()(HostRequest const& params) const noexcept { std::hash hasher{}; return hasher(params.plugin_path) ^ @@ -79,12 +79,12 @@ struct std::hash { }; /** - * The response sent back after the group host process receives a `GroupRequest` + * The response sent back after the group host process receives a `HostRequest` * object. This only holds the group process's PID because we need to know if * the group process crashes while it is initializing the plugin to prevent us * from waiting indefinitely for the socket to be connected to. */ -struct GroupResponse { +struct HostResponse { pid_t pid; template diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index 1d949d3e..b0f83c37 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -183,10 +183,10 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, write_object( group_socket, // TODO: The plugin type should of course not be hardcoded - GroupRequest{.plugin_type = PluginType::vst2, - .plugin_path = plugin_path.string(), - .endpoint_base_dir = endpoint_base_dir.string()}); - const auto response = read_object(group_socket); + HostRequest{.plugin_type = PluginType::vst2, + .plugin_path = plugin_path.string(), + .endpoint_base_dir = endpoint_base_dir.string()}); + const auto response = read_object(group_socket); assert(response.pid > 0); }; diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index fd7f6d2f..3fee24da 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -184,8 +184,8 @@ void GroupBridge::accept_requests() { // TODO: Do something with the plugin type // TODO: Maybe try to merge instantiation with `individual_host`? // Might only make things messier - const auto request = read_object(socket); - write_object(socket, GroupResponse{boost::this_process::get_id()}); + const auto request = read_object(socket); + write_object(socket, HostResponse{boost::this_process::get_id()}); // The plugin has to be initiated on the IO context's thread because // this has to be done on the same thread that's handling messages, diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 0cb68926..dd5774f3 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -68,7 +68,7 @@ class Vst2Bridge { * or if communication could not be set up. * * TODO: Make these two arguments `boost::filesystem::path`, also use those - * in `GroupRequest`. + * in `HostRequest`. */ Vst2Bridge(MainContext& main_context, std::string plugin_dll_path, From 278cd2e710d2959d6b4e247f26ca7228e492c51c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 30 Nov 2020 23:25:56 +0100 Subject: [PATCH 039/456] Spawn all hosts directly using HostRequest This way we can set the plugin type inside of the `Vst*PluginBridge` instance. --- src/plugin/bridges/vst2.cpp | 29 +++++++++++++++++------------ src/plugin/host-process.cpp | 32 +++++++++++++------------------- src/plugin/host-process.h | 12 +++++++----- src/wine-host/bridges/vst2.h | 3 --- 4 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index b539b91f..299363c5 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -61,18 +61,23 @@ Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) logger(Logger::create_from_environment( create_logger_prefix(sockets.base_dir))), wine_version(get_wine_version()), - vst_host(config.group - ? std::unique_ptr( - std::make_unique(io_context, - logger, - vst_plugin_path, - sockets, - *config.group)) - : std::unique_ptr( - std::make_unique(io_context, - logger, - vst_plugin_path, - sockets))), + vst_host( + config.group + ? std::unique_ptr(std::make_unique( + io_context, + logger, + HostRequest{.plugin_type = PluginType::vst2, + .plugin_path = vst_plugin_path.string(), + .endpoint_base_dir = sockets.base_dir.string()}, + sockets, + *config.group)) + : std::unique_ptr(std::make_unique( + io_context, + logger, + HostRequest{ + .plugin_type = PluginType::vst2, + .plugin_path = vst_plugin_path.string(), + .endpoint_base_dir = sockets.base_dir.string()}))), has_realtime_priority(set_realtime_priority()), wine_io_handler([&]() { io_context.run(); }) { log_init_message(); diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index b0f83c37..99620788 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -86,20 +86,19 @@ void HostProcess::async_log_pipe_lines(patched_async_pipe& pipe, IndividualHost::IndividualHost(boost::asio::io_context& io_context, Logger& logger, - fs::path plugin_path, - const Sockets& sockets) + const HostRequest& plugin_info) : HostProcess(io_context, logger), - plugin_arch(find_dll_architecture(plugin_path)), + // FIXME: This will require changing for VST3 bundles + plugin_arch(find_dll_architecture(plugin_info.plugin_path)), host_path(find_vst_host(plugin_arch, false)), host(launch_host(host_path, - // TODO: This should of course not be hardcoded - plugin_type_to_string(PluginType::vst2), + plugin_type_to_string(plugin_info.plugin_type), #ifdef WITH_WINEDBG - plugin_path.filename(), + plugin_info.plugin_path.filename(), #else - plugin_path, + plugin_info.plugin_path, #endif - sockets.base_dir, + plugin_info.endpoint_base_dir, bp::env = set_wineprefix(), bp::std_out = stdout_pipe, bp::std_err = stderr_pipe @@ -135,11 +134,12 @@ void IndividualHost::terminate() { GroupHost::GroupHost(boost::asio::io_context& io_context, Logger& logger, - fs::path plugin_path, + const HostRequest& host_request, Sockets& sockets, std::string group_name) : HostProcess(io_context, logger), - plugin_arch(find_dll_architecture(plugin_path)), + // FIXME: This will require changing for VST3 bundles + plugin_arch(find_dll_architecture(host_request.plugin_path)), host_path(find_vst_host(plugin_arch, true)), sockets(sockets) { #ifdef WITH_WINEDBG @@ -175,17 +175,12 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, const fs::path endpoint_base_dir = sockets.base_dir; const fs::path group_socket_path = generate_group_endpoint(group_name, wine_prefix, plugin_arch); - const auto connect = [&io_context, plugin_path, endpoint_base_dir, + const auto connect = [&io_context, host_request, endpoint_base_dir, group_socket_path]() { boost::asio::local::stream_protocol::socket group_socket(io_context); group_socket.connect(group_socket_path.string()); - write_object( - group_socket, - // TODO: The plugin type should of course not be hardcoded - HostRequest{.plugin_type = PluginType::vst2, - .plugin_path = plugin_path.string(), - .endpoint_base_dir = endpoint_base_dir.string()}); + write_object(group_socket, host_request); const auto response = read_object(group_socket); assert(response.pid > 0); }; @@ -205,8 +200,7 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, const pid_t group_host_pid = group_host.id(); group_host_connect_handler = - std::jthread([this, connect, group_socket_path, plugin_path, - endpoint_base_dir, group_host_pid]() { + std::jthread([this, connect, group_host_pid]() { using namespace std::literals::chrono_literals; // We'll first try to connect to the group host we just spawned diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index ca0dc25e..443c0eb8 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -119,16 +119,16 @@ class IndividualHost : public HostProcess { * handled on. * @param logger The `Logger` instance the redirected STDIO streams will be * written to. - * @param sockets The socket endpoints that will be used for communication - * with the plugin. + * @param plugin_info The information about the plugin we should launch a + * host process for. The values in the struct will be used as command line + * arguments. * * @throw std::runtime_error When `plugin_path` does not point to a valid * 32-bit or 64-bit .dll file. */ IndividualHost(boost::asio::io_context& io_context, Logger& logger, - boost::filesystem::path plugin_path, - const Sockets& sockets); + const HostRequest& plugin_info); LibArchitecture architecture() override; boost::filesystem::path path() override; @@ -162,6 +162,8 @@ class GroupHost : public HostProcess { * handled on. * @param logger The `Logger` instance the redirected STDIO streams will be * written to. + * @param host_request The information about the plugin we should launch a + * host process for. This object will be sent to the group host process. * @param sockets The socket endpoints that will be used for communication * with the plugin. When the plugin shuts down, we'll terminate the * dispatch socket contained in this object. @@ -169,7 +171,7 @@ class GroupHost : public HostProcess { */ GroupHost(boost::asio::io_context& io_context, Logger& logger, - boost::filesystem::path plugin_path, + const HostRequest& host_request, Sockets& socket_endpoint, std::string group_name); diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index dd5774f3..9ddfe3b0 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -66,9 +66,6 @@ class Vst2Bridge { * * @throw std::runtime_error Thrown when the VST plugin could not be loaded, * or if communication could not be set up. - * - * TODO: Make these two arguments `boost::filesystem::path`, also use those - * in `HostRequest`. */ Vst2Bridge(MainContext& main_context, std::string plugin_dll_path, From a55fbb0dafc9aa518825abcdf0fba697de21199c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 1 Dec 2020 16:21:32 +0100 Subject: [PATCH 040/456] Mention the version and patches of the VST3 SDK --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index faf8105e..b202be39 100644 --- a/README.md +++ b/README.md @@ -525,7 +525,8 @@ The following dependencies are included in the repository as a Meson wrap: - [bitsery](https://github.com/fraillt/bitsery) - [function2](https://github.com/Naios/function2) - [tomlplusplus](https://github.com/marzer/tomlplusplus) -- The [VST3 SDK](https://github.com/robbert-vdh/vst3sdk) +- Version 3.7.1 of the [VST3 SDK](https://github.com/robbert-vdh/vst3sdk) with + some [patches](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/tools/patch-vst3-sdk.sh) The project can then be compiled as follows: From 2230b5099fdba6cbfed59c3d1cdd041952db880c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 1 Dec 2020 21:13:31 +0100 Subject: [PATCH 041/456] Move logger to common/logging/common.h --- meson.build | 6 +++--- src/common/communication/common.h | 2 +- src/common/communication/vst2.h | 2 +- src/common/{logging.cpp => logging/common.cpp} | 4 +--- src/common/{logging.h => logging/common.h} | 2 +- src/plugin/bridges/vst2.h | 2 +- src/plugin/host-process.h | 2 +- src/plugin/vst2-plugin.cpp | 2 +- src/wine-host/bridges/vst2.h | 2 +- 9 files changed, 11 insertions(+), 13 deletions(-) rename src/common/{logging.cpp => logging/common.cpp} (99%) rename src/common/{logging.h => logging/common.h} (99%) diff --git a/meson.build b/meson.build index 2fa69ec7..d488a3d1 100644 --- a/meson.build +++ b/meson.build @@ -84,7 +84,7 @@ shared_library( 'src/common/communication/vst2.cpp', 'src/common/serialization/vst2.cpp', 'src/common/configuration.cpp', - 'src/common/logging.cpp', + 'src/common/logging/common.cpp', 'src/common/plugins.cpp', 'src/common/utils.cpp', 'src/plugin/bridges/vst2.cpp', @@ -203,7 +203,7 @@ if with_vst3 shared_library( 'yabridge-vst3', [ - 'src/common/logging.cpp', + 'src/common/logging/common.cpp', 'src/plugin/vst3-plugin.cpp', version_header, ], @@ -228,7 +228,7 @@ host_sources = [ 'src/common/communication/vst2.cpp', 'src/common/serialization/vst2.cpp', 'src/common/configuration.cpp', - 'src/common/logging.cpp', + 'src/common/logging/common.cpp', 'src/common/plugins.cpp', 'src/common/utils.cpp', 'src/wine-host/bridges/vst2.cpp', diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 08349eb2..c4757296 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -28,7 +28,7 @@ #include #include -#include "../logging.h" +#include "../logging/common.h" template using OutputAdapter = bitsery::OutputBufferAdapter; diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index da713612..f0476355 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -18,7 +18,7 @@ #include -#include "../logging.h" +#include "../logging/common.h" #include "common.h" /** diff --git a/src/common/logging.cpp b/src/common/logging/common.cpp similarity index 99% rename from src/common/logging.cpp rename to src/common/logging/common.cpp index 98aa8300..d77891cb 100644 --- a/src/common/logging.cpp +++ b/src/common/logging/common.cpp @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "logging.h" +#include "common.h" #ifdef __WINE__ #include "../wine-host/boost-fix.h" @@ -29,8 +29,6 @@ #include #include -#include "vst24.h" - /** * The environment variable indicating whether to log to a file. Will log to * STDERR if not specified. diff --git a/src/common/logging.h b/src/common/logging/common.h similarity index 99% rename from src/common/logging.h rename to src/common/logging/common.h index 8ce0a8e8..06390ef2 100644 --- a/src/common/logging.h +++ b/src/common/logging/common.h @@ -22,7 +22,7 @@ // TODO: Split up the plugin API specific logging functions so we don't have to // include a bunch of stuff we don't need -#include "serialization/vst2.h" +#include "../serialization/vst2.h" /** * Super basic logging facility meant for debugging malfunctioning VST diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index 33faeb98..45c9e3d2 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -25,7 +25,7 @@ #include "../../common/communication/vst2.h" #include "../../common/configuration.h" -#include "../../common/logging.h" +#include "../../common/logging/common.h" #include "../host-process.h" /** diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index 443c0eb8..c1196b25 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -26,7 +26,7 @@ #include #include "../common/communication/common.h" -#include "../common/logging.h" +#include "../common/logging/common.h" #include "../common/plugins.h" #include "utils.h" diff --git a/src/plugin/vst2-plugin.cpp b/src/plugin/vst2-plugin.cpp index 75a860f2..50c17c4c 100644 --- a/src/plugin/vst2-plugin.cpp +++ b/src/plugin/vst2-plugin.cpp @@ -19,7 +19,7 @@ #include #include -#include "../common/logging.h" +#include "../common/logging/common.h" #include "bridges/vst2.h" #define VST_EXPORT __attribute__((visibility("default"))) diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 9ddfe3b0..11014889 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -31,7 +31,7 @@ #include "../../common/communication/vst2.h" #include "../../common/configuration.h" -#include "../../common/logging.h" +#include "../../common/logging/common.h" #include "../editor.h" #include "../utils.h" From f9a1bcd7bd4203696ae85fc6530ac3ac87daf3ec Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 1 Dec 2020 21:42:33 +0100 Subject: [PATCH 042/456] Split VST2 specific functionality into Vst2Logger --- meson.build | 3 + src/common/communication/common.h | 1 + src/common/communication/vst2.h | 7 +- src/common/logging/common.cpp | 513 +---------------------------- src/common/logging/common.h | 53 +-- src/common/logging/vst2.cpp | 530 ++++++++++++++++++++++++++++++ src/common/logging/vst2.h | 66 ++++ src/plugin/bridges/vst2.cpp | 10 +- src/plugin/bridges/vst2.h | 4 +- src/plugin/host-process.h | 1 + src/wine-host/bridges/vst2.h | 1 - 11 files changed, 617 insertions(+), 572 deletions(-) create mode 100644 src/common/logging/vst2.cpp create mode 100644 src/common/logging/vst2.h diff --git a/meson.build b/meson.build index d488a3d1..9471ffa0 100644 --- a/meson.build +++ b/meson.build @@ -85,6 +85,7 @@ shared_library( 'src/common/serialization/vst2.cpp', 'src/common/configuration.cpp', 'src/common/logging/common.cpp', + 'src/common/logging/vst2.cpp', 'src/common/plugins.cpp', 'src/common/utils.cpp', 'src/plugin/bridges/vst2.cpp', @@ -204,6 +205,7 @@ if with_vst3 'yabridge-vst3', [ 'src/common/logging/common.cpp', + 'src/common/logging/vst2.cpp', 'src/plugin/vst3-plugin.cpp', version_header, ], @@ -229,6 +231,7 @@ host_sources = [ 'src/common/serialization/vst2.cpp', 'src/common/configuration.cpp', 'src/common/logging/common.cpp', + 'src/common/logging/vst2.cpp', 'src/common/plugins.cpp', 'src/common/utils.cpp', 'src/wine-host/bridges/vst2.cpp', diff --git a/src/common/communication/common.h b/src/common/communication/common.h index c4757296..39b97b4b 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -18,6 +18,7 @@ #include #include +#include #ifdef __WINE__ #include "../wine-host/boost-fix.h" diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index f0476355..8ecab67b 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -18,7 +18,8 @@ #include -#include "../logging/common.h" +#include "../logging/vst2.h" +#include "../serialization/vst2.h" #include "common.h" /** @@ -151,7 +152,7 @@ class EventHandler : public AdHocSocketHandler { */ template intptr_t send_event(D& data_converter, - std::optional> logging, + std::optional> logging, int opcode, int index, intptr_t value, @@ -226,7 +227,7 @@ class EventHandler : public AdHocSocketHandler { * @relates passthrough_event */ template - void receive_events(std::optional> logging, + void receive_events(std::optional> logging, F callback) { // Reading, processing, and writing back event data from the sockets // works in the same way regardless of which socket we're using diff --git a/src/common/logging/common.cpp b/src/common/logging/common.cpp index d77891cb..55ba54e7 100644 --- a/src/common/logging/common.cpp +++ b/src/common/logging/common.cpp @@ -46,7 +46,7 @@ constexpr char logging_verbosity_environment_variable[] = Logger::Logger(std::shared_ptr stream, Verbosity verbosity_level, std::string prefix) - : stream(stream), verbosity(verbosity_level), prefix(prefix) {} + : verbosity(verbosity_level), stream(stream), prefix(prefix) {} Logger Logger::create_from_environment(std::string prefix) { auto env = boost::this_process::environment(); @@ -108,514 +108,3 @@ void Logger::log_trace(const std::string& message) { log(message); } } - -void Logger::log_get_parameter(int index) { - if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { - std::ostringstream message; - message << ">> getParameter() " << index; - - log(message.str()); - } -} - -void Logger::log_get_parameter_response(float value) { - if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { - std::ostringstream message; - message << " getParameter() :: " << value; - - log(message.str()); - } -} - -void Logger::log_set_parameter(int index, float value) { - if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { - std::ostringstream message; - message << ">> setParameter() " << index << " = " << value; - - log(message.str()); - } -} - -void Logger::log_set_parameter_response() { - if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { - log(" setParameter() :: OK"); - } -} - -void Logger::log_event(bool is_dispatch, - int opcode, - int index, - intptr_t value, - const EventPayload& payload, - float option, - const std::optional& value_payload) { - if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { - if (should_filter_event(is_dispatch, opcode)) { - return; - } - - std::ostringstream message; - if (is_dispatch) { - message << ">> dispatch() "; - } else { - message << ">> audioMasterCallback() "; - } - - const auto opcode_name = opcode_to_string(is_dispatch, opcode); - if (opcode_name) { - message << *opcode_name; - } else { - message << ""; - } - - message << "(index = " << index << ", value = " << value - << ", option = " << option << ", data = "; - - // Only used during `effSetSpeakerArrangement` and - // `effGetSpeakerArrangement` - if (value_payload) { - std::visit( - overload{ - [&](auto) {}, - [&](const DynamicSpeakerArrangement& speaker_arrangement) { - message << "<" << speaker_arrangement.speakers.size() - << " input_speakers>, "; - }}, - *value_payload); - } - - std::visit( - overload{ - [&](const std::nullptr_t&) { message << ""; }, - [&](const std::string& s) { - if (s.size() < 32) { - message << "\"" << s << "\""; - } else { - // Long strings contain binary data that we probably - // don't want to print - message << "<" << s.size() << " bytes>"; - } - }, - [&](const ChunkData& chunk) { - message << "<" << chunk.buffer.size() << " byte chunk>"; - }, - [&](const native_size_t& window_id) { - message << ""; - }, - [&](const AEffect&) { message << ""; }, - [&](const DynamicVstEvents& events) { - message << "<" << events.events.size() << " midi_events>"; - }, - [&](const DynamicSpeakerArrangement& speaker_arrangement) { - message << "<" << speaker_arrangement.speakers.size() - << " output_speakers>"; - }, - [&](const VstIOProperties&) { message << ""; }, - [&](const VstMidiKeyName&) { message << ""; }, - [&](const VstParameterProperties&) { - message << ""; - }, - [&](const WantsAEffectUpdate&) { message << ""; }, - [&](const WantsChunkBuffer&) { - message << ""; - }, - [&](const WantsVstRect&) { message << ""; }, - [&](const WantsVstTimeInfo&) { message << ""; }, - [&](const WantsString&) { message << ""; }}, - payload); - - message << ")"; - - log(message.str()); - } -} - -void Logger::log_event_response( - bool is_dispatch, - int opcode, - intptr_t return_value, - const EventResultPayload& payload, - const std::optional& value_payload) { - if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { - if (should_filter_event(is_dispatch, opcode)) { - return; - } - - std::ostringstream message; - if (is_dispatch) { - message << " dispatch() :: "; - } else { - message << " audioMasterCallback() :: "; - } - - message << return_value; - - // Only used during `effSetSpeakerArrangement` and - // `effGetSpeakerArrangement` - if (value_payload) { - std::visit( - overload{ - [&](auto) {}, - [&](const DynamicSpeakerArrangement& speaker_arrangement) { - message << ", <" << speaker_arrangement.speakers.size() - << " input_speakers>"; - }}, - *value_payload); - } - - std::visit( - overload{ - [&](const std::nullptr_t&) {}, - [&](const std::string& s) { - if (s.size() < 32) { - message << ", \"" << s << "\""; - } else { - // Long strings contain binary data that we probably - // don't want to print - message << ", <" << s.size() << " bytes>"; - } - }, - [&](const ChunkData& chunk) { - message << ", <" << chunk.buffer.size() << " byte chunk>"; - }, - [&](const AEffect&) { message << ", "; }, - [&](const DynamicSpeakerArrangement& speaker_arrangement) { - message << ", <" << speaker_arrangement.speakers.size() - << " output_speakers>"; - }, - [&](const VstIOProperties&) { message << ", "; }, - [&](const VstMidiKeyName&) { message << ", "; }, - [&](const VstParameterProperties& props) { - message << ", "; - }, - [&](const VstRect& rect) { - message << ", {l: " << rect.left << ", t: " << rect.top - << ", r: " << rect.right << ", b: " << rect.bottom - << "}"; - }, - [&](const VstTimeInfo& info) { - message << ", <" - << "quarter_notes = " << info.ppqPos - << ", samples = " << info.samplePos << ">"; - }}, - payload); - - log(message.str()); - } -} - -bool Logger::should_filter_event(bool is_dispatch, int opcode) const { - if (verbosity >= Verbosity::all_events) { - return false; - } - - // Filter out log messages related to these events by default since they are - // called tens of times per second - // TODO: Figure out what opcode 52 is - if ((is_dispatch && - (opcode == effEditIdle || opcode == 52 || opcode == effIdle)) || - (!is_dispatch && (opcode == audioMasterGetTime || - opcode == audioMasterGetCurrentProcessLevel))) { - return true; - } - - return false; -} - -std::optional opcode_to_string(bool is_dispatch, int opcode) { - if (is_dispatch) { - // Opcodes for a plugin's dispatch function - switch (opcode) { - case effOpen: - return "effOpen"; - break; - case effClose: - return "effClose"; - break; - case effSetProgram: - return "effSetProgram"; - break; - case effGetProgram: - return "effGetProgram"; - break; - case effSetProgramName: - return "effSetProgramName"; - break; - case effGetProgramName: - return "effGetProgramName"; - break; - case effGetParamLabel: - return "effGetParamLabel"; - break; - case effGetParamDisplay: - return "effGetParamDisplay"; - break; - case effGetParamName: - return "effGetParamName"; - break; - case effSetSampleRate: - return "effSetSampleRate"; - break; - case effSetBlockSize: - return "effSetBlockSize"; - break; - case effMainsChanged: - return "effMainsChanged"; - break; - case effEditGetRect: - return "effEditGetRect"; - break; - case effEditOpen: - return "effEditOpen"; - break; - case effEditClose: - return "effEditClose"; - break; - case effEditIdle: - return "effEditIdle"; - break; - case effEditTop: - return "effEditTop"; - break; - case effIdentify: - return "effIdentify"; - break; - case effGetChunk: - return "effGetChunk"; - break; - case effSetChunk: - return "effSetChunk"; - break; - case effProcessEvents: - return "effProcessEvents"; - break; - case effCanBeAutomated: - return "effCanBeAutomated"; - break; - case effGetProgramNameIndexed: - return "effGetProgramNameIndexed"; - break; - case effGetPlugCategory: - return "effGetPlugCategory"; - break; - case effGetEffectName: - return "effGetEffectName"; - break; - case effGetParameterProperties: - return "effGetParameterProperties"; - break; - case effGetVendorString: - return "effGetVendorString"; - break; - case effGetProductString: - return "effGetProductString"; - break; - case effGetVendorVersion: - return "effGetVendorVersion"; - break; - case effCanDo: - return "effCanDo"; - break; - case effIdle: - return "effIdle"; - break; - case effGetVstVersion: - return "effGetVstVersion"; - break; - case effBeginSetProgram: - return "effBeginSetProgram"; - break; - case effEndSetProgram: - return "effEndSetProgram"; - break; - case effShellGetNextPlugin: - return "effShellGetNextPlugin"; - break; - case effBeginLoadBank: - return "effBeginLoadBank"; - break; - case effBeginLoadProgram: - return "effBeginLoadProgram"; - break; - case effStartProcess: - return "effStartProcess"; - break; - case effStopProcess: - return "effStopProcess"; - break; - case effGetInputProperties: - return "effGetInputProperties"; - break; - case effGetOutputProperties: - return "effGetOutputProperties"; - break; - case effGetMidiKeyName: - return "effGetMidiKeyName"; - break; - case effSetSpeakerArrangement: - return "effSetSpeakerArrangement"; - break; - case effGetSpeakerArrangement: - return "effGetSpeakerArrangement "; - break; - default: - return std::nullopt; - break; - } - } else { - // Opcodes for the host callback - switch (opcode) { - case audioMasterAutomate: - return "audioMasterAutomate"; - break; - case audioMasterVersion: - return "audioMasterVersion"; - break; - case audioMasterCurrentId: - return "audioMasterCurrentId"; - break; - case audioMasterIdle: - return "audioMasterIdle"; - break; - case audioMasterPinConnected: - return "audioMasterPinConnected"; - break; - case audioMasterWantMidi: - return "audioMasterWantMidi"; - break; - case audioMasterGetTime: - return "audioMasterGetTime"; - break; - case audioMasterProcessEvents: - return "audioMasterProcessEvents"; - break; - case audioMasterSetTime: - return "audioMasterSetTime"; - break; - case audioMasterTempoAt: - return "audioMasterTempoAt"; - break; - case audioMasterGetNumAutomatableParameters: - return "audioMasterGetNumAutomatableParameters"; - break; - case audioMasterGetParameterQuantization: - return "audioMasterGetParameterQuantization"; - break; - case audioMasterIOChanged: - return "audioMasterIOChanged"; - break; - case audioMasterNeedIdle: - return "audioMasterNeedIdle"; - break; - case audioMasterSizeWindow: - return "audioMasterSizeWindow"; - break; - case audioMasterGetSampleRate: - return "audioMasterGetSampleRate"; - break; - case audioMasterGetBlockSize: - return "audioMasterGetBlockSize"; - break; - case audioMasterGetInputLatency: - return "audioMasterGetInputLatency"; - break; - case audioMasterGetOutputLatency: - return "audioMasterGetOutputLatency"; - break; - case audioMasterGetPreviousPlug: - return "audioMasterGetPreviousPlug"; - break; - case audioMasterGetNextPlug: - return "audioMasterGetNextPlug"; - break; - case audioMasterWillReplaceOrAccumulate: - return "audioMasterWillReplaceOrAccumulate"; - break; - case audioMasterGetCurrentProcessLevel: - return "audioMasterGetCurrentProcessLevel"; - break; - case audioMasterGetAutomationState: - return "audioMasterGetAutomationState"; - break; - case audioMasterOfflineStart: - return "audioMasterOfflineStart"; - break; - case audioMasterOfflineRead: - return "audioMasterOfflineRead"; - break; - case audioMasterOfflineWrite: - return "audioMasterOfflineWrite"; - break; - case audioMasterOfflineGetCurrentPass: - return "audioMasterOfflineGetCurrentPass"; - break; - case audioMasterOfflineGetCurrentMetaPass: - return "audioMasterOfflineGetCurrentMetaPass"; - break; - case audioMasterSetOutputSampleRate: - return "audioMasterSetOutputSampleRate"; - break; - case audioMasterGetSpeakerArrangement: - return "audioMasterGetSpeakerArrangement"; - break; - case audioMasterGetVendorString: - return "audioMasterGetVendorString"; - break; - case audioMasterGetProductString: - return "audioMasterGetProductString"; - break; - case audioMasterGetVendorVersion: - return "audioMasterGetVendorVersion"; - break; - case audioMasterVendorSpecific: - return "audioMasterVendorSpecific"; - break; - case audioMasterSetIcon: - return "audioMasterSetIcon"; - break; - case audioMasterCanDo: - return "audioMasterCanDo"; - break; - case audioMasterGetLanguage: - return "audioMasterGetLanguage"; - break; - case audioMasterOpenWindow: - return "audioMasterOpenWindow"; - break; - case audioMasterCloseWindow: - return "audioMasterCloseWindow"; - break; - case audioMasterGetDirectory: - return "audioMasterGetDirectory"; - break; - case audioMasterUpdateDisplay: - return "audioMasterUpdateDisplay"; - break; - case audioMasterBeginEdit: - return "audioMasterBeginEdit"; - break; - case audioMasterEndEdit: - return "audioMasterEndEdit"; - break; - case audioMasterOpenFileSelector: - return "audioMasterOpenFileSelector"; - break; - case audioMasterCloseFileSelector: - return "audioMasterCloseFileSelector"; - break; - case audioMasterEditFile: - return "audioMasterEditFile"; - break; - case audioMasterGetChunkFile: - return "audioMasterGetChunkFile"; - break; - case audioMasterGetInputSpeakerArrangement: - return "audioMasterGetInputSpeakerArrangement"; - break; - default: - return std::nullopt; - break; - } - } -} diff --git a/src/common/logging/common.h b/src/common/logging/common.h index 06390ef2..c26ecc8f 100644 --- a/src/common/logging/common.h +++ b/src/common/logging/common.h @@ -20,10 +20,6 @@ #include #include -// TODO: Split up the plugin API specific logging functions so we don't have to -// include a bunch of stuff we don't need -#include "../serialization/vst2.h" - /** * Super basic logging facility meant for debugging malfunctioning VST * plugins. This is also used to redirect the output of the Wine process @@ -97,29 +93,6 @@ class Logger { */ void log(const std::string& message); - // The following functions are for logging specific events, they are only - // enabled for verbosity levels higher than 1 (i.e. `Verbosity::events`) - void log_get_parameter(int index); - void log_get_parameter_response(float vlaue); - void log_set_parameter(int index, float value); - void log_set_parameter_response(); - // If `is_dispatch` is `true`, then use opcode names from the plugin's - // dispatch function. Otherwise use names for the host callback function - // opcodes. - void log_event(bool is_dispatch, - int opcode, - int index, - intptr_t value, - const EventPayload& payload, - float option, - const std::optional& value_payload); - void log_event_response( - bool is_dispatch, - int opcode, - intptr_t return_value, - const EventResultPayload& payload, - const std::optional& value_payload); - /** * Log a message that should only be printed when the `verbosity` is set to * `all_events`. This should only be used for simple primitive messages @@ -130,38 +103,20 @@ class Logger { */ void log_trace(const std::string& message); - private: /** - * Determine whether an event should be filtered based on the current - * verbosity level. + * The verbosity level of this logger instance. Based on this certain + * messages may or may not be shown. */ - bool should_filter_event(bool is_dispatch, int opcode) const; + const Verbosity verbosity; + private: /** * The output stream to write the log messages to. Typically either STDERR * or a file stream. */ std::shared_ptr stream; - /** - * The verbosity level of this logger instance. Based on this certain - * messages may or may not be shown. - */ - Verbosity verbosity; /** * A prefix that gets prepended before every message. */ std::string prefix; }; - -/** - * Convert an event opcode to a human readable string for debugging purposes. - * See `src/include/vestige/aeffectx.h` for a complete list of these opcodes. - * - * @param is_dispatch Whether to use opcodes for the `dispatch` function. Will - * use the names from the host callback function if set to false. - * @param opcode The opcode of the event. - * - * @return Either the name from `aeffectx.h`, or a nullopt if it was not listed - * there. - */ -std::optional opcode_to_string(bool is_dispatch, int opcode); diff --git a/src/common/logging/vst2.cpp b/src/common/logging/vst2.cpp new file mode 100644 index 00000000..0c2406c7 --- /dev/null +++ b/src/common/logging/vst2.cpp @@ -0,0 +1,530 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "vst2.h" + +#include + +std::optional opcode_to_string(bool is_dispatch, int opcode) { + if (is_dispatch) { + // Opcodes for a plugin's dispatch function + switch (opcode) { + case effOpen: + return "effOpen"; + break; + case effClose: + return "effClose"; + break; + case effSetProgram: + return "effSetProgram"; + break; + case effGetProgram: + return "effGetProgram"; + break; + case effSetProgramName: + return "effSetProgramName"; + break; + case effGetProgramName: + return "effGetProgramName"; + break; + case effGetParamLabel: + return "effGetParamLabel"; + break; + case effGetParamDisplay: + return "effGetParamDisplay"; + break; + case effGetParamName: + return "effGetParamName"; + break; + case effSetSampleRate: + return "effSetSampleRate"; + break; + case effSetBlockSize: + return "effSetBlockSize"; + break; + case effMainsChanged: + return "effMainsChanged"; + break; + case effEditGetRect: + return "effEditGetRect"; + break; + case effEditOpen: + return "effEditOpen"; + break; + case effEditClose: + return "effEditClose"; + break; + case effEditIdle: + return "effEditIdle"; + break; + case effEditTop: + return "effEditTop"; + break; + case effIdentify: + return "effIdentify"; + break; + case effGetChunk: + return "effGetChunk"; + break; + case effSetChunk: + return "effSetChunk"; + break; + case effProcessEvents: + return "effProcessEvents"; + break; + case effCanBeAutomated: + return "effCanBeAutomated"; + break; + case effGetProgramNameIndexed: + return "effGetProgramNameIndexed"; + break; + case effGetPlugCategory: + return "effGetPlugCategory"; + break; + case effGetEffectName: + return "effGetEffectName"; + break; + case effGetParameterProperties: + return "effGetParameterProperties"; + break; + case effGetVendorString: + return "effGetVendorString"; + break; + case effGetProductString: + return "effGetProductString"; + break; + case effGetVendorVersion: + return "effGetVendorVersion"; + break; + case effCanDo: + return "effCanDo"; + break; + case effIdle: + return "effIdle"; + break; + case effGetVstVersion: + return "effGetVstVersion"; + break; + case effBeginSetProgram: + return "effBeginSetProgram"; + break; + case effEndSetProgram: + return "effEndSetProgram"; + break; + case effShellGetNextPlugin: + return "effShellGetNextPlugin"; + break; + case effBeginLoadBank: + return "effBeginLoadBank"; + break; + case effBeginLoadProgram: + return "effBeginLoadProgram"; + break; + case effStartProcess: + return "effStartProcess"; + break; + case effStopProcess: + return "effStopProcess"; + break; + case effGetInputProperties: + return "effGetInputProperties"; + break; + case effGetOutputProperties: + return "effGetOutputProperties"; + break; + case effGetMidiKeyName: + return "effGetMidiKeyName"; + break; + case effSetSpeakerArrangement: + return "effSetSpeakerArrangement"; + break; + case effGetSpeakerArrangement: + return "effGetSpeakerArrangement "; + break; + default: + return std::nullopt; + break; + } + } else { + // Opcodes for the host callback + switch (opcode) { + case audioMasterAutomate: + return "audioMasterAutomate"; + break; + case audioMasterVersion: + return "audioMasterVersion"; + break; + case audioMasterCurrentId: + return "audioMasterCurrentId"; + break; + case audioMasterIdle: + return "audioMasterIdle"; + break; + case audioMasterPinConnected: + return "audioMasterPinConnected"; + break; + case audioMasterWantMidi: + return "audioMasterWantMidi"; + break; + case audioMasterGetTime: + return "audioMasterGetTime"; + break; + case audioMasterProcessEvents: + return "audioMasterProcessEvents"; + break; + case audioMasterSetTime: + return "audioMasterSetTime"; + break; + case audioMasterTempoAt: + return "audioMasterTempoAt"; + break; + case audioMasterGetNumAutomatableParameters: + return "audioMasterGetNumAutomatableParameters"; + break; + case audioMasterGetParameterQuantization: + return "audioMasterGetParameterQuantization"; + break; + case audioMasterIOChanged: + return "audioMasterIOChanged"; + break; + case audioMasterNeedIdle: + return "audioMasterNeedIdle"; + break; + case audioMasterSizeWindow: + return "audioMasterSizeWindow"; + break; + case audioMasterGetSampleRate: + return "audioMasterGetSampleRate"; + break; + case audioMasterGetBlockSize: + return "audioMasterGetBlockSize"; + break; + case audioMasterGetInputLatency: + return "audioMasterGetInputLatency"; + break; + case audioMasterGetOutputLatency: + return "audioMasterGetOutputLatency"; + break; + case audioMasterGetPreviousPlug: + return "audioMasterGetPreviousPlug"; + break; + case audioMasterGetNextPlug: + return "audioMasterGetNextPlug"; + break; + case audioMasterWillReplaceOrAccumulate: + return "audioMasterWillReplaceOrAccumulate"; + break; + case audioMasterGetCurrentProcessLevel: + return "audioMasterGetCurrentProcessLevel"; + break; + case audioMasterGetAutomationState: + return "audioMasterGetAutomationState"; + break; + case audioMasterOfflineStart: + return "audioMasterOfflineStart"; + break; + case audioMasterOfflineRead: + return "audioMasterOfflineRead"; + break; + case audioMasterOfflineWrite: + return "audioMasterOfflineWrite"; + break; + case audioMasterOfflineGetCurrentPass: + return "audioMasterOfflineGetCurrentPass"; + break; + case audioMasterOfflineGetCurrentMetaPass: + return "audioMasterOfflineGetCurrentMetaPass"; + break; + case audioMasterSetOutputSampleRate: + return "audioMasterSetOutputSampleRate"; + break; + case audioMasterGetSpeakerArrangement: + return "audioMasterGetSpeakerArrangement"; + break; + case audioMasterGetVendorString: + return "audioMasterGetVendorString"; + break; + case audioMasterGetProductString: + return "audioMasterGetProductString"; + break; + case audioMasterGetVendorVersion: + return "audioMasterGetVendorVersion"; + break; + case audioMasterVendorSpecific: + return "audioMasterVendorSpecific"; + break; + case audioMasterSetIcon: + return "audioMasterSetIcon"; + break; + case audioMasterCanDo: + return "audioMasterCanDo"; + break; + case audioMasterGetLanguage: + return "audioMasterGetLanguage"; + break; + case audioMasterOpenWindow: + return "audioMasterOpenWindow"; + break; + case audioMasterCloseWindow: + return "audioMasterCloseWindow"; + break; + case audioMasterGetDirectory: + return "audioMasterGetDirectory"; + break; + case audioMasterUpdateDisplay: + return "audioMasterUpdateDisplay"; + break; + case audioMasterBeginEdit: + return "audioMasterBeginEdit"; + break; + case audioMasterEndEdit: + return "audioMasterEndEdit"; + break; + case audioMasterOpenFileSelector: + return "audioMasterOpenFileSelector"; + break; + case audioMasterCloseFileSelector: + return "audioMasterCloseFileSelector"; + break; + case audioMasterEditFile: + return "audioMasterEditFile"; + break; + case audioMasterGetChunkFile: + return "audioMasterGetChunkFile"; + break; + case audioMasterGetInputSpeakerArrangement: + return "audioMasterGetInputSpeakerArrangement"; + break; + default: + return std::nullopt; + break; + } + } +} + +void Vst2Logger::log_get_parameter(int index) { + if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { + std::ostringstream message; + message << ">> getParameter() " << index; + + log(message.str()); + } +} + +void Vst2Logger::log_get_parameter_response(float value) { + if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { + std::ostringstream message; + message << " getParameter() :: " << value; + + log(message.str()); + } +} + +void Vst2Logger::log_set_parameter(int index, float value) { + if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { + std::ostringstream message; + message << ">> setParameter() " << index << " = " << value; + + log(message.str()); + } +} + +void Vst2Logger::log_set_parameter_response() { + if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { + log(" setParameter() :: OK"); + } +} + +void Vst2Logger::log_event(bool is_dispatch, + int opcode, + int index, + intptr_t value, + const EventPayload& payload, + float option, + const std::optional& value_payload) { + if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { + if (should_filter_event(is_dispatch, opcode)) { + return; + } + + std::ostringstream message; + if (is_dispatch) { + message << ">> dispatch() "; + } else { + message << ">> audioMasterCallback() "; + } + + const auto opcode_name = opcode_to_string(is_dispatch, opcode); + if (opcode_name) { + message << *opcode_name; + } else { + message << ""; + } + + message << "(index = " << index << ", value = " << value + << ", option = " << option << ", data = "; + + // Only used during `effSetSpeakerArrangement` and + // `effGetSpeakerArrangement` + if (value_payload) { + std::visit( + overload{ + [&](auto) {}, + [&](const DynamicSpeakerArrangement& speaker_arrangement) { + message << "<" << speaker_arrangement.speakers.size() + << " input_speakers>, "; + }}, + *value_payload); + } + + std::visit( + overload{ + [&](const std::nullptr_t&) { message << ""; }, + [&](const std::string& s) { + if (s.size() < 32) { + message << "\"" << s << "\""; + } else { + // Long strings contain binary data that we probably + // don't want to print + message << "<" << s.size() << " bytes>"; + } + }, + [&](const ChunkData& chunk) { + message << "<" << chunk.buffer.size() << " byte chunk>"; + }, + [&](const native_size_t& window_id) { + message << ""; + }, + [&](const AEffect&) { message << ""; }, + [&](const DynamicVstEvents& events) { + message << "<" << events.events.size() << " midi_events>"; + }, + [&](const DynamicSpeakerArrangement& speaker_arrangement) { + message << "<" << speaker_arrangement.speakers.size() + << " output_speakers>"; + }, + [&](const VstIOProperties&) { message << ""; }, + [&](const VstMidiKeyName&) { message << ""; }, + [&](const VstParameterProperties&) { + message << ""; + }, + [&](const WantsAEffectUpdate&) { message << ""; }, + [&](const WantsChunkBuffer&) { + message << ""; + }, + [&](const WantsVstRect&) { message << ""; }, + [&](const WantsVstTimeInfo&) { message << ""; }, + [&](const WantsString&) { message << ""; }}, + payload); + + message << ")"; + + log(message.str()); + } +} + +void Vst2Logger::log_event_response( + bool is_dispatch, + int opcode, + intptr_t return_value, + const EventResultPayload& payload, + const std::optional& value_payload) { + if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { + if (should_filter_event(is_dispatch, opcode)) { + return; + } + + std::ostringstream message; + if (is_dispatch) { + message << " dispatch() :: "; + } else { + message << " audioMasterCallback() :: "; + } + + message << return_value; + + // Only used during `effSetSpeakerArrangement` and + // `effGetSpeakerArrangement` + if (value_payload) { + std::visit( + overload{ + [&](auto) {}, + [&](const DynamicSpeakerArrangement& speaker_arrangement) { + message << ", <" << speaker_arrangement.speakers.size() + << " input_speakers>"; + }}, + *value_payload); + } + + std::visit( + overload{ + [&](const std::nullptr_t&) {}, + [&](const std::string& s) { + if (s.size() < 32) { + message << ", \"" << s << "\""; + } else { + // Long strings contain binary data that we probably + // don't want to print + message << ", <" << s.size() << " bytes>"; + } + }, + [&](const ChunkData& chunk) { + message << "<" << chunk.buffer.size() << " byte chunk>"; + }, + [&](const AEffect&) { message << ", "; }, + [&](const DynamicSpeakerArrangement& speaker_arrangement) { + message << ", <" << speaker_arrangement.speakers.size() + << " output_speakers>"; + }, + [&](const VstIOProperties&) { message << ", "; }, + [&](const VstMidiKeyName&) { message << ", "; }, + [&](const VstParameterProperties& props) { + message << ", "; + }, + [&](const VstRect& rect) { + message << ", {l: " << rect.left << ", t: " << rect.top + << ", r: " << rect.right << ", b: " << rect.bottom + << "}"; + }, + [&](const VstTimeInfo& info) { + message << ", <" + << "quarter_notes = " << info.ppqPos + << ", samples = " << info.samplePos << ">"; + }}, + payload); + + log(message.str()); + } +} + +bool Vst2Logger::should_filter_event(bool is_dispatch, int opcode) const { + if (verbosity >= Verbosity::all_events) { + return false; + } + + // Filter out log messages related to these events by default since they are + // called tens of times per second + // TODO: Figure out what opcode 52 is + if ((is_dispatch && + (opcode == effEditIdle || opcode == 52 || opcode == effIdle)) || + (!is_dispatch && (opcode == audioMasterGetTime || + opcode == audioMasterGetCurrentProcessLevel))) { + return true; + } + + return false; +} diff --git a/src/common/logging/vst2.h b/src/common/logging/vst2.h new file mode 100644 index 00000000..841b46bf --- /dev/null +++ b/src/common/logging/vst2.h @@ -0,0 +1,66 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "../serialization/vst2.h" +#include "common.h" + +/** + * Convert an event opcode to a human readable string for debugging purposes. + * See `src/include/vestige/aeffectx.h` for a complete list of these opcodes. + * + * @param is_dispatch Whether to use opcodes for the `dispatch` function. Will + * use the names from the host callback function if set to false. + * @param opcode The opcode of the event. + * + * @return Either the name from `aeffectx.h`, or a nullopt if it was not listed + * there. + */ +std::optional opcode_to_string(bool is_dispatch, int opcode); + +class Vst2Logger : public Logger { + public: + // The following functions are for logging specific events, they are only + // enabled for verbosity levels higher than 1 (i.e. `Verbosity::events`) + void log_get_parameter(int index); + void log_get_parameter_response(float vlaue); + void log_set_parameter(int index, float value); + void log_set_parameter_response(); + // If `is_dispatch` is `true`, then use opcode names from the plugin's + // dispatch function. Otherwise use names for the host callback function + // opcodes. + void log_event(bool is_dispatch, + int opcode, + int index, + intptr_t value, + const EventPayload& payload, + float option, + const std::optional& value_payload); + void log_event_response( + bool is_dispatch, + int opcode, + intptr_t return_value, + const EventResultPayload& payload, + const std::optional& value_payload); + + private: + /** + * Determine whether an event should be filtered based on the current + * verbosity level. + */ + bool should_filter_event(bool is_dispatch, int opcode) const; +}; diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index 299363c5..7944a29c 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -127,7 +127,7 @@ Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) // lockstep anyway host_callback_handler = std::jthread([&]() { sockets.vst_host_callback.receive_events( - std::pair(logger, false), + std::pair(logger, false), [&](Event& event, bool /*on_main_thread*/) { // MIDI events sent from the plugin back to the host are a // special case here. They have to sent during the @@ -431,8 +431,8 @@ intptr_t Vst2PluginBridge::dispatch(AEffect* /*plugin*/, try { // TODO: Add some kind of timeout? return_value = sockets.host_vst_dispatch.send_event( - converter, std::pair(logger, true), opcode, - index, value, data, option); + converter, std::pair(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 @@ -483,8 +483,8 @@ intptr_t Vst2PluginBridge::dispatch(AEffect* /*plugin*/, // receiving function temporarily allocate a large enough buffer rather than // to have a bunch of allocated memory sitting around doing nothing. return sockets.host_vst_dispatch.send_event( - converter, std::pair(logger, true), opcode, index, value, - data, option); + converter, std::pair(logger, true), opcode, index, + value, data, option); } template diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index 45c9e3d2..ce28a7aa 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -25,7 +25,7 @@ #include "../../common/communication/vst2.h" #include "../../common/configuration.h" -#include "../../common/logging/common.h" +#include "../../common/logging/vst2.h" #include "../host-process.h" /** @@ -171,7 +171,7 @@ class Vst2PluginBridge { * * @see Logger::create_from_env */ - Logger logger; + Vst2Logger logger; /** * The version of Wine currently in use. Used in the debug output on plugin diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index c1196b25..ca5987c0 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -28,6 +28,7 @@ #include "../common/communication/common.h" #include "../common/logging/common.h" #include "../common/plugins.h" +#include "../common/serialization/common.h" #include "utils.h" /** diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 11014889..070357b3 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -31,7 +31,6 @@ #include "../../common/communication/vst2.h" #include "../../common/configuration.h" -#include "../../common/logging/common.h" #include "../editor.h" #include "../utils.h" From 20606068645f30e1a57bb7c1d19f7e227ee2eb69 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 1 Dec 2020 22:27:01 +0100 Subject: [PATCH 043/456] Add a list of things to watch out for with VST3 --- src/plugin/vst3-plugin.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index 67f4df8b..ed84b022 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -32,6 +32,19 @@ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { // those classes. // // We should wrap this in our `Vst3PluginBridge` + // TODO: We should also create a list of which extensions we have + // already implemented and which are left + // TODO: And when we get a query for some interface that we do not (yet) + // support, we should print some easy to spot warning message + // TODO: The documentation mentions that private communication through + // VST3's message system should be handled on a separate timer + // thread. Do we need special handling for this on the Wine side + // (e.g. during the event handling loop)? Probably not, since the + // actual host should manage all messaging. + // TODO: The docs very explicitly mention that + // the`IComponentHandler::{begin,perform,end}Edit()` functions + // have to be called from the UI thread. Should we have special + // handling for this or does everything just magically work out? // static Steinberg::PFactoryInfo factoryInfo(vendor, url, email, // flags); gPluginFactory = new Steinberg::CPluginFactory(factoryInfo); From 969ad75da57186b02adfb64e23e522edead30d9d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 1 Dec 2020 23:22:41 +0100 Subject: [PATCH 044/456] Add some more VST3 implementation considerations --- src/plugin/vst3-plugin.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index ed84b022..bc27dc69 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -36,6 +36,9 @@ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { // already implemented and which are left // TODO: And when we get a query for some interface that we do not (yet) // support, we should print some easy to spot warning message + // TODO: Should we always use plugin groups or for VST3 plugins? Since + // they seem to be very keen on sharing resources and leaving + // modules loaded. // TODO: The documentation mentions that private communication through // VST3's message system should be handled on a separate timer // thread. Do we need special handling for this on the Wine side @@ -45,6 +48,26 @@ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { // the`IComponentHandler::{begin,perform,end}Edit()` functions // have to be called from the UI thread. Should we have special // handling for this or does everything just magically work out? + // TODO: Something that's not relevant here but that will require some + // thinking is that VST3 requires all plugins to be installed in + // ~/.vst3. I can think of two options and I"m not sure what's the + // best one: + // + // 1. We can add the required files for the Linux VST3 plugin to + // the location of the Windows VST3 plugin (by adding some + // files to the bundle or creating a bundle next to it) and + // then symlink that bundle to ~/.vst3. + // 2. We can create the bundle in ~/.vst3 and symlink the Windows + // plugin and all of its resources into bundle as if they were + // also installed there. + // + // The second one sounds much better, but it will still need some + // more consideration. Also, yabridgectl will need to do some + // extra work there to detect removed plugins. + // TODO: Also symlink presets, and allow pruning broken symlinks there + // as well + // TODO: And how do we choose between 32-bit and 64-bit versions of a + // VST3 plugin if they exist? Config files? // static Steinberg::PFactoryInfo factoryInfo(vendor, url, email, // flags); gPluginFactory = new Steinberg::CPluginFactory(factoryInfo); From cf72c135790cc9fae01f59aa37e54c2beefb8548 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 1 Dec 2020 23:55:40 +0100 Subject: [PATCH 045/456] Add a define for VST3 support --- meson.build | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/meson.build b/meson.build index 9471ffa0..118d3d69 100644 --- a/meson.build +++ b/meson.build @@ -49,6 +49,10 @@ if with_winedbg compiler_options += '-DWITH_WINEDBG' endif +if with_vst3 + compiler_options += '-DWITH_VST3' +endif + # Generate header files for configuration variables such as the current git tag # and the name of the host binary subdir('src/common/config') From a9b7a6a8356b64dec25dcb4d2e2a9a9e30e2d5a2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 1 Dec 2020 23:55:51 +0100 Subject: [PATCH 046/456] List VST3 support in the initialization message --- src/plugin/bridges/vst2.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index 7944a29c..359aefc6 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -706,7 +706,10 @@ void Vst2PluginBridge::log_init_message() { #ifdef WITH_WINEDBG init_msg << "- winedbg" << std::endl; #endif -#if !(defined(WITH_BITBRIDGE) || defined(WITH_WINEDBG)) +#ifdef WITH_VST3 + init_msg << "- VST3 support" << std::endl; +#endif +#if !(defined(WITH_BITBRIDGE) || defined(WITH_WINEDBG) || defined(WITH_VST3)) init_msg << " " << std::endl; #endif init_msg << std::endl; From eeb6acf8dd97a3cd8b695607c7239c8812ba9598 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 00:06:28 +0100 Subject: [PATCH 047/456] Move event handling to a common HostBridge --- meson.build | 1 + src/wine-host/bridges/common.cpp | 38 ++++++++++++++++++++ src/wine-host/bridges/common.h | 61 ++++++++++++++++++++++++++++++++ src/wine-host/bridges/vst2.cpp | 21 ----------- src/wine-host/bridges/vst2.h | 45 ++--------------------- 5 files changed, 102 insertions(+), 64 deletions(-) create mode 100644 src/wine-host/bridges/common.cpp create mode 100644 src/wine-host/bridges/common.h diff --git a/meson.build b/meson.build index 118d3d69..3e67f555 100644 --- a/meson.build +++ b/meson.build @@ -238,6 +238,7 @@ host_sources = [ 'src/common/logging/vst2.cpp', 'src/common/plugins.cpp', 'src/common/utils.cpp', + 'src/wine-host/bridges/common.cpp', 'src/wine-host/bridges/vst2.cpp', 'src/wine-host/editor.cpp', 'src/wine-host/editor.cpp', diff --git a/src/wine-host/bridges/common.cpp b/src/wine-host/bridges/common.cpp new file mode 100644 index 00000000..81bec63e --- /dev/null +++ b/src/wine-host/bridges/common.cpp @@ -0,0 +1,38 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "common.h" + +void HostBridge::handle_win32_events() { + if (editor) { + editor->handle_win32_events(); + } else { + MSG msg; + + for (int i = 0; i < max_win32_messages && + PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); + i++) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +} + +void HostBridge::handle_x11_events() { + if (editor) { + editor->handle_x11_events(); + } +} diff --git a/src/wine-host/bridges/common.h b/src/wine-host/bridges/common.h new file mode 100644 index 00000000..d932ac50 --- /dev/null +++ b/src/wine-host/bridges/common.h @@ -0,0 +1,61 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "../editor.h" + +/** + * The base for the Wine plugin host bridge interface for all plugin types. This + * only has to be able to handle Win32 and X11 events. Implementations of this + * will actually host a plugin and do all the function call forwarding. + */ +class HostBridge { + public: + /** + * Handle X11 events for the editor window if it is open. This can safely be + * run from any thread. + */ + void handle_x11_events(); + + /** + * Run the message loop for this plugin. This is only used for the + * individual plugin host, so that we can filter out some unnecessary timer + * events. When hosting multiple plugins, a simple central message loop + * should be used instead. This is run on a timer in the same IO context as + * the one that handles the events, i.e. `main_context`. + * + * Because of the way the Win32 API works we have to process events on the + * same thread as the one the window was created on, and that thread is the + * thread that's handling dispatcher calls. Some plugins will also rely on + * the Win32 message loop to run tasks on a timer and to defer loading, so + * we have to make sure to always run this loop. The only exception is a in + * specific situation that can cause a race condition in some plugins + * because of incorrect assumptions made by the plugin. See the dostring for + * `HostBridge::editor` for more information. + */ + void handle_win32_events(); + + protected: + /** + * The plugin editor window. Allows embedding the plugin's editor into a + * Wine window, and embedding that Wine window into a window provided by the + * host. Should be empty when the editor is not open. + * + * @see should_postpone_message_loop + */ + std::optional editor; +}; diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index a52cdb8f..216d7e02 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -392,27 +392,6 @@ intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin, } } -void Vst2Bridge::handle_win32_events() { - if (editor) { - editor->handle_win32_events(); - } else { - MSG msg; - - for (int i = 0; i < max_win32_messages && - PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); - i++) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } -} - -void Vst2Bridge::handle_x11_events() { - if (editor) { - editor->handle_x11_events(); - } -} - class HostCallbackDataConverter : DefaultDataConverter { public: HostCallbackDataConverter(AEffect* plugin, diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 070357b3..5d82c146 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -31,22 +31,14 @@ #include "../../common/communication/vst2.h" #include "../../common/configuration.h" -#include "../editor.h" #include "../utils.h" +#include "common.h" /** * This hosts a Windows VST2 plugin, forwards messages sent by the Linux VST * plugin and provides host callback function for the plugin to talk back. - * - * @remark Because of Win32 API limitations, all window handling has to be done - * from a single thread. Most plugins won't have any issues when using - * multiple message loops, but the Melda plugins for instance will only update - * their GUIs from the message loop of the thread that created the first - * instance. This is why we pass an IO context to this class so everything - * that's not performance critical (audio and midi event handling) is handled - * on the same thread, even when hosting multiple plugins. */ -class Vst2Bridge { +class Vst2Bridge : public HostBridge { public: /** * Initializes the Windows VST plugin and set up communication with the @@ -83,30 +75,6 @@ class Vst2Bridge { */ void handle_dispatch(); - /** - * Handle X11 events for the editor window if it is open. This can safely be - * run from any thread. - */ - void handle_x11_events(); - - /** - * Run the message loop for this plugin. This is only used for the - * individual plugin host, so that we can filter out some unnecessary timer - * events. When hosting multiple plugins, a simple central message loop - * should be used instead. This is run on a timer in the same IO context as - * the one that handles the events, i.e. `main_context`. - * - * Because of the way the Win32 API works we have to process events on the - * same thread as the one the window was created on, and that thread is the - * thread that's handling dispatcher calls. Some plugins will also rely on - * the Win32 message loop to run tasks on a timer and to defer loading, so - * we have to make sure to always run this loop. The only exception is a in - * specific situation that can cause a race condition in some plugins - * because of incorrect assumptions made by the plugin. See the dostring for - * `Vst2Bridge::editor` for more information. - */ - void handle_win32_events(); - /** * Forward the host callback made by the plugin to the host and return the * results. @@ -203,13 +171,4 @@ class Vst2Bridge { * now happens in two different threads. */ std::mutex next_buffer_midi_events_mutex; - - /** - * The plugin editor window. Allows embedding the plugin's editor into a - * Wine window, and embedding that Wine window into a window provided by the - * host. Should be empty when the editor is not open. - * - * @see should_postpone_message_loop - */ - std::optional editor; }; From 41da621e29109a869ae4966d456436a7c677ee00 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 00:06:46 +0100 Subject: [PATCH 048/456] Add a todo for decoupling Editor from VST2 --- src/wine-host/editor.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index c5989fb5..db725239 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -84,7 +84,9 @@ class WindowClass { * window without using XEmbed. If anyone knows how to work around these two * issues, please let me know and I'll switch to using XEmbed again. * - * This workaround was inspired by LinVST. + * This workaround was inspired by LinVst. + * + * TODO: Decouple this from VST2 */ class Editor { public: From 3dd5090023b07ecd3334b99d766328874d4160c4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 00:27:09 +0100 Subject: [PATCH 049/456] Support multiple plugin types in individual host --- src/wine-host/individual-host.cpp | 49 +++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/wine-host/individual-host.cpp b/src/wine-host/individual-host.cpp index a31fca60..f3f75e10 100644 --- a/src/wine-host/individual-host.cpp +++ b/src/wine-host/individual-host.cpp @@ -54,11 +54,11 @@ main(int argc, char* argv[]) { return 1; } - // TODO: Do something with the plugin type // TODO: On the Wine side of things, we should only allow hosting VST3 // plugins when the Meson build option is enabled (because, well, // otherwise we'd get compile errors) - const PluginType plugin_type = plugin_type_from_string(argv[1]); + const std::string plugin_type_str(argv[1]); + const PluginType plugin_type = plugin_type_from_string(plugin_type_str); const std::string plugin_location(argv[2]); const std::string socket_endpoint_path(argv[3]); @@ -76,12 +76,39 @@ main(int argc, char* argv[]) { // don't need to differentiate between individually hosted plugins and // plugin groups when it comes to event handling. MainContext main_context{}; - std::unique_ptr bridge; + Win32Thread worker_thread; + std::shared_ptr bridge; try { - bridge = std::make_unique(main_context, plugin_location, - socket_endpoint_path); + switch (plugin_type) { + case PluginType::vst2: + bridge = std::make_shared( + main_context, plugin_location, socket_endpoint_path); + + // We'll listen for `dispatcher()` calls on a different thread, + // but the actual events will still be executed within the IO + // context + worker_thread = Win32Thread([&]() { + std::static_pointer_cast(bridge) + ->handle_dispatch(); + + // When the sockets get closed, this application should + // terminate gracefully + main_context.stop(); + }); + break; + case PluginType::vst3: + std::cerr << "TODO: Not yet implemented" << std::endl; + return 1; + break; + case PluginType::unknown: + std::cerr << "Unknown plugin type '" << plugin_type_str << "'" + << std::endl; + return 1; + break; + }; } catch (const std::runtime_error& error) { - std::cerr << "Error while initializing Wine VST host:" << std::endl; + std::cerr << "Error while initializing the Wine plugin host:" + << std::endl; std::cerr << error.what() << std::endl; return 1; @@ -90,16 +117,6 @@ main(int argc, char* argv[]) { std::cout << "Finished initializing '" << plugin_location << "'" << std::endl; - // We'll listen for `dispatcher()` calls on a different thread, but the - // actual events will still be executed within the IO context - Win32Thread dispatch_handler([&]() { - bridge->handle_dispatch(); - - // When the sockets get closed, this application should terminate - // gracefully - main_context.stop(); - }); - // Handle Win32 messages and X11 events on a timer, just like in // `GroupBridge::async_handle_events()`` main_context.async_handle_events([&]() { From 0eb80fe866d3e097f80c1d92b714223903bbcb92 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 00:35:41 +0100 Subject: [PATCH 050/456] Add a general entry point to HostBridge Since for all plugin types we would need to start listening for incoming events this way. --- src/wine-host/bridges/common.h | 15 +++++++++++++++ src/wine-host/bridges/group.cpp | 2 +- src/wine-host/bridges/vst2.cpp | 2 +- src/wine-host/bridges/vst2.h | 13 +------------ src/wine-host/individual-host.cpp | 28 +++++++++++++--------------- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/wine-host/bridges/common.h b/src/wine-host/bridges/common.h index d932ac50..85eac4e1 100644 --- a/src/wine-host/bridges/common.h +++ b/src/wine-host/bridges/common.h @@ -25,6 +25,21 @@ */ class HostBridge { public: + virtual ~HostBridge(){}; + + /** + * Handle events until the plugin exits. The actual events are posted to + * `main_context` to ensure that all operations to could potentially + * interact with Win32 code are run from a single thread, even when hosting + * multiple plugins. The message loop should be run on a timer within the + * same IO context. + * + * @note Because of the reasons mentioned above, for this to work the plugin + * should be initialized within the same thread that calls + * `main_context.run()`. + */ + virtual void run() = 0; + /** * Handle X11 events for the editor window if it is open. This can safely be * run from any thread. diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index 3fee24da..25dc67bd 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -116,7 +116,7 @@ void GroupBridge::handle_plugin_dispatch(size_t plugin_id) { // Blocks this thread until the plugin shuts down, handling all events on // the main IO context - bridge->handle_dispatch(); + bridge->run(); logger.log("'" + bridge->vst_plugin_path.string() + "' has exited"); // After the plugin has exited we'll remove this thread's plugin from the diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index 216d7e02..03bb7ca1 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -271,7 +271,7 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context, }); } -void Vst2Bridge::handle_dispatch() { +void Vst2Bridge::run() { sockets.host_vst_dispatch.receive_events( std::nullopt, [&](Event& event, bool /*on_main_thread*/) { if (event.opcode == effProcessEvents) { diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 5d82c146..65e719ea 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -62,18 +62,7 @@ class Vst2Bridge : public HostBridge { std::string plugin_dll_path, std::string endpoint_base_dir); - /** - * Handle events until the plugin exits. The actual events are posted to - * `main_context` to ensure that all operations to could potentially - * interact with Win32 code are run from a single thread, even when hosting - * multiple plugins. The message loop should be run on a timer within the - * same IO context. - * - * @note Because of the reasons mentioned above, for this to work the plugin - * should be initialized within the same thread that calls - * `main_context.run()`. - */ - void handle_dispatch(); + void run() override; /** * Forward the host callback made by the plugin to the host and return the diff --git a/src/wine-host/individual-host.cpp b/src/wine-host/individual-host.cpp index f3f75e10..5ea79a4e 100644 --- a/src/wine-host/individual-host.cpp +++ b/src/wine-host/individual-host.cpp @@ -76,25 +76,12 @@ main(int argc, char* argv[]) { // don't need to differentiate between individually hosted plugins and // plugin groups when it comes to event handling. MainContext main_context{}; - Win32Thread worker_thread; - std::shared_ptr bridge; + std::unique_ptr bridge; try { switch (plugin_type) { case PluginType::vst2: - bridge = std::make_shared( + bridge = std::make_unique( main_context, plugin_location, socket_endpoint_path); - - // We'll listen for `dispatcher()` calls on a different thread, - // but the actual events will still be executed within the IO - // context - worker_thread = Win32Thread([&]() { - std::static_pointer_cast(bridge) - ->handle_dispatch(); - - // When the sockets get closed, this application should - // terminate gracefully - main_context.stop(); - }); break; case PluginType::vst3: std::cerr << "TODO: Not yet implemented" << std::endl; @@ -114,6 +101,17 @@ main(int argc, char* argv[]) { return 1; } + // Let the plugin receive and handle its events on its own thread. Some + // potentially unsafe events that should always be run from the UI thread + // will be posted to `main_context`. + Win32Thread worker_thread([&]() { + bridge->run(); + + // When the sockets get closed, this application should + // terminate gracefully + main_context.stop(); + }); + std::cout << "Finished initializing '" << plugin_location << "'" << std::endl; From 3db099e0fc1df45a781afbb63a0973c5516bdbfe Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 00:43:04 +0100 Subject: [PATCH 051/456] Also make the GroupHost plugin type agnostic --- src/wine-host/bridges/common.cpp | 3 +++ src/wine-host/bridges/common.h | 8 ++++++++ src/wine-host/bridges/group.cpp | 27 +++++++++++++++++++-------- src/wine-host/bridges/group.h | 2 +- src/wine-host/bridges/vst2.cpp | 2 +- src/wine-host/bridges/vst2.h | 5 ----- 6 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/wine-host/bridges/common.cpp b/src/wine-host/bridges/common.cpp index 81bec63e..01bd6359 100644 --- a/src/wine-host/bridges/common.cpp +++ b/src/wine-host/bridges/common.cpp @@ -16,6 +16,9 @@ #include "common.h" +HostBridge::HostBridge(boost::filesystem::path plugin_path) + : plugin_path(plugin_path) {} + void HostBridge::handle_win32_events() { if (editor) { editor->handle_win32_events(); diff --git a/src/wine-host/bridges/common.h b/src/wine-host/bridges/common.h index 85eac4e1..eee60936 100644 --- a/src/wine-host/bridges/common.h +++ b/src/wine-host/bridges/common.h @@ -24,6 +24,9 @@ * will actually host a plugin and do all the function call forwarding. */ class HostBridge { + protected: + HostBridge(boost::filesystem::path plugin_path); + public: virtual ~HostBridge(){}; @@ -64,6 +67,11 @@ class HostBridge { */ void handle_win32_events(); + /** + * The path to the .dll being loaded in the Wine plugin host. + */ + const boost::filesystem::path plugin_path; + protected: /** * The plugin editor window. Allows embedding the plugin's editor into a diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index 25dc67bd..7c743e7c 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -108,7 +108,7 @@ GroupBridge::~GroupBridge() { void GroupBridge::handle_plugin_dispatch(size_t plugin_id) { // At this point the `active_plugins` map will already contain the // intialized plugin's `Vst2Bridge` instance and this thread's handle - Vst2Bridge* bridge; + HostBridge* bridge; { std::lock_guard lock(active_plugins_mutex); bridge = active_plugins[plugin_id].second.get(); @@ -117,7 +117,7 @@ void GroupBridge::handle_plugin_dispatch(size_t plugin_id) { // Blocks this thread until the plugin shuts down, handling all events on // the main IO context bridge->run(); - logger.log("'" + bridge->vst_plugin_path.string() + "' has exited"); + logger.log("'" + bridge->plugin_path.string() + "' has exited"); // After the plugin has exited we'll remove this thread's plugin from the // active plugins. This is done within the IO context because the call to @@ -181,9 +181,6 @@ void GroupBridge::accept_requests() { // yabridge plugin will be able to tell if the plugin has caused // this process to crash during its initialization to prevent // waiting indefinitely on the sockets to be connected to. - // TODO: Do something with the plugin type - // TODO: Maybe try to merge instantiation with `individual_host`? - // Might only make things messier const auto request = read_object(socket); write_object(socket, HostResponse{boost::this_process::get_id()}); @@ -196,9 +193,23 @@ void GroupBridge::accept_requests() { "' using socket endpoint base directory '" + request.endpoint_base_dir + "'"); try { - auto bridge = std::make_unique( - main_context, request.plugin_path, - request.endpoint_base_dir); + std::unique_ptr bridge = nullptr; + switch (request.plugin_type) { + case PluginType::vst2: + bridge = std::make_unique( + main_context, request.plugin_path, + request.endpoint_base_dir); + break; + case PluginType::vst3: + throw std::runtime_error("TODO: Not yet implemented"); + break; + case PluginType::unknown: + throw std::runtime_error( + "Invalid plugin host request received, how did you " + "even manage to do this?"); + break; + } + logger.log("Finished initializing '" + request.plugin_path + "'"); diff --git a/src/wine-host/bridges/group.h b/src/wine-host/bridges/group.h index 0313d249..a255f75e 100644 --- a/src/wine-host/bridges/group.h +++ b/src/wine-host/bridges/group.h @@ -252,7 +252,7 @@ class GroupBridge { * on `next_plugin_id`. */ std::unordered_map>> + std::pair>> active_plugins; /** * A counter for the next unique plugin ID. When hosting a new plugin we'll diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index 03bb7ca1..ac349839 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -69,7 +69,7 @@ Vst2Bridge& get_bridge_instance(const AEffect* plugin) { Vst2Bridge::Vst2Bridge(MainContext& main_context, std::string plugin_dll_path, std::string endpoint_base_dir) - : vst_plugin_path(plugin_dll_path), + : HostBridge(plugin_dll_path), main_context(main_context), plugin_handle(LoadLibrary(plugin_dll_path.c_str()), FreeLibrary), sockets(main_context.context, endpoint_base_dir, false) { diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 65e719ea..5694010a 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -78,11 +78,6 @@ class Vst2Bridge : public HostBridge { */ std::optional time_info; - /** - * The path to the .dll being loaded in the Wine VST host. - */ - const boost::filesystem::path vst_plugin_path; - private: /** * A wrapper around `plugin->dispatcher` that handles the opening and From 84e13e556c60bfde90bae32475472daaa36403f0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 14:49:00 +0100 Subject: [PATCH 052/456] Add #ifdef WITH_VST3 guards around VST3 hosting --- src/wine-host/bridges/group.cpp | 10 ++++++++++ src/wine-host/bridges/group.h | 4 +++- src/wine-host/bridges/vst3.h | 18 ++++++++++++++++++ src/wine-host/individual-host.cpp | 13 ++++++++++--- 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 src/wine-host/bridges/vst3.h diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index 7c743e7c..2f421c47 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -22,6 +22,10 @@ #include #include "../../common/communication/common.h" +#include "vst2.h" +#ifdef WITH_VST3 +#include "vst3.h" +#endif // FIXME: `std::filesystem` is broken in wineg++, at least under Wine 5.8. Any // path operation will thrown an encoding related error @@ -201,7 +205,13 @@ void GroupBridge::accept_requests() { request.endpoint_base_dir); break; case PluginType::vst3: +#ifdef WITH_VST3 throw std::runtime_error("TODO: Not yet implemented"); +#else + throw std::runtime_error( + "This version of yabridge has not been compiled " + "with VST3 support"); +#endif break; case PluginType::unknown: throw std::runtime_error( diff --git a/src/wine-host/bridges/group.h b/src/wine-host/bridges/group.h index a255f75e..7c0e65a3 100644 --- a/src/wine-host/bridges/group.h +++ b/src/wine-host/bridges/group.h @@ -18,6 +18,7 @@ #include "../boost-fix.h" +#include #include #include #include @@ -25,7 +26,8 @@ #include #include -#include "vst2.h" +#include "../common/logging/common.h" +#include "common.h" /** * Encapsulate capturing the STDOUT or STDERR stream by opening a pipe and diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h new file mode 100644 index 00000000..768a86af --- /dev/null +++ b/src/wine-host/bridges/vst3.h @@ -0,0 +1,18 @@ + +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once diff --git a/src/wine-host/individual-host.cpp b/src/wine-host/individual-host.cpp index 5ea79a4e..8c2679e9 100644 --- a/src/wine-host/individual-host.cpp +++ b/src/wine-host/individual-host.cpp @@ -23,6 +23,9 @@ #include "../common/utils.h" #include "bridges/vst2.h" +#ifdef WITH_VST3 +#include "bridges/vst3.h" +#endif /** * This is the default plugin host application. It will load the specified @@ -54,9 +57,6 @@ main(int argc, char* argv[]) { return 1; } - // TODO: On the Wine side of things, we should only allow hosting VST3 - // plugins when the Meson build option is enabled (because, well, - // otherwise we'd get compile errors) const std::string plugin_type_str(argv[1]); const PluginType plugin_type = plugin_type_from_string(plugin_type_str); const std::string plugin_location(argv[2]); @@ -84,8 +84,15 @@ main(int argc, char* argv[]) { main_context, plugin_location, socket_endpoint_path); break; case PluginType::vst3: +#ifdef WITH_VST3 std::cerr << "TODO: Not yet implemented" << std::endl; return 1; +#else + std::cerr << "This version of yabridge has not been compiled " + "with VST3 support" + << std::endl; + return 1; +#endif break; case PluginType::unknown: std::cerr << "Unknown plugin type '" << plugin_type_str << "'" From e273051a6cee609daabf9a76219fbc4696f40959 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 15:41:53 +0100 Subject: [PATCH 053/456] Add a Vst3Bridge implementation file --- meson.build | 6 ++++++ src/wine-host/bridges/vst3.cpp | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/wine-host/bridges/vst3.cpp diff --git a/meson.build b/meson.build index 3e67f555..1c31e6ed 100644 --- a/meson.build +++ b/meson.build @@ -246,6 +246,12 @@ host_sources = [ version_header, ] +if with_vst3 + host_sources += [ + 'src/wine-host/bridges/vst3.cpp' + ] +endif + individual_host_sources = host_sources + ['src/wine-host/individual-host.cpp'] group_host_sources = host_sources + [ 'src/wine-host/bridges/group.cpp', diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp new file mode 100644 index 00000000..cafad935 --- /dev/null +++ b/src/wine-host/bridges/vst3.cpp @@ -0,0 +1,18 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "vst3.h" + From d848498d9b4bd662329566d6434e680cb5a997e9 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 15:45:06 +0100 Subject: [PATCH 054/456] Define all sources at the top of meson.build To make things a bit more organized since it's growing a bit out of hand. --- meson.build | 102 +++++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 49 deletions(-) diff --git a/meson.build b/meson.build index 1c31e6ed..545bc81c 100644 --- a/meson.build +++ b/meson.build @@ -57,6 +57,57 @@ endif # and the name of the host binary subdir('src/common/config') +vst2_plugin_sources = [ + 'src/common/communication/common.cpp', + 'src/common/communication/vst2.cpp', + 'src/common/serialization/vst2.cpp', + 'src/common/configuration.cpp', + 'src/common/logging/common.cpp', + 'src/common/logging/vst2.cpp', + 'src/common/plugins.cpp', + 'src/common/utils.cpp', + 'src/plugin/bridges/vst2.cpp', + 'src/plugin/host-process.cpp', + 'src/plugin/utils.cpp', + 'src/plugin/vst2-plugin.cpp', + version_header, +] + +vst3_plugin_sources = [ + 'src/common/logging/common.cpp', + 'src/common/logging/vst2.cpp', + 'src/plugin/vst3-plugin.cpp', + version_header, +] + +host_sources = [ + 'src/common/communication/vst2.cpp', + 'src/common/serialization/vst2.cpp', + 'src/common/configuration.cpp', + 'src/common/logging/common.cpp', + 'src/common/logging/vst2.cpp', + 'src/common/plugins.cpp', + 'src/common/utils.cpp', + 'src/wine-host/bridges/common.cpp', + 'src/wine-host/bridges/vst2.cpp', + 'src/wine-host/editor.cpp', + 'src/wine-host/editor.cpp', + 'src/wine-host/utils.cpp', + version_header, +] + +if with_vst3 + host_sources += [ + 'src/wine-host/bridges/vst3.cpp' + ] +endif + +individual_host_sources = host_sources + ['src/wine-host/individual-host.cpp'] +group_host_sources = host_sources + [ + 'src/wine-host/bridges/group.cpp', + 'src/wine-host/group-host.cpp', +] + # Statically link against Boost.Filesystem, otherwise it would become impossible # to distribute a prebuilt version of yabridge boost_dep = dependency('boost', version : '>=1.66', static : with_static_boost) @@ -83,21 +134,7 @@ include_dir = include_directories('src/include') shared_library( 'yabridge-vst2', - [ - 'src/common/communication/common.cpp', - 'src/common/communication/vst2.cpp', - 'src/common/serialization/vst2.cpp', - 'src/common/configuration.cpp', - 'src/common/logging/common.cpp', - 'src/common/logging/vst2.cpp', - 'src/common/plugins.cpp', - 'src/common/utils.cpp', - 'src/plugin/bridges/vst2.cpp', - 'src/plugin/host-process.cpp', - 'src/plugin/utils.cpp', - 'src/plugin/vst2-plugin.cpp', - version_header, - ], + vst2_plugin_sources, native : true, include_directories : include_dir, dependencies : [ @@ -207,12 +244,7 @@ if with_vst3 # applications can handle both VST2 and VST3 plugins. shared_library( 'yabridge-vst3', - [ - 'src/common/logging/common.cpp', - 'src/common/logging/vst2.cpp', - 'src/plugin/vst3-plugin.cpp', - version_header, - ], + vst3_plugin_sources, native : true, include_directories : include_dir, dependencies : [ @@ -230,34 +262,6 @@ else message('VST3 support has been disabled') endif -host_sources = [ - 'src/common/communication/vst2.cpp', - 'src/common/serialization/vst2.cpp', - 'src/common/configuration.cpp', - 'src/common/logging/common.cpp', - 'src/common/logging/vst2.cpp', - 'src/common/plugins.cpp', - 'src/common/utils.cpp', - 'src/wine-host/bridges/common.cpp', - 'src/wine-host/bridges/vst2.cpp', - 'src/wine-host/editor.cpp', - 'src/wine-host/editor.cpp', - 'src/wine-host/utils.cpp', - version_header, -] - -if with_vst3 - host_sources += [ - 'src/wine-host/bridges/vst3.cpp' - ] -endif - -individual_host_sources = host_sources + ['src/wine-host/individual-host.cpp'] -group_host_sources = host_sources + [ - 'src/wine-host/bridges/group.cpp', - 'src/wine-host/group-host.cpp', -] - executable( individual_host_name_64bit, individual_host_sources, From f51f920426281e7986141ffb00ef92f53132a6d4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 15:47:21 +0100 Subject: [PATCH 055/456] Move VST3 dependency defs to the rest of the deps --- meson.build | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/meson.build b/meson.build index 545bc81c..dfae7846 100644 --- a/meson.build +++ b/meson.build @@ -127,27 +127,6 @@ xcb_dep = dependency('xcb') include_dir = include_directories('src/include') -# The application consists of a plugin (`libyabridge-{vst2,vst3}.so`) that calls -# a Winelib application (`yabridge-{host,group}{,-32}.exe`) that can host -# Windows VST plugins. More information about the way these two components work -# together can be found in `docs/architecture.md`. - -shared_library( - 'yabridge-vst2', - vst2_plugin_sources, - native : true, - include_directories : include_dir, - dependencies : [ - boost_dep, - boost_filesystem_dep, - bitsery_dep, - threads_dep, - tomlplusplus_dep, - ], - cpp_args : compiler_options, - link_args : ['-ldl'] -) - if with_vst3 # Meson does not allow mixing native and non native dependencies from # subprojects. The only workaround is to only define the necessary variables @@ -239,7 +218,30 @@ if with_vst3 link_with : vst3_sdk_hosting_wine, include_directories : vst3_include_dir, ) +endif +# The application consists of a plugin (`libyabridge-{vst2,vst3}.so`) that calls +# a Winelib application (`yabridge-{host,group}{,-32}.exe`) that can host +# Windows VST plugins. More information about the way these two components work +# together can be found in `docs/architecture.md`. + +shared_library( + 'yabridge-vst2', + vst2_plugin_sources, + native : true, + include_directories : include_dir, + dependencies : [ + boost_dep, + boost_filesystem_dep, + bitsery_dep, + threads_dep, + tomlplusplus_dep, + ], + cpp_args : compiler_options, + link_args : ['-ldl'] +) + +if with_vst3 # This is the VST3 equivalent of `libyabridge-vst2.so`. The Wine host # applications can handle both VST2 and VST3 plugins. shared_library( @@ -258,8 +260,6 @@ if with_vst3 cpp_args : compiler_options, link_args : ['-ldl'], ) -else - message('VST3 support has been disabled') endif executable( From 06884ebf6768322c0f4705c65609b10c748d27b7 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 16:10:50 +0100 Subject: [PATCH 056/456] Also fix shlobj.h import casing --- tools/patch-vst3-sdk.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/patch-vst3-sdk.sh b/tools/patch-vst3-sdk.sh index fad11cac..3be72ad3 100755 --- a/tools/patch-vst3-sdk.sh +++ b/tools/patch-vst3-sdk.sh @@ -21,7 +21,7 @@ fi # Make sure all imports use the correct casing find "$sdk_directory" -type f \( -iname '*.h' -or -iname '*.cpp' \) -print0 | - xargs -0 sed -i 's/^#include $/#include /' + xargs -0 sed -i -E 's/^#include <(Windows.h|ShlObj.h)>$/#include <\L\1\E>/' # Use the string manipulation functions from the C standard library sed -i 's/\bSMTG_OS_WINDOWS\b/0/g;s/\bSMTG_OS_LINUX\b/1/g' "$sdk_directory/base/source/fstring.cpp" From 715a95075ba92690d4d08d777993a5391fa5a234 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 16:31:59 +0100 Subject: [PATCH 057/456] Add required compiler args to the Wine VST3 dep --- meson.build | 1 + src/wine-host/bridges/vst2.h | 2 ++ src/wine-host/editor.h | 2 ++ src/wine-host/utils.h | 2 ++ 4 files changed, 7 insertions(+) diff --git a/meson.build b/meson.build index dfae7846..69ae87f6 100644 --- a/meson.build +++ b/meson.build @@ -217,6 +217,7 @@ if with_vst3 vst3_sdk_hosting_wine_dep = declare_dependency( link_with : vst3_sdk_hosting_wine, include_directories : vst3_include_dir, + compile_args : vst3_wine_compiler_options, ) endif diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 5694010a..403977b5 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -18,11 +18,13 @@ #include "../boost-fix.h" +#ifndef NOMINMAX #define NOMINMAX #define NOSERVICE #define NOMCX #define NOIMM #define WIN32_LEAN_AND_MEAN +#endif #include #include diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index db725239..b03b2f2c 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -20,11 +20,13 @@ #include #pragma pop_macro("_WIN32") +#ifndef NOMINMAX #define NOMINMAX #define NOSERVICE #define NOMCX #define NOIMM #define WIN32_LEAN_AND_MEAN +#endif #include #include diff --git a/src/wine-host/utils.h b/src/wine-host/utils.h index d9683ef7..8358800b 100644 --- a/src/wine-host/utils.h +++ b/src/wine-host/utils.h @@ -21,11 +21,13 @@ #include #include +#ifndef NOMINMAX #define NOMINMAX #define NOSERVICE #define NOMCX #define NOIMM #define WIN32_LEAN_AND_MEAN +#endif #include #include From 9a57ce0c5e2a42d9649c0f22edcf74b515a66863 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 17:17:33 +0100 Subject: [PATCH 058/456] Use non-experimental in VST3 loading --- tools/patch-vst3-sdk.sh | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tools/patch-vst3-sdk.sh b/tools/patch-vst3-sdk.sh index 3be72ad3..dcd2387f 100755 --- a/tools/patch-vst3-sdk.sh +++ b/tools/patch-vst3-sdk.sh @@ -23,6 +23,12 @@ fi find "$sdk_directory" -type f \( -iname '*.h' -or -iname '*.cpp' \) -print0 | xargs -0 sed -i -E 's/^#include <(Windows.h|ShlObj.h)>$/#include <\L\1\E>/' +# We're building with `WIN32_LEAN_AND_MEAN` because some of the definitions in +# there conflict with the C standard library as provided by GCC. This also +# excludes the shell API, which the VST3 SDK uses to open URLs. +sed -i "s/^#include $/#include \\/\\/ patched for yabridge\\ +#include /" "$sdk_directory/public.sdk/source/common/openurl.cpp" + # Use the string manipulation functions from the C standard library sed -i 's/\bSMTG_OS_WINDOWS\b/0/g;s/\bSMTG_OS_LINUX\b/1/g' "$sdk_directory/base/source/fstring.cpp" sed -i 's/\bSMTG_OS_WINDOWS\b/0/g;s/\bSMTG_OS_LINUX\b/1/g' "$sdk_directory/pluginterfaces/base/fstrdefs.h" @@ -57,11 +63,9 @@ replace_char16 "using Converter = std::wstring_convert$/#include \\/\\/ patched for yabridge\\ -#include /" "$sdk_directory/public.sdk/source/common/openurl.cpp" +# Use the proper `` header instead of the experimental one +# TODO: Check if now works with Winelib, or replace with Boost +sed -i 's/^#if _HAS_CXX17 && defined(_MSC_VER)$/#if 1/' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" # Meson requires this program to output something, or else it will error out # when trying to encode the empty output From f3d17b5e990fef2b7e2eb67095ed10c4bcbc6432 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 17:18:23 +0100 Subject: [PATCH 059/456] Also patch string conversion for winelib --- tools/patch-vst3-sdk.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/patch-vst3-sdk.sh b/tools/patch-vst3-sdk.sh index dcd2387f..9a0e7036 100755 --- a/tools/patch-vst3-sdk.sh +++ b/tools/patch-vst3-sdk.sh @@ -63,6 +63,11 @@ replace_char16 "using Converter = std::wstring_convert` header instead of the experimental one # TODO: Check if now works with Winelib, or replace with Boost sed -i 's/^#if _HAS_CXX17 && defined(_MSC_VER)$/#if 1/' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" From a4af1a2535cfab48b67b8ae627a20c79a11283e5 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 18:01:15 +0100 Subject: [PATCH 060/456] Fix compiling VST3 module system with winegcc --- README.md | 3 +- meson.build | 168 ++++++++++++++++++++++++++-------------- tools/patch-vst3-sdk.sh | 6 +- 3 files changed, 117 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index b202be39..72fcb93b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ imcomplete list of things that still have to be done before this can be used: - Mention that this update will break all existing symlinks and that the old `libyabridge.so` file should be removed when upgrading. - Update all the AUR packages. -- Add CMake to the AUR package dependencies and to our Docker images +- Test the binaries built on GitHub on plain Ubuntu 18.04, are we missing any + static linking? ![yabridge screenshot](https://raw.githubusercontent.com/robbert-vdh/yabridge/master/screenshot.png) diff --git a/meson.build b/meson.build index 69ae87f6..ccbbc8f9 100644 --- a/meson.build +++ b/meson.build @@ -125,6 +125,12 @@ tomlplusplus_dep = subproject('tomlplusplus', version : '2.1.0').get_variable('t wine_threads_dep = declare_dependency(link_args : '-lpthread') xcb_dep = dependency('xcb') +# These are required for the VST3 SDK's module import system +# TODO: Statically link this on the ubuntu-18.04 build +native_filesystem_dep = declare_dependency(link_args : '-lstdc++fs') +wine_ole32_dep = declare_dependency(link_args : '-lole32') +wine_uuid_dep = declare_dependency(link_args : '-luuid') + include_dir = include_directories('src/include') if with_vst3 @@ -192,7 +198,7 @@ if with_vst3 vst3_base_wine = static_library( 'base_wine', vst3.get_variable('base_sources'), - cpp_args : vst3_compiler_options + vst3_wine_compiler_options + [ '-Wno-cpp'], + cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-m64', '-Wno-cpp'], include_directories : vst3_include_dir, override_options : ['warning_level=0'], native : false, @@ -200,7 +206,7 @@ if with_vst3 vst3_pluginterfaces_wine = static_library( 'pluginterfaces_wine', vst3.get_variable('pluginterfaces_sources'), - cpp_args : vst3_compiler_options + vst3_wine_compiler_options, + cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-m64'], include_directories : vst3_include_dir, override_options : ['warning_level=0'], native : false, @@ -209,7 +215,7 @@ if with_vst3 'sdk_hosting_wine', vst3.get_variable('sdk_common_sources') + vst3.get_variable('sdk_hosting_sources'), link_with : [vst3_base_wine, vst3_pluginterfaces_wine], - cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-Wno-multichar'], + cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-m64', '-Wno-multichar'], include_directories : vst3_include_dir, override_options : ['warning_level=0'], native : false, @@ -217,8 +223,48 @@ if with_vst3 vst3_sdk_hosting_wine_dep = declare_dependency( link_with : vst3_sdk_hosting_wine, include_directories : vst3_include_dir, + # This does mean that we now have a lot of defines in our code, but the + # alternative would be patching every location in the SDK where they include + # `windows.h` compile_args : vst3_wine_compiler_options, ) + + # And another time for the 32-bit version + if with_bitbridge + vst3_base_wine_32 = static_library( + 'base_wine_32', + vst3.get_variable('base_sources'), + cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-m32', '-Wno-cpp'], + include_directories : vst3_include_dir, + override_options : ['warning_level=0'], + native : false, + ) + vst3_pluginterfaces_wine_32 = static_library( + 'pluginterfaces_wine_32', + vst3.get_variable('pluginterfaces_sources'), + cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-m32'], + include_directories : vst3_include_dir, + override_options : ['warning_level=0'], + native : false, + ) + vst3_sdk_hosting_wine_32 = static_library( + 'sdk_hosting_wine_32', + vst3.get_variable('sdk_common_sources') + vst3.get_variable('sdk_hosting_sources'), + link_with : [vst3_base_wine_32, vst3_pluginterfaces_wine_32], + cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-m32', '-Wno-multichar'], + include_directories : vst3_include_dir, + override_options : ['warning_level=0'], + native : false, + ) + vst3_sdk_hosting_wine_32_dep = declare_dependency( + link_with : vst3_sdk_hosting_wine_32, + include_directories : vst3_include_dir, + # This does mean that we now have a lot of defines in our code, but the + # alternative would be patching every location in the SDK where they include + # `windows.h` + compile_args : vst3_wine_compiler_options, + ) + endif endif # The application consists of a plugin (`libyabridge-{vst2,vst3}.so`) that calls @@ -263,41 +309,23 @@ if with_vst3 ) endif -executable( - individual_host_name_64bit, - individual_host_sources, - native : false, - include_directories : include_dir, - dependencies : [ - boost_dep, - boost_filesystem_dep, - bitsery_dep, - function2_dep, - tomlplusplus_dep, - wine_threads_dep, - xcb_dep, - ], - cpp_args : compiler_options + ['-m64'], - link_args : ['-m64'], -) - -executable( - group_host_name_64bit, - group_host_sources, - native : false, - include_directories : include_dir, - dependencies : [ - boost_dep, - boost_filesystem_dep, - bitsery_dep, - function2_dep, - tomlplusplus_dep, - wine_threads_dep, - xcb_dep, - ], - cpp_args : compiler_options + ['-m64'], - link_args : ['-m64'], -) +host_deps = [ + boost_dep, + boost_filesystem_dep, + bitsery_dep, + function2_dep, + tomlplusplus_dep, + wine_threads_dep, + xcb_dep, +] +if with_vst3 + host_deps += [ + native_filesystem_dep, + vst3_sdk_hosting_wine_dep, + wine_ole32_dep, + wine_uuid_dep, + ] +endif if with_bitbridge message('Bitbridge enabled, configuring a 32-bit host application') @@ -308,7 +336,7 @@ if with_bitbridge # search path set by the system in `find_library()`. If anyone does know how # to properly do this, please let me know! winegcc = meson.get_compiler('cpp', native : false) - boost_filesystem_dep = winegcc.find_library( + boost_filesystem_32_dep = winegcc.find_library( 'boost_filesystem', static : with_static_boost, dirs : [ @@ -325,22 +353,54 @@ if with_bitbridge '/usr/lib', ] ) - xcb_dep = winegcc.find_library('xcb') + xcb_32_dep = winegcc.find_library('xcb') + host_32_deps = [ + boost_dep, + boost_filesystem_32_dep, + bitsery_dep, + function2_dep, + tomlplusplus_dep, + wine_threads_dep, + xcb_32_dep, + ] + if with_vst3 + host_32_deps += [ + native_filesystem_dep, + vst3_sdk_hosting_wine_32_dep, + wine_ole32_dep, + wine_uuid_dep, + ] + endif +endif + +executable( + individual_host_name_64bit, + individual_host_sources, + native : false, + include_directories : include_dir, + dependencies : host_deps, + cpp_args : compiler_options + ['-m64'], + link_args : ['-m64'], +) + +executable( + group_host_name_64bit, + group_host_sources, + native : false, + include_directories : include_dir, + dependencies : host_deps, + cpp_args : compiler_options + ['-m64'], + link_args : ['-m64'], +) + +if with_bitbridge executable( individual_host_name_32bit, individual_host_sources, native : false, include_directories : include_dir, - dependencies : [ - boost_dep, - boost_filesystem_dep, - bitsery_dep, - function2_dep, - tomlplusplus_dep, - wine_threads_dep, - xcb_dep, - ], + dependencies : host_32_deps, cpp_args : compiler_options + ['-m32'], link_args : ['-m32'], ) @@ -350,15 +410,7 @@ if with_bitbridge group_host_sources, native : false, include_directories : include_dir, - dependencies : [ - boost_dep, - boost_filesystem_dep, - bitsery_dep, - function2_dep, - tomlplusplus_dep, - wine_threads_dep, - xcb_dep, - ], + dependencies : host_32_deps, cpp_args : compiler_options + ['-m32'], link_args : ['-m32'], ) diff --git a/tools/patch-vst3-sdk.sh b/tools/patch-vst3-sdk.sh index 9a0e7036..d968b3fe 100755 --- a/tools/patch-vst3-sdk.sh +++ b/tools/patch-vst3-sdk.sh @@ -70,7 +70,11 @@ sed -i 's/^#if defined(_MSC_VER) && .\+$/#if __WINE__/' "$sdk_directory/public.s # Use the proper `` header instead of the experimental one # TODO: Check if now works with Winelib, or replace with Boost -sed -i 's/^#if _HAS_CXX17 && defined(_MSC_VER)$/#if 1/' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" +# sed -i 's/^#if _HAS_CXX17 && defined(_MSC_VER)$/#if 1/' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" + +# Don't try adding `std::u8string` to an `std::vector`. MSVC +# probably coerces them, but GCC doesn't +sed -i 's/\bgeneric_u8string\b/generic_string/g' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" # Meson requires this program to output something, or else it will error out # when trying to encode the empty output From 9291ae7e42c8ba7adaa14b094594cb5bb6e796aa Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 18:01:56 +0100 Subject: [PATCH 061/456] Include the Win32 module loading implementation --- src/wine-host/boost-fix.h | 4 ++++ src/wine-host/bridges/group.cpp | 2 ++ src/wine-host/bridges/vst3.cpp | 2 ++ src/wine-host/bridges/vst3.h | 1 - 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/wine-host/boost-fix.h b/src/wine-host/boost-fix.h index bd82b480..764760bb 100644 --- a/src/wine-host/boost-fix.h +++ b/src/wine-host/boost-fix.h @@ -23,11 +23,13 @@ // it's running under a WIN32 environment. If anyone knows a better way to do // this, please let me know! +#pragma push_macro("__MINGW32__") #pragma push_macro("WIN32") #pragma push_macro("_WIN32") #pragma push_macro("__WIN32__") #pragma push_macro("_WIN64") +#undef __MINGW32__ #undef WIN32 #undef _WIN32 #undef __WIN32__ @@ -38,9 +40,11 @@ // included here, but including headers from the detail directory directly // didn't sound like a great idea. +#include #include // #include +#pragma pop_macro("__MINGW32__") #pragma pop_macro("WIN32") #pragma pop_macro("_WIN32") #pragma pop_macro("__WIN32__") diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index 2f421c47..350e400b 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -16,6 +16,8 @@ #include "group.h" +#include "../boost-fix.h" + #include #include #include diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index cafad935..817dd545 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -16,3 +16,5 @@ #include "vst3.h" +// TODO: Do something with this, I just wanted to get the build working +#include diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 768a86af..e50f13e6 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -1,4 +1,3 @@ - // yabridge: a Wine VST bridge // Copyright (C) 2020 Robbert van der Helm // From a25aea1a76dddf1e219e2f3298d7ac466e9839dd Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 18:05:17 +0100 Subject: [PATCH 062/456] Use consistent architecture naming for Wine deps --- meson.build | 64 ++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/meson.build b/meson.build index ccbbc8f9..2424a7b8 100644 --- a/meson.build +++ b/meson.build @@ -195,33 +195,33 @@ if with_vst3 '-DNOMCX', '-DWIN32_LEAN_AND_MEAN', ] - vst3_base_wine = static_library( - 'base_wine', + vst3_base_wine_64bit = static_library( + 'vst3_base_wine_64bit', vst3.get_variable('base_sources'), cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-m64', '-Wno-cpp'], include_directories : vst3_include_dir, override_options : ['warning_level=0'], native : false, ) - vst3_pluginterfaces_wine = static_library( - 'pluginterfaces_wine', + vst3_pluginterfaces_wine_64bit = static_library( + 'vst3_pluginterfaces_wine_64bit', vst3.get_variable('pluginterfaces_sources'), cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-m64'], include_directories : vst3_include_dir, override_options : ['warning_level=0'], native : false, ) - vst3_sdk_hosting_wine = static_library( - 'sdk_hosting_wine', + vst3_sdk_hosting_wine_64bit = static_library( + 'vst3_sdk_hosting_wine_64bit', vst3.get_variable('sdk_common_sources') + vst3.get_variable('sdk_hosting_sources'), - link_with : [vst3_base_wine, vst3_pluginterfaces_wine], + link_with : [vst3_base_wine_64bit, vst3_pluginterfaces_wine_64bit], cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-m64', '-Wno-multichar'], include_directories : vst3_include_dir, override_options : ['warning_level=0'], native : false, ) - vst3_sdk_hosting_wine_dep = declare_dependency( - link_with : vst3_sdk_hosting_wine, + vst3_sdk_hosting_wine_64bit_dep = declare_dependency( + link_with : vst3_sdk_hosting_wine_64bit, include_directories : vst3_include_dir, # This does mean that we now have a lot of defines in our code, but the # alternative would be patching every location in the SDK where they include @@ -231,33 +231,33 @@ if with_vst3 # And another time for the 32-bit version if with_bitbridge - vst3_base_wine_32 = static_library( - 'base_wine_32', + vst3_base_wine_32bit = static_library( + 'vst3_base_wine_32bit', vst3.get_variable('base_sources'), cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-m32', '-Wno-cpp'], include_directories : vst3_include_dir, override_options : ['warning_level=0'], native : false, ) - vst3_pluginterfaces_wine_32 = static_library( - 'pluginterfaces_wine_32', + vst3_pluginterfaces_wine_32bit = static_library( + 'vst3_pluginterfaces_wine_32bit', vst3.get_variable('pluginterfaces_sources'), cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-m32'], include_directories : vst3_include_dir, override_options : ['warning_level=0'], native : false, ) - vst3_sdk_hosting_wine_32 = static_library( - 'sdk_hosting_wine_32', + vst3_sdk_hosting_wine_32bit = static_library( + 'vst3_sdk_hosting_wine_32bit', vst3.get_variable('sdk_common_sources') + vst3.get_variable('sdk_hosting_sources'), - link_with : [vst3_base_wine_32, vst3_pluginterfaces_wine_32], + link_with : [vst3_base_wine_32bit, vst3_pluginterfaces_wine_32bit], cpp_args : vst3_compiler_options + vst3_wine_compiler_options + ['-m32', '-Wno-multichar'], include_directories : vst3_include_dir, override_options : ['warning_level=0'], native : false, ) - vst3_sdk_hosting_wine_32_dep = declare_dependency( - link_with : vst3_sdk_hosting_wine_32, + vst3_sdk_hosting_wine_32bit_dep = declare_dependency( + link_with : vst3_sdk_hosting_wine_32bit, include_directories : vst3_include_dir, # This does mean that we now have a lot of defines in our code, but the # alternative would be patching every location in the SDK where they include @@ -309,7 +309,7 @@ if with_vst3 ) endif -host_deps = [ +host_64bit_deps = [ boost_dep, boost_filesystem_dep, bitsery_dep, @@ -319,9 +319,9 @@ host_deps = [ xcb_dep, ] if with_vst3 - host_deps += [ + host_64bit_deps += [ native_filesystem_dep, - vst3_sdk_hosting_wine_dep, + vst3_sdk_hosting_wine_64bit_dep, wine_ole32_dep, wine_uuid_dep, ] @@ -336,7 +336,7 @@ if with_bitbridge # search path set by the system in `find_library()`. If anyone does know how # to properly do this, please let me know! winegcc = meson.get_compiler('cpp', native : false) - boost_filesystem_32_dep = winegcc.find_library( + boost_filesystem_32bit_dep = winegcc.find_library( 'boost_filesystem', static : with_static_boost, dirs : [ @@ -353,21 +353,21 @@ if with_bitbridge '/usr/lib', ] ) - xcb_32_dep = winegcc.find_library('xcb') + xcb_32bit_dep = winegcc.find_library('xcb') - host_32_deps = [ + host_32bit_deps = [ boost_dep, - boost_filesystem_32_dep, + boost_filesystem_32bit_dep, bitsery_dep, function2_dep, tomlplusplus_dep, wine_threads_dep, - xcb_32_dep, + xcb_32bit_dep, ] if with_vst3 - host_32_deps += [ + host_32bit_deps += [ native_filesystem_dep, - vst3_sdk_hosting_wine_32_dep, + vst3_sdk_hosting_wine_32bit_dep, wine_ole32_dep, wine_uuid_dep, ] @@ -379,7 +379,7 @@ executable( individual_host_sources, native : false, include_directories : include_dir, - dependencies : host_deps, + dependencies : host_64bit_deps, cpp_args : compiler_options + ['-m64'], link_args : ['-m64'], ) @@ -389,7 +389,7 @@ executable( group_host_sources, native : false, include_directories : include_dir, - dependencies : host_deps, + dependencies : host_64bit_deps, cpp_args : compiler_options + ['-m64'], link_args : ['-m64'], ) @@ -400,7 +400,7 @@ if with_bitbridge individual_host_sources, native : false, include_directories : include_dir, - dependencies : host_32_deps, + dependencies : host_32bit_deps, cpp_args : compiler_options + ['-m32'], link_args : ['-m32'], ) @@ -410,7 +410,7 @@ if with_bitbridge group_host_sources, native : false, include_directories : include_dir, - dependencies : host_32_deps, + dependencies : host_32bit_deps, cpp_args : compiler_options + ['-m32'], link_args : ['-m32'], ) From 8381b4a83604c51b2379dab29ba843cf23636254 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 18:24:52 +0100 Subject: [PATCH 063/456] Link to shell32.dll For some reason this is required on the Ubuntu 18.04 build, but not on my computer nor on the Ubuntu 20.04 build all using the same version of Wine and GCC. --- meson.build | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/meson.build b/meson.build index 2424a7b8..8328465f 100644 --- a/meson.build +++ b/meson.build @@ -129,6 +129,8 @@ xcb_dep = dependency('xcb') # TODO: Statically link this on the ubuntu-18.04 build native_filesystem_dep = declare_dependency(link_args : '-lstdc++fs') wine_ole32_dep = declare_dependency(link_args : '-lole32') +# The SDK includes a comment pragma that would link to this on MSVC +wine_shell32_dep = declare_dependency(link_args : '-lshell32') wine_uuid_dep = declare_dependency(link_args : '-luuid') include_dir = include_directories('src/include') @@ -323,6 +325,7 @@ if with_vst3 native_filesystem_dep, vst3_sdk_hosting_wine_64bit_dep, wine_ole32_dep, + wine_shell32_dep, wine_uuid_dep, ] endif @@ -369,6 +372,7 @@ if with_bitbridge native_filesystem_dep, vst3_sdk_hosting_wine_32bit_dep, wine_ole32_dep, + wine_shell32_dep, wine_uuid_dep, ] endif From 211f6156a78e9ac784938ef752eae4e61daaea02 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 19:08:11 +0100 Subject: [PATCH 064/456] Get rid of the string conversion patch This solved some compiler errors early on but it ended up not being needed, and as it turns out this change actually breaks things because the SDK uses a ton of reinterpret casts to convert between string types internally. --- tools/patch-vst3-sdk.sh | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tools/patch-vst3-sdk.sh b/tools/patch-vst3-sdk.sh index d968b3fe..8caa7c99 100755 --- a/tools/patch-vst3-sdk.sh +++ b/tools/patch-vst3-sdk.sh @@ -63,15 +63,6 @@ replace_char16 "using Converter = std::wstring_convert` header instead of the experimental one -# TODO: Check if now works with Winelib, or replace with Boost -# sed -i 's/^#if _HAS_CXX17 && defined(_MSC_VER)$/#if 1/' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" - # Don't try adding `std::u8string` to an `std::vector`. MSVC # probably coerces them, but GCC doesn't sed -i 's/\bgeneric_u8string\b/generic_string/g' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" From 0b462c034e453b07b4dc5b446a6584f18e7dfde5 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 19:09:55 +0100 Subject: [PATCH 065/456] Allow loading VST3 modules Doesn't actually work yet or do anything, but it compiles and it runs. --- src/plugin/vst3-plugin.cpp | 2 ++ src/wine-host/bridges/vst3.cpp | 16 ++++++++++++++++ src/wine-host/bridges/vst3.h | 4 ++++ src/wine-host/individual-host.cpp | 2 ++ 4 files changed, 24 insertions(+) diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index bc27dc69..bb55be74 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -36,6 +36,8 @@ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { // already implemented and which are left // TODO: And when we get a query for some interface that we do not (yet) // support, we should print some easy to spot warning message + // TODO: Check whether `IPlugView::isPlatformTypeSupported` needs + // special handling. // TODO: Should we always use plugin groups or for VST3 plugins? Since // they seem to be very keen on sharing resources and leaving // modules loaded. diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 817dd545..e6d29728 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -17,4 +17,20 @@ #include "vst3.h" // TODO: Do something with this, I just wanted to get the build working +// TODO: Check if this can load both regular Unix style paths with Wine. Oh and +// check if it works at all. +// TODO: Check if now works with Winelib, or replace with Boost #include + +void justdewit(const std::string& path) { + std::string error; + std::shared_ptr plugin = + VST3::Hosting::Win32Module::create(path, error); + + if (plugin) { + std::cerr << "Hooray!" << std::endl; + } else { + std::cerr << "Ohnoes!" << std::endl; + std::cerr << error << std::endl; + } +} diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index e50f13e6..e6206b82 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -15,3 +15,7 @@ // along with this program. If not, see . #pragma once + +#include + +void justdewit(const std::string& path); diff --git a/src/wine-host/individual-host.cpp b/src/wine-host/individual-host.cpp index 8c2679e9..1201ecf0 100644 --- a/src/wine-host/individual-host.cpp +++ b/src/wine-host/individual-host.cpp @@ -85,6 +85,8 @@ main(int argc, char* argv[]) { break; case PluginType::vst3: #ifdef WITH_VST3 + justdewit(plugin_location); + std::cerr << "TODO: Not yet implemented" << std::endl; return 1; #else From ebc7802c082a310f3fd5b49f92115bbaccc5d15d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 19:43:34 +0100 Subject: [PATCH 066/456] Patch Win32 module loading to use Boost.Filesystem C++17's `` header still doesn't seem to work with winegcc. --- meson.build | 5 ----- src/wine-host/bridges/vst3.cpp | 2 ++ tools/patch-vst3-sdk.sh | 7 +++++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index 8328465f..f25f8565 100644 --- a/meson.build +++ b/meson.build @@ -125,9 +125,6 @@ tomlplusplus_dep = subproject('tomlplusplus', version : '2.1.0').get_variable('t wine_threads_dep = declare_dependency(link_args : '-lpthread') xcb_dep = dependency('xcb') -# These are required for the VST3 SDK's module import system -# TODO: Statically link this on the ubuntu-18.04 build -native_filesystem_dep = declare_dependency(link_args : '-lstdc++fs') wine_ole32_dep = declare_dependency(link_args : '-lole32') # The SDK includes a comment pragma that would link to this on MSVC wine_shell32_dep = declare_dependency(link_args : '-lshell32') @@ -322,7 +319,6 @@ host_64bit_deps = [ ] if with_vst3 host_64bit_deps += [ - native_filesystem_dep, vst3_sdk_hosting_wine_64bit_dep, wine_ole32_dep, wine_shell32_dep, @@ -369,7 +365,6 @@ if with_bitbridge ] if with_vst3 host_32bit_deps += [ - native_filesystem_dep, vst3_sdk_hosting_wine_32bit_dep, wine_ole32_dep, wine_shell32_dep, diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index e6d29728..920f0f5a 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -16,6 +16,8 @@ #include "vst3.h" +#include "../boost-fix.h" + // TODO: Do something with this, I just wanted to get the build working // TODO: Check if this can load both regular Unix style paths with Wine. Oh and // check if it works at all. diff --git a/tools/patch-vst3-sdk.sh b/tools/patch-vst3-sdk.sh index 8caa7c99..5e712099 100755 --- a/tools/patch-vst3-sdk.sh +++ b/tools/patch-vst3-sdk.sh @@ -63,6 +63,13 @@ replace_char16 "using Converter = std::wstring_convert$/#include /' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" +sed -i 's/^using namespace std\(::experimental\)\?;$/namespace filesystem = boost::filesystem;/' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" +sed -i 's/\bfile_type::directory\b/file_type::directory_file/g' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" +sed -i 's/\bp\.native ()/p.wstring ()/g' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" + # Don't try adding `std::u8string` to an `std::vector`. MSVC # probably coerces them, but GCC doesn't sed -i 's/\bgeneric_u8string\b/generic_string/g' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" From 6179fddbc8eedc4a912916ba7eecec30f3e49b1f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 20:00:50 +0100 Subject: [PATCH 067/456] Do some more testing and print some plugin info --- src/wine-host/bridges/vst3.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 920f0f5a..11535ba9 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -19,9 +19,6 @@ #include "../boost-fix.h" // TODO: Do something with this, I just wanted to get the build working -// TODO: Check if this can load both regular Unix style paths with Wine. Oh and -// check if it works at all. -// TODO: Check if now works with Winelib, or replace with Boost #include void justdewit(const std::string& path) { @@ -30,7 +27,13 @@ void justdewit(const std::string& path) { VST3::Hosting::Win32Module::create(path, error); if (plugin) { - std::cerr << "Hooray!" << std::endl; + // TODO: They use some thin wrappers around the interfaces, we can + // probably reuse these instead of having to make our own + VST3::Hosting::FactoryInfo info = plugin->getFactory().info(); + std::cout << "Plugin name: " << plugin->getName() << std::endl; + std::cout << "Vendor: " << info.vendor() << std::endl; + std::cout << "URL: " << info.url() << std::endl; + std::cout << "Send spam to: " << info.email() << std::endl; } else { std::cerr << "Ohnoes!" << std::endl; std::cerr << error << std::endl; From 6e5aa1c1c601610688058d704a1fc6705d217a84 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 21:19:55 +0100 Subject: [PATCH 068/456] Add the Vst3Bridge boilerplate --- src/common/communication/vst3.h | 65 +++++++++++++++++++++++++++++++ src/wine-host/bridges/group.cpp | 4 +- src/wine-host/bridges/vst2.h | 9 ++--- src/wine-host/bridges/vst3.cpp | 32 ++++++++++----- src/wine-host/bridges/vst3.h | 63 +++++++++++++++++++++++++++++- src/wine-host/individual-host.cpp | 6 +-- 6 files changed, 159 insertions(+), 20 deletions(-) create mode 100644 src/common/communication/vst3.h diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h new file mode 100644 index 00000000..bab15217 --- /dev/null +++ b/src/common/communication/vst3.h @@ -0,0 +1,65 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "common.h" + +/** + * Manages all the sockets used for communicating between the plugin and the + * Wine host when hosting a VST3 plugin. + * + * On the plugin side this class should be initialized with `listen` set to + * `true` before launching the Wine VST host. This will start listening on the + * sockets, and the call to `connect()` will then accept any incoming + * connections. + * + * @tparam Thread The thread implementation to use. On the Linux side this + * should be `std::jthread` and on the Wine side this should be `Win32Thread`. + */ +template +class Vst3Sockets : public Sockets { + public: + /** + * Sets up the sockets using the specified base directory. The sockets won't + * be active until `connect()` gets called. + * + * @param io_context The IO context the sockets should be bound to. Relevant + * when doing asynchronous operations. + * @param endpoint_base_dir The base directory that will be used for the + * Unix domain sockets. + * @param listen If `true`, start listening on the sockets. Incoming + * connections will be accepted when `connect()` gets called. This should + * be set to `true` on the plugin side, and `false` on the Wine host side. + * + * @see Vst3Sockets::connect + */ + Vst3Sockets(boost::asio::io_context& io_context, + const boost::filesystem::path& endpoint_base_dir, + bool listen) + : Sockets(endpoint_base_dir) {} + + ~Vst3Sockets() { close(); } + + void connect() override {} + + void close() override { + // Manually close all sockets so we break out of any blocking operations + // that may still be active + } +}; diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index 350e400b..d38cbf49 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -208,7 +208,9 @@ void GroupBridge::accept_requests() { break; case PluginType::vst3: #ifdef WITH_VST3 - throw std::runtime_error("TODO: Not yet implemented"); + bridge = std::make_unique( + main_context, request.plugin_path, + request.endpoint_base_dir); #else throw std::runtime_error( "This version of yabridge has not been compiled " diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 403977b5..5f80c029 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -28,7 +28,6 @@ #include #include -#include #include #include "../../common/communication/vst2.h" @@ -43,14 +42,14 @@ class Vst2Bridge : public HostBridge { public: /** - * Initializes the Windows VST plugin and set up communication with the - * native Linux VST plugin. + * Initializes the Windows VST2 plugin and set up communication with the + * native Linux VST2 plugin. * * @param main_context The main IO context for this application. Most events * will be dispatched to this context, and the event handling loop should * also be run from this context. - * @param plugin_dll_path A (Unix style) path to the VST plugin .dll file to - * load. + * @param plugin_dll_path A (Unix style) path to the VST2 plugin .dll file + * to load. * @param endpoint_base_dir The base directory used for the socket * endpoints. See `Sockets` for more information. * diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 11535ba9..700a46d0 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -18,24 +18,38 @@ #include "../boost-fix.h" -// TODO: Do something with this, I just wanted to get the build working #include -void justdewit(const std::string& path) { +Vst3Bridge::Vst3Bridge(MainContext& main_context, + std::string plugin_dll_path, + std::string endpoint_base_dir) + : HostBridge(plugin_dll_path), + main_context(main_context), + sockets(main_context.context, endpoint_base_dir, false) { std::string error; - std::shared_ptr plugin = - VST3::Hosting::Win32Module::create(path, error); + module = VST3::Hosting::Win32Module::create(plugin_dll_path, error); - if (plugin) { + // TODO: Do something more useful with this + if (module) { // TODO: They use some thin wrappers around the interfaces, we can // probably reuse these instead of having to make our own - VST3::Hosting::FactoryInfo info = plugin->getFactory().info(); - std::cout << "Plugin name: " << plugin->getName() << std::endl; + VST3::Hosting::FactoryInfo info = module->getFactory().info(); + std::cout << "Plugin name: " << module->getName() << std::endl; std::cout << "Vendor: " << info.vendor() << std::endl; std::cout << "URL: " << info.url() << std::endl; std::cout << "Send spam to: " << info.email() << std::endl; } else { - std::cerr << "Ohnoes!" << std::endl; - std::cerr << error << std::endl; + throw std::runtime_error("Could not load the VST3 module for '" + + plugin_dll_path + "': " + error); } + + sockets.connect(); + + // TODO: We should send a copy of the configuration from the plugin at this + // point config = sockets.host_vst_control.receive_single(); +} + +void Vst3Bridge::run() { + // TODO: Do something + std::cerr << "TODO: Not yet implemented" << std::endl; } diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index e6206b82..45ca30a6 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -18,4 +18,65 @@ #include -void justdewit(const std::string& path); +#include + +#include "../../common/communication/vst3.h" +#include "../../common/configuration.h" +#include "common.h" + +/** + * This hosts a Windows VST3 plugin, forwards messages sent by the Linux VST + * plugin and provides host callback function for the plugin to talk back. + */ +class Vst3Bridge : public HostBridge { + public: + /** + * Initializes the Windows VST3 plugin and set up communication with the + * native Linux VST plugin. + * + * @param main_context The main IO context for this application. Most events + * will be dispatched to this context, and the event handling loop should + * also be run from this context. + * @param plugin_dll_path A (Unix style) path to the VST plugin .dll file to + * load. + * @param endpoint_base_dir The base directory used for the socket + * endpoints. See `Sockets` for more information. + * + * @note The object has to be constructed from the same thread that calls + * `main_context.run()`. + * + * @throw std::runtime_error Thrown when the VST plugin could not be loaded, + * or if communication could not be set up. + */ + Vst3Bridge(MainContext& main_context, + std::string plugin_dll_path, + std::string endpoint_base_dir); + + void run() override; + + private: + /** + * The IO context used for event handling so that all events and window + * message handling can be performed from a single thread, even when hosting + * multiple plugins. + */ + MainContext& main_context; + + /** + * The configuration for this instance of yabridge based on the `.so` file + * that got loaded by the host. This configuration gets loaded on the plugin + * side, and then sent over to the Wine host as part of the startup process. + */ + Configuration config; + + std::shared_ptr module; + + /** + * All sockets used for communicating with this specific plugin. + * + * NOTE: This is defined **after** the threads on purpose. This way the + * sockets will be closed first, and we can then safely wait for the + * threads to exit. + */ + Vst3Sockets sockets; +}; diff --git a/src/wine-host/individual-host.cpp b/src/wine-host/individual-host.cpp index 1201ecf0..aeff5f5a 100644 --- a/src/wine-host/individual-host.cpp +++ b/src/wine-host/individual-host.cpp @@ -85,10 +85,8 @@ main(int argc, char* argv[]) { break; case PluginType::vst3: #ifdef WITH_VST3 - justdewit(plugin_location); - - std::cerr << "TODO: Not yet implemented" << std::endl; - return 1; + bridge = std::make_unique( + main_context, plugin_location, socket_endpoint_path); #else std::cerr << "This version of yabridge has not been compiled " "with VST3 support" From ff2807c9398a6c0b2e65ed4486d60ddba09f634e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 2 Dec 2020 22:41:06 +0100 Subject: [PATCH 069/456] Add all the boilerplate for the Vst3PluginBridge And now that I also have an idea of what the communication model will look like, this can server as a base for instantiating plugins. --- README.md | 3 + meson.build | 7 ++ src/common/communication/vst3.h | 9 +++ src/common/logging/vst2.h | 3 + src/common/logging/vst3.h | 27 +++++++ src/plugin/bridges/vst2.h | 3 +- src/plugin/bridges/vst3.cpp | 61 +++++++++++++++ src/plugin/bridges/vst3.h | 129 ++++++++++++++++++++++++++++++++ src/plugin/vst3-plugin.cpp | 72 +++++++++++++++--- 9 files changed, 303 insertions(+), 11 deletions(-) create mode 100644 src/common/logging/vst3.h create mode 100644 src/plugin/bridges/vst3.cpp create mode 100644 src/plugin/bridges/vst3.h diff --git a/README.md b/README.md index 72fcb93b..86786e49 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,9 @@ imcomplete list of things that still have to be done before this can be used: figure out how to do this in the least confusing way possible. - Mention that this update will break all existing symlinks and that the old `libyabridge.so` file should be removed when upgrading. +- Pay close attention to the plugin groups section, since VST3 plugins by design + cannot be hosted completely individually (as in, each plugin is basically in + its own group). - Update all the AUR packages. - Test the binaries built on GitHub on plain Ubuntu 18.04, are we missing any static linking? diff --git a/meson.build b/meson.build index f25f8565..7a19cfa9 100644 --- a/meson.build +++ b/meson.build @@ -74,8 +74,15 @@ vst2_plugin_sources = [ ] vst3_plugin_sources = [ + 'src/common/communication/common.cpp', 'src/common/logging/common.cpp', 'src/common/logging/vst2.cpp', + 'src/common/configuration.cpp', + 'src/common/plugins.cpp', + 'src/common/utils.cpp', + 'src/plugin/bridges/vst3.cpp', + 'src/plugin/host-process.cpp', + 'src/plugin/utils.cpp', 'src/plugin/vst3-plugin.cpp', version_header, ] diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index bab15217..06252170 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -29,6 +29,9 @@ * sockets, and the call to `connect()` will then accept any incoming * connections. * + * TODO: I have no idea what the best approach here is yet, so this is very much + * subject to change + * * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ @@ -62,4 +65,10 @@ class Vst3Sockets : public Sockets { // Manually close all sockets so we break out of any blocking operations // that may still be active } + + // TODO: I still don't know if recursive callbacks are a thing in VST3. If + // not, then we should probably have two `AdHocSocketHandler`s per + // plugin instance (one for each direction, as with `dispatcher()` and + // `audioMaster()` in VST2). Using fewer probably also works, but we + // wouldn't want to have to spawn new sockets during audio processing. }; diff --git a/src/common/logging/vst2.h b/src/common/logging/vst2.h index 841b46bf..e02b1f16 100644 --- a/src/common/logging/vst2.h +++ b/src/common/logging/vst2.h @@ -32,6 +32,9 @@ */ std::optional opcode_to_string(bool is_dispatch, int opcode); +/** + * Provides VST2 specific logging functionality for debugging plugins. + */ class Vst2Logger : public Logger { public: // The following functions are for logging specific events, they are only diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h new file mode 100644 index 00000000..8a22a848 --- /dev/null +++ b/src/common/logging/vst3.h @@ -0,0 +1,27 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "common.h" + +/** + * Provides VST3-specific logging functionality for debugging plugins. + */ +class Vst3Logger : public Logger { + public: + // TODO: Logging interface for VST3 plugins +}; diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index ce28a7aa..09f15d56 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -19,7 +19,6 @@ #include #include -#include #include #include @@ -122,7 +121,7 @@ class Vst2PluginBridge { * The configuration for this instance of yabridge. Set based on the values * from a `yabridge.toml`, if it exists. * - * @see ./utils.h:load_config_for + * @see ../utils.h:load_config_for */ Configuration config; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp new file mode 100644 index 00000000..a02e2aae --- /dev/null +++ b/src/plugin/bridges/vst3.cpp @@ -0,0 +1,61 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "vst3.h" + +#include "../../common/utils.h" + +// TODO: Do all of the initialization stuff from `Vst2PluginBridge` +Vst3PluginBridge::Vst3PluginBridge() + : // TODO: This is technically correct because we can configure the entire + // directory at once + config(load_config_for(get_this_file_location())), + // TODO: This is incorrect for VST3 modules + plugin_module_path(find_vst_plugin()), + io_context(), + sockets(io_context, + // TODO: This is incorrect + generate_endpoint_base( + plugin_module_path.filename().replace_extension("").string()), + true), + logger(Logger::create_from_environment( + create_logger_prefix(sockets.base_dir))), + wine_version(get_wine_version()), + vst_host( + config.group + ? std::unique_ptr(std::make_unique( + io_context, + logger, + HostRequest{.plugin_type = PluginType::vst2, + .plugin_path = plugin_module_path.string(), + .endpoint_base_dir = sockets.base_dir.string()}, + sockets, + *config.group)) + : std::unique_ptr(std::make_unique( + io_context, + logger, + HostRequest{ + .plugin_type = PluginType::vst2, + .plugin_path = plugin_module_path.string(), + .endpoint_base_dir = sockets.base_dir.string()}))), + has_realtime_priority(set_realtime_priority()), + wine_io_handler([&]() { io_context.run(); }) { + log_init_message(); +} + +void Vst3PluginBridge::log_init_message() { + // TODO: Move `Vst2PluginBridge::log_init_message()` to utils and call that +} diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h new file mode 100644 index 00000000..50cd70ec --- /dev/null +++ b/src/plugin/bridges/vst3.h @@ -0,0 +1,129 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include +#include + +#include "../../common/communication/vst3.h" +#include "../../common/configuration.h" +#include "../../common/logging/vst3.h" +#include "../host-process.h" + +/** + * This handles the communication between the native host and a VST3 plugin + * hosted in our Wine plugin host. VST3 is handled very differently from VST2 + * because a plugin is no longer its own entity, but rather a definition of + * objects that the host can create and interconnect. This `Vst3PluginBridge` + * will be instantiated when the plugin first gets loaded, and it will survive + * until the last instance of the plugin gets removed. The Wine host process + * will thus also have the same lifetime, and even with yabridge's 'individual' + * plugin hosting other instances of the same plugin will be handled by a single + * process. + * + * @remark See the comments at the top of `vst3-plugin.cpp` for more + * information. + * + * The naming scheme of all of these 'bridge' classes is `{,Plugin}Bridge` + * for greppability reasons. The `Plugin` infix is added on the native plugin + * side. + */ +class Vst3PluginBridge { + public: + /** + * Initializes the VST3 module by starting and setting up communicating with + * the Wine plugin host. + * + * @throw std::runtime_error Thrown when the Wine plugin host could not be + * found, or if it could not locate and load a VST3 module. + */ + Vst3PluginBridge(); + + /** + * The configuration for this instance of yabridge. Set based on the values + * from a `yabridge.toml`, if it exists. + * + * @see ../utils.h:load_config_for + */ + Configuration config; + + /** + * The path to the VST3 module being loaded in the Wine VST host. This is + * normally a directory called `MyPlugin.vst3` that contains + * `MyPlugin.vst3/Contents/x86-win/MyPlugin.vst3`, but there's also an older + * deprecated (but still ubiquitous) format where the top level + * `MyPlugin.vst3` is not a directory but a .dll file. This points to either + * of those things, and then `VST3::Hosting::Win32Module::create()` will be + * able to load it. + * + * https://developer.steinberg.help/pages/viewpage.action?pageId=9798275 + */ + const boost::filesystem::path plugin_module_path; + + private: + /** + * Format and log all relevant debug information during initialization. + */ + void log_init_message(); + + boost::asio::io_context io_context; + Vst3Sockets sockets; + + /** + * The logging facility used for this instance of yabridge. See + * `Logger::create_from_env()` for how this is configured. + * + * @see Logger::create_from_env + */ + Vst3Logger logger; + + /** + * The version of Wine currently in use. Used in the debug output on plugin + * startup. + */ + const std::string wine_version; + + /** + * The Wine process hosting the Windows VST3 plugin. + * + * @see launch_vst_host + */ + std::unique_ptr vst_host; + + /** + * A thread used during the initialisation process to terminate listening on + * the sockets if the Wine process cannot start for whatever reason. This + * has to be defined here instead of in the constructor we can't simply + * detach the thread as it has to check whether the Wine plugin host is + * still running. + */ + std::jthread host_guard_handler; + + /** + * Whether this process runs with realtime priority. We'll set this _after_ + * spawning the Wine process because from my testing running wineserver with + * realtime priority can actually increase latency. + */ + bool has_realtime_priority; + + /** + * Runs the Boost.Asio `io_context` thread for logging the Wine process + * STDOUT and STDERR messages. + */ + std::jthread wine_io_handler; +}; diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index bb55be74..cd12d108 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -1,17 +1,66 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + #include -// TODO: Should you include this implementation file or copy everything over? #include +#include "bridges/vst3.h" + using Steinberg::gPluginFactory; -// TODO: What should we do here? Also, note to self, don't forget to call these -// on the Wine host side if the host SDK doesn't already do that for us. +// Because VST3 plugins consist of completely independent components that have +// to be initialized and connected by the host, hosting a VST3 plugin through +// yabridge works very differently from hosting VST2 plugin. Even with +// individually hosted plugins, all instances of the plugin will be handled by a +// single dynamic library (that VST3 calls a 'module'). Because of this, we'll +// spawn our host process when the first instance of a plugin gets initialized, +// and when the last instance exits so will the host process. +// +// Even though the new VST3 module format where everything's inside of a bundle +// is not particularly common, it is the only standard for Linux and that's what +// we'll use. The installation format for yabridge will thus have the Windows +// plugin symlinked to either the `x86_64-win` or the `x86-win` directory inside +// of the bundle, even if it does not come in a bundle itself. + +Vst3PluginBridge* bridge = nullptr; + bool InitModule() { - return true; + assert(bridge == nullptr); + + try { + // 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. + bridge = new Vst3PluginBridge(); + + return true; + } catch (const std::exception& error) { + Logger logger = Logger::create_from_environment(); + logger.log("Error during initialization:"); + logger.log(error.what()); + + return false; + } } bool DeinitModule() { + assert(bridge != nullptr); + + delete bridge; return true; } @@ -21,9 +70,11 @@ bool DeinitModule() { * classes, and then recreate it here. */ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { - // TODO: So from this I can imagine that the host is supposed to keep this - // module loaded into memory and reuse it for multiple plugins? How - // should Wine host instances be tied to native plugin instances? + // TODO: Check the VST3::Hosting module loading source to see if + // gPluginFactory is used directly by the host or not. + // TODO: We can do all of our allocations and things indie of + // `Vst3PluginBridge`, so this function should call some function on + // Vst3Bridge that does the initialization if (!gPluginFactory) { // TODO: Here we want to: // 1) Load the plugin on the Wine host @@ -64,8 +115,11 @@ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { // also installed there. // // The second one sounds much better, but it will still need some - // more consideration. Also, yabridgectl will need to do some - // extra work there to detect removed plugins. + // more consideration. Aside from that VST3 plugins also have a + // centralized preset location, even though barely anyone uses it, + // yabridgectl will also have to make a symlink of that. Also, + // yabridgectl will need to do some extra work there to detect + // removed plugins. // TODO: Also symlink presets, and allow pruning broken symlinks there // as well // TODO: And how do we choose between 32-bit and 64-bit versions of a From 3c72ab31f85b9efbc8cf7fc2b65e800b7d918324 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 3 Dec 2020 00:07:35 +0100 Subject: [PATCH 070/456] Shut up clang/ccls --- src/plugin/bridges/vst2.cpp | 5 +++-- src/plugin/bridges/vst3.cpp | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index 359aefc6..af513dfe 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -58,8 +58,9 @@ Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) vst_plugin_path.filename().replace_extension("").string()), true), host_callback_function(host_callback), - logger(Logger::create_from_environment( - create_logger_prefix(sockets.base_dir))), + // This weird cast is not needed, but without it clang/ccls won't shut up + logger(static_cast(Logger::create_from_environment( + create_logger_prefix(sockets.base_dir)))), wine_version(get_wine_version()), vst_host( config.group diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index a02e2aae..b81e81b4 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -31,8 +31,11 @@ Vst3PluginBridge::Vst3PluginBridge() generate_endpoint_base( plugin_module_path.filename().replace_extension("").string()), true), - logger(Logger::create_from_environment( - create_logger_prefix(sockets.base_dir))), + // This weird cast is not needed, but without it clang/ccls won't shut up + // TODO: Apparently this is UB even though it works fine, so we should + // probably just use composition here instead + logger(static_cast(Logger::create_from_environment( + create_logger_prefix(sockets.base_dir)))), wine_version(get_wine_version()), vst_host( config.group From c57fd67aa87d873f9c684c7e9bcde8bf41789bba Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 3 Dec 2020 12:00:56 +0100 Subject: [PATCH 071/456] Don't store the Wine version as a field It's only needed for the initialisation message, and it doesn't throw so doing this later shouldn't make a difference. --- src/plugin/bridges/vst2.cpp | 3 +-- src/plugin/bridges/vst2.h | 6 ------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index af513dfe..4bc237f4 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -61,7 +61,6 @@ Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) // This weird cast is not needed, but without it clang/ccls won't shut up logger(static_cast(Logger::create_from_environment( create_logger_prefix(sockets.base_dir)))), - wine_version(get_wine_version()), vst_host( config.group ? std::unique_ptr(std::make_unique( @@ -646,7 +645,7 @@ void Vst2PluginBridge::log_init_message() { } init_msg << "'" << std::endl; - init_msg << "wine version: '" << wine_version << "'" << std::endl; + init_msg << "wine version: '" << get_wine_version() << "'" << std::endl; init_msg << std::endl; // Print the path to the currently loaded configuration file and all diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index 09f15d56..967cd7a7 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -172,12 +172,6 @@ class Vst2PluginBridge { */ Vst2Logger logger; - /** - * The version of Wine currently in use. Used in the debug output on plugin - * startup. - */ - const std::string wine_version; - /** * The Wine process hosting the Windows VST plugin. * From f845763af09c1e121f48f855f55e6acf5313fd9b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 3 Dec 2020 12:08:24 +0100 Subject: [PATCH 072/456] Print the plugin type during initialisation --- src/plugin/bridges/vst2.cpp | 7 ++++--- src/plugin/bridges/vst2.h | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index 4bc237f4..c716d8ed 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -66,7 +66,7 @@ Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) ? std::unique_ptr(std::make_unique( io_context, logger, - HostRequest{.plugin_type = PluginType::vst2, + HostRequest{.plugin_type = plugin_type, .plugin_path = vst_plugin_path.string(), .endpoint_base_dir = sockets.base_dir.string()}, sockets, @@ -75,7 +75,7 @@ Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) io_context, logger, HostRequest{ - .plugin_type = PluginType::vst2, + .plugin_type = plugin_type, .plugin_path = vst_plugin_path.string(), .endpoint_base_dir = sockets.base_dir.string()}))), has_realtime_priority(set_realtime_priority()), @@ -622,13 +622,14 @@ void Vst2PluginBridge::set_parameter(AEffect* /*plugin*/, void Vst2PluginBridge::log_init_message() { std::stringstream init_msg; - // TODO: This should also list the plugin type init_msg << "Initializing yabridge version " << yabridge_git_version << std::endl; init_msg << "host: '" << vst_host->path().string() << "'" << std::endl; init_msg << "plugin: '" << vst_plugin_path.string() << "'" << std::endl; + init_msg << "plugin type: '" << plugin_type_to_string(plugin_type) << "'" + << std::endl; init_msg << "realtime: '" << (has_realtime_priority ? "yes" : "no") << "'" << std::endl; init_msg << "sockets: '" << sockets.base_dir.string() << "'" diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index 967cd7a7..6a2fcd1a 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -50,6 +50,12 @@ class Vst2PluginBridge { */ Vst2PluginBridge(audioMasterCallback host_callback); + /** + * The type of the plugin we're dealing with. Passed to the host process and + * printed in the initialisation message. + */ + static constexpr PluginType plugin_type = PluginType::vst2; + // The four below functions are the handlers from the VST2 API. They are // called through proxy functions in `plugin.cpp`. From 6b9ae78b2749588eabe67154013b6dd1d28f6290 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 3 Dec 2020 14:20:42 +0100 Subject: [PATCH 073/456] Factor out all plumbing in Vst2PluginBridge So we can reuse it in Vst3PluginBridge later. --- src/plugin/bridges/common.h | 247 ++++++++++++++++++++++++++++++++++++ src/plugin/bridges/vst2.cpp | 153 +++------------------- src/plugin/bridges/vst2.h | 58 +-------- src/plugin/bridges/vst3.h | 8 +- src/plugin/utils.cpp | 2 + 5 files changed, 273 insertions(+), 195 deletions(-) create mode 100644 src/plugin/bridges/common.h diff --git a/src/plugin/bridges/common.h b/src/plugin/bridges/common.h new file mode 100644 index 00000000..c845a2c9 --- /dev/null +++ b/src/plugin/bridges/common.h @@ -0,0 +1,247 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +// Generated inside of the build directory +#include +#include + +#include "../../common/configuration.h" +#include "../../common/utils.h" +#include "../host-process.h" + +/** + * Handles all common operations for hosting plugins such as setting up the + * plugin host process, the logger, and logging debug information on startup. + * + * @tparam Sockets the `Sockets` implementation to use. We have to initialize it + * here because we need to pass it to our `HostProcess`. + */ +template TSockets> +class PluginBridge { + public: + /** + * Sets up everything needed to start the host process. Classes deriving + * from this should call `log_init_message()` themselves after their + * initialization list. + * + * @param plugin_type The type of the plugin we're handling. + * @param plugin_path The path to the plugin. For VST2 plugins this is the + * path to the `.dll` file, and for VST3 plugins this is the path to the + * module (either a `.vst3` DLL file or a bundle). + * @param create_socket_instance A function to create a socket instance. + * Using a lambda here feels wrong, but I can't think of a better + * solution right now. + */ + template + PluginBridge(PluginType plugin_type, + const boost::filesystem::path& plugin_path, + F create_socket_instance) + : plugin_type(plugin_type), + plugin_path(plugin_path), + io_context(), + sockets(create_socket_instance(io_context)), + config(load_config_for(get_this_file_location())), + generic_logger(Logger::create_from_environment( + create_logger_prefix(sockets.base_dir))), + plugin_host( + config.group + ? std::unique_ptr(std::make_unique( + io_context, + generic_logger, + HostRequest{ + .plugin_type = plugin_type, + .plugin_path = plugin_path.string(), + .endpoint_base_dir = sockets.base_dir.string()}, + sockets, + *config.group)) + : std::unique_ptr( + std::make_unique( + io_context, + generic_logger, + HostRequest{.plugin_type = plugin_type, + .plugin_path = plugin_path.string(), + .endpoint_base_dir = + sockets.base_dir.string()}))), + has_realtime_priority(set_realtime_priority()), + wine_io_handler([&]() { io_context.run(); }) {} + + virtual ~PluginBridge(){}; + + protected: + /** + * Format and log all relevant debug information during initialization. + */ + void log_init_message() { + std::stringstream init_msg; + + init_msg << "Initializing yabridge version " << yabridge_git_version + << std::endl; + init_msg << "host: '" << plugin_host->path().string() << "'" + << std::endl; + init_msg << "plugin: '" << plugin_path.string() << "'" + << std::endl; + init_msg << "plugin type: '" << plugin_type_to_string(plugin_type) + << "'" << std::endl; + init_msg << "realtime: '" << (has_realtime_priority ? "yes" : "no") + << "'" << std::endl; + init_msg << "sockets: '" << sockets.base_dir.string() << "'" + << std::endl; + init_msg << "wine prefix: '"; + + // If the Wine prefix is manually overridden, then this should be made + // clear. This follows the behaviour of `set_wineprefix()`. + boost::process::environment env = boost::this_process::environment(); + if (!env["WINEPREFIX"].empty()) { + init_msg << env["WINEPREFIX"].to_string() << " "; + } else { + init_msg << find_wineprefix().value_or("").string(); + } + init_msg << "'" << std::endl; + + init_msg << "wine version: '" << get_wine_version() << "'" << std::endl; + init_msg << std::endl; + + // Print the path to the currently loaded configuration file and all + // settings in use. Printing the matched glob pattern could also be + // useful but it'll be very noisy and it's likely going to be clear from + // the shown values anyways. + init_msg << "config from: '" + << config.matched_file.value_or("").string() << "'" + << std::endl; + + init_msg << "hosting mode: '"; + if (config.group) { + init_msg << "plugin group \"" << *config.group << "\""; + } else { + init_msg << "individually"; + } + if (plugin_host->architecture() == LibArchitecture::dll_32) { + init_msg << ", 32-bit"; + } else { + init_msg << ", 64-bit"; + } + init_msg << "'" << std::endl; + + init_msg << "other options: "; + std::vector other_options; + if (config.cache_time_info) { + other_options.push_back("hack: time info cache"); + } + if (config.editor_double_embed) { + other_options.push_back("editor: double embed"); + } + if (!other_options.empty()) { + init_msg << join_quoted_strings(other_options) << std::endl; + } else { + init_msg << "''" << std::endl; + } + + // To make debugging easier, we'll print both unrecognized options (that + // might be left over when an option gets removed) as well as options + // have the wrong argument types + if (!config.invalid_options.empty()) { + init_msg << "invalid arguments: " + << join_quoted_strings(config.invalid_options) + << " (check the readme for more information)" << std::endl; + } + if (!config.unknown_options.empty()) { + init_msg << "unrecognized options: " + << join_quoted_strings(config.unknown_options) + << std::endl; + } + init_msg << std::endl; + + // Include a list of enabled compile-tiem features, mostly to make debug + // logs more useful + init_msg << "Enabled features:" << std::endl; +#ifdef WITH_BITBRIDGE + init_msg << "- bitbridge support" << std::endl; +#endif +#ifdef WITH_WINEDBG + init_msg << "- winedbg" << std::endl; +#endif +#ifdef WITH_VST3 + init_msg << "- VST3 support" << std::endl; +#endif +#if !(defined(WITH_BITBRIDGE) || defined(WITH_WINEDBG) || defined(WITH_VST3)) + init_msg << " " << std::endl; +#endif + init_msg << std::endl; + + for (std::string line = ""; std::getline(init_msg, line);) { + generic_logger.log(line); + } + } + + /** + * The type of the plugin we're dealing with. Passed to the host process and + * printed in the initialisation message. + */ + const PluginType plugin_type; + + /** + * The path to the plugin (`.dll` or module) being loaded in the Wine plugin + * host. + */ + const boost::filesystem::path plugin_path; + + boost::asio::io_context io_context; + + /** + * The sockets used for communication with the Wine process. + * + * @see PluginBridge::log_init_message + */ + TSockets sockets; + + /** + * The configuration for this instance of yabridge. Set based on the values + * from a `yabridge.toml`, if it exists. + * + * @see ../utils.h:load_config_for + */ + Configuration config; + + /** + * The logging facility used for this instance of yabridge. See + * `Logger::create_from_env()` for how this is configured. + * + * @see Logger::create_from_env + */ + Logger generic_logger; + + /** + * The Wine process hosting our plugins. In the case of group hosts a + * `PluginBridge` instance doesn't actually own a process, but rather either + * spawns a new detached process or it connects to an existing one. + */ + std::unique_ptr plugin_host; + + /** + * Whether this process runs with realtime priority. We'll set this _after_ + * spawning the Wine process because from my testing running wineserver with + * realtime priority can actually increase latency. + */ + bool has_realtime_priority; + + /** + * Runs the Boost.Asio `io_context` thread for logging the Wine process + * STDOUT and STDERR messages. + */ + std::jthread wine_io_handler; +}; diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index c716d8ed..c52213bd 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -16,15 +16,9 @@ #include "vst2.h" -// Generated inside of the build directory -#include -#include - #include "../../common/communication/vst2.h" -#include "../../common/utils.h" #include "../utils.h" -namespace bp = boost::process; // I'd rather use std::filesystem instead, but Boost.Process depends on // boost::filesystem namespace fs = boost::filesystem; @@ -46,42 +40,28 @@ Vst2PluginBridge& get_bridge_instance(const AEffect& plugin) { } Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) - : config(load_config_for(get_this_file_location())), - vst_plugin_path(find_vst_plugin()), + : PluginBridge(PluginType::vst2, + find_vst_plugin(), + [](boost::asio::io_context& io_context) { + return Vst2Sockets( + io_context, + generate_endpoint_base(find_vst_plugin() + .filename() + .replace_extension("") + .string()), + true); + }), // All the fields should be zero initialized because // `Vst2PluginInstance::vstAudioMasterCallback` from Bitwig's plugin // bridge will crash otherwise plugin(), - io_context(), - sockets(io_context, - generate_endpoint_base( - vst_plugin_path.filename().replace_extension("").string()), - true), host_callback_function(host_callback), - // This weird cast is not needed, but without it clang/ccls won't shut up + // TODO: This is UB, use composition with `generic_logger` instead logger(static_cast(Logger::create_from_environment( - create_logger_prefix(sockets.base_dir)))), - vst_host( - config.group - ? std::unique_ptr(std::make_unique( - io_context, - logger, - HostRequest{.plugin_type = plugin_type, - .plugin_path = vst_plugin_path.string(), - .endpoint_base_dir = sockets.base_dir.string()}, - sockets, - *config.group)) - : std::unique_ptr(std::make_unique( - io_context, - logger, - HostRequest{ - .plugin_type = plugin_type, - .plugin_path = vst_plugin_path.string(), - .endpoint_base_dir = sockets.base_dir.string()}))), - has_realtime_priority(set_realtime_priority()), - wine_io_handler([&]() { io_context.run(); }) { + create_logger_prefix(sockets.base_dir)))) { log_init_message(); + // TODO: Also move his to `PluginHost` #ifndef WITH_WINEDBG // If the Wine process fails to start, then nothing will connect to the // sockets and we'll be hanging here indefinitely. To prevent this, we'll @@ -93,7 +73,7 @@ Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) using namespace std::literals::chrono_literals; while (!st.stop_requested()) { - if (!vst_host->running()) { + if (!plugin_host->running()) { logger.log( "The Wine host process has exited unexpectedly. Check the " "output above for more information."); @@ -439,7 +419,7 @@ intptr_t Vst2PluginBridge::dispatch(AEffect* /*plugin*/, logger.log("The plugin crashed during shutdown, ignoring"); } - vst_host->terminate(); + plugin_host->terminate(); // The `stop()` method will cause the IO context to just drop all of // its work immediately and not throw any exceptions that would have @@ -619,107 +599,6 @@ void Vst2PluginBridge::set_parameter(AEffect* /*plugin*/, assert(!response.value); } -void Vst2PluginBridge::log_init_message() { - std::stringstream init_msg; - - init_msg << "Initializing yabridge version " << yabridge_git_version - << std::endl; - init_msg << "host: '" << vst_host->path().string() << "'" - << std::endl; - init_msg << "plugin: '" << vst_plugin_path.string() << "'" - << std::endl; - init_msg << "plugin type: '" << plugin_type_to_string(plugin_type) << "'" - << std::endl; - init_msg << "realtime: '" << (has_realtime_priority ? "yes" : "no") - << "'" << std::endl; - init_msg << "sockets: '" << sockets.base_dir.string() << "'" - << std::endl; - init_msg << "wine prefix: '"; - - // If the Wine prefix is manually overridden, then this should be made - // clear. This follows the behaviour of `set_wineprefix()`. - bp::environment env = boost::this_process::environment(); - if (!env["WINEPREFIX"].empty()) { - init_msg << env["WINEPREFIX"].to_string() << " "; - } else { - init_msg << find_wineprefix().value_or("").string(); - } - init_msg << "'" << std::endl; - - init_msg << "wine version: '" << get_wine_version() << "'" << std::endl; - init_msg << std::endl; - - // Print the path to the currently loaded configuration file and all - // settings in use. Printing the matched glob pattern could also be useful - // but it'll be very noisy and it's likely going to be clear from the shown - // values anyways. - init_msg << "config from: '" - << config.matched_file.value_or("").string() << "'" - << std::endl; - - init_msg << "hosting mode: '"; - if (config.group) { - init_msg << "plugin group \"" << *config.group << "\""; - } else { - init_msg << "individually"; - } - if (vst_host->architecture() == LibArchitecture::dll_32) { - init_msg << ", 32-bit"; - } else { - init_msg << ", 64-bit"; - } - init_msg << "'" << std::endl; - - init_msg << "other options: "; - std::vector other_options; - if (config.cache_time_info) { - other_options.push_back("hack: time info cache"); - } - if (config.editor_double_embed) { - other_options.push_back("editor: double embed"); - } - if (!other_options.empty()) { - init_msg << join_quoted_strings(other_options) << std::endl; - } else { - init_msg << "''" << std::endl; - } - - // To make debugging easier, we'll print both unrecognized options (that - // might be left over when an option gets removed) as well as options have - // the wrong argument types - if (!config.invalid_options.empty()) { - init_msg << "invalid arguments: " - << join_quoted_strings(config.invalid_options) - << " (check the readme for more information)" << std::endl; - } - if (!config.unknown_options.empty()) { - init_msg << "unrecognized options: " - << join_quoted_strings(config.unknown_options) << std::endl; - } - init_msg << std::endl; - - // Include a list of enabled compile-tiem features, mostly to make debug - // logs more useful - init_msg << "Enabled features:" << std::endl; -#ifdef WITH_BITBRIDGE - init_msg << "- bitbridge support" << std::endl; -#endif -#ifdef WITH_WINEDBG - init_msg << "- winedbg" << std::endl; -#endif -#ifdef WITH_VST3 - init_msg << "- VST3 support" << std::endl; -#endif -#if !(defined(WITH_BITBRIDGE) || defined(WITH_WINEDBG) || defined(WITH_VST3)) - init_msg << " " << std::endl; -#endif - init_msg << std::endl; - - for (std::string line = ""; std::getline(init_msg, line);) { - logger.log(line); - } -} - // The below functions are proxy functions for the methods defined in // `Bridge.cpp` diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index 6a2fcd1a..19d6e3de 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -23,9 +23,8 @@ #include #include "../../common/communication/vst2.h" -#include "../../common/configuration.h" #include "../../common/logging/vst2.h" -#include "../host-process.h" +#include "common.h" /** * This handles the communication between the Linux native VST2 plugin and the @@ -36,7 +35,7 @@ * for greppability reasons. The `Plugin` infix is added on the native plugin * side. */ -class Vst2PluginBridge { +class Vst2PluginBridge : PluginBridge> { public: /** * Initializes the Wine VST bridge. This sets up the sockets for event @@ -50,12 +49,6 @@ class Vst2PluginBridge { */ Vst2PluginBridge(audioMasterCallback host_callback); - /** - * The type of the plugin we're dealing with. Passed to the host process and - * printed in the initialisation message. - */ - static constexpr PluginType plugin_type = PluginType::vst2; - // The four below functions are the handlers from the VST2 API. They are // called through proxy functions in `plugin.cpp`. @@ -123,19 +116,6 @@ class Vst2PluginBridge { template void do_process(T** inputs, T** outputs, int sample_frames); - /** - * The configuration for this instance of yabridge. Set based on the values - * from a `yabridge.toml`, if it exists. - * - * @see ../utils.h:load_config_for - */ - Configuration config; - - /** - * The path to the .dll being loaded in the Wine VST host. - */ - const boost::filesystem::path vst_plugin_path; - /** * 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 @@ -144,14 +124,6 @@ class Vst2PluginBridge { AEffect plugin; private: - /** - * Format and log all relevant debug information during initialization. - */ - void log_init_message(); - - boost::asio::io_context io_context; - Vst2Sockets sockets; - /** * The thread that handles host callbacks. */ @@ -171,20 +143,11 @@ class Vst2PluginBridge { audioMasterCallback host_callback_function; /** - * The logging facility used for this instance of yabridge. See - * `Logger::create_from_env()` for how this is configured. - * - * @see Logger::create_from_env + * The logging facility used for this instance of yabridge. Wraps around + * `PluginBridge::generic_logger`. */ Vst2Logger logger; - /** - * The Wine process hosting the Windows VST plugin. - * - * @see launch_vst_host - */ - std::unique_ptr vst_host; - /** * A thread used during the initialisation process to terminate listening on * the sockets if the Wine process cannot start for whatever reason. This @@ -194,19 +157,6 @@ class Vst2PluginBridge { */ std::jthread host_guard_handler; - /** - * Whether this process runs with realtime priority. We'll set this _after_ - * spawning the Wine process because from my testing running wineserver with - * realtime priority can actually increase latency. - */ - bool has_realtime_priority; - - /** - * Runs the Boost.Asio `io_context` thread for logging the Wine process - * STDOUT and STDERR messages. - */ - std::jthread wine_io_handler; - /** * A scratch buffer for sending and receiving data during `process`, * `processReplacing` and `processDoubleReplacing` calls. diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 50cd70ec..106201d2 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -42,6 +42,8 @@ * The naming scheme of all of these 'bridge' classes is `{,Plugin}Bridge` * for greppability reasons. The `Plugin` infix is added on the native plugin * side. + * + * TODO: Also inherit this from PluginBridge */ class Vst3PluginBridge { public: @@ -85,10 +87,8 @@ class Vst3PluginBridge { Vst3Sockets sockets; /** - * The logging facility used for this instance of yabridge. See - * `Logger::create_from_env()` for how this is configured. - * - * @see Logger::create_from_env + * The logging facility used for this instance of yabridge. Wraps around + * `PluginBridge::generic_logger`. */ Vst3Logger logger; diff --git a/src/plugin/utils.cpp b/src/plugin/utils.cpp index a9e7d894..0693b39c 100644 --- a/src/plugin/utils.cpp +++ b/src/plugin/utils.cpp @@ -81,6 +81,8 @@ fs::path find_vst_host(LibArchitecture plugin_arch, bool use_plugin_groups) { } fs::path find_vst_plugin() { + // TODO: This has to be able to differentiate between VST2 plugins and VST3 + // modules const fs::path this_plugin_path = get_this_file_location(); fs::path plugin_path(this_plugin_path); From 21a8c232a115745f61ff019755cc1ddb0014491e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 3 Dec 2020 14:26:23 +0100 Subject: [PATCH 074/456] Note that debug builds on winegcc fail now --- tools/patch-vst3-sdk.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/patch-vst3-sdk.sh b/tools/patch-vst3-sdk.sh index 5e712099..1d5d46e5 100755 --- a/tools/patch-vst3-sdk.sh +++ b/tools/patch-vst3-sdk.sh @@ -19,6 +19,8 @@ if [[ -z $sdk_directory ]]; then exit 1 fi +# TODO: Debug builds fail now because it's using an unimplemented variation of printf + # Make sure all imports use the correct casing find "$sdk_directory" -type f \( -iname '*.h' -or -iname '*.cpp' \) -print0 | xargs -0 sed -i -E 's/^#include <(Windows.h|ShlObj.h)>$/#include <\L\1\E>/' From c2d2ac8fbfa37e1c7a17a073f8fd621fe6b6a030 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 00:12:05 +0100 Subject: [PATCH 075/456] Inherit Vst3PluginBridge init from PluginBridge --- src/plugin/bridges/common.h | 23 +++++++++++- src/plugin/bridges/vst3.cpp | 56 ++++++++-------------------- src/plugin/bridges/vst3.h | 73 +------------------------------------ 3 files changed, 38 insertions(+), 114 deletions(-) diff --git a/src/plugin/bridges/common.h b/src/plugin/bridges/common.h index c845a2c9..7f217ac4 100644 --- a/src/plugin/bridges/common.h +++ b/src/plugin/bridges/common.h @@ -25,8 +25,9 @@ #include "../host-process.h" /** - * Handles all common operations for hosting plugins such as setting up the - * plugin host process, the logger, and logging debug information on startup. + * Handles all common operations for hosting plugins such as initializing up the + * plugin host process, setting up the logger, and logging debug information on + * startup. * * @tparam Sockets the `Sockets` implementation to use. We have to initialize it * here because we need to pass it to our `HostProcess`. @@ -46,6 +47,12 @@ class PluginBridge { * @param create_socket_instance A function to create a socket instance. * Using a lambda here feels wrong, but I can't think of a better * solution right now. + * + * @tparam F A `TSockets(boost::asio::io_context&)` function to create the + * `TSockets` instance. + * + * @throw std::runtime_error Thrown when the Wine plugin host could not be + * found, or if it could not locate and load a VST3 module. */ template PluginBridge(PluginType plugin_type, @@ -55,6 +62,8 @@ class PluginBridge { plugin_path(plugin_path), io_context(), sockets(create_socket_instance(io_context)), + // This is still correct for VST3 plugins because we can configure an + // entire directory (the module's bundle) at once config(load_config_for(get_this_file_location())), generic_logger(Logger::create_from_environment( create_logger_prefix(sockets.base_dir))), @@ -197,6 +206,16 @@ class PluginBridge { /** * The path to the plugin (`.dll` or module) being loaded in the Wine plugin * host. + * + * Forst VST2 plugins this will be a `.dll` file. For VST3 plugins this is + * normally a directory called `MyPlugin.vst3` that contains + * `MyPlugin.vst3/Contents/x86-win/MyPlugin.vst3`, but there's also an older + * deprecated (but still ubiquitous) format where the top level + * `MyPlugin.vst3` is not a directory but a .dll file. This points to either + * of those things, and then `VST3::Hosting::Win32Module::create()` will be + * able to load it. + * + * https://developer.steinberg.help/pages/viewpage.action?pageId=9798275 */ const boost::filesystem::path plugin_path; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index b81e81b4..095f0cd0 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -16,49 +16,23 @@ #include "vst3.h" -#include "../../common/utils.h" - -// TODO: Do all of the initialization stuff from `Vst2PluginBridge` Vst3PluginBridge::Vst3PluginBridge() - : // TODO: This is technically correct because we can configure the entire - // directory at once - config(load_config_for(get_this_file_location())), - // TODO: This is incorrect for VST3 modules - plugin_module_path(find_vst_plugin()), - io_context(), - sockets(io_context, - // TODO: This is incorrect - generate_endpoint_base( - plugin_module_path.filename().replace_extension("").string()), - true), - // This weird cast is not needed, but without it clang/ccls won't shut up - // TODO: Apparently this is UB even though it works fine, so we should - // probably just use composition here instead + : PluginBridge(PluginType::vst3, + // TODO: This is incorrect for VST3 modules + find_vst_plugin(), + [](boost::asio::io_context& io_context) { + return Vst3Sockets( + io_context, + generate_endpoint_base(find_vst_plugin() + .filename() + .replace_extension("") + .string()), + true); + }), + // TODO: This is UB, use composition with `generic_logger` instead logger(static_cast(Logger::create_from_environment( - create_logger_prefix(sockets.base_dir)))), - wine_version(get_wine_version()), - vst_host( - config.group - ? std::unique_ptr(std::make_unique( - io_context, - logger, - HostRequest{.plugin_type = PluginType::vst2, - .plugin_path = plugin_module_path.string(), - .endpoint_base_dir = sockets.base_dir.string()}, - sockets, - *config.group)) - : std::unique_ptr(std::make_unique( - io_context, - logger, - HostRequest{ - .plugin_type = PluginType::vst2, - .plugin_path = plugin_module_path.string(), - .endpoint_base_dir = sockets.base_dir.string()}))), - has_realtime_priority(set_realtime_priority()), - wine_io_handler([&]() { io_context.run(); }) { + create_logger_prefix(sockets.base_dir)))) { log_init_message(); -} -void Vst3PluginBridge::log_init_message() { - // TODO: Move `Vst2PluginBridge::log_init_message()` to utils and call that + // TODO: Call the host guard handler } diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 106201d2..b1083304 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -16,14 +16,11 @@ #pragma once -#include -#include #include #include "../../common/communication/vst3.h" -#include "../../common/configuration.h" #include "../../common/logging/vst3.h" -#include "../host-process.h" +#include "common.h" /** * This handles the communication between the native host and a VST3 plugin @@ -42,10 +39,8 @@ * The naming scheme of all of these 'bridge' classes is `{,Plugin}Bridge` * for greppability reasons. The `Plugin` infix is added on the native plugin * side. - * - * TODO: Also inherit this from PluginBridge */ -class Vst3PluginBridge { +class Vst3PluginBridge : PluginBridge> { public: /** * Initializes the VST3 module by starting and setting up communicating with @@ -56,74 +51,10 @@ class Vst3PluginBridge { */ Vst3PluginBridge(); - /** - * The configuration for this instance of yabridge. Set based on the values - * from a `yabridge.toml`, if it exists. - * - * @see ../utils.h:load_config_for - */ - Configuration config; - - /** - * The path to the VST3 module being loaded in the Wine VST host. This is - * normally a directory called `MyPlugin.vst3` that contains - * `MyPlugin.vst3/Contents/x86-win/MyPlugin.vst3`, but there's also an older - * deprecated (but still ubiquitous) format where the top level - * `MyPlugin.vst3` is not a directory but a .dll file. This points to either - * of those things, and then `VST3::Hosting::Win32Module::create()` will be - * able to load it. - * - * https://developer.steinberg.help/pages/viewpage.action?pageId=9798275 - */ - const boost::filesystem::path plugin_module_path; - private: - /** - * Format and log all relevant debug information during initialization. - */ - void log_init_message(); - - boost::asio::io_context io_context; - Vst3Sockets sockets; - /** * The logging facility used for this instance of yabridge. Wraps around * `PluginBridge::generic_logger`. */ Vst3Logger logger; - - /** - * The version of Wine currently in use. Used in the debug output on plugin - * startup. - */ - const std::string wine_version; - - /** - * The Wine process hosting the Windows VST3 plugin. - * - * @see launch_vst_host - */ - std::unique_ptr vst_host; - - /** - * A thread used during the initialisation process to terminate listening on - * the sockets if the Wine process cannot start for whatever reason. This - * has to be defined here instead of in the constructor we can't simply - * detach the thread as it has to check whether the Wine plugin host is - * still running. - */ - std::jthread host_guard_handler; - - /** - * Whether this process runs with realtime priority. We'll set this _after_ - * spawning the Wine process because from my testing running wineserver with - * realtime priority can actually increase latency. - */ - bool has_realtime_priority; - - /** - * Runs the Boost.Asio `io_context` thread for logging the Wine process - * STDOUT and STDERR messages. - */ - std::jthread wine_io_handler; }; From b9e63ea335f8a97ed8b543d925c942c73d4d6fc2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 00:31:22 +0100 Subject: [PATCH 076/456] Move the host guard handler to PluginBridge --- src/plugin/bridges/common.h | 60 +++++++++++++++++++++++++++++++++++-- src/plugin/bridges/vst2.cpp | 29 +----------------- src/plugin/bridges/vst2.h | 9 ------ src/plugin/bridges/vst3.cpp | 4 ++- 4 files changed, 61 insertions(+), 41 deletions(-) diff --git a/src/plugin/bridges/common.h b/src/plugin/bridges/common.h index 7f217ac4..a252cc4e 100644 --- a/src/plugin/bridges/common.h +++ b/src/plugin/bridges/common.h @@ -37,8 +37,8 @@ class PluginBridge { public: /** * Sets up everything needed to start the host process. Classes deriving - * from this should call `log_init_message()` themselves after their - * initialization list. + * from this should call `log_init_message()` and + * `connect_sockets_guarded()` themselves after their initialization list. * * @param plugin_type The type of the plugin we're handling. * @param plugin_path The path to the plugin. For VST2 plugins this is the @@ -197,6 +197,47 @@ class PluginBridge { } } + /** + * Connect the sockets, while starting another thread that will terminate + * the plugin (through `std::terminate`/SIGABRT) when the host process fails + * to start. This is the only way to stop listening on our sockets without + * moving everything over to asynchronous listeners (which may actually be a + * good idea just for this use case). Otherwise the plugin would be stuck + * loading indefinitely when Wine is not configured correctly. + * + * TODO: Asynchronously connect our sockets so we can interrupt it, maybe + */ + void connect_sockets_guarded() { +#ifndef WITH_WINEDBG + // If the Wine process fails to start, then nothing will connect to the + // sockets and we'll be hanging here indefinitely. To prevent this, + // we'll periodically poll whether the Wine process is still running, + // and throw when it is not. The alternative would be to rewrite this to + // using `async_accept`, Boost.Asio timers, and another IO context, but + // I feel like this a much simpler solution. + host_guard_handler = std::jthread([&](std::stop_token st) { + using namespace std::literals::chrono_literals; + + while (!st.stop_requested()) { + if (!plugin_host->running()) { + generic_logger.log( + "The Wine host process has exited unexpectedly. Check " + "the " + "output above for more information."); + std::terminate(); + } + + std::this_thread::sleep_for(20ms); + } + }); +#endif + + sockets.connect(); +#ifndef WITH_WINEDBG + host_guard_handler.request_stop(); +#endif + } + /** * The type of the plugin we're dealing with. Passed to the host process and * printed in the initialisation message. @@ -224,7 +265,10 @@ class PluginBridge { /** * The sockets used for communication with the Wine process. * - * @see PluginBridge::log_init_message + * @remark `sockets.connect()` should not be called directly. + * `connect_sockets_guarded()` should be used instead. + * + * @see PluginBridge::connect_sockets_guarded */ TSockets sockets; @@ -263,4 +307,14 @@ class PluginBridge { * STDOUT and STDERR messages. */ std::jthread wine_io_handler; + + private: + /** + * A thread used during the initialisation process to terminate listening on + * the sockets if the Wine process cannot start for whatever reason. This + * has to be defined here instead of in the constructor we can't simply + * detach the thread as it has to check whether the VST host is still + * running. + */ + std::jthread host_guard_handler; }; diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index c52213bd..27011d29 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -61,36 +61,9 @@ Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) create_logger_prefix(sockets.base_dir)))) { log_init_message(); - // TODO: Also move his to `PluginHost` -#ifndef WITH_WINEDBG - // If the Wine process fails to start, then nothing will connect to the - // sockets and we'll be hanging here indefinitely. To prevent this, we'll - // periodically poll whether the Wine process is still running, and throw - // when it is not. The alternative would be to rewrite this to using - // `async_accept`, Boost.Asio timers, and another IO context, but I feel - // like this a much simpler solution. - host_guard_handler = std::jthread([&](std::stop_token st) { - using namespace std::literals::chrono_literals; - - while (!st.stop_requested()) { - if (!plugin_host->running()) { - logger.log( - "The Wine host process has exited unexpectedly. Check the " - "output above for more information."); - std::terminate(); - } - - std::this_thread::sleep_for(20ms); - } - }); -#endif - // This will block until all sockets have been connected to by the Wine VST // host - sockets.connect(); -#ifndef WITH_WINEDBG - host_guard_handler.request_stop(); -#endif + connect_sockets_guarded(); // 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. diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index 19d6e3de..0b7cb3c9 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -148,15 +148,6 @@ class Vst2PluginBridge : PluginBridge> { */ Vst2Logger logger; - /** - * A thread used during the initialisation process to terminate listening on - * the sockets if the Wine process cannot start for whatever reason. This - * has to be defined here instead of in the constructor we can't simply - * detach the thread as it has to check whether the VST host is still - * running. - */ - std::jthread host_guard_handler; - /** * A scratch buffer for sending and receiving data during `process`, * `processReplacing` and `processDoubleReplacing` calls. diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 095f0cd0..5b00d0cd 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -34,5 +34,7 @@ Vst3PluginBridge::Vst3PluginBridge() create_logger_prefix(sockets.base_dir)))) { log_init_message(); - // TODO: Call the host guard handler + // This will block until all sockets have been connected to by the Wine VST + // host + connect_sockets_guarded(); } From 170cd53c27326a4e65b393c6621a9e91e4459156 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 12:28:35 +0100 Subject: [PATCH 077/456] Add todo about readme changes after merging --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 86786e49..0e9d2034 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ imcomplete list of things that still have to be done before this can be used: - Update all the AUR packages. - Test the binaries built on GitHub on plain Ubuntu 18.04, are we missing any static linking? +- When this is in a usable enough state to be merged into master, make sure to + put a notice up at the top of the readme saying that `libyabridge.so` is now + called `libyabridge-vst2.so` for the master branch version. ![yabridge screenshot](https://raw.githubusercontent.com/robbert-vdh/yabridge/master/screenshot.png) From ab7449a0e06345cdf92320ce35916b127f9fdd21 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 13:32:58 +0100 Subject: [PATCH 078/456] Add control sockets to Vst3Sockets --- src/common/communication/common.h | 8 +++--- src/common/communication/vst3.h | 41 +++++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 39b97b4b..578f5b39 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -412,10 +412,14 @@ class SocketHandler { * * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. + * + * TODO: Once we have figured out a way to encapsulate the usage patterns in + * `Vst3Sockets` we should make the constructor, `send()` and + * `receive_multi()` protected again to avoid weirdness */ template class AdHocSocketHandler { - protected: + public: /** * Sets up a single primary socket. The sockets won't be active until * `connect()` gets called. @@ -441,7 +445,6 @@ class AdHocSocketHandler { } } - public: /** * Depending on the value of the `listen` argument passed to the * constructor, either accept connections made to the sockets on the Linux @@ -474,7 +477,6 @@ class AdHocSocketHandler { socket.close(); } - protected: /** * Serialize and send an event over a socket. This is used for both the host * -> plugin 'dispatch' events and the plugin -> host 'audioMaster' host diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 06252170..8b6f2a53 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -55,20 +55,47 @@ class Vst3Sockets : public Sockets { Vst3Sockets(boost::asio::io_context& io_context, const boost::filesystem::path& endpoint_base_dir, bool listen) - : Sockets(endpoint_base_dir) {} + : Sockets(endpoint_base_dir), + host_vst_control(io_context, + (base_dir / "host_vst_control.sock").string(), + listen), + vst_host_callback(io_context, + (base_dir / "vst_host_callback.sock").string(), + listen) {} ~Vst3Sockets() { close(); } - void connect() override {} + void connect() override { + host_vst_control.connect(); + vst_host_callback.connect(); + } void close() override { // Manually close all sockets so we break out of any blocking operations // that may still be active + host_vst_control.close(); + vst_host_callback.close(); } - // TODO: I still don't know if recursive callbacks are a thing in VST3. If - // not, then we should probably have two `AdHocSocketHandler`s per - // plugin instance (one for each direction, as with `dispatcher()` and - // `audioMaster()` in VST2). Using fewer probably also works, but we - // wouldn't want to have to spawn new sockets during audio processing. + // TODO: Since audio processing may be done completely in parallel we might + // want to have a dedicated socket per processor/controller pair. For + // this we would need to figure out how to associate a plugin instance + // with a socket. + + /** + * For sending messages from the host to the plugin. After we have a better + * idea of what our communication model looks like we'll probably want to + * provide an abstraction similar to `EventHandler`. + * + * This will be listened on by the Wine plugin host when it calls + * `receive_multi()`. + */ + AdHocSocketHandler host_vst_control; + + /** + * For sending callbacks from the plugin back to the host. After we have a + * better idea of what our communication model looks like we'll probably + * want to provide an abstraction similar to `EventHandler`. + */ + AdHocSocketHandler vst_host_callback; }; From 19eb33a7e2954778d1e680f26893fc7021205d4a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 13:50:05 +0100 Subject: [PATCH 079/456] Add a AdHocSocketHandler::receive_multi overload With only a single callback. --- src/common/communication/common.h | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 578f5b39..bcdbed84 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -542,8 +542,6 @@ class AdHocSocketHandler { * same thing as `primary_callback`, but secondary sockets may need some * different handling. * - * TODO: Add an overload with a single callback - * * @tparam F A function type in the form of * `void(boost::asio::local::stream_protocol::socket&)`. * @tparam G The same as `F`. @@ -619,6 +617,18 @@ class AdHocSocketHandler { acceptor.reset(); } + /** + * The same as the above, but with a single callback for incoming + * connections on the primary socket and on secondary sockets. + * + * @overload + */ + template + void receive_multi(std::optional> logging, + F callback) { + receive_multi(logging, callback, callback); + } + private: /** * Used in `receive_multi()` to asynchronously listen for secondary socket From e889ad22e202c1323eafe0ecba6f692bbda40c54 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 14:03:07 +0100 Subject: [PATCH 080/456] Only pass Logger reference to AdHocSocketHandler The boolean flag wasn't used, and it doesn't make any sense in the context of VST3. --- src/common/communication/common.h | 32 +++++++++++++++---------------- src/common/communication/vst2.h | 2 +- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index bcdbed84..26af855b 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -531,9 +531,8 @@ class AdHocSocketHandler { * then a blocking loop that handles incoming data from the primary * `socket`. * - * @param logging A pair containing a logger instance and whether or not - * this is for sending `dispatch()` events or host callbacks. Optional - * since it doesn't have to be done on both sides. + * @param logger A logger instance for logging connection errors. This + * should only be passed on the plugin side. * @param primary_callback A function that will do a single read cycle for * the primary socket socket that should do a single read cycle. This is * called in a loop so it shouldn't do any looping itself. @@ -547,7 +546,7 @@ class AdHocSocketHandler { * @tparam G The same as `F`. */ template - void receive_multi(std::optional> logging, + void receive_multi(std::optional> logger, F primary_callback, G secondary_callback) { // As described above we'll handle incoming requests for `socket` on @@ -568,7 +567,7 @@ class AdHocSocketHandler { std::atomic_size_t next_request_id{}; std::mutex active_secondary_requests_mutex{}; accept_requests( - *acceptor, logging, + *acceptor, logger, [&](boost::asio::local::stream_protocol::socket secondary_socket) { const size_t request_id = next_request_id.fetch_add(1); @@ -624,9 +623,9 @@ class AdHocSocketHandler { * @overload */ template - void receive_multi(std::optional> logging, + void receive_multi(std::optional> logger, F callback) { - receive_multi(logging, callback, callback); + receive_multi(logger, callback, callback); } private: @@ -636,9 +635,8 @@ class AdHocSocketHandler { * called until the IO context gets stopped. * * @param acceptor The acceptor we will be listening on. - * @param logging A pair containing a logger instance and whether or not - * this is for sending `dispatch()` events or host callbacks. Optional - * since it doesn't have to be done on both sides. + * @param logger A logger instance for logging connection errors. This + * should only be passed on the plugin side. * @param callback A function that handles the new socket connection. * * @tparam F A function in the form @@ -648,10 +646,10 @@ class AdHocSocketHandler { template void accept_requests( boost::asio::local::stream_protocol::acceptor& acceptor, - std::optional> logging, + std::optional> logger, F callback) { acceptor.async_accept( - [&, logging, callback]( + [&, logger, callback]( const boost::system::error_code& error, boost::asio::local::stream_protocol::socket secondary_socket) { if (error.failed()) { @@ -659,10 +657,10 @@ class AdHocSocketHandler { // connection will be dropped during shutdown, so we can // silently ignore any related socket errors on the Wine // side - if (logging) { - auto [logger, is_dispatch] = *logging; - logger.log("Failure while accepting connections: " + - error.message()); + if (logger) { + logger->get().log( + "Failure while accepting connections: " + + error.message()); } return; @@ -670,7 +668,7 @@ class AdHocSocketHandler { callback(std::move(secondary_socket)); - accept_requests(acceptor, logging, callback); + accept_requests(acceptor, logger, callback); }); } diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index 8ecab67b..f3304449 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -254,7 +254,7 @@ class EventHandler : public AdHocSocketHandler { }; this->receive_multi( - logging, + logging ? std::optional(std::ref(logging->first)) : std::nullopt, [&](boost::asio::local::stream_protocol::socket& socket) { process_event(socket, true); }, From 2e9b1000905ad8b7bcb21d43ce8ac631730ef448 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 14:06:16 +0100 Subject: [PATCH 081/456] Add handlers for control messages and callbacks --- src/plugin/bridges/vst3.cpp | 5 +++++ src/plugin/bridges/vst3.h | 6 ++++++ src/wine-host/bridges/vst3.cpp | 5 +++++ src/wine-host/bridges/vst3.h | 5 +++++ 4 files changed, 21 insertions(+) diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 5b00d0cd..5039126c 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -37,4 +37,9 @@ Vst3PluginBridge::Vst3PluginBridge() // This will block until all sockets have been connected to by the Wine VST // host connect_sockets_guarded(); + + host_callback_handler = std::jthread([&]() { + // TODO: Handle callbacks + // sockets.vst_host_callback.receive_multi(); + }); } diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index b1083304..f997a11d 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -52,6 +52,12 @@ class Vst3PluginBridge : PluginBridge> { Vst3PluginBridge(); private: + /** + * Handles callbacks from the plugin to the host over the + * `vst_host_callback` sockets. + */ + std::jthread host_callback_handler; + /** * The logging facility used for this instance of yabridge. Wraps around * `PluginBridge::generic_logger`. diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 700a46d0..71ad6bce 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -47,6 +47,11 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context, // TODO: We should send a copy of the configuration from the plugin at this // point config = sockets.host_vst_control.receive_single(); + + control_handler = Win32Thread([&]() { + // TODO: Handle control messages + // sockets.host_vst_control.receive_multi(); + }); } void Vst3Bridge::run() { diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 45ca30a6..9dbb4be0 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -79,4 +79,9 @@ class Vst3Bridge : public HostBridge { * threads to exit. */ Vst3Sockets sockets; + + /** + * Handles control messages host over the `hsot_vst_control` sockets. + */ + Win32Thread control_handler; }; From db1a51af5c6b33e06579af0a1216299d40fce74a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 14:59:46 +0100 Subject: [PATCH 082/456] Add serialization primitives for VST3 --- src/common/serialization/vst2.h | 4 +- src/common/serialization/vst3.h | 100 ++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 src/common/serialization/vst3.h diff --git a/src/common/serialization/vst2.h b/src/common/serialization/vst2.h index ada95336..16766edd 100644 --- a/src/common/serialization/vst2.h +++ b/src/common/serialization/vst2.h @@ -16,14 +16,14 @@ #pragma once +#include + #include #include #include #include #include -#include - #include "../vst24.h" #include "common.h" diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h new file mode 100644 index 00000000..3267de2b --- /dev/null +++ b/src/common/serialization/vst3.h @@ -0,0 +1,100 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include + +#include "../configuration.h" +#include "common.h" + +// Event handling for our VST3 plugins works slightly different from how we +// handle VST2 plugins. VST3 does not have a centralized event dispatching +// interface like VST2 does, and it uses a bunch of separate interfaces instead. +// Instead of having an a single event/result with accompanying payload values +// for both host -> plugin `dispatcher()` and plugin -> host `audioMaster()` +// calls, we'll just send request and response payloads directly without any +// metadata. We also split everything up into host -> plugin 'control' payloads +// and plugin -> host 'callback' payloads for maintainability's sake. + +// TODO: If this approach works, maybe we can also refactor the VST2 handling to +// do this since it's a bit safer and easier to read + +/** + * Marker struct to indicate the other side (the plugin) should send a copy of + * the configuration. + */ +struct WantsConfiguration { + using Response = Configuration; +}; + +/** + * When we send a control message from the plugin to the Wine VST host, this + * encodes the information we request or the operation we want to perform. A + * request of type `T` should send back a `CallbackResponse` containing + * `T::Reponse`. + * + * @relates ControlResponse + */ +using ControlRequest = std::variant<>; + +template +void serialize(S& s, ControlRequest& payload) { + s.ext(payload, bitsery::ext::StdVariant{}); +} + +/** + * A response to a control message. Tee `T::Reponse` for the correct type to + * return here. + * + * @relates ControlRrequest + */ +using ControlResponse = std::variant<>; + +template +void serialize(S& s, ControlResponse& payload) { + s.ext(payload, bitsery::ext::StdVariant{}); +} + +/** + * When we do a callback from the Wine VST host to the plugin, this encodes the + * information we want or the operation we want to perform. A request of type + * `T` should send back a `CallbackResponse` containing `T::Reponse`. + * + * @relates CallbackResponse + */ +using CallbackRequest = std::variant; + +template +void serialize(S& s, CallbackRequest& payload) { + s.ext(payload, bitsery::ext::StdVariant{[](S&, WantsConfiguration&) {}}); +} + +/** + * A response to a callback. Tee `T::Reponse` for the correct type to return + * here. + * + * @relates CallbackRrequest + */ +using CallbackResponse = std::variant; + +template +void serialize(S& s, CallbackResponse& payload) { + s.ext(payload, bitsery::ext::StdVariant{ + [](S& s, Configuration& config) { s.object(config); }}); +} From 70405e8917dfba9f2245afd97962e06b8daab212 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 15:28:43 +0100 Subject: [PATCH 083/456] Encapsulate our VST3 message handling pattern Similarly to how how we do it in `EventHandler`. --- src/common/communication/common.h | 8 +- src/common/communication/vst3.h | 166 +++++++++++++++++++++++++++++- src/common/serialization/vst3.h | 9 +- 3 files changed, 172 insertions(+), 11 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 26af855b..cabddd4f 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -412,14 +412,10 @@ class SocketHandler { * * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. - * - * TODO: Once we have figured out a way to encapsulate the usage patterns in - * `Vst3Sockets` we should make the constructor, `send()` and - * `receive_multi()` protected again to avoid weirdness */ template class AdHocSocketHandler { - public: + protected: /** * Sets up a single primary socket. The sockets won't be active until * `connect()` gets called. @@ -445,6 +441,7 @@ class AdHocSocketHandler { } } + public: /** * Depending on the value of the `listen` argument passed to the * constructor, either accept connections made to the sockets on the Linux @@ -477,6 +474,7 @@ class AdHocSocketHandler { socket.close(); } + protected: /** * Serialize and send an event over a socket. This is used for both the host * -> plugin 'dispatch' events and the plugin -> host 'audioMaster' host diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 8b6f2a53..ea363aae 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -18,8 +18,168 @@ #include +#include "../logging/vst3.h" +#include "../serialization/vst3.h" #include "common.h" +/** + * An instance of `AdHocSocketHandler` that encapsulates the simple + * communication model we use for sending requests and receiving responses. A + * request of type `T`, where `T` is in `{Control,Callback}Request`, should be + * answered with an object of type `T::Response`. + * + * See the docstrings on `EventHandler` and `AdHocSocketHandler` for more + * information on how this works internally and why it works the way it does. + * + * @note The name of this class is not to be confused with VST3's `IMessage` as + * this is very much just general purpose messaging between yabridge's two + * components. Of course, this will handle `IMessage` function calls as well. + * + * @tparam Thread The thread implementation to use. On the Linux side this + * should be `std::jthread` and on the Wine side this should be `Win32Thread`. + * @tparam Request Either `ControlRequest` or `CallbackRequest`. + * @tparam Response Either `ControlResponse` or `CallbackResponse`, depending on + * the type of `Request`. + */ +template +class Vst3MessageHandler : public AdHocSocketHandler { + public: + /** + * Sets up a single main socket for this type of events. The sockets won't + * be active until `connect()` gets called. + * + * @param io_context The IO context the main socket should be bound to. A + * new IO context will be created for accepting the additional incoming + * connections. + * @param endpoint The socket endpoint used for this event handler. + * @param listen If `true`, start listening on the sockets. Incoming + * connections will be accepted when `connect()` gets called. This should + * be set to `true` on the plugin side, and `false` on the Wine host side. + * + * @see Sockets::connect + */ + Vst3MessageHandler(boost::asio::io_context& io_context, + boost::asio::local::stream_protocol::endpoint endpoint, + bool listen) + : AdHocSocketHandler(io_context, endpoint, listen) {} + + /** + * Serialize and send an event over a socket and return the appropriate + * response. + * + * As described above, if this function is currently being called from + * another thread, then this will create a new socket connection and send + * the event there instead. + * + * @param logging A pair containing a logger instance and whether or not + * this is for sending host -> plugin control messages. If set to false, + * then this indicates that this `Vst3MessageHandler` is handling plugin + * -> host callbacks isntead. Optional since it only has to be set on the + * plugin's side. + * + * TODO: Is it feasible to move `logging` to the constructor instead? + * + * @relates Vst3MessageHandler::receive_messages + */ + template + typename T::Response send_message( + const T& object, + std::optional> logging) { + using TResponse = typename T::Response; + + if (logging) { + auto [logger, is_host_vst] = *logging; + // TODO: Log the request + // logger.log_event(is_dispatch, opcode, index, value, payload, + // option, + // value_payload); + } + + // A socket only handles a single request at a time as to prevent + // messages from arriving out of order. `AdHocSocketHandler::send()` + // will either use a long-living primary socket, or if that's currently + // in use it will spawn a new socket for us. + const TResponse response = this->template send( + [&](boost::asio::local::stream_protocol::socket& socket) { + write_object(socket, Request(object)); + const auto response = read_object(socket); + + // If the other side handled the request correctly, the Response + // variant should now contain an object of type `T::Response` + return std::get(response); + }); + + if (logging) { + auto [logger, is_host_vst] = *logging; + // TODO: Log the response + // logger.log_event_response(is_dispatch, opcode, + // response.return_value, + // response.payload, + // response.value_payload); + } + + return response; + } + + /** + * Spawn a new thread to listen for extra connections to `endpoint`, and + * then start a blocking loop that handles messages from the primary + * `socket`. + * + * The specified function receives a `Request` variant object containing an + * object of type `T`, and it should return the corresponding `Response` of + * type `T::Response`. + * + * @param logging A pair containing a logger instance and whether or not + * this is for sending host -> plugin control messages. If set to false, + * then this indicates that this `Vst3MessageHandler` is handling plugin + * -> host callbacks isntead. Optional since it only has to be set on the + * plugin's side. + * @param callback The function used to generate a response out of the + * request. See the definition of `F` for more information. + * + * @tparam F A function type in the form of `Reponse(Request)`. + * + * @relates Vst3MessageHandler::send_event + */ + template + void receive_messages(std::optional> logging, + F callback) { + // Reading, processing, and writing back the response for the requests + // we receive works in the same way regardless of which socket we're + // using + const auto process_message = + [&](boost::asio::local::stream_protocol::socket& socket) { + auto request = read_object(socket); + if (logging) { + auto [logger, is_host_vst] = *logging; + // TODO: Log the request, use the visitor with an auto + // lambda so we can use the same overloads as we use + // in `send_message()` + // logger.log_event(is_dispatch, opcode, index, value, + // payload, option, + // value_payload); + } + + Response response = callback(request); + if (logging) { + // TODO: Log the response, use the visitor with an auto + // lambda so we can use the same overloads as we use + // in `send_message()` + // logger.log_event_response( + // is_dispatch, event.opcode, response.return_value, + // response.payload, response.value_payload); + } + + write_object(socket, response); + }; + + this->receive_multi( + logging ? std::optional(std::ref(logging->first)) : std::nullopt, + process_message); + } +}; + /** * Manages all the sockets used for communicating between the plugin and the * Wine host when hosting a VST3 plugin. @@ -90,12 +250,14 @@ class Vst3Sockets : public Sockets { * This will be listened on by the Wine plugin host when it calls * `receive_multi()`. */ - AdHocSocketHandler host_vst_control; + Vst3MessageHandler + host_vst_control; /** * For sending callbacks from the plugin back to the host. After we have a * better idea of what our communication model looks like we'll probably * want to provide an abstraction similar to `EventHandler`. */ - AdHocSocketHandler vst_host_callback; + Vst3MessageHandler + vst_host_callback; }; diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 3267de2b..72396435 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -66,10 +66,11 @@ void serialize(S& s, ControlRequest& payload) { */ using ControlResponse = std::variant<>; -template -void serialize(S& s, ControlResponse& payload) { - s.ext(payload, bitsery::ext::StdVariant{}); -} +// TODO: Uncomment when this is no longer empty +// template +// void serialize(S& s, ControlResponse& payload) { +// s.ext(payload, bitsery::ext::StdVariant{}); +// } /** * When we do a callback from the Wine VST host to the plugin, this encodes the From 0819e9fda9f3e713e468178ee0c2b9440ffc2066 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 15:49:32 +0100 Subject: [PATCH 084/456] Request/send config for VST3 plugins Using the new Vst3MessageHandler. --- src/common/communication/common.h | 6 +++++- src/plugin/bridges/vst3.cpp | 16 ++++++++++++++-- src/wine-host/bridges/common.h | 5 ++++- src/wine-host/bridges/vst2.h | 4 ++++ src/wine-host/bridges/vst3.cpp | 17 +++++++++-------- src/wine-host/bridges/vst3.h | 9 ++++----- 6 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index cabddd4f..4439d72b 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -195,7 +195,11 @@ class Sockets { /** * Depending on the value of the `listen` argument passed to the * constructor, either accept connections made to the sockets on the Linux - * side or connect to the sockets on the Wine side + * side or connect to the sockets on the Wine side. + * + * @remark On the plugin side `PluginBridge::connect_sockets_guarded()` + * should be used instead so we can terminate everything in the event that + * Wine fails to start. */ virtual void connect() = 0; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 5039126c..d1c7b02b 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -38,8 +38,20 @@ Vst3PluginBridge::Vst3PluginBridge() // host connect_sockets_guarded(); + // Now that communication is set up the Wine host can send callbacks to this + // bridge class, and we can send control messages to the Wine host. This + // messaging mechanism is how we relay the VST3 communication protocol. As a + // first thing, the Wine VST host will ask us for a copy of the + // configuration. host_callback_handler = std::jthread([&]() { - // TODO: Handle callbacks - // sockets.vst_host_callback.receive_multi(); + sockets.vst_host_callback.receive_messages( + std::pair(logger, false), + [&](CallbackRequest request) -> CallbackResponse { + return std::visit(overload{[&](const WantsConfiguration&) + -> WantsConfiguration::Response { + return config; + }}, + request); + }); }); } diff --git a/src/wine-host/bridges/common.h b/src/wine-host/bridges/common.h index eee60936..efefd9ee 100644 --- a/src/wine-host/bridges/common.h +++ b/src/wine-host/bridges/common.h @@ -78,7 +78,10 @@ class HostBridge { * Wine window, and embedding that Wine window into a window provided by the * host. Should be empty when the editor is not open. * - * @see should_postpone_message_loop + * TODO: This should be moved back to `Vst2Bridge`, `handle_x11_events()`` + * and `handle_win32_events()` should be made pure virtual. A single + * `Vst3Bridge` instance will handle multiple plugin instances because + * of the way VST3 works. */ std::optional editor; }; diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 5f80c029..9062870f 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -63,6 +63,10 @@ class Vst2Bridge : public HostBridge { std::string plugin_dll_path, std::string endpoint_base_dir); + /** + * Here we'll handle incoming `dispatch()` messages until the sockets get + * closed during `effClose()`. + */ void run() override; /** diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 71ad6bce..6830a090 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -45,16 +45,17 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context, sockets.connect(); - // TODO: We should send a copy of the configuration from the plugin at this - // point config = sockets.host_vst_control.receive_single(); - - control_handler = Win32Thread([&]() { - // TODO: Handle control messages - // sockets.host_vst_control.receive_multi(); - }); + // Fetch this instance's configuration from the plugin to finish the setup + // process + config = sockets.vst_host_callback.send_message(WantsConfiguration{}, + std::nullopt); } void Vst3Bridge::run() { - // TODO: Do something + // TODO: Handle events + // sockets.host_vst_control.receive_messages( + // std::nullopt, [&](ControlRequest request) -> ControlResponse { + // }); + std::cerr << "TODO: Not yet implemented" << std::endl; } diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 9dbb4be0..bb9e4804 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -52,6 +52,10 @@ class Vst3Bridge : public HostBridge { std::string plugin_dll_path, std::string endpoint_base_dir); + /** + * Here we'll listen for and handle incoming control messages until the + * sockets get closed. + */ void run() override; private: @@ -79,9 +83,4 @@ class Vst3Bridge : public HostBridge { * threads to exit. */ Vst3Sockets sockets; - - /** - * Handles control messages host over the `hsot_vst_control` sockets. - */ - Win32Thread control_handler; }; From c1e7f53cd0945933c7400f17780c02243ed2a0e8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 18:13:52 +0100 Subject: [PATCH 085/456] :boom: Major refactor of initialization plumbing To account for the differences in VST2 plugins and VST3 modules we had to wrap most of our old functions from `src/plugin/utils.h` in a new `PluginInfo` struct that gathers all of this information while taking into account the differences between VST2 and VST3 plugins. With this change things are also a lot more organized. We can just query the plugin information we need rather than having to store things separately or having to recalculate things. This also moved the responsibility of all the weird `WINEPREFIX` behaviour to a single place instead of having it spread around `utils.pp`, the initialisation message, and `host-procoess.cpp`. --- src/common/serialization/common.h | 9 - src/common/serialization/vst2.h | 1 + src/common/serialization/vst3.h | 1 + src/common/utils.h | 9 + src/plugin/bridges/common.h | 79 ++++---- src/plugin/bridges/vst2.cpp | 21 +- src/plugin/bridges/vst3.cpp | 22 +-- src/plugin/host-process.cpp | 58 ++---- src/plugin/host-process.h | 22 +-- src/plugin/utils.cpp | 316 ++++++++++++++++++++++-------- src/plugin/utils.h | 187 +++++++++++++----- 11 files changed, 464 insertions(+), 261 deletions(-) diff --git a/src/common/serialization/common.h b/src/common/serialization/common.h index 5b77082c..3eef07e2 100644 --- a/src/common/serialization/common.h +++ b/src/common/serialization/common.h @@ -41,15 +41,6 @@ static_assert(std::is_same_v); using native_size_t = uint64_t; using native_intptr_t = int64_t; -// The cannonical overloading template for `std::visitor`, not sure why this -// isn't part of the standard library -template -struct overload : Ts... { - using Ts::operator()...; -}; -template -overload(Ts...) -> overload; - /** * An object containing the startup options for hosting a plugin. These options * are passed to `yabridge-host.exe` as command line arguments, and they are diff --git a/src/common/serialization/vst2.h b/src/common/serialization/vst2.h index 16766edd..eae8f1d2 100644 --- a/src/common/serialization/vst2.h +++ b/src/common/serialization/vst2.h @@ -24,6 +24,7 @@ #include #include +#include "../utils.h" #include "../vst24.h" #include "common.h" diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 72396435..065bcb1b 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -21,6 +21,7 @@ #include #include "../configuration.h" +#include "../utils.h" #include "common.h" // Event handling for our VST3 plugins works slightly different from how we diff --git a/src/common/utils.h b/src/common/utils.h index a21fb0dd..909411b6 100644 --- a/src/common/utils.h +++ b/src/common/utils.h @@ -21,6 +21,15 @@ #endif #include +// The cannonical overloading template for `std::visitor`, not sure why this +// isn't part of the standard library +template +struct overload : Ts... { + using Ts::operator()...; +}; +template +overload(Ts...) -> overload; + /** * Return the path to the directory for story temporary files. This will be * `$XDG_RUNTIME_DIR` if set, and `/tmp` otherwise. diff --git a/src/plugin/bridges/common.h b/src/plugin/bridges/common.h index a252cc4e..6df18c44 100644 --- a/src/plugin/bridges/common.h +++ b/src/plugin/bridges/common.h @@ -48,23 +48,20 @@ class PluginBridge { * Using a lambda here feels wrong, but I can't think of a better * solution right now. * - * @tparam F A `TSockets(boost::asio::io_context&)` function to create the - * `TSockets` instance. + * @tparam F A `TSockets(boost::asio::io_context&, const PluginInfo&)` + * function to create the `TSockets` instance. * * @throw std::runtime_error Thrown when the Wine plugin host could not be * found, or if it could not locate and load a VST3 module. */ template - PluginBridge(PluginType plugin_type, - const boost::filesystem::path& plugin_path, - F create_socket_instance) - : plugin_type(plugin_type), - plugin_path(plugin_path), + PluginBridge(PluginType plugin_type, F create_socket_instance) + : info(plugin_type), io_context(), - sockets(create_socket_instance(io_context)), + sockets(create_socket_instance(io_context, info)), // This is still correct for VST3 plugins because we can configure an // entire directory (the module's bundle) at once - config(load_config_for(get_this_file_location())), + config(load_config_for(info.native_library_path)), generic_logger(Logger::create_from_environment( create_logger_prefix(sockets.base_dir))), plugin_host( @@ -72,9 +69,10 @@ class PluginBridge { ? std::unique_ptr(std::make_unique( io_context, generic_logger, + info, HostRequest{ .plugin_type = plugin_type, - .plugin_path = plugin_path.string(), + .plugin_path = info.windows_plugin_path.string(), .endpoint_base_dir = sockets.base_dir.string()}, sockets, *config.group)) @@ -82,8 +80,10 @@ class PluginBridge { std::make_unique( io_context, generic_logger, + info, HostRequest{.plugin_type = plugin_type, - .plugin_path = plugin_path.string(), + .plugin_path = + info.windows_plugin_path.string(), .endpoint_base_dir = sockets.base_dir.string()}))), has_realtime_priority(set_realtime_priority()), @@ -102,9 +102,9 @@ class PluginBridge { << std::endl; init_msg << "host: '" << plugin_host->path().string() << "'" << std::endl; - init_msg << "plugin: '" << plugin_path.string() << "'" - << std::endl; - init_msg << "plugin type: '" << plugin_type_to_string(plugin_type) + init_msg << "plugin: '" << info.windows_plugin_path.string() + << "'" << std::endl; + init_msg << "plugin type: '" << plugin_type_to_string(info.plugin_type) << "'" << std::endl; init_msg << "realtime: '" << (has_realtime_priority ? "yes" : "no") << "'" << std::endl; @@ -112,14 +112,17 @@ class PluginBridge { << std::endl; init_msg << "wine prefix: '"; - // If the Wine prefix is manually overridden, then this should be made - // clear. This follows the behaviour of `set_wineprefix()`. - boost::process::environment env = boost::this_process::environment(); - if (!env["WINEPREFIX"].empty()) { - init_msg << env["WINEPREFIX"].to_string() << " "; - } else { - init_msg << find_wineprefix().value_or("").string(); - } + std::visit( + overload{ + [&](const OverridenWinePrefix& prefix) { + init_msg << prefix.value.string() << " "; + }, + [&](const boost::filesystem::path& prefix) { + init_msg << prefix.string(); + }, + [&](const DefaultWinePrefix&) { init_msg << ""; }, + }, + info.wine_prefix); init_msg << "'" << std::endl; init_msg << "wine version: '" << get_wine_version() << "'" << std::endl; @@ -139,10 +142,13 @@ class PluginBridge { } else { init_msg << "individually"; } - if (plugin_host->architecture() == LibArchitecture::dll_32) { - init_msg << ", 32-bit"; - } else { - init_msg << ", 64-bit"; + switch (info.plugin_arch) { + case LibArchitecture::dll_32: + init_msg << ", 32-bit"; + break; + case LibArchitecture::dll_64: + init_msg << ", 64-bit"; + break; } init_msg << "'" << std::endl; @@ -239,26 +245,9 @@ class PluginBridge { } /** - * The type of the plugin we're dealing with. Passed to the host process and - * printed in the initialisation message. + * Information about the plugin we're bridging. */ - const PluginType plugin_type; - - /** - * The path to the plugin (`.dll` or module) being loaded in the Wine plugin - * host. - * - * Forst VST2 plugins this will be a `.dll` file. For VST3 plugins this is - * normally a directory called `MyPlugin.vst3` that contains - * `MyPlugin.vst3/Contents/x86-win/MyPlugin.vst3`, but there's also an older - * deprecated (but still ubiquitous) format where the top level - * `MyPlugin.vst3` is not a directory but a .dll file. This points to either - * of those things, and then `VST3::Hosting::Win32Module::create()` will be - * able to load it. - * - * https://developer.steinberg.help/pages/viewpage.action?pageId=9798275 - */ - const boost::filesystem::path plugin_path; + const PluginInfo info; boost::asio::io_context io_context; diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index 27011d29..70e64351 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -40,17 +40,16 @@ Vst2PluginBridge& get_bridge_instance(const AEffect& plugin) { } Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) - : PluginBridge(PluginType::vst2, - find_vst_plugin(), - [](boost::asio::io_context& io_context) { - return Vst2Sockets( - io_context, - generate_endpoint_base(find_vst_plugin() - .filename() - .replace_extension("") - .string()), - true); - }), + : PluginBridge( + PluginType::vst2, + [](boost::asio::io_context& io_context, const PluginInfo& info) { + return Vst2Sockets( + io_context, + generate_endpoint_base(info.native_library_path.filename() + .replace_extension("") + .string()), + true); + }), // All the fields should be zero initialized because // `Vst2PluginInstance::vstAudioMasterCallback` from Bitwig's plugin // bridge will crash otherwise diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index d1c7b02b..fbc5dc54 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -17,18 +17,16 @@ #include "vst3.h" Vst3PluginBridge::Vst3PluginBridge() - : PluginBridge(PluginType::vst3, - // TODO: This is incorrect for VST3 modules - find_vst_plugin(), - [](boost::asio::io_context& io_context) { - return Vst3Sockets( - io_context, - generate_endpoint_base(find_vst_plugin() - .filename() - .replace_extension("") - .string()), - true); - }), + : PluginBridge( + PluginType::vst3, + [](boost::asio::io_context& io_context, const PluginInfo& info) { + return Vst3Sockets( + io_context, + generate_endpoint_base(info.native_library_path.filename() + .replace_extension("") + .string()), + true); + }), // TODO: This is UB, use composition with `generic_logger` instead logger(static_cast(Logger::create_from_environment( create_logger_prefix(sockets.base_dir)))) { diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index 99620788..a130e7fa 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -86,20 +86,22 @@ void HostProcess::async_log_pipe_lines(patched_async_pipe& pipe, IndividualHost::IndividualHost(boost::asio::io_context& io_context, Logger& logger, - const HostRequest& plugin_info) + const PluginInfo& plugin_info, + const HostRequest& host_request) : HostProcess(io_context, logger), - // FIXME: This will require changing for VST3 bundles - plugin_arch(find_dll_architecture(plugin_info.plugin_path)), - host_path(find_vst_host(plugin_arch, false)), + plugin_info(plugin_info), + host_path(find_vst_host(plugin_info.native_library_path, + plugin_info.plugin_arch, + false)), host(launch_host(host_path, - plugin_type_to_string(plugin_info.plugin_type), + plugin_type_to_string(host_request.plugin_type), #ifdef WITH_WINEDBG - plugin_info.plugin_path.filename(), + host_request.plugin_path.filename(), #else - plugin_info.plugin_path, + host_request.plugin_path, #endif - plugin_info.endpoint_base_dir, - bp::env = set_wineprefix(), + host_request.endpoint_base_dir, + bp::env = plugin_info.create_host_env(), bp::std_out = stdout_pipe, bp::std_err = stderr_pipe #ifdef WITH_WINEDBG @@ -115,10 +117,6 @@ IndividualHost::IndividualHost(boost::asio::io_context& io_context, #endif } -LibArchitecture IndividualHost::architecture() { - return plugin_arch; -} - fs::path IndividualHost::path() { return host_path; } @@ -134,13 +132,15 @@ void IndividualHost::terminate() { GroupHost::GroupHost(boost::asio::io_context& io_context, Logger& logger, + const PluginInfo& plugin_info, const HostRequest& host_request, Sockets& sockets, std::string group_name) : HostProcess(io_context, logger), - // FIXME: This will require changing for VST3 bundles - plugin_arch(find_dll_architecture(host_request.plugin_path)), - host_path(find_vst_host(plugin_arch, true)), + plugin_info(plugin_info), + host_path(find_vst_host(plugin_info.native_library_path, + plugin_info.plugin_arch, + true)), sockets(sockets) { #ifdef WITH_WINEDBG if (plugin_path.string().find(' ') != std::string::npos) { @@ -156,25 +156,10 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, // other processes will exit. When a plugin's host process has exited, it // will try to connect to the socket once more in the case that another // process is now listening on it. - const bp::environment host_env = set_wineprefix(); - fs::path wine_prefix; - if (auto wine_prefix_envvar = host_env.find("WINEPREFIX"); - wine_prefix_envvar != host_env.end()) { - // This is a bit ugly, but Boost.Process's environment does not have a - // graceful way to check for empty environment variables in const - // qualified environments - wine_prefix = wine_prefix_envvar->to_string(); - } else { - // Fall back to `~/.wine` if this has not been set or detected. This - // would happen if the plugin's .dll file is not inside of a Wine - // prefix. If this happens, then the Wine instance will be launched in - // the default Wine prefix, so we should reflect that here. - wine_prefix = fs::path(host_env.at("HOME").to_string()) / ".wine"; - } - const fs::path endpoint_base_dir = sockets.base_dir; const fs::path group_socket_path = - generate_group_endpoint(group_name, wine_prefix, plugin_arch); + generate_group_endpoint(group_name, plugin_info.normalize_wine_prefix(), + plugin_info.plugin_arch); const auto connect = [&io_context, host_request, endpoint_base_dir, group_socket_path]() { boost::asio::local::stream_protocol::socket group_socket(io_context); @@ -194,7 +179,8 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, // because it should run independently of this yabridge instance as // it will likely outlive it. bp::child group_host = - launch_host(host_path, group_socket_path, bp::env = host_env, + launch_host(host_path, group_socket_path, + bp::env = plugin_info.create_host_env(), bp::std_out = stdout_pipe, bp::std_err = stderr_pipe); group_host.detach(); @@ -231,10 +217,6 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, } } -LibArchitecture GroupHost::architecture() { - return plugin_arch; -} - fs::path GroupHost::path() { return host_path; } diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index ca5987c0..5c584a61 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -41,12 +41,6 @@ class HostProcess { public: virtual ~HostProcess(){}; - /** - * Return the architecture of the plugin we are loading, i.e. whether it is - * 32-bit or 64-bit. - */ - virtual LibArchitecture architecture() = 0; - /** * Return the full path to the host application in use. The host application * is chosen depending on the architecture of the plugin's DLL file and on @@ -120,7 +114,9 @@ class IndividualHost : public HostProcess { * handled on. * @param logger The `Logger` instance the redirected STDIO streams will be * written to. - * @param plugin_info The information about the plugin we should launch a + * @param plugin_info Information about the plugin we're going to use. Used + * to retrieve the Wine prefix and the plugin's architecture. + * @param host_request The information about the plugin we should launch a * host process for. The values in the struct will be used as command line * arguments. * @@ -129,15 +125,15 @@ class IndividualHost : public HostProcess { */ IndividualHost(boost::asio::io_context& io_context, Logger& logger, - const HostRequest& plugin_info); + const PluginInfo& plugin_info, + const HostRequest& host_request); - LibArchitecture architecture() override; boost::filesystem::path path() override; bool running() override; void terminate() override; private: - LibArchitecture plugin_arch; + const PluginInfo& plugin_info; boost::filesystem::path host_path; boost::process::child host; }; @@ -163,6 +159,8 @@ class GroupHost : public HostProcess { * handled on. * @param logger The `Logger` instance the redirected STDIO streams will be * written to. + * @param plugin_info Information about the plugin we're going to use. Used + * to retrieve the Wine prefix and the plugin's architecture. * @param host_request The information about the plugin we should launch a * host process for. This object will be sent to the group host process. * @param sockets The socket endpoints that will be used for communication @@ -172,17 +170,17 @@ class GroupHost : public HostProcess { */ GroupHost(boost::asio::io_context& io_context, Logger& logger, + const PluginInfo& plugin_info, const HostRequest& host_request, Sockets& socket_endpoint, std::string group_name); - LibArchitecture architecture() override; boost::filesystem::path path() override; bool running() override; void terminate() override; private: - LibArchitecture plugin_arch; + const PluginInfo& plugin_info; boost::filesystem::path host_path; /** diff --git a/src/plugin/utils.cpp b/src/plugin/utils.cpp index 0693b39c..24dc4b1a 100644 --- a/src/plugin/utils.cpp +++ b/src/plugin/utils.cpp @@ -32,6 +32,230 @@ namespace bp = boost::process; namespace fs = boost::filesystem; +// These functions are used to populate the fields in `PluginInfo`. See the +// docstrings for the corresponding fields for more information on what we're +// actually doing here. +fs::path find_plugin_library(const fs::path& this_plugin_path, + PluginType plugin_type); +fs::path normalize_plugin_path(const fs::path& windows_library_path, + PluginType plugin_type); +std::variant find_wine_prefix( + fs::path windows_plugin_path); + +PluginInfo::PluginInfo(PluginType plugin_type) + : plugin_type(plugin_type), + native_library_path(get_this_file_location()), + // As explained in the docstring, this is the actual Windows library. For + // VST3 plugins that come in a module we should be loading that module + // instead of the `.vst3` file within in, which is where + // `windows_plugin_path` comes in. + windows_library_path( + find_plugin_library(native_library_path, plugin_type)), + plugin_arch(find_dll_architecture(windows_library_path)), + windows_plugin_path( + normalize_plugin_path(windows_library_path, plugin_type)), + wine_prefix(find_wine_prefix(windows_plugin_path)) {} + +bp::environment PluginInfo::create_host_env() const { + bp::environment env = boost::this_process::environment(); + + // Only set the prefix when could auto detect it and it's not being + // overridden (this entire `std::visit` instead of `std::has_alternative` is + // just for clarity's sake) + std::visit(overload{ + [](const OverridenWinePrefix&) {}, + [&](const boost::filesystem::path& prefix) { + env["WINEPREFIX"] = prefix.string(); + }, + [](const DefaultWinePrefix&) {}, + }, + wine_prefix); + + return env; +} + +boost::filesystem::path PluginInfo::normalize_wine_prefix() const { + return std::visit( + overload{ + [](const OverridenWinePrefix& prefix) { return prefix.value; }, + [](const boost::filesystem::path& prefix) { return prefix; }, + [](const DefaultWinePrefix&) { + const bp::environment env = boost::this_process::environment(); + return fs::path(env.at("HOME").to_string()) / ".wine"; + }, + }, + wine_prefix); +} + +fs::path find_plugin_library(const fs::path& this_plugin_path, + PluginType plugin_type) { + switch (plugin_type) { + case PluginType::vst2: { + fs::path plugin_path(this_plugin_path); + plugin_path.replace_extension(".dll"); + if (fs::exists(plugin_path)) { + // Also resolve symlinks here, to support symlinked .dll files + return fs::canonical(plugin_path); + } + + // In case this files does not exist and our `.so` file is a + // symlink, we'll also repeat this check after resolving that + // symlink to support links to copies of `libyabridge-vst2.so` as + // described in issue #3 + fs::path alternative_plugin_path = fs::canonical(this_plugin_path); + alternative_plugin_path.replace_extension(".dll"); + if (fs::exists(alternative_plugin_path)) { + return fs::canonical(alternative_plugin_path); + } + + // This function is used in the constructor's initializer list so we + // have to throw when the path could not be found + throw std::runtime_error("'" + plugin_path.string() + + "' does not exist, make sure to rename " + "'libyabridge-vst2.so' to match a " + "VST plugin .dll file."); + } break; + case PluginType::vst3: { + // A VST3 plugin in Linux always has to be inside of a bundle (= + // directory) named `X.vst3` that contains a static object + // `X.vst3/Contents/x86_64-linux/X.so`. On Linux `X.so` is not + // allowed to be standalone, so for yabridge this should also always + // be installed this way. + // https://developer.steinberg.help/pages/viewpage.action?pageId=9798275 + const fs::path bundle_home = + this_plugin_path.parent_path().parent_path().parent_path(); + const fs::path win_module_name = + this_plugin_path.filename().replace_extension(".vst3"); + + // Quick check in case the plugin was set up without yabridgectl, + // since the format is very specific and any deviations from that + // will be incorrect. + if (bundle_home.extension() != ".vst3") { + throw std::runtime_error( + "'" + this_plugin_path.string() + + "' is not inside of a VST3 bundle. Use yabridgectl to " + "set up yabridge for VST3 plugins or check the readme " + "for the correct format."); + } + + // Finding the Windows plugin consists of two steps because + // Steinberg changed the format around: + // - First we'll find the plugin in the VST3 bundle created by + // yabridgectl in `~/.vst3`. The plugin can be either 32-bit or + // 64-bit. + // TODO: Right now we can't select between the 64-bit and the + // 32-bit version and we'll just pick whichever one is + // available + // - After that we'll resolve the symlink to the module in the Wine + // prefix, and then we'll have to figure out if this module is an + // old style standalone module (< 3.6.10) or if it's inside of + // a bundle (>= 3.6.10) + fs::path candidate_path = + bundle_home / "Contents" / "x86_64-win" / win_module_name; + if (!fs::exists(candidate_path)) { + // Try the 32-bit version no 64-bit version exists (although, is + // there a single VST3 plugin where this is the case?) + fs::path candidate_path = + bundle_home / "Contents" / "x86-win" / win_module_name; + } + + // After this we'll have to use `normalize_plugin_path()` to get the + // actual module entry point in case the plugin is using a VST + // 3.6.10 style bundle + if (fs::exists(candidate_path)) { + return fs::canonical(candidate_path); + } + + throw std::runtime_error( + "'" + bundle_home.string() + + "' does not contain a Windows VST3 module. Use yabridgectl to " + "set up yabridge for VST3 plugins or check the readme " + "for the correct format."); + } break; + default: + throw std::runtime_error("How did you manage to get this?"); + break; + } +} + +fs::path normalize_plugin_path(const fs::path& windows_library_path, + PluginType plugin_type) { + switch (plugin_type) { + case PluginType::vst2: + return windows_library_path; + break; + case PluginType::vst3: { + // Now we'll have to figure out if this is a new-style bundle or + // an old standalone module + const fs::path win_module_name = + windows_library_path.filename().replace_extension(".vst3"); + const fs::path windows_bundle_home = + windows_library_path.parent_path().parent_path().parent_path(); + if (equals_case_insensitive(windows_bundle_home.filename().string(), + win_module_name.string())) { + return windows_bundle_home; + } else { + return windows_library_path; + } + } break; + default: + throw std::runtime_error("How did you manage to get this?"); + break; + } +} + +std::variant find_wine_prefix( + fs::path windows_plugin_path) { + bp::environment env = boost::this_process::environment(); + if (!env["WINEPREFIX"].empty()) { + return OverridenWinePrefix{env["WINEPREFIX"].to_string()}; + } + + std::optional dosdevices_dir = find_dominating_file( + "dosdevices", windows_plugin_path, fs::is_directory); + if (!dosdevices_dir) { + return DefaultWinePrefix{}; + } + + return dosdevices_dir->parent_path(); +} + +fs::path get_this_file_location() { + // HACK: Not sure why, but `boost::dll::this_line_location()` returns a path + // starting with a double slash on some systems. I've seen this happen + // on both Ubuntu 18.04 and 20.04, but not on Arch based distros. + // Under Linux a path starting with two slashes is treated the same as + // a path starting with only a single slash, but Wine will refuse to + // load any files when the path starts with two slashes. The easiest + // way to work around this if this happens is to just add another + // leading slash and then normalize the path, since three or more + // slashes will be coerced into a single slash. + fs::path this_file = boost::dll::this_line_location(); + if (this_file.string().starts_with("//")) { + this_file = ("/" / this_file).lexically_normal(); + } + + return this_file; +} + +bool equals_case_insensitive(const std::string& a, const std::string& b) { + return std::equal(a.begin(), a.end(), b.begin(), + [](const char& a_char, const char& b_char) { + return std::tolower(a_char) == std::tolower(b_char); + }); +} + +std::string join_quoted_strings(std::vector& strings) { + bool is_first = true; + std::ostringstream joined_strings{}; + for (const auto& option : strings) { + joined_strings << (is_first ? "'" : ", '") << option << "'"; + is_first = false; + } + + return joined_strings.str(); +} + std::string create_logger_prefix(const fs::path& endpoint_base_dir) { // Use the name of the base directory used for our sockets as the logger // prefix, but strip the `yabridge-` part since that's redundant @@ -44,17 +268,9 @@ std::string create_logger_prefix(const fs::path& endpoint_base_dir) { return "[" + endpoint_name + "] "; } -std::optional find_wineprefix() { - std::optional dosdevices_dir = - find_dominating_file("dosdevices", find_vst_plugin(), fs::is_directory); - if (!dosdevices_dir) { - return std::nullopt; - } - - return dosdevices_dir->parent_path(); -} - -fs::path find_vst_host(LibArchitecture plugin_arch, bool use_plugin_groups) { +fs::path find_vst_host(const boost::filesystem::path& this_plugin_path, + LibArchitecture plugin_arch, + bool use_plugin_groups) { auto host_name = use_plugin_groups ? yabridge_group_host_name : yabridge_individual_host_name; if (plugin_arch == LibArchitecture::dll_32) { @@ -62,8 +278,10 @@ fs::path find_vst_host(LibArchitecture plugin_arch, bool use_plugin_groups) { : yabridge_individual_host_name_32bit; } + // If our `.so` file is a symlink, then search for the host in the directory + // of the file that symlink points to fs::path host_path = - fs::canonical(get_this_file_location()).remove_filename() / host_name; + fs::canonical(this_plugin_path).remove_filename() / host_name; if (fs::exists(host_path)) { return host_path; } @@ -80,35 +298,6 @@ fs::path find_vst_host(LibArchitecture plugin_arch, bool use_plugin_groups) { return vst_host_path; } -fs::path find_vst_plugin() { - // TODO: This has to be able to differentiate between VST2 plugins and VST3 - // modules - const fs::path this_plugin_path = get_this_file_location(); - - fs::path plugin_path(this_plugin_path); - plugin_path.replace_extension(".dll"); - if (fs::exists(plugin_path)) { - // Also resolve symlinks here, to support symlinked .dll files - return fs::canonical(plugin_path); - } - - // In case this files does not exist and our `.so` file is a symlink, we'll - // also repeat this check after resolving that symlink to support links to - // copies of `libyabridge-vst2.so` as described in issue #3 - fs::path alternative_plugin_path = fs::canonical(this_plugin_path); - alternative_plugin_path.replace_extension(".dll"); - if (fs::exists(alternative_plugin_path)) { - return fs::canonical(alternative_plugin_path); - } - - // This function is used in the constructor's initializer list so we have to - // throw when the path could not be found - throw std::runtime_error("'" + plugin_path.string() + - "' does not exist, make sure to rename " - "'libyabridge-vst2.so' to match a " - "VST plugin .dll file."); -} - boost::filesystem::path generate_group_endpoint( const std::string& group_name, const boost::filesystem::path& wine_prefix, @@ -145,24 +334,6 @@ std::vector get_augmented_search_path() { return search_path; } -fs::path get_this_file_location() { - // HACK: Not sure why, but `boost::dll::this_line_location()` returns a path - // starting with a double slash on some systems. I've seen this happen - // on both Ubuntu 18.04 and 20.04, but not on Arch based distros. - // Under Linux a path starting with two slashes is treated the same as - // a path starting with only a single slash, but Wine will refuse to - // load any files when the path starts with two slashes. The easiest - // way to work around this if this happens is to just add another - // leading slash and then normalize the path, since three or more - // slashes will be coerced into a single slash. - fs::path this_file = boost::dll::this_line_location(); - if (this_file.string().starts_with("//")) { - this_file = ("/" / this_file).lexically_normal(); - } - - return this_file; -} - std::string get_wine_version() { // The '*.exe' scripts generated by winegcc allow you to override the binary // used to run Wine, so will will respect this as well @@ -196,17 +367,6 @@ std::string get_wine_version() { return version_string; } -std::string join_quoted_strings(std::vector& strings) { - bool is_first = true; - std::ostringstream joined_strings{}; - for (const auto& option : strings) { - joined_strings << (is_first ? "'" : ", '") << option << "'"; - is_first = false; - } - - return joined_strings.str(); -} - Configuration load_config_for(const fs::path& yabridge_path) { // First find the closest `yabridge.tmol` file for the plugin, falling back // to default configuration settings if it doesn't exist @@ -218,19 +378,3 @@ Configuration load_config_for(const fs::path& yabridge_path) { return Configuration(*config_file, yabridge_path); } - -bp::environment set_wineprefix() { - bp::environment env = boost::this_process::environment(); - - // Allow the wine prefix to be overridden manually - if (!env["WINEPREFIX"].empty()) { - return env; - } - - const auto wineprefix_path = find_wineprefix(); - if (wineprefix_path) { - env["WINEPREFIX"] = wineprefix_path->string(); - } - - return env; -} diff --git a/src/plugin/utils.h b/src/plugin/utils.h index c68175be..1ad98d81 100644 --- a/src/plugin/utils.h +++ b/src/plugin/utils.h @@ -16,6 +16,8 @@ #pragma once +#include + #include #include @@ -39,6 +41,134 @@ class patched_async_pipe : public boost::process::async_pipe { typedef typename handle_type::executor_type executor_type; }; +/** + * Marker struct for when we use the default Wine prefix. + */ +struct DefaultWinePrefix {}; + +/** + * Marker struct for when the Wine prefix is overriden using the `WINEPREFIX` + * environment variable. + */ +struct OverridenWinePrefix { + boost::filesystem::path value; +}; + +/** + * This will locate the plugin we're going to host based on the location of the + * `.so` that we're currently operating from and provides information and + * utility functions based on that. + */ +struct PluginInfo { + public: + /** + * Locate the Windows plugin based on the location of this copy of + * `libyabridge-{vst2,vst3}.so` file and the type of the plugin we're going + * to load. For VST2 plugins this is a file with the same name but with a + * `.dll` file extension instead of `.so`. In case this file does not exist + * and the `.so` file is a symlink, we'll also repeat this check for the + * file it links to. This is to support the workflow described in issue #3 + * where you use symlinks to copies of `libyabridge-vst2.so`. + * + * For VST3 plugins there is a strict format as defined by Steinberg, and + * we'll have yabridgectl create a 'merged bundle' that also contains the + * Windows VST3 plugin. + * + * TODO: At the moment we can't choose to use the 32-bit VST3 if a 64-bit + * plugin exists. Potential solutions are to add a config option to + * use the 32-bit version, or we can add a filename suffix to all + * 32-bit versions so they can live alongside each other. + * + * @param plugin_type The type of the plugin we're going to load. The + * detection works slightly differently depending on the plugin type. + * + * @throw std::runtime_error If we cannot find a corresponding Windows + * plugin. The error message contains a human readable description of what + * went wrong. + */ + PluginInfo(PluginType plugin_type); + + /** + * Create the environment for the plugin host based on `wine_prefix`. If + * `WINEPREFIX` was already set then nothing will be changed. Otherwise + * we'll set `WINEPREFIX` to the detected Wine prefix, or it will be left + * unset if we could not detect a prefix. + */ + boost::process::environment create_host_env() const; + + /** + * Return the path to the actual Wine prefix in use, taking into account + * `WINEPREFIX` overrides and the default `~/.wine` fallback. + */ + boost::filesystem::path normalize_wine_prefix() const; + + const PluginType plugin_type; + + /** + * The path to our `.so` file. For VST3 plugins this is *not* the VST3 + * module (since that has to be bundle on Linux) but rather the .so file + * contained in that bundle. + */ + const boost::filesystem::path native_library_path; + + private: + /** + * The path to the Windows library (`.dll` or `.vst3`, not to be confused + * with a `.vst3` bundle) that we're targeting. This should **not** be + * passed to the plugin host and `windows_plugin_path` should be used + * instead. We store this intermediate value so we can determine the + * plugin's architecture. + */ + const boost::filesystem::path windows_library_path; + + public: + const LibArchitecture plugin_arch; + + /** + * The path to the plugin (`.dll` or module) we're going to in the Wine + * plugin host. + * + * For VST2 plugins this will be a `.dll` file. For VST3 plugins this is + * normally a directory called `MyPlugin.vst3` that contains + * `MyPlugin.vst3/Contents/x86-win/MyPlugin.vst3`, but there's also an older + * deprecated (but still ubiquitous) format where the top level + * `MyPlugin.vst3` is not a directory but a .dll file. This points to either + * of those things, and then `VST3::Hosting::Win32Module::create()` will be + * able to load it. + * + * https://developer.steinberg.help/pages/viewpage.action?pageId=9798275 + */ + const boost::filesystem::path windows_plugin_path; + + /** + * The Wine prefix to use for hosting `windows_plugin_path`. If the + * `WINEPREFIX` environment variable is set, then that will be used as an + * override. Otherwise, we'll try to find the Wine prefix + * `windows_plugin_path` is located in. The detection works by looking for a + * directory containing a directory called `dosdevices`. If the plugin is + * not inside of a Wine prefix, this will be left empty, and the default + * prefix will be used instead. + */ + const std:: + variant + wine_prefix; +}; + +/** + * Returns equality for two strings when ignoring casing. Used for comparing + * filenames inside of Wine prefixes since Windows/Wine does case folding for + * filenames. + */ +bool equals_case_insensitive(const std::string& a, const std::string& b); + +/** + * Join a vector of strings with commas while wrapping the strings in quotes. + * For example, `join_quoted_strings(std::vector{"string", "another + * string", "also a string"})` outputs `"'string', 'another string', 'also a + * string'"`. This is used to format the initialisation message. + */ +std::string join_quoted_strings(std::vector& strings); + /** * Create a logger prefix based on the endpoint base directory used for the * sockets for easy identification. This will result in a prefix of the form @@ -63,6 +193,8 @@ std::string create_logger_prefix( * 2. In the regular search path, augmented with `~/.local/share/yabridge` to * ease the setup process. * + * @param this_plugin_path The path to the `.so` file this code is being run + * from. * @param plugin_arch The architecture of the plugin, either 64-bit or 32-bit. * Used to determine which host application to use, if available. * @param use_plugin_groups Whether the plugin is using plugin groups and we @@ -70,35 +202,13 @@ std::string create_logger_prefix( * * @return The a path to the VST host, if found. * @throw std::runtime_error If the Wine VST host could not be found. - */ -boost::filesystem::path find_vst_host(LibArchitecture plugin_arch, - bool use_plugin_groups); - -/** - * Find the VST plugin .dll file that corresponds to this copy of - * `libyabridge-vst2.so`. This should be the same as the name of this file but - * with a `.dll` file extension instead of `.so`. In case this file does not - * exist and the `.so` file is a symlink, we'll also repeat this check for the - * file it links to. This is to support the workflow described in issue #3 where - * you use symlinks to copies of `libyabridge-vst2.so`. * - * TODO: This should probably be renamed to `find_vst2_plugin()` so we can have - * a separate `find_vst3_plugin()` - * - * @return The a path to the accompanying VST plugin .dll file. - * @throw std::runtime_error If no matching .dll file could be found. + * TODO: Perhaps also move this somewhere else */ -boost::filesystem::path find_vst_plugin(); - -/** - * Locate the Wine prefix this file is located in, if it is inside of a wine - * prefix. This is done by locating the first parent directory that contains a - * directory named `dosdevices`. - * - * @return Either the path to the Wine prefix (containing the `drive_c?` - * directory), or `std::nullopt` if it is not inside of a wine prefix. - */ -std::optional find_wineprefix(); +boost::filesystem::path find_vst_host( + const boost::filesystem::path& this_plugin_path, + LibArchitecture plugin_arch, + bool use_plugin_groups); /** * Generate the group socket endpoint name used based on the name of the group, @@ -111,16 +221,12 @@ std::optional find_wineprefix(); * * @param group_name The name of the plugin group. * @param wine_prefix The name of the Wine prefix in use. This should be - * obtained by first calling `set_wineprefix()` to allow the user to override - * this, and then falling back to `$HOME/.wine` if the environment variable is - * unset. Otherwise plugins run from outwide of a Wine prefix will not be - * groupable with those run from within `~/.wine` even though they both run - * under the same prefix. + * obtained from `PluginInfo::normalize_wine_prefix()`. * @param architecture The architecture the plugin is using, since 64-bit * processes can't host 32-bit plugins and the other way around. * * @return A socket endpoint path that corresponds to the format described - * above. + * above. */ boost::filesystem::path generate_group_endpoint( const std::string& group_name, @@ -153,14 +259,6 @@ boost::filesystem::path get_this_file_location(); */ std::string get_wine_version(); -/** - * Join a vector of strings with commas while wrapping the strings in quotes. - * For example, `join_quoted_strings(std::vector{"string", "another - * string", "also a string"})` outputs `"'string', 'another string', 'also a - * string'"`. This is used to format the initialisation message. - */ -std::string join_quoted_strings(std::vector& strings); - /** * Load the configuration that belongs to a copy of or symlink to * `libyabridge-{vst2,vst3}.so`. If no configuration file could be found then @@ -184,13 +282,6 @@ std::string join_quoted_strings(std::vector& strings); */ Configuration load_config_for(const boost::filesystem::path& yabridge_path); -/** - * Locate the Wine prefix and set the `WINEPREFIX` environment variable if - * found. This way it's also possible to run .dll files outside of a Wine prefix - * using the user's default prefix. - */ -boost::process::environment set_wineprefix(); - /** * Starting from the starting file or directory, go up in the directory * hierarchy until we find a file named `filename`. From 426231a22bfd93646baaa747f82d0b975804870b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 18:52:33 +0100 Subject: [PATCH 086/456] Avoid potential UB in loggers using composition This cast would work fine, but any other fields added to those loggers would be left uninitialized. --- meson.build | 2 +- src/common/communication/vst2.h | 3 ++- src/common/communication/vst3.h | 7 ++++--- src/common/logging/vst2.cpp | 16 +++++++++------- src/common/logging/vst2.h | 23 +++++++++++++++++++++-- src/common/logging/vst3.cpp | 19 +++++++++++++++++++ src/common/logging/vst3.h | 20 ++++++++++++++++++-- src/plugin/bridges/vst2.cpp | 4 +--- src/plugin/bridges/vst3.cpp | 4 +--- 9 files changed, 76 insertions(+), 22 deletions(-) create mode 100644 src/common/logging/vst3.cpp diff --git a/meson.build b/meson.build index 7a19cfa9..4c04cf4a 100644 --- a/meson.build +++ b/meson.build @@ -76,7 +76,7 @@ vst2_plugin_sources = [ vst3_plugin_sources = [ 'src/common/communication/common.cpp', 'src/common/logging/common.cpp', - 'src/common/logging/vst2.cpp', + 'src/common/logging/vst3.cpp', 'src/common/configuration.cpp', 'src/common/plugins.cpp', 'src/common/utils.cpp', diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index f3304449..26c038a6 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -254,7 +254,8 @@ class EventHandler : public AdHocSocketHandler { }; this->receive_multi( - logging ? std::optional(std::ref(logging->first)) : std::nullopt, + logging ? std::optional(std::ref(logging->first.logger)) + : std::nullopt, [&](boost::asio::local::stream_protocol::socket& socket) { process_event(socket, true); }, diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index ea363aae..d36fdbc2 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -174,9 +174,10 @@ class Vst3MessageHandler : public AdHocSocketHandler { write_object(socket, response); }; - this->receive_multi( - logging ? std::optional(std::ref(logging->first)) : std::nullopt, - process_message); + this->receive_multi(logging + ? std::optional(std::ref(logging->first.logger)) + : std::nullopt, + process_message); } }; diff --git a/src/common/logging/vst2.cpp b/src/common/logging/vst2.cpp index 0c2406c7..e03c7662 100644 --- a/src/common/logging/vst2.cpp +++ b/src/common/logging/vst2.cpp @@ -18,6 +18,8 @@ #include +Vst2Logger::Vst2Logger(Logger& generic_logger) : logger(generic_logger) {} + std::optional opcode_to_string(bool is_dispatch, int opcode) { if (is_dispatch) { // Opcodes for a plugin's dispatch function @@ -316,7 +318,7 @@ std::optional opcode_to_string(bool is_dispatch, int opcode) { } void Vst2Logger::log_get_parameter(int index) { - if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { std::ostringstream message; message << ">> getParameter() " << index; @@ -325,7 +327,7 @@ void Vst2Logger::log_get_parameter(int index) { } void Vst2Logger::log_get_parameter_response(float value) { - if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { std::ostringstream message; message << " getParameter() :: " << value; @@ -334,7 +336,7 @@ void Vst2Logger::log_get_parameter_response(float value) { } void Vst2Logger::log_set_parameter(int index, float value) { - if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { std::ostringstream message; message << ">> setParameter() " << index << " = " << value; @@ -343,7 +345,7 @@ void Vst2Logger::log_set_parameter(int index, float value) { } void Vst2Logger::log_set_parameter_response() { - if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { log(" setParameter() :: OK"); } } @@ -355,7 +357,7 @@ void Vst2Logger::log_event(bool is_dispatch, const EventPayload& payload, float option, const std::optional& value_payload) { - if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { if (should_filter_event(is_dispatch, opcode)) { return; } @@ -442,7 +444,7 @@ void Vst2Logger::log_event_response( intptr_t return_value, const EventResultPayload& payload, const std::optional& value_payload) { - if (BOOST_UNLIKELY(verbosity >= Verbosity::most_events)) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { if (should_filter_event(is_dispatch, opcode)) { return; } @@ -512,7 +514,7 @@ void Vst2Logger::log_event_response( } bool Vst2Logger::should_filter_event(bool is_dispatch, int opcode) const { - if (verbosity >= Verbosity::all_events) { + if (logger.verbosity >= Logger::Verbosity::all_events) { return false; } diff --git a/src/common/logging/vst2.h b/src/common/logging/vst2.h index e02b1f16..2fe1d60c 100644 --- a/src/common/logging/vst2.h +++ b/src/common/logging/vst2.h @@ -33,10 +33,24 @@ std::optional opcode_to_string(bool is_dispatch, int opcode); /** - * Provides VST2 specific logging functionality for debugging plugins. + * Wraps around `Logger` to provide VST2 specific logging functionality for + * debugging plugins. This way we can have all the complex initialisation be + * performed in one place. */ -class Vst2Logger : public Logger { +class Vst2Logger { public: + Vst2Logger(Logger& generic_logger); + + /** + * @see Logger::log + */ + inline void log(const std::string& message) { logger.log(message); } + + /** + * @see Logger::log_trace + */ + inline void log_trace(const std::string& message) { logger.log(message); } + // The following functions are for logging specific events, they are only // enabled for verbosity levels higher than 1 (i.e. `Verbosity::events`) void log_get_parameter(int index); @@ -60,6 +74,11 @@ class Vst2Logger : public Logger { const EventResultPayload& payload, const std::optional& value_payload); + /** + * The underlying logger instance we're wrapping. + */ + Logger& logger; + private: /** * Determine whether an event should be filtered based on the current diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp new file mode 100644 index 00000000..774954a2 --- /dev/null +++ b/src/common/logging/vst3.cpp @@ -0,0 +1,19 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "vst3.h" + +Vst3Logger::Vst3Logger(Logger& generic_logger) : logger(generic_logger) {} diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 8a22a848..229e9003 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -19,9 +19,25 @@ #include "common.h" /** - * Provides VST3-specific logging functionality for debugging plugins. + * Wraps around `Logger` to provide VST3 specific logging functionality for + * debugging plugins. This way we can have all the complex initialisation be + * performed in one place. */ -class Vst3Logger : public Logger { +class Vst3Logger { public: + Vst3Logger(Logger& generic_logger); + + /** + * @see Logger::log + */ + inline void log(const std::string& message) { logger.log(message); } + + /** + * @see Logger::log_trace + */ + inline void log_trace(const std::string& message) { logger.log(message); } + // TODO: Logging interface for VST3 plugins + + Logger& logger; }; diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index 70e64351..e4a07e60 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -55,9 +55,7 @@ Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) // bridge will crash otherwise plugin(), host_callback_function(host_callback), - // TODO: This is UB, use composition with `generic_logger` instead - logger(static_cast(Logger::create_from_environment( - create_logger_prefix(sockets.base_dir)))) { + logger(generic_logger) { log_init_message(); // This will block until all sockets have been connected to by the Wine VST diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index fbc5dc54..c8755d55 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -27,9 +27,7 @@ Vst3PluginBridge::Vst3PluginBridge() .string()), true); }), - // TODO: This is UB, use composition with `generic_logger` instead - logger(static_cast(Logger::create_from_environment( - create_logger_prefix(sockets.base_dir)))) { + logger(generic_logger) { log_init_message(); // This will block until all sockets have been connected to by the Wine VST From d87afa99e037dff395a012769435a45f5b83c4df Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 4 Dec 2020 19:40:13 +0100 Subject: [PATCH 087/456] Add logging for the VST3 plugin --- src/common/communication/vst3.h | 36 +++++++++++++-------------------- src/common/logging/vst3.cpp | 24 ++++++++++++++++++++++ src/common/logging/vst3.h | 20 +++++++++++++++++- 3 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index d36fdbc2..04739fe3 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -89,10 +89,7 @@ class Vst3MessageHandler : public AdHocSocketHandler { if (logging) { auto [logger, is_host_vst] = *logging; - // TODO: Log the request - // logger.log_event(is_dispatch, opcode, index, value, payload, - // option, - // value_payload); + logger.log_request(is_host_vst, object); } // A socket only handles a single request at a time as to prevent @@ -111,11 +108,7 @@ class Vst3MessageHandler : public AdHocSocketHandler { if (logging) { auto [logger, is_host_vst] = *logging; - // TODO: Log the response - // logger.log_event_response(is_dispatch, opcode, - // response.return_value, - // response.payload, - // response.value_payload); + logger.log_response(!is_host_vst, response); } return response; @@ -152,23 +145,22 @@ class Vst3MessageHandler : public AdHocSocketHandler { [&](boost::asio::local::stream_protocol::socket& socket) { auto request = read_object(socket); if (logging) { - auto [logger, is_host_vst] = *logging; - // TODO: Log the request, use the visitor with an auto - // lambda so we can use the same overloads as we use - // in `send_message()` - // logger.log_event(is_dispatch, opcode, index, value, - // payload, option, - // value_payload); + std::visit( + [&](const auto& object) { + auto [logger, is_host_vst] = *logging; + logger.log_request(is_host_vst, object); + }, + request); } Response response = callback(request); if (logging) { - // TODO: Log the response, use the visitor with an auto - // lambda so we can use the same overloads as we use - // in `send_message()` - // logger.log_event_response( - // is_dispatch, event.opcode, response.return_value, - // response.payload, response.value_payload); + std::visit( + [&](const auto& object) { + auto [logger, is_host_vst] = *logging; + logger.log_response(!is_host_vst, object); + }, + response); } write_object(socket, response); diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 774954a2..f3c2f029 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -16,4 +16,28 @@ #include "vst3.h" +#include + +// TODO: Reconsider the output format +// TODO: Maybe think of an alterantive that's a little less boilerplaty + Vst3Logger::Vst3Logger(Logger& generic_logger) : logger(generic_logger) {} + +void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { + std::ostringstream message; + message << get_log_prefix(is_host_vst) + << " >> Requesting "; + + log(message.str()); + } +} + +void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { + std::ostringstream message; + message << get_log_prefix(is_host_vst) << " "; + + log(message.str()); + } +} diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 229e9003..adc493eb 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -16,6 +16,7 @@ #pragma once +#include "../serialization/vst3.h" #include "common.h" /** @@ -37,7 +38,24 @@ class Vst3Logger { */ inline void log_trace(const std::string& message) { logger.log(message); } - // TODO: Logging interface for VST3 plugins + // For every object we send using `Vst3MessageHandler` we have overloads + // that print information about the request and the response. The boolean + // flag here indicates whether the request was initiated on the host side + // (what we'll call a control message). + + void log_request(bool is_host_vst, const WantsConfiguration&); + + void log_response(bool is_host_vst, const Configuration&); Logger& logger; + + private: + /** + * Get the `host -> vst` or `vst -> host` prefix based on the boolean flag + * we pass to every logging function so we don't have to repeat it + * everywhere. + */ + inline std::string get_log_prefix(bool is_host_vst) { + return is_host_vst ? "[host -> vst]" : "[vst -> host]"; + } }; From 76ad3775222dab618bf6f8b14f864ecc320f2737 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 02:30:53 +0100 Subject: [PATCH 088/456] Don't set __MINGW32__ This took me a few hours of non-stop headaches to figure out. Apparently deep inside of Wine's headers having __MINGW32__ defined will cause some GUIDs to be defined slightly differently. This normally wouldn't cause issues, but when including `shellobj.h` or `objbase.h` this results in multiple definition linking errors that are basically impossible to diagnose. --- meson.build | 14 +++++--------- src/wine-host/boost-fix.h | 3 --- src/wine-host/bridges/vst2.h | 4 ---- src/wine-host/editor.h | 4 ---- src/wine-host/utils.h | 4 ---- tools/patch-vst3-sdk.sh | 17 ++++++++--------- 6 files changed, 13 insertions(+), 33 deletions(-) diff --git a/meson.build b/meson.build index 4c04cf4a..20e7a306 100644 --- a/meson.build +++ b/meson.build @@ -31,7 +31,7 @@ compiler_options = [ '-fvisibility-inlines-hidden', # Disable the use of concepts in Boost.Asio until Boost 1.73 gets released # https://github.com/boostorg/asio/issues/312 - '-DBOOST_ASIO_DISABLE_CONCEPTS' + '-DBOOST_ASIO_DISABLE_CONCEPTS', ] with_bitbridge = get_option('with-bitbridge') @@ -176,6 +176,7 @@ if with_vst3 vst3_sdk_native_dep = declare_dependency( link_with : vst3_sdk_native, include_directories : vst3_include_dir, + compile_args : vst3_compiler_options, ) # And another dependency for the host SDK for our Wine host applications @@ -192,14 +193,9 @@ if with_vst3 endif vst3_wine_compiler_options = [ - # Removes some MSVC-isms for us - '-D__MINGW32__', - # We don't need all of this stuff from `Windows.h`, and it only causes more - # issues + # Some stuff from `windows.h` results in conflicting definitions '-DNOMINMAX', - '-DNOSERVICE', - '-DNOMCX', - '-DWIN32_LEAN_AND_MEAN', + '-DWINE_NOWINSOCK', ] vst3_base_wine_64bit = static_library( 'vst3_base_wine_64bit', @@ -268,7 +264,7 @@ if with_vst3 # This does mean that we now have a lot of defines in our code, but the # alternative would be patching every location in the SDK where they include # `windows.h` - compile_args : vst3_wine_compiler_options, + compile_args : vst3_compiler_options + vst3_wine_compiler_options, ) endif endif diff --git a/src/wine-host/boost-fix.h b/src/wine-host/boost-fix.h index 764760bb..478aaeef 100644 --- a/src/wine-host/boost-fix.h +++ b/src/wine-host/boost-fix.h @@ -23,13 +23,11 @@ // it's running under a WIN32 environment. If anyone knows a better way to do // this, please let me know! -#pragma push_macro("__MINGW32__") #pragma push_macro("WIN32") #pragma push_macro("_WIN32") #pragma push_macro("__WIN32__") #pragma push_macro("_WIN64") -#undef __MINGW32__ #undef WIN32 #undef _WIN32 #undef __WIN32__ @@ -44,7 +42,6 @@ #include // #include -#pragma pop_macro("__MINGW32__") #pragma pop_macro("WIN32") #pragma pop_macro("_WIN32") #pragma pop_macro("__WIN32__") diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 9062870f..b5a8d25c 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -20,10 +20,6 @@ #ifndef NOMINMAX #define NOMINMAX -#define NOSERVICE -#define NOMCX -#define NOIMM -#define WIN32_LEAN_AND_MEAN #endif #include #include diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index b03b2f2c..e483c722 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -22,10 +22,6 @@ #ifndef NOMINMAX #define NOMINMAX -#define NOSERVICE -#define NOMCX -#define NOIMM -#define WIN32_LEAN_AND_MEAN #endif #include #include diff --git a/src/wine-host/utils.h b/src/wine-host/utils.h index 8358800b..3bc8a55e 100644 --- a/src/wine-host/utils.h +++ b/src/wine-host/utils.h @@ -23,10 +23,6 @@ #ifndef NOMINMAX #define NOMINMAX -#define NOSERVICE -#define NOMCX -#define NOIMM -#define WIN32_LEAN_AND_MEAN #endif #include diff --git a/tools/patch-vst3-sdk.sh b/tools/patch-vst3-sdk.sh index 1d5d46e5..7e9edc1f 100755 --- a/tools/patch-vst3-sdk.sh +++ b/tools/patch-vst3-sdk.sh @@ -25,11 +25,14 @@ fi find "$sdk_directory" -type f \( -iname '*.h' -or -iname '*.cpp' \) -print0 | xargs -0 sed -i -E 's/^#include <(Windows.h|ShlObj.h)>$/#include <\L\1\E>/' -# We're building with `WIN32_LEAN_AND_MEAN` because some of the definitions in -# there conflict with the C standard library as provided by GCC. This also -# excludes the shell API, which the VST3 SDK uses to open URLs. -sed -i "s/^#include $/#include \\/\\/ patched for yabridge\\ -#include /" "$sdk_directory/public.sdk/source/common/openurl.cpp" +# Use the attributes and types from GCC +sed -i 's/defined(__MINGW32__)/defined(__WINE__)/g' "$sdk_directory/pluginterfaces/base/ftypes.h" + +# There are some more places where the SDK includes better compatibility with +# GCC that we can use +# NOTE: We should **not** define __MINGW32__ globally, since that also breaks +# Wine's headers in headache inducing ways +sed -i 's/defined(__MINGW32__)/defined(__WINE__)/g' "$sdk_directory/public.sdk/source/common/systemclipboard_win32.cpp" # Use the string manipulation functions from the C standard library sed -i 's/\bSMTG_OS_WINDOWS\b/0/g;s/\bSMTG_OS_LINUX\b/1/g' "$sdk_directory/base/source/fstring.cpp" @@ -61,10 +64,6 @@ replace_char16 "using ConverterFacet = std::codecvt_utf8_utf16;" "$sdk replace_char16 "using Converter = std::wstring_convert;" "$sdk_directory/base/source/fstring.cpp" replace_char16 "using Converter = std::wstring_convert, char16_t>;" "$sdk_directory/pluginterfaces/base/ustring.cpp" -# The definitions of long doesn't match up between platforms, and the mingw -# version here is trying to do something funky -sed -i 's/\b__MINGW32__\b/__NOPE__/g' "$sdk_directory/pluginterfaces/base/funknown.cpp" - # libstdc++fs doesn't work under Winelib, for whatever reason that might be. # We'll patch the Win32 module loading to use Boost.Filesystem instead. sed -i 's/^#include <\(experimental\/\)\?filesystem>$/#include /' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" From a77e2fbfae65e9ca86946ee9732f58dcdcdcba7e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 02:33:11 +0100 Subject: [PATCH 089/456] Add Bitsery serialization for FUIDs --- src/common/bitsery/ext/vst3.h | 63 +++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/common/bitsery/ext/vst3.h diff --git a/src/common/bitsery/ext/vst3.h b/src/common/bitsery/ext/vst3.h new file mode 100644 index 00000000..458c66cd --- /dev/null +++ b/src/common/bitsery/ext/vst3.h @@ -0,0 +1,63 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include +#include +#include + +namespace bitsery { +namespace ext { + +/** + * An adapter for serializing and deserializing `Steinberg::FUID`s. + */ +class FUID { + public: + template + void serialize(Ser& ser, const Steinberg::FUID& uid, Fnc&&) const { + Steinberg::FUID::String uid_str; + uid.toString(uid_str); + + ser.text1b(uid_str); + } + + template + void deserialize(Des& des, Steinberg::FUID& uid, Fnc&&) const { + Steinberg::FUID::String uid_str; + des.text1b(uid_str); + + uid.fromString(uid_str); + } +}; + +} // namespace ext + +namespace traits { + +template <> +struct ExtensionTraits { + using TValue = void; + static constexpr bool SupportValueOverload = false; + static constexpr bool SupportObjectOverload = true; + static constexpr bool SupportLambdaOverload = false; +}; + +} // namespace traits +} // namespace bitsery From 27892d9e40a0dec84b6e5650a4bdad4fed54d0e4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 02:33:37 +0100 Subject: [PATCH 090/456] Get rid of debug prints in Vst3Bridge --- src/wine-host/bridges/vst3.cpp | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 6830a090..d9ea7513 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -28,17 +28,7 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context, sockets(main_context.context, endpoint_base_dir, false) { std::string error; module = VST3::Hosting::Win32Module::create(plugin_dll_path, error); - - // TODO: Do something more useful with this - if (module) { - // TODO: They use some thin wrappers around the interfaces, we can - // probably reuse these instead of having to make our own - VST3::Hosting::FactoryInfo info = module->getFactory().info(); - std::cout << "Plugin name: " << module->getName() << std::endl; - std::cout << "Vendor: " << info.vendor() << std::endl; - std::cout << "URL: " << info.url() << std::endl; - std::cout << "Send spam to: " << info.email() << std::endl; - } else { + if (!module) { throw std::runtime_error("Could not load the VST3 module for '" + plugin_dll_path + "': " + error); } From 930ebbf7d18d049d26eca5b12abca7b3d5042e8a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 02:34:47 +0100 Subject: [PATCH 091/456] Add more todos for VST3 handling --- src/plugin/vst3-plugin.cpp | 30 +++++++++++++++++++++++------- src/wine-host/bridges/vst3.cpp | 7 +++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index cd12d108..0e12f3fe 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -16,10 +16,10 @@ #include -#include - #include "bridges/vst3.h" +#include + using Steinberg::gPluginFactory; // Because VST3 plugins consist of completely independent components that have @@ -70,11 +70,27 @@ bool DeinitModule() { * classes, and then recreate it here. */ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { - // TODO: Check the VST3::Hosting module loading source to see if - // gPluginFactory is used directly by the host or not. - // TODO: We can do all of our allocations and things indie of - // `Vst3PluginBridge`, so this function should call some function on - // Vst3Bridge that does the initialization + // The host should have called `InitModule()` first + assert(bridge); + + // TODO: Instead of using gPluginFactory we'll use a field in + // `Vst3PluginBridge` + // TODO: First thing we should do is query the factory on the Wine side and + // preset a copy of it to the host. The important bits there are that + // we use the same interface version as the one presented the plugin. + // TODO: We have two options for the implementation: + // 1. We can query the interface version, and then have three + // different implementations for the interface version. + // 2. We can implement version 3, but copy the iid from the plugin so + // it always uses the correct version. + + // TODO: Remove, this is just for type checking + if (false) { + boost::asio::local::stream_protocol::socket* socket; + YaPluginFactory* object; + write_object(*socket, *object); + } + if (!gPluginFactory) { // TODO: Here we want to: // 1) Load the plugin on the Wine host diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index d9ea7513..a905d94f 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -42,6 +42,13 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context, } void Vst3Bridge::run() { + // TODO: Remove, this is just for type checking + if (false) { + boost::asio::local::stream_protocol::socket* socket; + YaPluginFactory* object; + write_object(*socket, *object); + } + // TODO: Handle events // sockets.host_vst_control.receive_messages( // std::nullopt, [&](ControlRequest request) -> ControlResponse { From 42f3639e93574cb29284084911a66b98edd104a3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 02:34:15 +0100 Subject: [PATCH 092/456] Add boilerplate for PluginFactory serialization --- meson.build | 4 +- src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/README.md | 54 +++++++++++ .../serialization/vst3/plugin-factory.cpp | 97 +++++++++++++++++++ .../serialization/vst3/plugin-factory.h | 81 ++++++++++++++++ 5 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 src/common/serialization/vst3/README.md create mode 100644 src/common/serialization/vst3/plugin-factory.cpp create mode 100644 src/common/serialization/vst3/plugin-factory.h diff --git a/meson.build b/meson.build index 20e7a306..54162984 100644 --- a/meson.build +++ b/meson.build @@ -77,6 +77,7 @@ vst3_plugin_sources = [ 'src/common/communication/common.cpp', 'src/common/logging/common.cpp', 'src/common/logging/vst3.cpp', + 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/configuration.cpp', 'src/common/plugins.cpp', 'src/common/utils.cpp', @@ -105,7 +106,8 @@ host_sources = [ if with_vst3 host_sources += [ - 'src/wine-host/bridges/vst3.cpp' + 'src/common/serialization/vst3/plugin-factory.cpp', + 'src/wine-host/bridges/vst3.cpp', ] endif diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 065bcb1b..27dfb589 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -23,6 +23,7 @@ #include "../configuration.h" #include "../utils.h" #include "common.h" +#include "vst3/plugin-factory.h" // Event handling for our VST3 plugins works slightly different from how we // handle VST2 plugins. VST3 does not have a centralized event dispatching diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md new file mode 100644 index 00000000..e25d87db --- /dev/null +++ b/src/common/serialization/vst3/README.md @@ -0,0 +1,54 @@ +# VST3 serialization + +TODO: Once this is more fleshed out, move this document to `docs/`, and perhaps +replace this readme with a link to that document. + +The VST3 SDK uses an architecture where every object inherits from an interface, +and every interface inherits from `FUnknown` which offers a sort of query +interface. Newer versions of an interface with added functionality then inherit +from the previous version of that interface. Every interface (and thus also +newer versions of an old interface) get a unique identifier. It then uses a +smart pointer system (`FUnknownPtr`) that queries whether the `FUnknown` +matches a certain interface by checking whether the IDs match up, allowing casts +to that interface if the `FUnkonwn` matches. This means that an +`IPluginFactory*` may also be an `IPluginFactory2*` or an `IPluginFactory3*`. +For yabridge we need to be able to pass concrete serializable objects that +implement these interfaces around. + +## Serializing simple objects + +TODO: Think of a better naming scheme + +Serializing an object that implements `ISimple` that only stores data and can't +perform any callbacks works as follows: + +1. We define a class called `YaSimple` that inherits from `ISimple`. +2. We fetch all data from `ISimple` and store it in `YaSimple`. +3. `YaSimpl` can then be serialized with bitsery and transmitted like any other + object. + +Our +solution approach for serializing Our solution here is to build an object that's compatible with +`IPluginFactory3` that copies all data from the original object + +## Serializing versioned interfaces + +For serializing versioned interfaces, such as `IPluginFactory3`, we'll do +something similar: + +1. As with simple object, we define a class called `YaPluginFactory` that + inherits from `IPluginFactory3`. +2. Now we start copying data starting with `IPluginFactory`, then moving on to + `IPluginFactory2`, and then finally `IPluginFactory3`. When at some point our + `FUnknownPtr` returns a null pointer we know that the object doesn't + implement that version of the interface and we can stop. +3. During the copying process we'll also copy over the `iid`. This allows our + object to appear as the highest version of the interface we were able to copy + from. Doing this avoids complicated inheritance chains in our own + implemetnation. +4. `YaPluginFactory` can then be serialized with bitsery and transmitted like + any other object. + +## Processors and controllers + +TODO: Not sure how this will work yet. diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp new file mode 100644 index 00000000..08fd4195 --- /dev/null +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -0,0 +1,97 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "plugin-factory.h" + +YaPluginFactory::YaPluginFactory(){FUNKNOWN_CTOR} + +YaPluginFactory::YaPluginFactory(Steinberg::IPluginFactory* /*factory*/){ + FUNKNOWN_CTOR + + // TODO: Copy everything from `factory` +} + +YaPluginFactory::~YaPluginFactory() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(YaPluginFactory) +#pragma GCC diagnostic pop + +tresult PLUGIN_API YaPluginFactory::queryInterface(Steinberg::FIDString _iid, + void** obj) { + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, + Steinberg::IPluginFactory) + QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory::iid, + Steinberg::IPluginFactory) + if (actual_iid == Steinberg::IPluginFactory2::iid || + actual_iid == Steinberg::IPluginFactory3::iid) { + QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory2::iid, + Steinberg::IPluginFactory2) + } + if (actual_iid == Steinberg::IPluginFactory3::iid) { + QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory3::iid, + Steinberg::IPluginFactory3) + } + + *obj = nullptr; + return Steinberg::kNoInterface; +} + +tresult PLUGIN_API +YaPluginFactory::getFactoryInfo(Steinberg::PFactoryInfo* info) { + // TODO: Implement + return 0; +} + +int32 PLUGIN_API YaPluginFactory::countClasses() { + // TODO: Implement + return 0; +} + +tresult PLUGIN_API YaPluginFactory::getClassInfo(Steinberg::int32 index, + Steinberg::PClassInfo* info) { + // TODO: Implement + return 0; +} + +tresult PLUGIN_API YaPluginFactory::createInstance(Steinberg::FIDString cid, + Steinberg::FIDString _iid, + void** obj) { + // TODO: Implement + return 0; +} + +tresult PLUGIN_API +YaPluginFactory::getClassInfo2(int32 index, Steinberg::PClassInfo2* info) { + // TODO: Implement + return 0; +} + +tresult PLUGIN_API +YaPluginFactory::getClassInfoUnicode(int32 index, + Steinberg::PClassInfoW* info) { + // TODO: Implement + return 0; +} + +tresult PLUGIN_API +YaPluginFactory::setHostContext(Steinberg::FUnknown* context) { + // TODO: Implement + return 0; +} diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h new file mode 100644 index 00000000..0ce61703 --- /dev/null +++ b/src/common/serialization/vst3/plugin-factory.h @@ -0,0 +1,81 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "../../bitsery/ext/vst3.h" + +namespace { +using Steinberg::int32, Steinberg::tresult; +} // namespace + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IPluginFactory{1,2,3}` for serialization purposes. See + * `README.md` for more information on how this works. + */ +class YaPluginFactory : public Steinberg::IPluginFactory3 { + public: + YaPluginFactory(); + + /** + * Create a copy of an existing plugin factory. Depending on the + supported + * interface function more or less of this struct will be left empty, and + * `iid` will be set accordingly. + * + * TODO: Check if we don't need a custom query interface, we probably do. + */ + explicit YaPluginFactory(Steinberg::IPluginFactory* factory); + + ~YaPluginFactory(); + + DECLARE_FUNKNOWN_METHODS + + // From `IPluginFactory` + tresult PLUGIN_API getFactoryInfo(Steinberg::PFactoryInfo* info) override; + int32 PLUGIN_API countClasses() override; + tresult PLUGIN_API getClassInfo(Steinberg::int32 index, + Steinberg::PClassInfo* info) override; + tresult PLUGIN_API createInstance(Steinberg::FIDString cid, + Steinberg::FIDString _iid, + void** obj) override; + + // From `IPluginFactory2` + tresult PLUGIN_API getClassInfo2(int32 index, + Steinberg::PClassInfo2* info) override; + + // From `IPluginFactory3` + tresult PLUGIN_API + getClassInfoUnicode(int32 index, Steinberg::PClassInfoW* info) override; + tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override; + + /** + * The IID of the interface we should report as. + */ + Steinberg::FUID actual_iid; + + template + void serialize(S& s) { + s.ext(actual_iid, bitsery::ext::FUID()); + } +}; + +#pragma GCC diagnostic pop From 8f152c7af54c9348075833be68a9f041c6565456 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 12:05:13 +0100 Subject: [PATCH 093/456] Clean up the query interface serialization --- src/common/configuration.h | 2 +- src/common/serialization/vst3/plugin-factory.cpp | 11 ++++++----- src/common/serialization/vst3/plugin-factory.h | 12 +++++++++--- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/common/configuration.h b/src/common/configuration.h index 3c827c8c..e499fc73 100644 --- a/src/common/configuration.h +++ b/src/common/configuration.h @@ -138,7 +138,7 @@ class Configuration { s.ext(group, bitsery::ext::StdOptional(), [](S& s, auto& v) { s.text1b(v, 4096); }); s.ext(matched_file, bitsery::ext::StdOptional(), - [](S& s, auto& v) { s.ext(v, bitsery::ext::BoostPath()); }); + [](S& s, auto& v) { s.ext(v, bitsery::ext::BoostPath{}); }); s.ext(matched_pattern, bitsery::ext::StdOptional(), [](S& s, auto& v) { s.text1b(v, 4096); }); diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index 08fd4195..d9b7ed9a 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -37,14 +37,15 @@ tresult PLUGIN_API YaPluginFactory::queryInterface(Steinberg::FIDString _iid, void** obj) { QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, Steinberg::IPluginFactory) - QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory::iid, - Steinberg::IPluginFactory) - if (actual_iid == Steinberg::IPluginFactory2::iid || - actual_iid == Steinberg::IPluginFactory3::iid) { + if (known_iids.contains(Steinberg::IPluginFactory::iid)) { + QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory::iid, + Steinberg::IPluginFactory) + } + if (known_iids.contains(Steinberg::IPluginFactory2::iid)) { QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory2::iid, Steinberg::IPluginFactory2) } - if (actual_iid == Steinberg::IPluginFactory3::iid) { + if (known_iids.contains(Steinberg::IPluginFactory3::iid)) { QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory3::iid, Steinberg::IPluginFactory3) } diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 0ce61703..2554087d 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -16,6 +16,9 @@ #pragma once +#include + +#include #include #include "../../bitsery/ext/vst3.h" @@ -68,13 +71,16 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override; /** - * The IID of the interface we should report as. + * The IIDs that the interface we serialized supports. */ - Steinberg::FUID actual_iid; + std::set known_iids; template void serialize(S& s) { - s.ext(actual_iid, bitsery::ext::FUID()); + s.ext(known_iids, bitsery::ext::StdSet{32}, + [](S& s, Steinberg::FUID& iid) { + s.ext(iid, bitsery::ext::FUID{}); + }); } }; From 82c1542b74ed877500ca5adf46b5cdbc111fbbf1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 12:16:20 +0100 Subject: [PATCH 094/456] Add boilerplate for copying factory interfaces --- .../serialization/vst3/plugin-factory.cpp | 53 +++++++++++++------ .../serialization/vst3/plugin-factory.h | 3 +- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index d9b7ed9a..5b2126f8 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -18,10 +18,28 @@ YaPluginFactory::YaPluginFactory(){FUNKNOWN_CTOR} -YaPluginFactory::YaPluginFactory(Steinberg::IPluginFactory* /*factory*/){ +YaPluginFactory::YaPluginFactory( + Steinberg::IPtr factory) { FUNKNOWN_CTOR - // TODO: Copy everything from `factory` + // TODO: Copy data from `IPluginFactory` + known_iids.insert(factory->iid); + + auto factory2 = Steinberg::FUnknownPtr(factory); + if (!factory2) { + return; + } + + // TODO: Copy data from `IPluginFactory2` + known_iids.insert(factory2->iid); + + auto factory3 = Steinberg::FUnknownPtr(factory); + if (!factory3) { + return; + } + + // TODO: Copy data from `IPluginFactory3` + known_iids.insert(factory3->iid); } YaPluginFactory::~YaPluginFactory() { @@ -55,7 +73,7 @@ tresult PLUGIN_API YaPluginFactory::queryInterface(Steinberg::FIDString _iid, } tresult PLUGIN_API -YaPluginFactory::getFactoryInfo(Steinberg::PFactoryInfo* info) { +YaPluginFactory::getFactoryInfo(Steinberg::PFactoryInfo* /*info*/) { // TODO: Implement return 0; } @@ -65,34 +83,37 @@ int32 PLUGIN_API YaPluginFactory::countClasses() { return 0; } -tresult PLUGIN_API YaPluginFactory::getClassInfo(Steinberg::int32 index, - Steinberg::PClassInfo* info) { - // TODO: Implement - return 0; -} - -tresult PLUGIN_API YaPluginFactory::createInstance(Steinberg::FIDString cid, - Steinberg::FIDString _iid, - void** obj) { +tresult PLUGIN_API +YaPluginFactory::getClassInfo(Steinberg::int32 /*index*/, + Steinberg::PClassInfo* /*info*/) { // TODO: Implement return 0; } tresult PLUGIN_API -YaPluginFactory::getClassInfo2(int32 index, Steinberg::PClassInfo2* info) { +YaPluginFactory::createInstance(Steinberg::FIDString /*cid*/, + Steinberg::FIDString /*_iid*/, + void** /*obj*/) { // TODO: Implement return 0; } tresult PLUGIN_API -YaPluginFactory::getClassInfoUnicode(int32 index, - Steinberg::PClassInfoW* info) { +YaPluginFactory::getClassInfo2(int32 /*index*/, + Steinberg::PClassInfo2* /*info*/) { // TODO: Implement return 0; } tresult PLUGIN_API -YaPluginFactory::setHostContext(Steinberg::FUnknown* context) { +YaPluginFactory::getClassInfoUnicode(int32 /*index*/, + Steinberg::PClassInfoW* /*info*/) { + // TODO: Implement + return 0; +} + +tresult PLUGIN_API +YaPluginFactory::setHostContext(Steinberg::FUnknown* /*context*/) { // TODO: Implement return 0; } diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 2554087d..c81c6687 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -46,7 +46,8 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { * * TODO: Check if we don't need a custom query interface, we probably do. */ - explicit YaPluginFactory(Steinberg::IPluginFactory* factory); + explicit YaPluginFactory( + Steinberg::IPtr factory); ~YaPluginFactory(); From fa10600114ab9d9b0437f87f3378f7849f88605b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 13:02:01 +0100 Subject: [PATCH 095/456] Add more thoughts on how the factory should work --- src/common/serialization/vst3/plugin-factory.cpp | 14 +++++++++++++- src/common/serialization/vst3/plugin-factory.h | 10 ++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index 5b2126f8..e310c3a5 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -23,6 +23,8 @@ YaPluginFactory::YaPluginFactory( FUNKNOWN_CTOR // TODO: Copy data from `IPluginFactory` + // TODO: We should only copy the interfaces that we support. This should use + // the same list as that used in `createInstance()`. known_iids.insert(factory->iid); auto factory2 = Steinberg::FUnknownPtr(factory); @@ -94,7 +96,17 @@ tresult PLUGIN_API YaPluginFactory::createInstance(Steinberg::FIDString /*cid*/, Steinberg::FIDString /*_iid*/, void** /*obj*/) { - // TODO: Implement + // TODO: Figure out how to implement this. Some considerations: + // - We have to sent a control message to the Wine plugin host to ask + // it to create an instance of `_iid`. + // - We then create a `Ya*` implementation of the same interface on + // the plugin side. + // - These two should be wired up so that when the host calls a + // function on it, it should be sent to the instance on the Wine + // plugin host side with the same cid. + // - We should have a list of interfaces we support. When we receive a + // request to create an instance of something we don't support, then + // we should log that and then fail. return 0; } diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index c81c6687..67194869 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -27,6 +27,9 @@ namespace { using Steinberg::int32, Steinberg::tresult; } // namespace +// TODO: After implementing one or two more of these, abstract away some of the +// nasty bits + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -36,6 +39,11 @@ using Steinberg::int32, Steinberg::tresult; */ class YaPluginFactory : public Steinberg::IPluginFactory3 { public: + /** + * TODO: Instead of a having a default constructor, we should probably be + * passing a callback to this constructor that lets us communicate + * with the Wine plugin host. + */ YaPluginFactory(); /** @@ -43,8 +51,6 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { supported * interface function more or less of this struct will be left empty, and * `iid` will be set accordingly. - * - * TODO: Check if we don't need a custom query interface, we probably do. */ explicit YaPluginFactory( Steinberg::IPtr factory); From 79df8fecc2673ea025ec2631768766934ccbf781 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 13:49:38 +0100 Subject: [PATCH 096/456] Serialize the factory info --- .../serialization/vst3/plugin-factory.cpp | 17 +++++++++++---- .../serialization/vst3/plugin-factory.h | 21 +++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index e310c3a5..b08abaad 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -26,6 +26,10 @@ YaPluginFactory::YaPluginFactory( // TODO: We should only copy the interfaces that we support. This should use // the same list as that used in `createInstance()`. known_iids.insert(factory->iid); + if (Steinberg::PFactoryInfo info; + factory->getFactoryInfo(&info) == Steinberg::kResultOk) { + factory_info = info; + } auto factory2 = Steinberg::FUnknownPtr(factory); if (!factory2) { @@ -75,9 +79,13 @@ tresult PLUGIN_API YaPluginFactory::queryInterface(Steinberg::FIDString _iid, } tresult PLUGIN_API -YaPluginFactory::getFactoryInfo(Steinberg::PFactoryInfo* /*info*/) { - // TODO: Implement - return 0; +YaPluginFactory::getFactoryInfo(Steinberg::PFactoryInfo* info) { + if (info && factory_info) { + *info = *factory_info; + return Steinberg::kResultOk; + } else { + return Steinberg::kNotInitialized; + } } int32 PLUGIN_API YaPluginFactory::countClasses() { @@ -126,6 +134,7 @@ YaPluginFactory::getClassInfoUnicode(int32 /*index*/, tresult PLUGIN_API YaPluginFactory::setHostContext(Steinberg::FUnknown* /*context*/) { - // TODO: Implement + // TODO: I guess this should do a callback and set the Wine host's host + // context, right? return 0; } diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 67194869..74c8fec0 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -18,7 +18,9 @@ #include +#include #include +#include #include #include "../../bitsery/ext/vst3.h" @@ -29,6 +31,8 @@ using Steinberg::int32, Steinberg::tresult; // TODO: After implementing one or two more of these, abstract away some of the // nasty bits +// TODO: Should we have some clearer way to indicate to us which fields are +// going to return copied results directly and which make a callback? #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -82,13 +86,30 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { */ std::set known_iids; + // For `IPluginFactory::getFactoryInfo` + std::optional factory_info; + template void serialize(S& s) { s.ext(known_iids, bitsery::ext::StdSet{32}, [](S& s, Steinberg::FUID& iid) { s.ext(iid, bitsery::ext::FUID{}); }); + s.ext(factory_info, bitsery::ext::StdOptional{}, + [](S& s, Steinberg::PFactoryInfo& info) { s.object(info); }); } }; #pragma GCC diagnostic pop + +// Serialization functions have to live in the same namespace as the objects +// they're serializing +namespace Steinberg { +template +void serialize(S& s, PFactoryInfo& factory_info) { + s.text1b(factory_info.vendor); + s.text1b(factory_info.url); + s.text1b(factory_info.email); + s.value4b(factory_info.flags); +} +} // namespace Steinberg From e6da03ae843b159dc54ee72b7c8d9d40c2ca1277 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 13:52:48 +0100 Subject: [PATCH 097/456] Implement IPluginFactory::countClasses() --- src/common/serialization/vst3/plugin-factory.cpp | 4 ++-- src/common/serialization/vst3/plugin-factory.h | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index b08abaad..cb1534fa 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -30,6 +30,7 @@ YaPluginFactory::YaPluginFactory( factory->getFactoryInfo(&info) == Steinberg::kResultOk) { factory_info = info; } + num_classes = factory->countClasses(); auto factory2 = Steinberg::FUnknownPtr(factory); if (!factory2) { @@ -89,8 +90,7 @@ YaPluginFactory::getFactoryInfo(Steinberg::PFactoryInfo* info) { } int32 PLUGIN_API YaPluginFactory::countClasses() { - // TODO: Implement - return 0; + return num_classes; } tresult PLUGIN_API diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 74c8fec0..3a6ef4a5 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -88,6 +88,8 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { // For `IPluginFactory::getFactoryInfo` std::optional factory_info; + // For `IPluginFactory::countClasses` + int num_classes; template void serialize(S& s) { @@ -97,6 +99,7 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { }); s.ext(factory_info, bitsery::ext::StdOptional{}, [](S& s, Steinberg::PFactoryInfo& info) { s.object(info); }); + s.value4b(num_classes); } }; From dd4852318352329dcb5808e8ca78c348c716b36b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 14:13:52 +0100 Subject: [PATCH 098/456] Implement IPluginFactory::getClassInfo --- .../serialization/vst3/plugin-factory.cpp | 29 ++++++++++++++--- .../serialization/vst3/plugin-factory.h | 31 ++++++++++++++++--- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index cb1534fa..ba9dd46c 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -26,12 +26,24 @@ YaPluginFactory::YaPluginFactory( // TODO: We should only copy the interfaces that we support. This should use // the same list as that used in `createInstance()`. known_iids.insert(factory->iid); + if (Steinberg::PFactoryInfo info; factory->getFactoryInfo(&info) == Steinberg::kResultOk) { factory_info = info; } + num_classes = factory->countClasses(); + // TODO: At this point we don't know what this class is and thus we can't + // filter unsupported classes, right? + class_infos_1.resize(num_classes); + for (int i = 0; i < num_classes; i++) { + Steinberg::PClassInfo info; + if (factory->getClassInfo(i, &info) == Steinberg::kResultOk) { + class_infos_1[i] = info; + } + } + auto factory2 = Steinberg::FUnknownPtr(factory); if (!factory2) { return; @@ -93,11 +105,18 @@ int32 PLUGIN_API YaPluginFactory::countClasses() { return num_classes; } -tresult PLUGIN_API -YaPluginFactory::getClassInfo(Steinberg::int32 /*index*/, - Steinberg::PClassInfo* /*info*/) { - // TODO: Implement - return 0; +tresult PLUGIN_API YaPluginFactory::getClassInfo(Steinberg::int32 index, + Steinberg::PClassInfo* info) { + if (index >= static_cast(class_infos_1.size())) { + return Steinberg::kInvalidArgument; + } + + if (class_infos_1[index]) { + *info = *class_infos_1[index]; + return Steinberg::kResultOk; + } else { + return Steinberg::kResultFalse; + } } tresult PLUGIN_API diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 3a6ef4a5..c93accde 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -86,10 +86,22 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { */ std::set known_iids; - // For `IPluginFactory::getFactoryInfo` + /** + * For `IPluginFactory::getFactoryInfo`. + */ std::optional factory_info; - // For `IPluginFactory::countClasses` + /** + * For `IPluginFactory::countClasses`. + */ int num_classes; + /** + * For `IPluginFactory::getClassInfo`. We need to store all four class info + * versions if the plugin can provide them since we don't know which version + * of the interface the host will use. Will be `std::nullopt` if the plugin + * doesn't return a class info. + */ + std::vector> class_infos_1; + // TODO: Callback interface for `createInstance()` template void serialize(S& s) { @@ -97,9 +109,12 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { [](S& s, Steinberg::FUID& iid) { s.ext(iid, bitsery::ext::FUID{}); }); - s.ext(factory_info, bitsery::ext::StdOptional{}, - [](S& s, Steinberg::PFactoryInfo& info) { s.object(info); }); + s.ext(factory_info, bitsery::ext::StdOptional{}); s.value4b(num_classes); + s.container(class_infos_1, 2048, + [](S& s, std::optional& info) { + s.ext(info, bitsery::ext::StdOptional{}); + }); } }; @@ -108,6 +123,14 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { // Serialization functions have to live in the same namespace as the objects // they're serializing namespace Steinberg { +template +void serialize(S& s, PClassInfo& class_info) { + s.container1b(class_info.cid); + s.value4b(class_info.cardinality); + s.text1b(class_info.category); + s.text1b(class_info.name); +} + template void serialize(S& s, PFactoryInfo& factory_info) { s.text1b(factory_info.vendor); From 85f818ab0b1b0b0d58f46a56f28ca32b19fc2245 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 14:23:35 +0100 Subject: [PATCH 099/456] Patch the SDK for winegcc debug builds --- meson.build | 1 + tools/patch-vst3-sdk.sh | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/meson.build b/meson.build index 54162984..1aeec32a 100644 --- a/meson.build +++ b/meson.build @@ -106,6 +106,7 @@ host_sources = [ if with_vst3 host_sources += [ + 'src/common/logging/vst3.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/wine-host/bridges/vst3.cpp', ] diff --git a/tools/patch-vst3-sdk.sh b/tools/patch-vst3-sdk.sh index 7e9edc1f..006a3cbf 100755 --- a/tools/patch-vst3-sdk.sh +++ b/tools/patch-vst3-sdk.sh @@ -19,12 +19,15 @@ if [[ -z $sdk_directory ]]; then exit 1 fi -# TODO: Debug builds fail now because it's using an unimplemented variation of printf - # Make sure all imports use the correct casing find "$sdk_directory" -type f \( -iname '*.h' -or -iname '*.cpp' \) -print0 | xargs -0 sed -i -E 's/^#include <(Windows.h|ShlObj.h)>$/#include <\L\1\E>/' +# Use the proper libc functions instead of the MSVC intrinsics. These are also +# used in `fstring.cpp`, but there we will patch the entire file to use the +# standard POSIX/GCC string formatting facilities. +sed -i 's/\b_vsnprintf\b/vsnprintf/g;s/\b_snprintf\b/snprintf/g' "$sdk_directory/base/source/fdebug.cpp" + # Use the attributes and types from GCC sed -i 's/defined(__MINGW32__)/defined(__WINE__)/g' "$sdk_directory/pluginterfaces/base/ftypes.h" @@ -64,6 +67,10 @@ replace_char16 "using ConverterFacet = std::codecvt_utf8_utf16;" "$sdk replace_char16 "using Converter = std::wstring_convert;" "$sdk_directory/base/source/fstring.cpp" replace_char16 "using Converter = std::wstring_convert, char16_t>;" "$sdk_directory/pluginterfaces/base/ustring.cpp" +# Don't try adding `std::u8string` to an `std::vector`. MSVC +# probably coerces them, but GCC doesn't +sed -i 's/\bgeneric_u8string\b/generic_string/g' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" + # libstdc++fs doesn't work under Winelib, for whatever reason that might be. # We'll patch the Win32 module loading to use Boost.Filesystem instead. sed -i 's/^#include <\(experimental\/\)\?filesystem>$/#include /' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" @@ -71,10 +78,6 @@ sed -i 's/^using namespace std\(::experimental\)\?;$/namespace filesystem = boos sed -i 's/\bfile_type::directory\b/file_type::directory_file/g' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" sed -i 's/\bp\.native ()/p.wstring ()/g' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" -# Don't try adding `std::u8string` to an `std::vector`. MSVC -# probably coerces them, but GCC doesn't -sed -i 's/\bgeneric_u8string\b/generic_string/g' "$sdk_directory/public.sdk/source/vst/hosting/module_win32.cpp" - # Meson requires this program to output something, or else it will error out # when trying to encode the empty output echo "Successfully patched '$sdk_directory' for winegcc compatibility" From 1db3c0371f9392610e8afff75b9e7d67427b6d9b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 15:05:05 +0100 Subject: [PATCH 100/456] Expand on the VST3 implementation readme --- src/common/serialization/vst3/README.md | 85 +++++++++++++------------ 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index e25d87db..9249ef9d 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -4,51 +4,52 @@ TODO: Once this is more fleshed out, move this document to `docs/`, and perhaps replace this readme with a link to that document. The VST3 SDK uses an architecture where every object inherits from an interface, -and every interface inherits from `FUnknown` which offers a sort of query -interface. Newer versions of an interface with added functionality then inherit -from the previous version of that interface. Every interface (and thus also -newer versions of an old interface) get a unique identifier. It then uses a -smart pointer system (`FUnknownPtr`) that queries whether the `FUnknown` -matches a certain interface by checking whether the IDs match up, allowing casts -to that interface if the `FUnkonwn` matches. This means that an -`IPluginFactory*` may also be an `IPluginFactory2*` or an `IPluginFactory3*`. -For yabridge we need to be able to pass concrete serializable objects that -implement these interfaces around. +and every interface inherits from `FUnknown` which offers a dynamic casting +interface through `queryInterface()`. Every interface gets a unique identifier. +It then uses a smart pointer system (`FUnknownPtr`) that queries whether the +`FUnknown` matches a certain interface by checking whether the IDs match up, +allowing casts to that interface if the `FUnkonwn` matches. -## Serializing simple objects +Another important part of this system is interface versioning. Old interfaces +cannot be changed, so when the SDK adds new functionality to an existing +interface it defines a new interface that inherits from the old one. The +`queryInterface()` implementation should then allow casts to all of the +implemented interface versions. -TODO: Think of a better naming scheme +Lastly, the interfaces mostly provided a lot of getters for data, but some of +the interfaces also provide callback functions that should perform some +operation on the component implementing the interface. -Serializing an object that implements `ISimple` that only stores data and can't -perform any callbacks works as follows: +Yabridge's serialization and communication model for VST3 is thus a lot more +complicated than for VST2 since all of these objects are loosely coupled and are +instantiated and managed by the host. The model works as follows: -1. We define a class called `YaSimple` that inherits from `ISimple`. -2. We fetch all data from `ISimple` and store it in `YaSimple`. -3. `YaSimpl` can then be serialized with bitsery and transmitted like any other - object. +1. For an interface `IFoo`, we provide a possibly abstract implementation called + `YaFoo`. +2. This class has a constructor that takes an `IPtr` interface pointer and + copies all of the data from the interface's functions that do not perform any + side effects. +3. `YaFoo` then implements all the boilerplate required for `FUnknown`. This + includes the constructor, destructor and methods required for reference + counting, as well as the query interface. +4. If `IFoo` is a versioned interface such as `IPluginFactory3`, the above two + steps work slightly differently. When copying the data for a plugin factory, + we'll start copying from `IPluginFactory`, and we'll copy data from each + newer version of the interface that the `IPtr` supports. + During this process we keep track of which interfaces were supported by the + native plugin. In our query interface method we then only report support for + the same itnerfaces that were supported by `IPtr` returns a null pointer we know that the object doesn't - implement that version of the interface and we can stop. -3. During the copying process we'll also copy over the `iid`. This allows our - object to appear as the highest version of the interface we were able to copy - from. Doing this avoids complicated inheritance chains in our own - implemetnation. -4. `YaPluginFactory` can then be serialized with bitsery and transmitted like - any other object. - -## Processors and controllers - -TODO: Not sure how this will work yet. +Aside form the above, the plugin factory is the only place where we may +potentially report different values from those reported by the Windows VST3 +plugin. If we encounter an itnerface we do not yet support, we will log a +warning and we'll skip the interface since we wouldn't know how to handle it. From 049eb257c5404797aca2ca2f4d354367a59f8cf4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 17:59:31 +0100 Subject: [PATCH 101/456] Make YaPluginFactory abstract And add separate implementations for the native plugin and the Wine plugin host. This way we can easily allow the native host to do callbacks without having to manage a load of lambdas. --- meson.build | 2 ++ src/common/serialization/vst3/README.md | 7 ++-- .../serialization/vst3/plugin-factory.cpp | 25 ------------- .../serialization/vst3/plugin-factory.h | 25 ++++++++++--- src/plugin/bridges/vst3-impls.cpp | 34 ++++++++++++++++++ src/plugin/bridges/vst3-impls.h | 36 +++++++++++++++++++ src/plugin/vst3-plugin.cpp | 7 ++-- src/wine-host/bridges/vst3-impls.cpp | 33 +++++++++++++++++ src/wine-host/bridges/vst3-impls.h | 31 ++++++++++++++++ src/wine-host/bridges/vst3.cpp | 6 ++-- 10 files changed, 169 insertions(+), 37 deletions(-) create mode 100644 src/plugin/bridges/vst3-impls.cpp create mode 100644 src/plugin/bridges/vst3-impls.h create mode 100644 src/wine-host/bridges/vst3-impls.cpp create mode 100644 src/wine-host/bridges/vst3-impls.h diff --git a/meson.build b/meson.build index 1aeec32a..cca4136c 100644 --- a/meson.build +++ b/meson.build @@ -82,6 +82,7 @@ vst3_plugin_sources = [ 'src/common/plugins.cpp', 'src/common/utils.cpp', 'src/plugin/bridges/vst3.cpp', + 'src/plugin/bridges/vst3-impls.cpp', 'src/plugin/host-process.cpp', 'src/plugin/utils.cpp', 'src/plugin/vst3-plugin.cpp', @@ -109,6 +110,7 @@ if with_vst3 'src/common/logging/vst3.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/wine-host/bridges/vst3.cpp', + 'src/wine-host/bridges/vst3-impls.cpp', ] endif diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 9249ef9d..6693162e 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -43,9 +43,10 @@ instantiated and managed by the host. The model works as follows: can be sent between the native plugin and the Wine plugin host. 6. If `IFoo` has methods that have side effects (such as instantiating a new object), then the implementations of those functions in `YaFoo` will be pure - virtual and both the native plugin and the Wine plugin host should provide - their own implementation. Since the functions will ever only be called from - one of the two sides, the other side can just throw in their implementation. + virtual. The side that requested the object (so for the plugin factory that + would be on the side of the native plugin) should then provide a `YaFoo{Plugin,Host}Impl` + that implements those functions through yabridge's `Vst3MessageHandler` + callback interface. ## Plugin Factory diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index ba9dd46c..e86ee90c 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -119,24 +119,6 @@ tresult PLUGIN_API YaPluginFactory::getClassInfo(Steinberg::int32 index, } } -tresult PLUGIN_API -YaPluginFactory::createInstance(Steinberg::FIDString /*cid*/, - Steinberg::FIDString /*_iid*/, - void** /*obj*/) { - // TODO: Figure out how to implement this. Some considerations: - // - We have to sent a control message to the Wine plugin host to ask - // it to create an instance of `_iid`. - // - We then create a `Ya*` implementation of the same interface on - // the plugin side. - // - These two should be wired up so that when the host calls a - // function on it, it should be sent to the instance on the Wine - // plugin host side with the same cid. - // - We should have a list of interfaces we support. When we receive a - // request to create an instance of something we don't support, then - // we should log that and then fail. - return 0; -} - tresult PLUGIN_API YaPluginFactory::getClassInfo2(int32 /*index*/, Steinberg::PClassInfo2* /*info*/) { @@ -150,10 +132,3 @@ YaPluginFactory::getClassInfoUnicode(int32 /*index*/, // TODO: Implement return 0; } - -tresult PLUGIN_API -YaPluginFactory::setHostContext(Steinberg::FUnknown* /*context*/) { - // TODO: I guess this should do a callback and set the Wine host's host - // context, right? - return 0; -} diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index c93accde..55b2acde 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -47,6 +47,9 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { * TODO: Instead of a having a default constructor, we should probably be * passing a callback to this constructor that lets us communicate * with the Wine plugin host. + * TODO: Alternative to requiring a bunch of `fu::unique_function<>` + * callbacks would be to make the callback functions pure virtual, and + * then implement those functions directly using `Vst3MessageHandler`. */ YaPluginFactory(); @@ -59,7 +62,7 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { explicit YaPluginFactory( Steinberg::IPtr factory); - ~YaPluginFactory(); + virtual ~YaPluginFactory(); DECLARE_FUNKNOWN_METHODS @@ -68,9 +71,20 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { int32 PLUGIN_API countClasses() override; tresult PLUGIN_API getClassInfo(Steinberg::int32 index, Steinberg::PClassInfo* info) override; - tresult PLUGIN_API createInstance(Steinberg::FIDString cid, - Steinberg::FIDString _iid, - void** obj) override; + // TODO: Figure out how to implement this. Some considerations: + // - We have to sent a control message to the Wine plugin host to ask + // it to create an instance of `_iid`. + // - We then create a `Ya*` implementation of the same interface on + // the plugin side. + // - These two should be wired up so that when the host calls a + // function on it, it should be sent to the instance on the Wine + // plugin host side with the same cid. + // - We should have a list of interfaces we support. When we receive a + // request to create an instance of something we don't support, then + // we should log that and then fail. + virtual tresult PLUGIN_API createInstance(Steinberg::FIDString cid, + Steinberg::FIDString _iid, + void** obj) override = 0; // From `IPluginFactory2` tresult PLUGIN_API getClassInfo2(int32 index, @@ -79,7 +93,8 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { // From `IPluginFactory3` tresult PLUGIN_API getClassInfoUnicode(int32 index, Steinberg::PClassInfoW* info) override; - tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override; + virtual tresult PLUGIN_API + setHostContext(Steinberg::FUnknown* context) override = 0; /** * The IIDs that the interface we serialized supports. diff --git a/src/plugin/bridges/vst3-impls.cpp b/src/plugin/bridges/vst3-impls.cpp new file mode 100644 index 00000000..0a18bf9e --- /dev/null +++ b/src/plugin/bridges/vst3-impls.cpp @@ -0,0 +1,34 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "vst3-impls.h" + +YaPluginFactoryPluginImpl::YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge) + : bridge(bridge) {} + +tresult PLUGIN_API +YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString /*cid*/, + Steinberg::FIDString /*_iid*/, + void** /*obj*/) { + // TODO: Send a control message + return 0; +} + +tresult PLUGIN_API +YaPluginFactoryPluginImpl::setHostContext(Steinberg::FUnknown* /*context*/) { + // TODO: Send a control message + return 0; +} diff --git a/src/plugin/bridges/vst3-impls.h b/src/plugin/bridges/vst3-impls.h new file mode 100644 index 00000000..39a058f8 --- /dev/null +++ b/src/plugin/bridges/vst3-impls.h @@ -0,0 +1,36 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "vst3.h" + +// These are implementation of the serialization clases in +// `src/common/serialization/vst3/` to provide callback support + +class YaPluginFactoryPluginImpl : public YaPluginFactory { + public: + YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge); + + tresult PLUGIN_API createInstance(Steinberg::FIDString cid, + Steinberg::FIDString _iid, + void** obj) override; + + tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override; + + private: + Vst3PluginBridge& bridge; +}; diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index 0e12f3fe..4052cd57 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -17,6 +17,9 @@ #include #include "bridges/vst3.h" +// TODO: Remove include, instantiating and returning the `YaPluginFactory` +// should be done in `Vst3PluginBridge` +#include "src/plugin/bridges/vst3-impls.h" #include @@ -87,8 +90,8 @@ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { // TODO: Remove, this is just for type checking if (false) { boost::asio::local::stream_protocol::socket* socket; - YaPluginFactory* object; - write_object(*socket, *object); + YaPluginFactoryPluginImpl object(*bridge); + write_object(*socket, object); } if (!gPluginFactory) { diff --git a/src/wine-host/bridges/vst3-impls.cpp b/src/wine-host/bridges/vst3-impls.cpp new file mode 100644 index 00000000..b169063e --- /dev/null +++ b/src/wine-host/bridges/vst3-impls.cpp @@ -0,0 +1,33 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "vst3-impls.h" + +YaPluginFactoryHostImpl::YaPluginFactoryHostImpl( + Steinberg::IPtr factory) + : YaPluginFactory(factory) {} + +tresult PLUGIN_API +YaPluginFactoryHostImpl::createInstance(Steinberg::FIDString /*cid*/, + Steinberg::FIDString /*_iid*/, + void** /*obj*/) { + throw std::runtime_error("Unexpected call to 'createInstance()'"); +} + +tresult PLUGIN_API +YaPluginFactoryHostImpl::setHostContext(Steinberg::FUnknown* /*context*/) { + throw std::runtime_error("Unexpected call to 'setHostContext()'"); +} diff --git a/src/wine-host/bridges/vst3-impls.h b/src/wine-host/bridges/vst3-impls.h new file mode 100644 index 00000000..59328696 --- /dev/null +++ b/src/wine-host/bridges/vst3-impls.h @@ -0,0 +1,31 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "vst3.h" + +class YaPluginFactoryHostImpl : public YaPluginFactory { + public: + YaPluginFactoryHostImpl(Steinberg::IPtr factory); + + tresult PLUGIN_API createInstance(Steinberg::FIDString cid, + Steinberg::FIDString _iid, + void** obj) override; + + tresult PLUGIN_API + setHostContext(Steinberg::FUnknown* /*context*/) override; +}; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index a905d94f..385e8009 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -17,6 +17,7 @@ #include "vst3.h" #include "../boost-fix.h" +#include "vst3-impls.h" #include @@ -45,8 +46,9 @@ void Vst3Bridge::run() { // TODO: Remove, this is just for type checking if (false) { boost::asio::local::stream_protocol::socket* socket; - YaPluginFactory* object; - write_object(*socket, *object); + Steinberg::IPtr factory; + YaPluginFactoryHostImpl object(factory); + write_object(*socket, object); } // TODO: Handle events From d8a4207748d3b9a3345da90ab73513a096c12004 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 18:24:36 +0100 Subject: [PATCH 102/456] Make the error messages more specific In case this does ever come up. --- src/wine-host/bridges/vst3-impls.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wine-host/bridges/vst3-impls.cpp b/src/wine-host/bridges/vst3-impls.cpp index b169063e..d52d8cc6 100644 --- a/src/wine-host/bridges/vst3-impls.cpp +++ b/src/wine-host/bridges/vst3-impls.cpp @@ -24,10 +24,12 @@ tresult PLUGIN_API YaPluginFactoryHostImpl::createInstance(Steinberg::FIDString /*cid*/, Steinberg::FIDString /*_iid*/, void** /*obj*/) { - throw std::runtime_error("Unexpected call to 'createInstance()'"); + throw std::runtime_error( + "Unexpected call to 'IPluginFactory::createInstance()'"); } tresult PLUGIN_API YaPluginFactoryHostImpl::setHostContext(Steinberg::FUnknown* /*context*/) { - throw std::runtime_error("Unexpected call to 'setHostContext()'"); + throw std::runtime_error( + "Unexpected call to 'IPluginFactory3::setHostContext()'"); } From ef3f82e00fb6781d33c8c2f567189c209e181dc3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 23:23:11 +0100 Subject: [PATCH 103/456] Implement IpluginFactory2::getClassInfo2 --- .../serialization/vst3/plugin-factory.cpp | 32 ++++++++++++------- .../serialization/vst3/plugin-factory.h | 32 ++++++++++++------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index e86ee90c..4291ff9b 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -22,18 +22,15 @@ YaPluginFactory::YaPluginFactory( Steinberg::IPtr factory) { FUNKNOWN_CTOR - // TODO: Copy data from `IPluginFactory` - // TODO: We should only copy the interfaces that we support. This should use - // the same list as that used in `createInstance()`. known_iids.insert(factory->iid); - + // `IPluginFactory::getFactoryInfo` if (Steinberg::PFactoryInfo info; factory->getFactoryInfo(&info) == Steinberg::kResultOk) { factory_info = info; } - + // `IPluginFactory::countClasses` num_classes = factory->countClasses(); - + // `IPluginFactory::getClassInfo` // TODO: At this point we don't know what this class is and thus we can't // filter unsupported classes, right? class_infos_1.resize(num_classes); @@ -49,8 +46,14 @@ YaPluginFactory::YaPluginFactory( return; } - // TODO: Copy data from `IPluginFactory2` known_iids.insert(factory2->iid); + // `IpluginFactory2::getClassInfo2` + for (int i = 0; i < num_classes; i++) { + Steinberg::PClassInfo2 info; + if (factory2->getClassInfo2(i, &info) == Steinberg::kResultOk) { + class_infos_2[i] = info; + } + } auto factory3 = Steinberg::FUnknownPtr(factory); if (!factory3) { @@ -120,10 +123,17 @@ tresult PLUGIN_API YaPluginFactory::getClassInfo(Steinberg::int32 index, } tresult PLUGIN_API -YaPluginFactory::getClassInfo2(int32 /*index*/, - Steinberg::PClassInfo2* /*info*/) { - // TODO: Implement - return 0; +YaPluginFactory::getClassInfo2(int32 index, Steinberg::PClassInfo2* info) { + if (index >= static_cast(class_infos_1.size())) { + return Steinberg::kInvalidArgument; + } + + if (class_infos_2[index]) { + *info = *class_infos_2[index]; + return Steinberg::kResultOk; + } else { + return Steinberg::kResultFalse; + } } tresult PLUGIN_API diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 55b2acde..926c97f4 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -25,9 +25,7 @@ #include "../../bitsery/ext/vst3.h" -namespace { using Steinberg::int32, Steinberg::tresult; -} // namespace // TODO: After implementing one or two more of these, abstract away some of the // nasty bits @@ -43,14 +41,6 @@ using Steinberg::int32, Steinberg::tresult; */ class YaPluginFactory : public Steinberg::IPluginFactory3 { public: - /** - * TODO: Instead of a having a default constructor, we should probably be - * passing a callback to this constructor that lets us communicate - * with the Wine plugin host. - * TODO: Alternative to requiring a bunch of `fu::unique_function<>` - * callbacks would be to make the callback functions pure virtual, and - * then implement those functions directly using `Vst3MessageHandler`. - */ YaPluginFactory(); /** @@ -116,7 +106,10 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { * doesn't return a class info. */ std::vector> class_infos_1; - // TODO: Callback interface for `createInstance()` + /** + * For `IPluginFactory2::getClassInfo2`, works the same way as the above. + */ + std::vector> class_infos_2; template void serialize(S& s) { @@ -130,6 +123,10 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { [](S& s, std::optional& info) { s.ext(info, bitsery::ext::StdOptional{}); }); + s.container(class_infos_2, 2048, + [](S& s, std::optional& info) { + s.ext(info, bitsery::ext::StdOptional{}); + }); } }; @@ -146,6 +143,19 @@ void serialize(S& s, PClassInfo& class_info) { s.text1b(class_info.name); } +template +void serialize(S& s, PClassInfo2& class_info) { + s.container1b(class_info.cid); + s.value4b(class_info.cardinality); + s.text1b(class_info.category); + s.text1b(class_info.name); + s.value4b(class_info.classFlags); + s.text1b(class_info.subCategories); + s.text1b(class_info.vendor); + s.text1b(class_info.version); + s.text1b(class_info.sdkVersion); +} + template void serialize(S& s, PFactoryInfo& factory_info) { s.text1b(factory_info.vendor); From 5f1495814680ebec51e25d98bdcbc9dfd8972e13 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 5 Dec 2020 23:31:48 +0100 Subject: [PATCH 104/456] Implement IpluginFactory3::getClassInfoUnicode --- .../serialization/vst3/plugin-factory.cpp | 26 ++++++++++++++----- .../serialization/vst3/plugin-factory.h | 22 ++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index 4291ff9b..9ea34214 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -60,8 +60,14 @@ YaPluginFactory::YaPluginFactory( return; } - // TODO: Copy data from `IPluginFactory3` known_iids.insert(factory3->iid); + // `IpluginFactory3::getClassInfoUnicode` + for (int i = 0; i < num_classes; i++) { + Steinberg::PClassInfoW info; + if (factory3->getClassInfoUnicode(i, &info) == Steinberg::kResultOk) { + class_infos_unicode[i] = info; + } + } } YaPluginFactory::~YaPluginFactory() { @@ -110,7 +116,7 @@ int32 PLUGIN_API YaPluginFactory::countClasses() { tresult PLUGIN_API YaPluginFactory::getClassInfo(Steinberg::int32 index, Steinberg::PClassInfo* info) { - if (index >= static_cast(class_infos_1.size())) { + if (index >= static_cast(class_infos_unicode.size())) { return Steinberg::kInvalidArgument; } @@ -137,8 +143,16 @@ YaPluginFactory::getClassInfo2(int32 index, Steinberg::PClassInfo2* info) { } tresult PLUGIN_API -YaPluginFactory::getClassInfoUnicode(int32 /*index*/, - Steinberg::PClassInfoW* /*info*/) { - // TODO: Implement - return 0; +YaPluginFactory::getClassInfoUnicode(int32 index, + Steinberg::PClassInfoW* info) { + if (index >= static_cast(class_infos_unicode.size())) { + return Steinberg::kInvalidArgument; + } + + if (class_infos_unicode[index]) { + *info = *class_infos_unicode[index]; + return Steinberg::kResultOk; + } else { + return Steinberg::kResultFalse; + } } diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 926c97f4..011258db 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -110,6 +110,11 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { * For `IPluginFactory2::getClassInfo2`, works the same way as the above. */ std::vector> class_infos_2; + /** + * For `IPluginFactory3::getClassInfoUnicode`, works the same way as the + * above. + */ + std::vector> class_infos_unicode; template void serialize(S& s) { @@ -127,6 +132,10 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { [](S& s, std::optional& info) { s.ext(info, bitsery::ext::StdOptional{}); }); + s.container(class_infos_unicode, 2048, + [](S& s, std::optional& info) { + s.ext(info, bitsery::ext::StdOptional{}); + }); } }; @@ -156,6 +165,19 @@ void serialize(S& s, PClassInfo2& class_info) { s.text1b(class_info.sdkVersion); } +template +void serialize(S& s, PClassInfoW& class_info) { + s.container1b(class_info.cid); + s.value4b(class_info.cardinality); + s.text1b(class_info.category); + s.text2b(class_info.name); + s.value4b(class_info.classFlags); + s.text1b(class_info.subCategories); + s.text2b(class_info.vendor); + s.text2b(class_info.version); + s.text2b(class_info.sdkVersion); +} + template void serialize(S& s, PFactoryInfo& factory_info) { s.text1b(factory_info.vendor); From 3be5836c071476b2f522600d2d29d8ee0d88e096 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 6 Dec 2020 00:51:20 +0100 Subject: [PATCH 105/456] Log and ignore unsupported interfaces --- .../serialization/vst3/plugin-factory.cpp | 64 ++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index 9ea34214..08a21ac3 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -16,6 +16,28 @@ #include "plugin-factory.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/** + * Return whether yabridge supports this class or not. This way we can skip over + * any classes that the plugin might support but we have not implemented yet. If + * we do not support a class, we will log it. + * + * @tparam Any of `Steinberg::PClassInfo`, `Steinberg::PClassInfo2` or + * `Steinberg::PClassInfoW`. + */ +template +bool is_supported_interface(const T& class_info); + YaPluginFactory::YaPluginFactory(){FUNKNOWN_CTOR} YaPluginFactory::YaPluginFactory( @@ -36,7 +58,8 @@ YaPluginFactory::YaPluginFactory( class_infos_1.resize(num_classes); for (int i = 0; i < num_classes; i++) { Steinberg::PClassInfo info; - if (factory->getClassInfo(i, &info) == Steinberg::kResultOk) { + if (factory->getClassInfo(i, &info) == Steinberg::kResultOk && + is_supported_interface(info)) { class_infos_1[i] = info; } } @@ -50,7 +73,8 @@ YaPluginFactory::YaPluginFactory( // `IpluginFactory2::getClassInfo2` for (int i = 0; i < num_classes; i++) { Steinberg::PClassInfo2 info; - if (factory2->getClassInfo2(i, &info) == Steinberg::kResultOk) { + if (factory2->getClassInfo2(i, &info) == Steinberg::kResultOk && + is_supported_interface(info)) { class_infos_2[i] = info; } } @@ -64,7 +88,8 @@ YaPluginFactory::YaPluginFactory( // `IpluginFactory3::getClassInfoUnicode` for (int i = 0; i < num_classes; i++) { Steinberg::PClassInfoW info; - if (factory3->getClassInfoUnicode(i, &info) == Steinberg::kResultOk) { + if (factory3->getClassInfoUnicode(i, &info) == Steinberg::kResultOk && + is_supported_interface(info)) { class_infos_unicode[i] = info; } } @@ -156,3 +181,36 @@ YaPluginFactory::getClassInfoUnicode(int32 index, return Steinberg::kResultFalse; } } + +template +bool is_supported_interface(const T& class_info) { + // I feel like we're not supposed to use this comparison function, but they + // don't offer any other ways to compare FUIDs/TUIDs + // TODO: Add these interfaces as we go along + if (Steinberg::FUnknownPrivate::iidEqual(class_info.cid, + Steinberg::Vst::IComponent::iid) + // || + // Steinberg::FUnknownPrivate::iidEqual( + // cid, Steinberg::Vst::IAudioProcessor::iid) || + // Steinberg::FUnknownPrivate::iidEqual( + // cid, Steinberg::Vst::IEditController::iid) + ) { + return true; + } else { + // TODO: These prints get logged correctly because we do this from the + // Wine side, but for neater logging we should add these to a list + // instead and then print them all when we receive the factory + // instance on the plugin's side + std::string class_name = VST3::StringConvert::convert( + class_info.name, Steinberg::PClassInfo::kNameSize); + + char interface_id_str[128]; + Steinberg::FUID(class_info.cid) + .print(interface_id_str, Steinberg::FUID::UIDPrintStyle::kFUID); + + std::cerr << "Unsupported interface '" << class_name + << "': " << interface_id_str << std::endl; + + return false; + } +} From 02dfe93ff02fae551422a6352adeb988cf784419 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 6 Dec 2020 11:46:40 +0100 Subject: [PATCH 106/456] Allow deserialization into an existing object We're going to need this for VST3 because we're going to have to explicitly instantiate our interface implementations since they need to be able to perform complicated callbacks. --- src/common/communication/common.h | 35 +++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 4439d72b..b6409b0e 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -92,6 +92,8 @@ inline void write_object(Socket& socket, const T& object) { * @param socket The Boost.Asio socket to read from. * @param buffer The buffer to read into. This is useful for sending audio and * chunk data since that can vary in size by a lot. + * @param object The object to serialize into. There are also overrides that + * create a new default initialized `T` * * @return The deserialized object. * @@ -102,7 +104,7 @@ inline void write_object(Socket& socket, const T& object) { * @relates write_object */ template -inline T read_object(Socket& socket, std::vector& buffer) { +inline T read_object(Socket& socket, std::vector& buffer, T& object) { // See the note above on the use of `uint64_t` instead of `size_t` std::array message_length; boost::asio::read(socket, boost::asio::buffer(message_length)); @@ -118,7 +120,6 @@ inline T read_object(Socket& socket, std::vector& buffer) { boost::asio::read(socket, boost::asio::buffer(buffer)); assert(size == actual_size); - T object; auto [_, success] = bitsery::quickDeserialization>>( {buffer.begin(), size}, object); @@ -132,14 +133,40 @@ inline T read_object(Socket& socket, std::vector& buffer) { } /** - * `read_object()` with a small default buffer for convenience. + * `read_object()` into a new default initialized object with an existing + * buffer. + * + * @overload + */ +template +inline T read_object(Socket& socket, std::vector& buffer) { + T object; + return read_object(socket, buffer, object); +} + +/** + * `read_object()` into an existing object a small default + * buffer for convenience. + * + * @overload + */ +template +inline T read_object(Socket& socket, T& object) { + std::vector buffer(64); + return read_object(socket, buffer, object); +} + +/** + * `read_object()` into a new default initialized object with a small default + * buffer for convenience. * * @overload */ template inline T read_object(Socket& socket) { + T object; std::vector buffer(64); - return read_object(socket, buffer); + return read_object(socket, buffer, object); } /** From 5423950a8a75c7c69e4d7e7a9100c8fffd778780 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 6 Dec 2020 11:53:45 +0100 Subject: [PATCH 107/456] Allow receiving VST3 messages into existing object --- src/common/communication/vst3.h | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 04739fe3..9643ab13 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -85,6 +85,22 @@ class Vst3MessageHandler : public AdHocSocketHandler { typename T::Response send_message( const T& object, std::optional> logging) { + typename T::Response response_object; + return send_message(object, response_object, logging); + } + + /** + * `Vst3MessageHandler::send_message()`, but deserializing the response into + * an existing object. + * + * TODO: We might also need overloads that reuse buffers + * + * @overload + */ + template + void send_message(const T& object, + typename T::Response& response_object, + std::optional> logging) { using TResponse = typename T::Response; if (logging) { @@ -99,7 +115,8 @@ class Vst3MessageHandler : public AdHocSocketHandler { const TResponse response = this->template send( [&](boost::asio::local::stream_protocol::socket& socket) { write_object(socket, Request(object)); - const auto response = read_object(socket); + const auto response = + read_object(socket, response_object); // If the other side handled the request correctly, the Response // variant should now contain an object of type `T::Response` From d5374e4540c290e3c3fbecc84a37bf3fe7fffe94 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 6 Dec 2020 12:21:37 +0100 Subject: [PATCH 108/456] :boom: Rework Vst3MessageHandler - Now allows direct deserialization into existing objects. This will be necessary for our VST3 implementations since the interface instances we'll deserialize into will not be trivially constructable because they have to be able to do callbacks. - `ControlResponse` and `CallbackResponse` were dropped. These response enums are not necessary because of the `T::Response` associated type and returning the types directly makes the direct deserialization possible. --- src/common/communication/vst3.h | 53 +++++++++++++++------------------ src/common/serialization/vst3.h | 37 ++--------------------- src/plugin/bridges/vst3.cpp | 2 +- 3 files changed, 27 insertions(+), 65 deletions(-) diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 9643ab13..d60c199f 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -16,7 +16,7 @@ #pragma once -#include +#include #include "../logging/vst3.h" #include "../serialization/vst3.h" @@ -38,10 +38,8 @@ * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. * @tparam Request Either `ControlRequest` or `CallbackRequest`. - * @tparam Response Either `ControlResponse` or `CallbackResponse`, depending on - * the type of `Request`. */ -template +template class Vst3MessageHandler : public AdHocSocketHandler { public: /** @@ -86,7 +84,9 @@ class Vst3MessageHandler : public AdHocSocketHandler { const T& object, std::optional> logging) { typename T::Response response_object; - return send_message(object, response_object, logging); + send_message(object, response_object, logging); + + return response_object; } /** @@ -112,23 +112,20 @@ class Vst3MessageHandler : public AdHocSocketHandler { // messages from arriving out of order. `AdHocSocketHandler::send()` // will either use a long-living primary socket, or if that's currently // in use it will spawn a new socket for us. - const TResponse response = this->template send( + this->template send( [&](boost::asio::local::stream_protocol::socket& socket) { write_object(socket, Request(object)); - const auto response = - read_object(socket, response_object); - - // If the other side handled the request correctly, the Response - // variant should now contain an object of type `T::Response` - return std::get(response); + read_object(socket, response_object); + // FIXME: We have to return something here, and ML was not yet + // invented when they came up with C++ so void is not + // valid here + return std::monostate{}; }); if (logging) { auto [logger, is_host_vst] = *logging; - logger.log_response(!is_host_vst, response); + logger.log_response(!is_host_vst, response_object); } - - return response; } /** @@ -137,8 +134,8 @@ class Vst3MessageHandler : public AdHocSocketHandler { * `socket`. * * The specified function receives a `Request` variant object containing an - * object of type `T`, and it should return the corresponding `Response` of - * type `T::Response`. + * object of type `T`, and it should then return the corresponding + * `T::Response`. * * @param logging A pair containing a logger instance and whether or not * this is for sending host -> plugin control messages. If set to false, @@ -148,7 +145,11 @@ class Vst3MessageHandler : public AdHocSocketHandler { * @param callback The function used to generate a response out of the * request. See the definition of `F` for more information. * - * @tparam F A function type in the form of `Reponse(Request)`. + * @tparam F A function type in the form of `T::Response(Request(T))`. C++ + * doesn't have syntax for this, but the function receives a `Request` + * variant containing a `T`, and the function should return a `T::Reponse` + * object. This way we can directly deserialize into a `T::Reponse` on the + * side that called `send_object(T)`. * * @relates Vst3MessageHandler::send_event */ @@ -170,14 +171,10 @@ class Vst3MessageHandler : public AdHocSocketHandler { request); } - Response response = callback(request); + const auto response = callback(request); if (logging) { - std::visit( - [&](const auto& object) { - auto [logger, is_host_vst] = *logging; - logger.log_response(!is_host_vst, object); - }, - response); + auto [logger, is_host_vst] = *logging; + logger.log_response(!is_host_vst, response); } write_object(socket, response); @@ -260,14 +257,12 @@ class Vst3Sockets : public Sockets { * This will be listened on by the Wine plugin host when it calls * `receive_multi()`. */ - Vst3MessageHandler - host_vst_control; + Vst3MessageHandler host_vst_control; /** * For sending callbacks from the plugin back to the host. After we have a * better idea of what our communication model looks like we'll probably * want to provide an abstraction similar to `EventHandler`. */ - Vst3MessageHandler - vst_host_callback; + Vst3MessageHandler vst_host_callback; }; diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 27dfb589..3dacf9ab 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -48,10 +48,7 @@ struct WantsConfiguration { /** * When we send a control message from the plugin to the Wine VST host, this * encodes the information we request or the operation we want to perform. A - * request of type `T` should send back a `CallbackResponse` containing - * `T::Reponse`. - * - * @relates ControlResponse + * request of type `ControlRequest(T)` should send back a `T::Reponse`. */ using ControlRequest = std::variant<>; @@ -60,26 +57,10 @@ void serialize(S& s, ControlRequest& payload) { s.ext(payload, bitsery::ext::StdVariant{}); } -/** - * A response to a control message. Tee `T::Reponse` for the correct type to - * return here. - * - * @relates ControlRrequest - */ -using ControlResponse = std::variant<>; - -// TODO: Uncomment when this is no longer empty -// template -// void serialize(S& s, ControlResponse& payload) { -// s.ext(payload, bitsery::ext::StdVariant{}); -// } - /** * When we do a callback from the Wine VST host to the plugin, this encodes the * information we want or the operation we want to perform. A request of type - * `T` should send back a `CallbackResponse` containing `T::Reponse`. - * - * @relates CallbackResponse + * `CallbackRequest(T)` should send back a `T::Reponse`. */ using CallbackRequest = std::variant; @@ -87,17 +68,3 @@ template void serialize(S& s, CallbackRequest& payload) { s.ext(payload, bitsery::ext::StdVariant{[](S&, WantsConfiguration&) {}}); } - -/** - * A response to a callback. Tee `T::Reponse` for the correct type to return - * here. - * - * @relates CallbackRrequest - */ -using CallbackResponse = std::variant; - -template -void serialize(S& s, CallbackResponse& payload) { - s.ext(payload, bitsery::ext::StdVariant{ - [](S& s, Configuration& config) { s.object(config); }}); -} diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index c8755d55..0d6165bb 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -42,7 +42,7 @@ Vst3PluginBridge::Vst3PluginBridge() host_callback_handler = std::jthread([&]() { sockets.vst_host_callback.receive_messages( std::pair(logger, false), - [&](CallbackRequest request) -> CallbackResponse { + [&](CallbackRequest request) -> auto { return std::visit(overload{[&](const WantsConfiguration&) -> WantsConfiguration::Response { return config; From a16cf3015f12d124cf56be9c50f38e7fb345e574 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 6 Dec 2020 14:02:59 +0100 Subject: [PATCH 109/456] Fix deserializing into existing objects `read_object()` was trying to create copies. --- src/common/communication/common.h | 12 ++++++++---- src/common/communication/vst3.h | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index b6409b0e..e430864c 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -104,7 +104,7 @@ inline void write_object(Socket& socket, const T& object) { * @relates write_object */ template -inline T read_object(Socket& socket, std::vector& buffer, T& object) { +inline T& read_object(Socket& socket, std::vector& buffer, T& object) { // See the note above on the use of `uint64_t` instead of `size_t` std::array message_length; boost::asio::read(socket, boost::asio::buffer(message_length)); @@ -141,7 +141,9 @@ inline T read_object(Socket& socket, std::vector& buffer, T& object) { template inline T read_object(Socket& socket, std::vector& buffer) { T object; - return read_object(socket, buffer, object); + read_object(socket, buffer, object); + + return object; } /** @@ -151,7 +153,7 @@ inline T read_object(Socket& socket, std::vector& buffer) { * @overload */ template -inline T read_object(Socket& socket, T& object) { +inline T& read_object(Socket& socket, T& object) { std::vector buffer(64); return read_object(socket, buffer, object); } @@ -166,7 +168,9 @@ template inline T read_object(Socket& socket) { T object; std::vector buffer(64); - return read_object(socket, buffer, object); + read_object(socket, buffer, object); + + return object; } /** diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index d60c199f..c279a640 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -69,6 +69,8 @@ class Vst3MessageHandler : public AdHocSocketHandler { * another thread, then this will create a new socket connection and send * the event there instead. * + * @param object The request object to send. Often a marker struct to ask + * for a specific object to be returned. * @param logging A pair containing a logger instance and whether or not * this is for sending host -> plugin control messages. If set to false, * then this indicates that this `Vst3MessageHandler` is handling plugin @@ -94,13 +96,18 @@ class Vst3MessageHandler : public AdHocSocketHandler { * an existing object. * * TODO: We might also need overloads that reuse buffers + * TODO: Rename to `receive_into()` to make it more apparent what's + * happening + * + * @param response_object The object to deserialize into. * * @overload */ template - void send_message(const T& object, - typename T::Response& response_object, - std::optional> logging) { + typename T::Response& send_message( + const T& object, + typename T::Response& response_object, + std::optional> logging) { using TResponse = typename T::Response; if (logging) { @@ -126,6 +133,8 @@ class Vst3MessageHandler : public AdHocSocketHandler { auto [logger, is_host_vst] = *logging; logger.log_response(!is_host_vst, response_object); } + + return response_object; } /** From 887a856e58c0869458043d5b3fa5de64e805c1ee Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 6 Dec 2020 14:05:52 +0100 Subject: [PATCH 110/456] Rename Vst3MessageHandler::send_message overload `receive_into()` looks much clearer in typical usage. --- src/common/communication/vst3.h | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index c279a640..89bf0601 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -86,7 +86,7 @@ class Vst3MessageHandler : public AdHocSocketHandler { const T& object, std::optional> logging) { typename T::Response response_object; - send_message(object, response_object, logging); + receive_into(object, response_object, logging); return response_object; } @@ -96,15 +96,13 @@ class Vst3MessageHandler : public AdHocSocketHandler { * an existing object. * * TODO: We might also need overloads that reuse buffers - * TODO: Rename to `receive_into()` to make it more apparent what's - * happening * * @param response_object The object to deserialize into. * - * @overload + * @overload Vst3MessageHandler::send_message */ template - typename T::Response& send_message( + typename T::Response& receive_into( const T& object, typename T::Response& response_object, std::optional> logging) { From 79c6f02d919d69c77819ffe8f34463c92b6e3a44 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 6 Dec 2020 14:07:21 +0100 Subject: [PATCH 111/456] Request and deserialize plugin factory from plugin --- src/common/bitsery/ext/boost-path.h | 2 - src/common/logging/vst3.cpp | 23 ++++ src/common/logging/vst3.h | 2 + src/common/serialization/vst3.h | 12 +- .../serialization/vst3/plugin-factory.cpp | 2 - .../serialization/vst3/plugin-factory.h | 45 +++---- src/plugin/bridges/vst3.cpp | 43 +++++++ src/plugin/bridges/vst3.h | 14 +++ src/plugin/vst3-plugin.cpp | 111 ++---------------- 9 files changed, 127 insertions(+), 127 deletions(-) diff --git a/src/common/bitsery/ext/boost-path.h b/src/common/bitsery/ext/boost-path.h index fe149bc2..ba103324 100644 --- a/src/common/bitsery/ext/boost-path.h +++ b/src/common/bitsery/ext/boost-path.h @@ -27,8 +27,6 @@ namespace bitsery { namespace ext { -// TODO: There's probably a better way to do all of this - /** * An adapter for serializing and deserializing filesystem paths since they're * not mutable. diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index f3c2f029..608b71a9 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -17,6 +17,7 @@ #include "vst3.h" #include +#include "src/common/serialization/vst3.h" // TODO: Reconsider the output format // TODO: Maybe think of an alterantive that's a little less boilerplaty @@ -33,6 +34,16 @@ void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { } } +void Vst3Logger::log_request(bool is_host_vst, const WantsPluginFactory&) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { + std::ostringstream message; + message << get_log_prefix(is_host_vst) + << " >> Requesting "; + + log(message.str()); + } +} + void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { std::ostringstream message; @@ -41,3 +52,15 @@ void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { log(message.str()); } } + +void Vst3Logger::log_response(bool is_host_vst, + const YaPluginFactory& factory) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { + std::ostringstream message; + message << get_log_prefix(is_host_vst) << " with " + << const_cast(factory).countClasses() + << " registered classes"; + + log(message.str()); + } +} diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index adc493eb..8eccac19 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -44,8 +44,10 @@ class Vst3Logger { // (what we'll call a control message). void log_request(bool is_host_vst, const WantsConfiguration&); + void log_request(bool is_host_vst, const WantsPluginFactory&); void log_response(bool is_host_vst, const Configuration&); + void log_response(bool is_host_vst, const YaPluginFactory&); Logger& logger; diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 3dacf9ab..1654d6e9 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -45,16 +45,24 @@ struct WantsConfiguration { using Response = Configuration; }; +/** + * Marker struct to indicate the other side (the Wine plugin host) should send a + * copy of the hosted Windows VST3 plugin's `IPluginFactory{,2,3}` interface. + */ +struct WantsPluginFactory { + using Response = YaPluginFactory; +}; + /** * When we send a control message from the plugin to the Wine VST host, this * encodes the information we request or the operation we want to perform. A * request of type `ControlRequest(T)` should send back a `T::Reponse`. */ -using ControlRequest = std::variant<>; +using ControlRequest = std::variant; template void serialize(S& s, ControlRequest& payload) { - s.ext(payload, bitsery::ext::StdVariant{}); + s.ext(payload, bitsery::ext::StdVariant{[](S&, WantsPluginFactory&) {}}); } /** diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index 08a21ac3..f21e812b 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -53,8 +53,6 @@ YaPluginFactory::YaPluginFactory( // `IPluginFactory::countClasses` num_classes = factory->countClasses(); // `IPluginFactory::getClassInfo` - // TODO: At this point we don't know what this class is and thus we can't - // filter unsupported classes, right? class_infos_1.resize(num_classes); for (int i = 0; i < num_classes; i++) { Steinberg::PClassInfo info; diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 011258db..7fe9d555 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -86,6 +86,29 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { virtual tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override = 0; + template + void serialize(S& s) { + s.ext(known_iids, bitsery::ext::StdSet{32}, + [](S& s, Steinberg::FUID& iid) { + s.ext(iid, bitsery::ext::FUID{}); + }); + s.ext(factory_info, bitsery::ext::StdOptional{}); + s.value4b(num_classes); + s.container(class_infos_1, 2048, + [](S& s, std::optional& info) { + s.ext(info, bitsery::ext::StdOptional{}); + }); + s.container(class_infos_2, 2048, + [](S& s, std::optional& info) { + s.ext(info, bitsery::ext::StdOptional{}); + }); + s.container(class_infos_unicode, 2048, + [](S& s, std::optional& info) { + s.ext(info, bitsery::ext::StdOptional{}); + }); + } + + private: /** * The IIDs that the interface we serialized supports. */ @@ -115,28 +138,6 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { * above. */ std::vector> class_infos_unicode; - - template - void serialize(S& s) { - s.ext(known_iids, bitsery::ext::StdSet{32}, - [](S& s, Steinberg::FUID& iid) { - s.ext(iid, bitsery::ext::FUID{}); - }); - s.ext(factory_info, bitsery::ext::StdOptional{}); - s.value4b(num_classes); - s.container(class_infos_1, 2048, - [](S& s, std::optional& info) { - s.ext(info, bitsery::ext::StdOptional{}); - }); - s.container(class_infos_2, 2048, - [](S& s, std::optional& info) { - s.ext(info, bitsery::ext::StdOptional{}); - }); - s.container(class_infos_unicode, 2048, - [](S& s, std::optional& info) { - s.ext(info, bitsery::ext::StdOptional{}); - }); - } }; #pragma GCC diagnostic pop diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 0d6165bb..1ac88ffb 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -16,6 +16,42 @@ #include "vst3.h" +#include "src/common/serialization/vst3.h" +#include "vst3-impls.h" + +// There are still some design decisions that need some more thought +// TODO: Check whether `IPlugView::isPlatformTypeSupported` needs special +// handling. +// TODO: The documentation mentions that private communication through VST3's +// message system should be handled on a separate timer thread. Do we +// need special handling for this on the Wine side (e.g. during the event +// handling loop)? Probably not, since the actual host should manage all +// messaging. +// TODO: The docs very explicitly mention that +// the`IComponentHandler::{begin,perform,end}Edit()` functions have to be +// called from the UI thread. Should we have special handling for this or +// does everything just magically work out? +// TODO: Something that's not relevant here but that will require some thinking +// is that VST3 requires all plugins to be installed in ~/.vst3. I can +// think of two options and I"m not sure what's the best one: +// +// 1. We can add the required files for the Linux VST3 plugin to the +// location of the Windows VST3 plugin (by adding some files to the +// bundle or creating a bundle next to it) and then symlink that bundle +// to ~/.vst3. +// 2. We can create the bundle in ~/.vst3 and symlink the Windows plugin +// and all of its resources into bundle as if they were also installed +// there. +// +// The second one sounds much better, but it will still need some more +// consideration. Aside from that VST3 plugins also have a centralized +// preset location, even though barely anyone uses it, yabridgectl will +// also have to make a symlink of that. Also, yabridgectl will need to do +// some extra work there to detect removed plugins. +// TODO: Also symlink presets, and allow pruning broken symlinks there as well +// TODO: And how do we choose between 32-bit and 64-bit versions of a VST3 +// plugin if they exist? Config files? + Vst3PluginBridge::Vst3PluginBridge() : PluginBridge( PluginType::vst3, @@ -34,6 +70,13 @@ Vst3PluginBridge::Vst3PluginBridge() // host connect_sockets_guarded(); + // Set up the plugin factory, since this is the first thing the host will + // request after loading the module + plugin_factory = std::make_unique(*this); + sockets.host_vst_control.receive_into( + WantsPluginFactory{}, *plugin_factory, + std::pair(logger, true)); + // Now that communication is set up the Wine host can send callbacks to this // bridge class, and we can send control messages to the Wine host. This // messaging mechanism is how we relay the VST3 communication protocol. As a diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index f997a11d..93fc0a7e 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -18,6 +18,7 @@ #include +#include "../..//common/serialization/vst3/plugin-factory.h" #include "../../common/communication/vst3.h" #include "../../common/logging/vst3.h" #include "common.h" @@ -63,4 +64,17 @@ class Vst3PluginBridge : PluginBridge> { * `PluginBridge::generic_logger`. */ Vst3Logger logger; + + public: + /** + * Our plugin factory. This will be set up directly after initialization. + * All information about the plugin and its supported classes are copied + * directly from the Windows VST3 plugin's factory on the Wine side, and + * we'll provide an implementation that can send control messages to the + * Wine plugin host. + * + * A pointer to this implementation will be returned to the host in + * GetPluginFactory(). + */ + std::unique_ptr plugin_factory; }; diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index 4052cd57..c2a26050 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -17,9 +17,6 @@ #include #include "bridges/vst3.h" -// TODO: Remove include, instantiating and returning the `YaPluginFactory` -// should be done in `Vst3PluginBridge` -#include "src/plugin/bridges/vst3-impls.h" #include @@ -76,101 +73,17 @@ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { // The host should have called `InitModule()` first assert(bridge); - // TODO: Instead of using gPluginFactory we'll use a field in - // `Vst3PluginBridge` - // TODO: First thing we should do is query the factory on the Wine side and - // preset a copy of it to the host. The important bits there are that - // we use the same interface version as the one presented the plugin. - // TODO: We have two options for the implementation: - // 1. We can query the interface version, and then have three - // different implementations for the interface version. - // 2. We can implement version 3, but copy the iid from the plugin so - // it always uses the correct version. + return bridge->plugin_factory.get(); - // TODO: Remove, this is just for type checking - if (false) { - boost::asio::local::stream_protocol::socket* socket; - YaPluginFactoryPluginImpl object(*bridge); - write_object(*socket, object); - } - - if (!gPluginFactory) { - // TODO: Here we want to: - // 1) Load the plugin on the Wine host - // 2) Create a factory using the plugins PFactoryInfo - // 3) Get all PClassInfo{,2,W} objects from the plugin, register - // those classes. - // - // We should wrap this in our `Vst3PluginBridge` - // TODO: We should also create a list of which extensions we have - // already implemented and which are left - // TODO: And when we get a query for some interface that we do not (yet) - // support, we should print some easy to spot warning message - // TODO: Check whether `IPlugView::isPlatformTypeSupported` needs - // special handling. - // TODO: Should we always use plugin groups or for VST3 plugins? Since - // they seem to be very keen on sharing resources and leaving - // modules loaded. - // TODO: The documentation mentions that private communication through - // VST3's message system should be handled on a separate timer - // thread. Do we need special handling for this on the Wine side - // (e.g. during the event handling loop)? Probably not, since the - // actual host should manage all messaging. - // TODO: The docs very explicitly mention that - // the`IComponentHandler::{begin,perform,end}Edit()` functions - // have to be called from the UI thread. Should we have special - // handling for this or does everything just magically work out? - // TODO: Something that's not relevant here but that will require some - // thinking is that VST3 requires all plugins to be installed in - // ~/.vst3. I can think of two options and I"m not sure what's the - // best one: - // - // 1. We can add the required files for the Linux VST3 plugin to - // the location of the Windows VST3 plugin (by adding some - // files to the bundle or creating a bundle next to it) and - // then symlink that bundle to ~/.vst3. - // 2. We can create the bundle in ~/.vst3 and symlink the Windows - // plugin and all of its resources into bundle as if they were - // also installed there. - // - // The second one sounds much better, but it will still need some - // more consideration. Aside from that VST3 plugins also have a - // centralized preset location, even though barely anyone uses it, - // yabridgectl will also have to make a symlink of that. Also, - // yabridgectl will need to do some extra work there to detect - // removed plugins. - // TODO: Also symlink presets, and allow pruning broken symlinks there - // as well - // TODO: And how do we choose between 32-bit and 64-bit versions of a - // VST3 plugin if they exist? Config files? - - // static Steinberg::PFactoryInfo factoryInfo(vendor, url, email, - // flags); gPluginFactory = new Steinberg::CPluginFactory(factoryInfo); - - // - // { - // Steinberg::TUID lcid = cid; - // static Steinberg::PClassInfo componentClass(lcid, cardinality, - // category, name); - // gPluginFactory->registerClass(&componentClass, createMethod); - // } - // { - // Steinberg::TUID lcid = cid; - // static Steinberg::PClassInfo2 componentClass( - // lcid, cardinality, category, name, classFlags, subCategories, - // 0, version, sdkVersion); - // gPluginFactory->registerClass(&componentClass, createMethod); - // } - // { - // TUID lcid = cid; - // static Steinberg::PClassInfoW componentClass( - // lcid, cardinality, category, name, classFlags, subCategories, - // 0, version, sdkVersion); - // gPluginFactory->registerClass(&componentClass, createMethod); - // } - } else { - gPluginFactory->addRef(); - } - - return gPluginFactory; + // TODO: In the normal implementation of this function they manually call + // part of the reference counting mechanism. Is this something we also + // have to? And how does the `delete self` in the `removeRef()` play + // with our `std::unique_ptr` (aka, should we also use IPtr here)? The + // normal implementation looks like this: + // if (!gPluginFactory) { + // // Instantiate the factory + // } else { + // gPluginFactory->addRef(); + // } + // return gPluginFactory; } From c2503f8aaa97fabf6cc334596137b70acab03875 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 6 Dec 2020 14:41:50 +0100 Subject: [PATCH 112/456] Send the factory from the Wine host to the plugin --- src/common/communication/vst3.h | 2 +- src/common/serialization/vst3.h | 4 +++- src/plugin/vst3-plugin.cpp | 4 ++++ src/wine-host/bridges/vst3.cpp | 27 +++++++++++++-------------- src/wine-host/bridges/vst3.h | 7 +++++++ 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 89bf0601..4b2c783e 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -178,7 +178,7 @@ class Vst3MessageHandler : public AdHocSocketHandler { request); } - const auto response = callback(request); + const auto& response = callback(request); if (logging) { auto [logger, is_host_vst] = *logging; logger.log_response(!is_host_vst, response); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 1654d6e9..e6880d76 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -50,7 +50,9 @@ struct WantsConfiguration { * copy of the hosted Windows VST3 plugin's `IPluginFactory{,2,3}` interface. */ struct WantsPluginFactory { - using Response = YaPluginFactory; + // TODO: Some things had to be changed to use references since this is an + // abstract class. Check if nothing breaks. + using Response = YaPluginFactory&; }; /** diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index c2a26050..5bb17f84 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -73,6 +73,10 @@ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { // The host should have called `InitModule()` first assert(bridge); + // TODO: I think there is a flag that indicates that the class configuration + // may change, but I don't remember if it's at runtime or every time + // the module is loaded. If it's the former then this will take some + // special handling. return bridge->plugin_factory.get(); // TODO: In the normal implementation of this function they manually call diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 385e8009..9920bf2c 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -36,6 +36,11 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context, sockets.connect(); + // Serialize the plugin's plugin factory. The native VST3 plugin will + // request a copy of this during its initialization. + plugin_factory = + std::make_unique(module->getFactory().get()); + // Fetch this instance's configuration from the plugin to finish the setup // process config = sockets.vst_host_callback.send_message(WantsConfiguration{}, @@ -43,18 +48,12 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context, } void Vst3Bridge::run() { - // TODO: Remove, this is just for type checking - if (false) { - boost::asio::local::stream_protocol::socket* socket; - Steinberg::IPtr factory; - YaPluginFactoryHostImpl object(factory); - write_object(*socket, object); - } - - // TODO: Handle events - // sockets.host_vst_control.receive_messages( - // std::nullopt, [&](ControlRequest request) -> ControlResponse { - // }); - - std::cerr << "TODO: Not yet implemented" << std::endl; + sockets.host_vst_control.receive_messages( + std::nullopt, [&](ControlRequest request) -> auto& { + return std::visit(overload{[&](const WantsPluginFactory&) + -> WantsPluginFactory::Response { + return *plugin_factory; + }}, + request); + }); } diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index bb9e4804..c5f52d5c 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -83,4 +83,11 @@ class Vst3Bridge : public HostBridge { * threads to exit. */ Vst3Sockets sockets; + + /** + * A plugin factory copied from the Windows VST3 plugin during + * initialization. The native VST3 plugin will request a copy of this + * information during its initialization. + */ + std::unique_ptr plugin_factory; }; From 7fb8cf97b15668689fc5860466fda21ae5cbdbfb Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 6 Dec 2020 14:47:19 +0100 Subject: [PATCH 113/456] Request factory after setting up callback handlers Otherwise we'll get a deadlock. --- src/plugin/bridges/vst3.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 1ac88ffb..167b7ede 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -70,13 +70,6 @@ Vst3PluginBridge::Vst3PluginBridge() // host connect_sockets_guarded(); - // Set up the plugin factory, since this is the first thing the host will - // request after loading the module - plugin_factory = std::make_unique(*this); - sockets.host_vst_control.receive_into( - WantsPluginFactory{}, *plugin_factory, - std::pair(logger, true)); - // Now that communication is set up the Wine host can send callbacks to this // bridge class, and we can send control messages to the Wine host. This // messaging mechanism is how we relay the VST3 communication protocol. As a @@ -93,4 +86,13 @@ Vst3PluginBridge::Vst3PluginBridge() request); }); }); + + // Set up the plugin factory, since this is the first thing the host will + // request after loading the module. Host callback handlers should have + // started before this since the Wine plugin host will request a copy of the + // configuration during its initialization. + plugin_factory = std::make_unique(*this); + sockets.host_vst_control.receive_into( + WantsPluginFactory{}, *plugin_factory, + std::pair(logger, true)); } From 8ea40cd9f909c9e7ad4d9a84ad2ac3c5ec237584 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 6 Dec 2020 15:09:02 +0100 Subject: [PATCH 114/456] Rework Vst3MessageHandler::receive_messages This now takes a regular overloaded function and the visiting is done in `receive_messages()` itself. This way we can use templates to ensure that the return type is correct. Otherwise auto will cause issues in the future when we want to return multiple concrete types from a function that takes a single variant. The alternative would be both receiving a variant as a parameter and then returning another variant as a result, but that is much less type safe. --- src/common/communication/vst3.h | 17 +++++++++++------ src/common/serialization/vst3.h | 4 ++-- src/plugin/bridges/vst3.cpp | 9 ++------- src/wine-host/bridges/vst3.cpp | 12 +++++------- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 4b2c783e..0f97478b 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -152,11 +152,9 @@ class Vst3MessageHandler : public AdHocSocketHandler { * @param callback The function used to generate a response out of the * request. See the definition of `F` for more information. * - * @tparam F A function type in the form of `T::Response(Request(T))`. C++ - * doesn't have syntax for this, but the function receives a `Request` - * variant containing a `T`, and the function should return a `T::Reponse` - * object. This way we can directly deserialize into a `T::Reponse` on the - * side that called `send_object(T)`. + * @tparam F A function type in the form of `T::Response(T)` for every `T` + * in `Request`. This way we can directly deserialize into a `T::Response` + * on the side that called `receive_into(T, T::Response&)`. * * @relates Vst3MessageHandler::send_event */ @@ -178,7 +176,14 @@ class Vst3MessageHandler : public AdHocSocketHandler { request); } - const auto& response = callback(request); + // We do the visiting here using a templated lambda. This way we + // always know for sure that the function returns the correct + // type, and we can scrap a lot of boilerplate elsewhere. + const auto& response = std::visit( + [&](const T object) -> + typename T::Response { return callback(object); }, + request); + if (logging) { auto [logger, is_host_vst] = *logging; logger.log_response(!is_host_vst, response); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index e6880d76..dc15b1c8 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -58,7 +58,7 @@ struct WantsPluginFactory { /** * When we send a control message from the plugin to the Wine VST host, this * encodes the information we request or the operation we want to perform. A - * request of type `ControlRequest(T)` should send back a `T::Reponse`. + * request of type `ControlRequest(T)` should send back a `T::Response`. */ using ControlRequest = std::variant; @@ -70,7 +70,7 @@ void serialize(S& s, ControlRequest& payload) { /** * When we do a callback from the Wine VST host to the plugin, this encodes the * information we want or the operation we want to perform. A request of type - * `CallbackRequest(T)` should send back a `T::Reponse`. + * `CallbackRequest(T)` should send back a `T::Response`. */ using CallbackRequest = std::variant; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 167b7ede..3575fbd4 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -78,13 +78,8 @@ Vst3PluginBridge::Vst3PluginBridge() host_callback_handler = std::jthread([&]() { sockets.vst_host_callback.receive_messages( std::pair(logger, false), - [&](CallbackRequest request) -> auto { - return std::visit(overload{[&](const WantsConfiguration&) - -> WantsConfiguration::Response { - return config; - }}, - request); - }); + overload{[&](const WantsConfiguration&) + -> WantsConfiguration::Response { return config; }}); }); // Set up the plugin factory, since this is the first thing the host will diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 9920bf2c..150cc16d 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -49,11 +49,9 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context, void Vst3Bridge::run() { sockets.host_vst_control.receive_messages( - std::nullopt, [&](ControlRequest request) -> auto& { - return std::visit(overload{[&](const WantsPluginFactory&) - -> WantsPluginFactory::Response { - return *plugin_factory; - }}, - request); - }); + std::nullopt, + overload{ + [&](const WantsPluginFactory&) -> WantsPluginFactory::Response { + return *plugin_factory; + }}); } From e20fc8c7e6df35441b17da53c7098254180a25d2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Dec 2020 00:25:02 +0100 Subject: [PATCH 115/456] Fix Vst{2,3}Logger::log_trace --- src/common/logging/vst2.h | 4 +++- src/common/logging/vst3.h | 4 +++- src/common/serialization/vst3.h | 2 -- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst2.h b/src/common/logging/vst2.h index 2fe1d60c..75af6412 100644 --- a/src/common/logging/vst2.h +++ b/src/common/logging/vst2.h @@ -49,7 +49,9 @@ class Vst2Logger { /** * @see Logger::log_trace */ - inline void log_trace(const std::string& message) { logger.log(message); } + inline void log_trace(const std::string& message) { + logger.log_trace(message); + } // The following functions are for logging specific events, they are only // enabled for verbosity levels higher than 1 (i.e. `Verbosity::events`) diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 8eccac19..45a9511d 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -36,7 +36,9 @@ class Vst3Logger { /** * @see Logger::log_trace */ - inline void log_trace(const std::string& message) { logger.log(message); } + inline void log_trace(const std::string& message) { + logger.log_trace(message); + } // For every object we send using `Vst3MessageHandler` we have overloads // that print information about the request and the response. The boolean diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index dc15b1c8..4b913e6d 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -50,8 +50,6 @@ struct WantsConfiguration { * copy of the hosted Windows VST3 plugin's `IPluginFactory{,2,3}` interface. */ struct WantsPluginFactory { - // TODO: Some things had to be changed to use references since this is an - // abstract class. Check if nothing breaks. using Response = YaPluginFactory&; }; From 547b11e8ba1681970c90e90069ba2709a222f2c2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Dec 2020 13:08:23 +0100 Subject: [PATCH 116/456] Remove interface filtering from the plugin factory This doesn't work that way, and CIDs really are class IDs (good thing that everything's documented, oh right). --- .../serialization/vst3/plugin-factory.cpp | 61 ++----------------- src/plugin/vst3-plugin.cpp | 4 -- 2 files changed, 5 insertions(+), 60 deletions(-) diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index f21e812b..1465a82b 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -19,25 +19,8 @@ #include #include -#include -#include -#include -#include -#include -#include #include -/** - * Return whether yabridge supports this class or not. This way we can skip over - * any classes that the plugin might support but we have not implemented yet. If - * we do not support a class, we will log it. - * - * @tparam Any of `Steinberg::PClassInfo`, `Steinberg::PClassInfo2` or - * `Steinberg::PClassInfoW`. - */ -template -bool is_supported_interface(const T& class_info); - YaPluginFactory::YaPluginFactory(){FUNKNOWN_CTOR} YaPluginFactory::YaPluginFactory( @@ -56,8 +39,7 @@ YaPluginFactory::YaPluginFactory( class_infos_1.resize(num_classes); for (int i = 0; i < num_classes; i++) { Steinberg::PClassInfo info; - if (factory->getClassInfo(i, &info) == Steinberg::kResultOk && - is_supported_interface(info)) { + if (factory->getClassInfo(i, &info) == Steinberg::kResultOk) { class_infos_1[i] = info; } } @@ -69,10 +51,10 @@ YaPluginFactory::YaPluginFactory( known_iids.insert(factory2->iid); // `IpluginFactory2::getClassInfo2` + class_infos_2.resize(num_classes); for (int i = 0; i < num_classes; i++) { Steinberg::PClassInfo2 info; - if (factory2->getClassInfo2(i, &info) == Steinberg::kResultOk && - is_supported_interface(info)) { + if (factory2->getClassInfo2(i, &info) == Steinberg::kResultOk) { class_infos_2[i] = info; } } @@ -84,10 +66,10 @@ YaPluginFactory::YaPluginFactory( known_iids.insert(factory3->iid); // `IpluginFactory3::getClassInfoUnicode` + class_infos_unicode.resize(num_classes); for (int i = 0; i < num_classes; i++) { Steinberg::PClassInfoW info; - if (factory3->getClassInfoUnicode(i, &info) == Steinberg::kResultOk && - is_supported_interface(info)) { + if (factory3->getClassInfoUnicode(i, &info) == Steinberg::kResultOk) { class_infos_unicode[i] = info; } } @@ -179,36 +161,3 @@ YaPluginFactory::getClassInfoUnicode(int32 index, return Steinberg::kResultFalse; } } - -template -bool is_supported_interface(const T& class_info) { - // I feel like we're not supposed to use this comparison function, but they - // don't offer any other ways to compare FUIDs/TUIDs - // TODO: Add these interfaces as we go along - if (Steinberg::FUnknownPrivate::iidEqual(class_info.cid, - Steinberg::Vst::IComponent::iid) - // || - // Steinberg::FUnknownPrivate::iidEqual( - // cid, Steinberg::Vst::IAudioProcessor::iid) || - // Steinberg::FUnknownPrivate::iidEqual( - // cid, Steinberg::Vst::IEditController::iid) - ) { - return true; - } else { - // TODO: These prints get logged correctly because we do this from the - // Wine side, but for neater logging we should add these to a list - // instead and then print them all when we receive the factory - // instance on the plugin's side - std::string class_name = VST3::StringConvert::convert( - class_info.name, Steinberg::PClassInfo::kNameSize); - - char interface_id_str[128]; - Steinberg::FUID(class_info.cid) - .print(interface_id_str, Steinberg::FUID::UIDPrintStyle::kFUID); - - std::cerr << "Unsupported interface '" << class_name - << "': " << interface_id_str << std::endl; - - return false; - } -} diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index 5bb17f84..32866497 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -14,14 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include - #include "bridges/vst3.h" #include -using Steinberg::gPluginFactory; - // Because VST3 plugins consist of completely independent components that have // to be initialized and connected by the host, hosting a VST3 plugin through // yabridge works very differently from hosting VST2 plugin. Even with From 99542820656c3f247098e2ab36962de9c0a875b0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Dec 2020 14:53:55 +0100 Subject: [PATCH 117/456] Add manual reference counting to GetPluginFactory Since even though we're passign raw pointers, it's expected that they are actually `IPtr`s. --- src/plugin/bridges/vst3-impls.cpp | 9 ++++++--- src/plugin/bridges/vst3.cpp | 29 ++++++++++++++++++++--------- src/plugin/bridges/vst3.h | 27 +++++++++++++++++++-------- src/plugin/vst3-plugin.cpp | 18 +----------------- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/plugin/bridges/vst3-impls.cpp b/src/plugin/bridges/vst3-impls.cpp index 0a18bf9e..d47c4dcd 100644 --- a/src/plugin/bridges/vst3-impls.cpp +++ b/src/plugin/bridges/vst3-impls.cpp @@ -24,11 +24,14 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString /*cid*/, Steinberg::FIDString /*_iid*/, void** /*obj*/) { // TODO: Send a control message - return 0; + return Steinberg::kNotImplemented; } tresult PLUGIN_API YaPluginFactoryPluginImpl::setHostContext(Steinberg::FUnknown* /*context*/) { - // TODO: Send a control message - return 0; + // TODO: The docs don't clearly specify what this should be doing, but from + // what I've seen this is only used to pass a `IHostApplication` + // instance. That's used to allow the plugin to create objects in the + // host. + return Steinberg::kNotImplemented; } diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 3575fbd4..780c199e 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -81,13 +81,24 @@ Vst3PluginBridge::Vst3PluginBridge() overload{[&](const WantsConfiguration&) -> WantsConfiguration::Response { return config; }}); }); - - // Set up the plugin factory, since this is the first thing the host will - // request after loading the module. Host callback handlers should have - // started before this since the Wine plugin host will request a copy of the - // configuration during its initialization. - plugin_factory = std::make_unique(*this); - sockets.host_vst_control.receive_into( - WantsPluginFactory{}, *plugin_factory, - std::pair(logger, true)); +} + +Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { + // Even though we're working with raw pointers here, we should pretend that + // we're `IPtr` and do the reference counting + // ourselves because you can't always safely pass those around + if (plugin_factory) { + plugin_factory->addRef(); + } else { + // Set up the plugin factory, since this is the first thing the host + // will request after loading the module. Host callback handlers should + // have started before this since the Wine plugin host will request a + // copy of the configuration during its initialization. + plugin_factory = std::make_unique(*this); + sockets.host_vst_control.receive_into( + WantsPluginFactory{}, *plugin_factory, + std::pair(logger, true)); + } + + return plugin_factory.get(); } diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 93fc0a7e..651ff3da 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -52,6 +52,17 @@ class Vst3PluginBridge : PluginBridge> { */ Vst3PluginBridge(); + /** + * When the host loads the module it will call `GetPluginFactory()` which + * will in turn call this function. The idea is that we return an + * `IPluginFactory*` while doing all the reference counting that `IPtr` + * would normally do for us ourselves. This means that when the host frees + * its last instance of this factory, `plugin_factory` will also be cleared. + * + * @see plugin_factory + */ + Steinberg::IPluginFactory* get_plugin_factory(); + private: /** * Handles callbacks from the plugin to the host over the @@ -65,16 +76,16 @@ class Vst3PluginBridge : PluginBridge> { */ Vst3Logger logger; - public: /** - * Our plugin factory. This will be set up directly after initialization. - * All information about the plugin and its supported classes are copied - * directly from the Windows VST3 plugin's factory on the Wine side, and - * we'll provide an implementation that can send control messages to the - * Wine plugin host. + * Our plugin factory. All information about the plugin and its supported + * classes are copied directly from the Windows VST3 plugin's factory on the + * Wine side, and we'll provide an implementation that can send control + * messages to the Wine plugin host. Even though we're passign plain + * pointers around, we should pretend that they're wrapped in the VST3 SDK's + * reference counting p pointers so we should do the reference counting + * ourselves. * - * A pointer to this implementation will be returned to the host in - * GetPluginFactory(). + * @related get_plugin_factory */ std::unique_ptr plugin_factory; }; diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index 32866497..75b5e201 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -69,21 +69,5 @@ SMTG_EXPORT_SYMBOL Steinberg::IPluginFactory* PLUGIN_API GetPluginFactory() { // The host should have called `InitModule()` first assert(bridge); - // TODO: I think there is a flag that indicates that the class configuration - // may change, but I don't remember if it's at runtime or every time - // the module is loaded. If it's the former then this will take some - // special handling. - return bridge->plugin_factory.get(); - - // TODO: In the normal implementation of this function they manually call - // part of the reference counting mechanism. Is this something we also - // have to? And how does the `delete self` in the `removeRef()` play - // with our `std::unique_ptr` (aka, should we also use IPtr here)? The - // normal implementation looks like this: - // if (!gPluginFactory) { - // // Instantiate the factory - // } else { - // gPluginFactory->addRef(); - // } - // return gPluginFactory; + return bridge->get_plugin_factory(); } From d485aa296aa2b85467a911a861c5cffd3ea68943 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Dec 2020 15:02:45 +0100 Subject: [PATCH 118/456] Fix VST2-only builds --- src/wine-host/bridges/vst2.h | 1 + src/wine-host/editor.h | 1 + src/wine-host/utils.h | 1 + 3 files changed, 3 insertions(+) diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index b5a8d25c..c7f68f04 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -20,6 +20,7 @@ #ifndef NOMINMAX #define NOMINMAX +#define WINE_NOWINSOCK #endif #include #include diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index e483c722..41ff5f38 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -22,6 +22,7 @@ #ifndef NOMINMAX #define NOMINMAX +#define WINE_NOWINSOCK #endif #include #include diff --git a/src/wine-host/utils.h b/src/wine-host/utils.h index 3bc8a55e..9634673c 100644 --- a/src/wine-host/utils.h +++ b/src/wine-host/utils.h @@ -23,6 +23,7 @@ #ifndef NOMINMAX #define NOMINMAX +#define WINE_NOWINSOCK #endif #include From 8e09d50a5463371fc94710f0531edebb953a77de Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Dec 2020 15:29:02 +0100 Subject: [PATCH 119/456] Describe how createInstance() is going to work --- src/plugin/bridges/vst3-impls.cpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/plugin/bridges/vst3-impls.cpp b/src/plugin/bridges/vst3-impls.cpp index d47c4dcd..eee5c02b 100644 --- a/src/plugin/bridges/vst3-impls.cpp +++ b/src/plugin/bridges/vst3-impls.cpp @@ -20,9 +20,33 @@ YaPluginFactoryPluginImpl::YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge) : bridge(bridge) {} tresult PLUGIN_API -YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString /*cid*/, - Steinberg::FIDString /*_iid*/, - void** /*obj*/) { +YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, + Steinberg::FIDString _iid, + void** obj) { + // TODO: This should: + // 1. Check which interface `_iid` belongs to. Let's call this + // interface `T`. If we do not (yet) support it, then we should log + // it and return `Steinberg::kNotImplemented`. + // 2. Send a control message to the Wine plugin host to instantiate a + // new object of type `T` with `cid` and `_iid` as parameters. + // 3. On the Wine side this calls `createIntance()` on the module's + // factory with thsoe same `cid` and `_iid` arguments. + // 4. It this was successful, we'll assign this object a unique number + // (by just doing a fetch-and-add on an atomic size_t) so we can + // refer to it and add it to an `std::map` (could + // also throw everything in a single map with `FUnknown`s, but + // while more verbose this sounds much less prone to breakage). + // 5. We'll serialize any payload data into a `YaT` **which includes + // that unique identifier we generated** and send it back to the + // plugin. + // 6. The plugin then converts it to the correct smart pointer format + // and writes it to `obj`. We don't have to keep track of these + // objects on the plugin side and the reference counting pointers + // will cause everything to clean up after itself. + // 7. Since those `YaT` objects we'll return from `createInstance()` + // will have a reference to `Vst3PluginBridge`, they can also send + // control messages themselves. + // TODO: Send a control message return Steinberg::kNotImplemented; } From 75e8cf91401c595a9598432764134ee86053b28a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Dec 2020 16:57:56 +0100 Subject: [PATCH 120/456] Add notes on things that can potentially go wrong --- src/common/serialization/vst3/README.md | 17 +++++++++++++---- src/plugin/bridges/vst3-impls.cpp | 25 +++++++++++++------------ src/plugin/bridges/vst3.h | 3 +++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 6693162e..7da78fd6 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -50,7 +50,16 @@ instantiated and managed by the host. The model works as follows: ## Plugin Factory -Aside form the above, the plugin factory is the only place where we may -potentially report different values from those reported by the Windows VST3 -plugin. If we encounter an itnerface we do not yet support, we will log a -warning and we'll skip the interface since we wouldn't know how to handle it. +TODO: Explain how we implement `createInstance()` + +## Safety notes + +- None of the destructors in the interfaces defined by the SDK are marked as + virtual because this could apparently [break binary + compatibility](https://github.com/steinbergmedia/vst3sdk/issues/21). This + means that the destructor of the class that implemented `release()` will be + called. This is something to keep in mind when dealing with inheritence. +- Since everything behind the scenes makes use of these `addRef()` and + `release()` reference counting functions, we can't use the standard library's + smart pointers when dealing with objects that are shared with the host or with + the Windows VST3 plugin. diff --git a/src/plugin/bridges/vst3-impls.cpp b/src/plugin/bridges/vst3-impls.cpp index eee5c02b..72686852 100644 --- a/src/plugin/bridges/vst3-impls.cpp +++ b/src/plugin/bridges/vst3-impls.cpp @@ -33,21 +33,22 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, // factory with thsoe same `cid` and `_iid` arguments. // 4. It this was successful, we'll assign this object a unique number // (by just doing a fetch-and-add on an atomic size_t) so we can - // refer to it and add it to an `std::map` (could - // also throw everything in a single map with `FUnknown`s, but - // while more verbose this sounds much less prone to breakage). - // 5. We'll serialize any payload data into a `YaT` **which includes + // refer to it and add it to an `std::map`, where + // `T` is the _original_ object (we don't have to and shouldn't + // wrap it). + // 5. We'll copy over any payload data into a `YaT` **which includes // that unique identifier we generated** and send it back to the // plugin. - // 6. The plugin then converts it to the correct smart pointer format - // and writes it to `obj`. We don't have to keep track of these - // objects on the plugin side and the reference counting pointers - // will cause everything to clean up after itself. - // 7. Since those `YaT` objects we'll return from `createInstance()` - // will have a reference to `Vst3PluginBridge`, they can also send - // control messages themselves. + // 6. On the plugin's side we'll create a new `YaTPluginImpl` inside + // of a VST smart pointer and deserialize the `YaT` we got sent + // into that. We then write that smart pointer into `obj`. We don't + // have to keep track of these objects on the plugin side and the + // reference counting pointers will cause everything to clean up + // after itself. + // 7. Since those `YaTPluginImpl` objects we'll return from + // `createInstance()` will have a reference to `Vst3PluginBridge`, + // they can also send control messages themselves. - // TODO: Send a control message return Steinberg::kNotImplemented; } diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 651ff3da..3f036152 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -86,6 +86,9 @@ class Vst3PluginBridge : PluginBridge> { * ourselves. * * @related get_plugin_factory + * + * FIXME: We can't use `std::unique_ptr` here because that breaks VST3's + * reference counting mechanism. */ std::unique_ptr plugin_factory; }; From d79bc3b936ae597cbe8a461d0657391bf5a724ed Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Dec 2020 18:09:49 +0100 Subject: [PATCH 121/456] Don't use STL smart pointers with VST3 interfaces This would cause double frees since those objects are supposed to clean up after themselves. --- src/common/serialization/vst3/README.md | 5 ++++- src/plugin/bridges/vst3.cpp | 7 +++++-- src/plugin/bridges/vst3.h | 12 ++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 7da78fd6..ab218c5d 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -62,4 +62,7 @@ TODO: Explain how we implement `createInstance()` - Since everything behind the scenes makes use of these `addRef()` and `release()` reference counting functions, we can't use the standard library's smart pointers when dealing with objects that are shared with the host or with - the Windows VST3 plugin. + the Windows VST3 plugin. In `IPtr`'s destructor it will call release, and + the objects will clean themselfs up with a `delete this;` when the reference + count reaches 0. Combining this with the STL cmart pointers this would result + in a double free. diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 780c199e..66447d9c 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -86,7 +86,10 @@ Vst3PluginBridge::Vst3PluginBridge() Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { // Even though we're working with raw pointers here, we should pretend that // we're `IPtr` and do the reference counting - // ourselves because you can't always safely pass those around + // ourselves because you can't always safely pass those around The VST3 + // interface can't pass smart pointers around because of binary + // compatibility, so we'll have to do the reference counting by hand like in + // the implementation in `public.sdk/source/main/pluginfactory.h`. if (plugin_factory) { plugin_factory->addRef(); } else { @@ -94,7 +97,7 @@ Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { // will request after loading the module. Host callback handlers should // have started before this since the Wine plugin host will request a // copy of the configuration during its initialization. - plugin_factory = std::make_unique(*this); + plugin_factory = Steinberg::owned(new YaPluginFactoryPluginImpl(*this)); sockets.host_vst_control.receive_into( WantsPluginFactory{}, *plugin_factory, std::pair(logger, true)); diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 3f036152..056418e6 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -80,15 +80,11 @@ class Vst3PluginBridge : PluginBridge> { * Our plugin factory. All information about the plugin and its supported * classes are copied directly from the Windows VST3 plugin's factory on the * Wine side, and we'll provide an implementation that can send control - * messages to the Wine plugin host. Even though we're passign plain - * pointers around, we should pretend that they're wrapped in the VST3 SDK's - * reference counting p pointers so we should do the reference counting - * ourselves. + * messages to the Wine plugin host. The VST3 interface only passes raw + * pointers around and the receiving side should then use `IPtr::adopt()` + * or `owned()` to get back the orignal smart pointer. * * @related get_plugin_factory - * - * FIXME: We can't use `std::unique_ptr` here because that breaks VST3's - * reference counting mechanism. */ - std::unique_ptr plugin_factory; + Steinberg::IPtr plugin_factory; }; From 7b3a6af7d16cdfd571ede490dffc648a7c80b658 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Dec 2020 18:23:04 +0100 Subject: [PATCH 122/456] Use raw pointers for the plugin factory Since the object cleans up after itself after the smart pointers are dropped on the host side this would result in a use after free by the smart pointers. --- src/plugin/bridges/vst3.cpp | 12 ++++++------ src/plugin/bridges/vst3.h | 8 ++++---- src/plugin/vst3-plugin.cpp | 3 --- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 66447d9c..ae2887e5 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -86,10 +86,10 @@ Vst3PluginBridge::Vst3PluginBridge() Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { // Even though we're working with raw pointers here, we should pretend that // we're `IPtr` and do the reference counting - // ourselves because you can't always safely pass those around The VST3 - // interface can't pass smart pointers around because of binary - // compatibility, so we'll have to do the reference counting by hand like in - // the implementation in `public.sdk/source/main/pluginfactory.h`. + // ourselves. This should work the same was as the standard implementation + // in `public.sdk/source/main/pluginfactory.h`. If we were to use an IPtr or + // an STL smart pointer we would get a double free (or rather, a use after + // free). if (plugin_factory) { plugin_factory->addRef(); } else { @@ -97,11 +97,11 @@ Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { // will request after loading the module. Host callback handlers should // have started before this since the Wine plugin host will request a // copy of the configuration during its initialization. - plugin_factory = Steinberg::owned(new YaPluginFactoryPluginImpl(*this)); + plugin_factory = new YaPluginFactoryPluginImpl(*this); sockets.host_vst_control.receive_into( WantsPluginFactory{}, *plugin_factory, std::pair(logger, true)); } - return plugin_factory.get(); + return plugin_factory; } diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 056418e6..0b383f4e 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -80,11 +80,11 @@ class Vst3PluginBridge : PluginBridge> { * Our plugin factory. All information about the plugin and its supported * classes are copied directly from the Windows VST3 plugin's factory on the * Wine side, and we'll provide an implementation that can send control - * messages to the Wine plugin host. The VST3 interface only passes raw - * pointers around and the receiving side should then use `IPtr::adopt()` - * or `owned()` to get back the orignal smart pointer. + * messages to the Wine plugin host. As explained in `get_plugin_factory()`, + * this cannot be a smart pointer because the factory is supposed to free + * itself when the host removes its last adopted `IPtr`. * * @related get_plugin_factory */ - Steinberg::IPtr plugin_factory; + YaPluginFactory* plugin_factory; }; diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index 75b5e201..3e27c884 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -38,9 +38,6 @@ bool InitModule() { assert(bridge == nullptr); try { - // 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. bridge = new Vst3PluginBridge(); return true; From f1fe0fa8a44dbd2d5349b8ad1bea01dc947feea0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Dec 2020 22:16:34 +0100 Subject: [PATCH 123/456] Log a warning when encountering unknown interfaces --- src/common/communication/vst3.h | 3 --- src/plugin/bridges/vst3-impls.cpp | 23 ++++++++++++++++++++++- src/plugin/bridges/vst3.h | 12 ++++++------ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 0f97478b..4bc3d13b 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -208,9 +208,6 @@ class Vst3MessageHandler : public AdHocSocketHandler { * sockets, and the call to `connect()` will then accept any incoming * connections. * - * TODO: I have no idea what the best approach here is yet, so this is very much - * subject to change - * * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ diff --git a/src/plugin/bridges/vst3-impls.cpp b/src/plugin/bridges/vst3-impls.cpp index 72686852..971264c5 100644 --- a/src/plugin/bridges/vst3-impls.cpp +++ b/src/plugin/bridges/vst3-impls.cpp @@ -16,6 +16,8 @@ #include "vst3-impls.h" +#include + YaPluginFactoryPluginImpl::YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge) : bridge(bridge) {} @@ -49,7 +51,26 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, // `createInstance()` will have a reference to `Vst3PluginBridge`, // they can also send control messages themselves. - return Steinberg::kNotImplemented; + if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { + // TODO: Instantiate an IComponent as described above + return Steinberg::kNotImplemented; + } else { + // When the host requests an interface we do not (yet) implement, we'll + // print a recognizable log message. I don't think they include a safe + // way to convert a `FIDString/char*` into a `FUID`, so this will have + // to do. + char iid_string[128] = ""; + constexpr size_t uid_size = sizeof(Steinberg::TUID); + if (_iid && strnlen(_iid, uid_size + 1) == uid_size) { + Steinberg::FUID iid = Steinberg::FUID::fromTUID( + *reinterpret_cast(&_iid)); + iid.print(iid_string, Steinberg::FUID::UIDPrintStyle::kCLASS_UID); + } + + bridge.logger.log("[Unknown interface] " + std::string(iid_string)); + + return Steinberg::kNotImplemented; + } } tresult PLUGIN_API diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 0b383f4e..1d6d6d69 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -63,6 +63,12 @@ class Vst3PluginBridge : PluginBridge> { */ Steinberg::IPluginFactory* get_plugin_factory(); + /** + * The logging facility used for this instance of yabridge. Wraps around + * `PluginBridge::generic_logger`. + */ + Vst3Logger logger; + private: /** * Handles callbacks from the plugin to the host over the @@ -70,12 +76,6 @@ class Vst3PluginBridge : PluginBridge> { */ std::jthread host_callback_handler; - /** - * The logging facility used for this instance of yabridge. Wraps around - * `PluginBridge::generic_logger`. - */ - Vst3Logger logger; - /** * Our plugin factory. All information about the plugin and its supported * classes are copied directly from the Windows VST3 plugin's factory on the From d59a96b3798dcea96b5a62cc941e830b04e7109e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Dec 2020 22:33:41 +0100 Subject: [PATCH 124/456] Move VST3 serializers to the structs --- src/common/serialization/vst3.h | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 4b913e6d..092a41ee 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -30,9 +30,9 @@ // interface like VST2 does, and it uses a bunch of separate interfaces instead. // Instead of having an a single event/result with accompanying payload values // for both host -> plugin `dispatcher()` and plugin -> host `audioMaster()` -// calls, we'll just send request and response payloads directly without any -// metadata. We also split everything up into host -> plugin 'control' payloads -// and plugin -> host 'callback' payloads for maintainability's sake. +// calls, we'll send objects of type `T` that should receive a response of type +// `T::Response`, where all of the possible `T`s are stored in an +// `std::variant`. This way we communicate in a completely type safe way. // TODO: If this approach works, maybe we can also refactor the VST2 handling to // do this since it's a bit safer and easier to read @@ -43,6 +43,9 @@ */ struct WantsConfiguration { using Response = Configuration; + + template + void serialize(S&) {} }; /** @@ -51,6 +54,9 @@ struct WantsConfiguration { */ struct WantsPluginFactory { using Response = YaPluginFactory&; + + template + void serialize(S&) {} }; /** @@ -62,7 +68,9 @@ using ControlRequest = std::variant; template void serialize(S& s, ControlRequest& payload) { - s.ext(payload, bitsery::ext::StdVariant{[](S&, WantsPluginFactory&) {}}); + // All of the objects in `ControlRequest` should have their own + // serialization function. + s.ext(payload, bitsery::ext::StdVariant{}); } /** @@ -74,5 +82,7 @@ using CallbackRequest = std::variant; template void serialize(S& s, CallbackRequest& payload) { - s.ext(payload, bitsery::ext::StdVariant{[](S&, WantsConfiguration&) {}}); + // All of the objects in `CallbackRequest` should have their own + // serialization function. + s.ext(payload, bitsery::ext::StdVariant{}); } From e5cd777713dde12dde492858b73201b9b680a2ea Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Dec 2020 23:49:25 +0100 Subject: [PATCH 125/456] Fix the templated visitor in Vst3MessageHandler Now it works as expected, since auto can't be initialized to multiple different values. --- src/common/communication/vst3.h | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 4bc3d13b..24601100 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -179,17 +179,18 @@ class Vst3MessageHandler : public AdHocSocketHandler { // We do the visiting here using a templated lambda. This way we // always know for sure that the function returns the correct // type, and we can scrap a lot of boilerplate elsewhere. - const auto& response = std::visit( - [&](const T object) -> - typename T::Response { return callback(object); }, + std::visit( + [&](const T object) { + typename T::Response response = callback(object); + + if (logging) { + auto [logger, is_host_vst] = *logging; + logger.log_response(!is_host_vst, response); + } + + write_object(socket, response); + }, request); - - if (logging) { - auto [logger, is_host_vst] = *logging; - logger.log_response(!is_host_vst, response); - } - - write_object(socket, response); }; this->receive_multi(logging From 5e85517130c251c7fba6f3d43355b78b04cdd19f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 7 Dec 2020 23:52:17 +0100 Subject: [PATCH 126/456] Add the base for an IComponent implementation --- src/common/logging/vst3.cpp | 22 +++++ src/common/logging/vst3.h | 2 + src/common/serialization/vst3.h | 19 +++- src/common/serialization/vst3/README.md | 2 +- src/common/serialization/vst3/component.cpp | 51 ++++++++++ src/common/serialization/vst3/component.h | 93 +++++++++++++++++++ .../serialization/vst3/plugin-factory.h | 22 ++--- src/wine-host/bridges/vst3.cpp | 27 ++++++ 8 files changed, 223 insertions(+), 15 deletions(-) create mode 100644 src/common/serialization/vst3/component.cpp create mode 100644 src/common/serialization/vst3/component.h diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 608b71a9..e61e30b7 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -24,6 +24,18 @@ Vst3Logger::Vst3Logger(Logger& generic_logger) : logger(generic_logger) {} +void Vst3Logger::log_request(bool is_host_vst, const CreateInstaneIComponent&) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { + std::ostringstream message; + // TODO: Log the cid in some readable way, if possible + message << get_log_prefix(is_host_vst) + << " >> IPluginFactory::createComponent(cid, IComponent::iid, " + "&obj)"; + + log(message.str()); + } +} + void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { std::ostringstream message; @@ -53,6 +65,16 @@ void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { } } +void Vst3Logger::log_response(bool is_host_vst, const YaComponent&) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { + std::ostringstream message; + // TODO: Add the instance ID after we implement that + message << get_log_prefix(is_host_vst) << " "; + + log(message.str()); + } +} + void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory& factory) { if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 45a9511d..f4cf1699 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -45,10 +45,12 @@ class Vst3Logger { // flag here indicates whether the request was initiated on the host side // (what we'll call a control message). + void log_request(bool is_host_vst, const CreateInstaneIComponent&); void log_request(bool is_host_vst, const WantsConfiguration&); void log_request(bool is_host_vst, const WantsPluginFactory&); void log_response(bool is_host_vst, const Configuration&); + void log_response(bool is_host_vst, const YaComponent&); void log_response(bool is_host_vst, const YaPluginFactory&); Logger& logger; diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 092a41ee..ca22b365 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -23,6 +23,7 @@ #include "../configuration.h" #include "../utils.h" #include "common.h" +#include "vst3/component.h" #include "vst3/plugin-factory.h" // Event handling for our VST3 plugins works slightly different from how we @@ -37,6 +38,21 @@ // TODO: If this approach works, maybe we can also refactor the VST2 handling to // do this since it's a bit safer and easier to read +/** + * Request the Wine plugin host to instantiate a new IComponent to pass through + * a call to `IPluginFactory::createInstance(cid, IComponent::iid, ...)`. + */ +struct CreateInstaneIComponent { + using Response = YaComponent&; + + Steinberg::TUID cid; + + template + void serialize(S& s) { + s.container1b(cid); + } +}; + /** * Marker struct to indicate the other side (the plugin) should send a copy of * the configuration. @@ -64,7 +80,8 @@ struct WantsPluginFactory { * encodes the information we request or the operation we want to perform. A * request of type `ControlRequest(T)` should send back a `T::Response`. */ -using ControlRequest = std::variant; +using ControlRequest = + std::variant; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index ab218c5d..2554f0b1 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -50,7 +50,7 @@ instantiated and managed by the host. The model works as follows: ## Plugin Factory -TODO: Explain how we implement `createInstance()` +TODO: Explain how we implement `createInstance()`, based on the todo comment there. ## Safety notes diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/component.cpp new file mode 100644 index 00000000..efe19abf --- /dev/null +++ b/src/common/serialization/vst3/component.cpp @@ -0,0 +1,51 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "component.h" + +YaComponent::YaComponent(){FUNKNOWN_CTOR} + +YaComponent::YaComponent( + Steinberg::IPtr component) { + FUNKNOWN_CTOR + + // `IComponent::getControllerClassId` + component->getControllerClassId(edit_controller_cid); + + // Everything else is handled directly through callbacks to minimize the + // potential for errors +} + +YaComponent::~YaComponent() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(YaComponent) +#pragma GCC diagnostic pop + +tresult PLUGIN_API YaComponent::queryInterface(Steinberg::FIDString _iid, + void** obj) { + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, Steinberg::IPluginBase) + QUERY_INTERFACE(_iid, obj, Steinberg::IPluginBase::iid, + Steinberg::IPluginBase) + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, + Steinberg::Vst::IComponent) + + *obj = nullptr; + return Steinberg::kNoInterface; +} diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h new file mode 100644 index 00000000..e7ba26b7 --- /dev/null +++ b/src/common/serialization/vst3/component.h @@ -0,0 +1,93 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +using Steinberg::TBool, Steinberg::int32, Steinberg::tresult; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IComponent` for serialization purposes. See `README.md` for + * more information on how this works. On the Wine plugin host side this is only + * used for serialization, and on the plugin side have an implementation that + * can send control messages. + * + * We might be able to do some caching here with the buss infos, but since that + * sounds like a huge potential source of errors we'll just do pure callbacks + * for everything other than the edit controller's class ID. + */ +class YaComponent : public Steinberg::Vst::IComponent { + public: + YaComponent(); + + /** + * Create a copy of an existing component. + */ + explicit YaComponent(Steinberg::IPtr component); + + virtual ~YaComponent(); + + DECLARE_FUNKNOWN_METHODS + + // From `IPluginBase` + virtual tresult PLUGIN_API initialize(FUnknown* context) override = 0; + virtual tresult PLUGIN_API terminate() override = 0; + + // From `IComponent` + tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override; + virtual tresult PLUGIN_API + setIoMode(Steinberg::Vst::IoMode mode) override = 0; + virtual int32 PLUGIN_API + getBusCount(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir) override = 0; + virtual tresult PLUGIN_API + getBusInfo(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::BusInfo& bus /*out*/) override = 0; + virtual tresult PLUGIN_API + getRoutingInfo(Steinberg::Vst::RoutingInfo& inInfo, + Steinberg::Vst::RoutingInfo& outInfo /*out*/) override = 0; + virtual tresult PLUGIN_API activateBus(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + TBool state) override = 0; + virtual tresult PLUGIN_API setActive(TBool state) override = 0; + virtual tresult PLUGIN_API + setState(Steinberg::IBStream* state) override = 0; + virtual tresult PLUGIN_API + getState(Steinberg::IBStream* state) override = 0; + + template + void serialize(S& s) { + s.container1b(edit_controller_cid); + } + + private: + /** + * The class ID of this component's corresponding editor controller. + */ + Steinberg::TUID edit_controller_cid; + + // TODO: As explained in a few other places, `YaComponent` objects should be + // assigned a unique ID for identification +}; + +#pragma GCC diagnostic pop diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 7fe9d555..a0e87daf 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -44,8 +44,7 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { YaPluginFactory(); /** - * Create a copy of an existing plugin factory. Depending on the - supported + * Create a copy of an existing plugin factory. Depending on the supported * interface function more or less of this struct will be left empty, and * `iid` will be set accordingly. */ @@ -61,17 +60,10 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { int32 PLUGIN_API countClasses() override; tresult PLUGIN_API getClassInfo(Steinberg::int32 index, Steinberg::PClassInfo* info) override; - // TODO: Figure out how to implement this. Some considerations: - // - We have to sent a control message to the Wine plugin host to ask - // it to create an instance of `_iid`. - // - We then create a `Ya*` implementation of the same interface on - // the plugin side. - // - These two should be wired up so that when the host calls a - // function on it, it should be sent to the instance on the Wine - // plugin host side with the same cid. - // - We should have a list of interfaces we support. When we receive a - // request to create an instance of something we don't support, then - // we should log that and then fail. + /** + * See the implementation in `YaPluginFactoryPluginImpl` for how this is + * handled. + */ virtual tresult PLUGIN_API createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, void** obj) override = 0; @@ -83,6 +75,10 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { // From `IPluginFactory3` tresult PLUGIN_API getClassInfoUnicode(int32 index, Steinberg::PClassInfoW* info) override; + /** + * We'll pass a `IHostApplication` to the Windows VST3 plugin's factory when + * this is called so it can send messages. + */ virtual tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override = 0; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 150cc16d..cd3e71d4 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -51,6 +51,33 @@ void Vst3Bridge::run() { sockets.host_vst_control.receive_messages( std::nullopt, overload{ + [&](const CreateInstaneIComponent& args) + -> CreateInstaneIComponent::Response { + Steinberg::IPtr component = + module->getFactory() + .createInstance(args.cid); + + // TODO: Next steps are: + // - Generate a new unique ID using an atomic size_t and + // fetch-and-add. + // - Add an `std::map` + // to this class and add `component` with the generated + // ID to that. + // - Add that ID to `YaComponent` and set it in the object + // we create here. + // - In case `factory` is a null pointer, allow returning + // `nullopt`. Not sure how that is going to work with + // the deserialization. + if (!component) { + // TODO: Handle + } + + // TODO: Implement `YaComponentHostImpl` and create an instance + // based on `component` + YaComponent* removeme = nullptr; + return *removeme; + }, [&](const WantsPluginFactory&) -> WantsPluginFactory::Response { return *plugin_factory; }}); From ed743e6f22056a8d5c1a537fdca589b51e700edf Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 8 Dec 2020 11:24:05 +0100 Subject: [PATCH 127/456] Bundle associated messages with their interfaces Once we start implementing all of the control messages/callbacks things could quickly get out of hand otherwise. --- src/common/logging/vst3.cpp | 2 +- src/common/logging/vst3.h | 2 +- src/common/serialization/vst3.h | 19 +++---------------- src/common/serialization/vst3/component.h | 16 ++++++++++++++++ src/wine-host/bridges/vst3.cpp | 4 ++-- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index e61e30b7..19e30adb 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -24,7 +24,7 @@ Vst3Logger::Vst3Logger(Logger& generic_logger) : logger(generic_logger) {} -void Vst3Logger::log_request(bool is_host_vst, const CreateInstaneIComponent&) { +void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Create&) { if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { std::ostringstream message; // TODO: Log the cid in some readable way, if possible diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index f4cf1699..48b1d0b3 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -45,7 +45,7 @@ class Vst3Logger { // flag here indicates whether the request was initiated on the host side // (what we'll call a control message). - void log_request(bool is_host_vst, const CreateInstaneIComponent&); + void log_request(bool is_host_vst, const YaComponent::Create&); void log_request(bool is_host_vst, const WantsConfiguration&); void log_request(bool is_host_vst, const WantsPluginFactory&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index ca22b365..74157ec9 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -38,20 +38,8 @@ // TODO: If this approach works, maybe we can also refactor the VST2 handling to // do this since it's a bit safer and easier to read -/** - * Request the Wine plugin host to instantiate a new IComponent to pass through - * a call to `IPluginFactory::createInstance(cid, IComponent::iid, ...)`. - */ -struct CreateInstaneIComponent { - using Response = YaComponent&; - - Steinberg::TUID cid; - - template - void serialize(S& s) { - s.container1b(cid); - } -}; +// All messages for creating objects and calling interfaces on them are defined +// as part of the interfaces and implementations in `vst3/` /** * Marker struct to indicate the other side (the plugin) should send a copy of @@ -80,8 +68,7 @@ struct WantsPluginFactory { * encodes the information we request or the operation we want to perform. A * request of type `ControlRequest(T)` should send back a `T::Response`. */ -using ControlRequest = - std::variant; +using ControlRequest = std::variant; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index e7ba26b7..9a966587 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -35,6 +35,22 @@ using Steinberg::TBool, Steinberg::int32, Steinberg::tresult; */ class YaComponent : public Steinberg::Vst::IComponent { public: + /** + * Request the Wine plugin host to instantiate a new IComponent to pass + * through a call to `IPluginFactory::createInstance(cid, IComponent::iid, + * ...)`. + */ + struct Create { + using Response = YaComponent&; + + Steinberg::TUID cid; + + template + void serialize(S& s) { + s.container1b(cid); + } + }; + YaComponent(); /** diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index cd3e71d4..f62f1068 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -51,8 +51,8 @@ void Vst3Bridge::run() { sockets.host_vst_control.receive_messages( std::nullopt, overload{ - [&](const CreateInstaneIComponent& args) - -> CreateInstaneIComponent::Response { + [&](const YaComponent::Create& args) + -> YaComponent::Create::Response { Steinberg::IPtr component = module->getFactory() .createInstance(args.cid); From f4a5aa91fbc2c479fc5d7193a7fe9630196f226e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 8 Dec 2020 14:21:54 +0100 Subject: [PATCH 128/456] Document the plugin factory approach --- src/common/serialization/vst3/README.md | 32 ++++++++++++++++++++++- src/common/serialization/vst3/component.h | 8 +++++- src/plugin/bridges/vst3-impls.cpp | 27 +------------------ 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 2554f0b1..0757e74e 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -50,7 +50,37 @@ instantiated and managed by the host. The model works as follows: ## Plugin Factory -TODO: Explain how we implement `createInstance()`, based on the todo comment there. +Creating a new instance of an interface using the plugin factory wroks as +follows: + +1. The host calls `createInterface(cid, _iid, obj)` on an IPluginFactory + implementation exposed to the host as described above. +2. We check which interface we support matches the `_iid`. If we don't support + the interface, we'll log a message about it and return that we do not support + the itnerface. +3. If we determine that `_iid` matches `IFoo`, then we'll send a + `YaFoo::Create{cid}` to the Wine plugin host process. +4. The Wine plugin host will then call + `module->getFactory().createInstance(cid)` using the Windows VST3 + plugin's plugin factory to ask it to create an instance of that interface. If + this operation fails and returns a null pointer, we'll send an `std::nullopt` + back to indicate that the instantiation was not successful and we relay this + on the plugin side. +5. Using the newly created instance (which will be returned by the factory as an + `IPtr`), we will instantiate a `YaFoo` object using `YaFooHostImpl`. + This will read all simple data members from the `IFoo` smart pointer just + like described in the above section. The `YaFoo` object will also gen a + unique identifier which we generate on the Wine side using an atomic + fetch-and-add on a counter. This way we can refer to this specific isntance + when doing callbacks. +6. Still on the Wine side of things, the `IPtr` will be moved to an + `std::map>` with that unique identifier we generated + earlier as a key so we can refer to it later. +7. Finally on the plugin side we will create an `YaFooPluginImpl` object that + can send control messages to the Wine plugin host, and then we'll deserialize + the `YaFoo` object we receive into that. +8. A pointer to this `YaFooPluginImpl` then gets returned as the final step of + the initialization process. ## Safety notes diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 9a966587..41f6718a 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -41,6 +41,8 @@ class YaComponent : public Steinberg::Vst::IComponent { * ...)`. */ struct Create { + // TODO: This should be `std::optional`, and we need a way + // to deserialize that into an existing YaComponent. using Response = YaComponent&; Steinberg::TUID cid; @@ -58,7 +60,11 @@ class YaComponent : public Steinberg::Vst::IComponent { */ explicit YaComponent(Steinberg::IPtr component); - virtual ~YaComponent(); + /** + * @remark The plugin side implementation should send a control message to + * clean up the instance on the Wine side in its destructor. + */ + virtual ~YaComponent() = 0; DECLARE_FUNKNOWN_METHODS diff --git a/src/plugin/bridges/vst3-impls.cpp b/src/plugin/bridges/vst3-impls.cpp index 971264c5..e8c78bc6 100644 --- a/src/plugin/bridges/vst3-impls.cpp +++ b/src/plugin/bridges/vst3-impls.cpp @@ -25,32 +25,7 @@ tresult PLUGIN_API YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, void** obj) { - // TODO: This should: - // 1. Check which interface `_iid` belongs to. Let's call this - // interface `T`. If we do not (yet) support it, then we should log - // it and return `Steinberg::kNotImplemented`. - // 2. Send a control message to the Wine plugin host to instantiate a - // new object of type `T` with `cid` and `_iid` as parameters. - // 3. On the Wine side this calls `createIntance()` on the module's - // factory with thsoe same `cid` and `_iid` arguments. - // 4. It this was successful, we'll assign this object a unique number - // (by just doing a fetch-and-add on an atomic size_t) so we can - // refer to it and add it to an `std::map`, where - // `T` is the _original_ object (we don't have to and shouldn't - // wrap it). - // 5. We'll copy over any payload data into a `YaT` **which includes - // that unique identifier we generated** and send it back to the - // plugin. - // 6. On the plugin's side we'll create a new `YaTPluginImpl` inside - // of a VST smart pointer and deserialize the `YaT` we got sent - // into that. We then write that smart pointer into `obj`. We don't - // have to keep track of these objects on the plugin side and the - // reference counting pointers will cause everything to clean up - // after itself. - // 7. Since those `YaTPluginImpl` objects we'll return from - // `createInstance()` will have a reference to `Vst3PluginBridge`, - // they can also send control messages themselves. - + // TODO: Implement as specified in `src/common/serialization/vst3/README.md` if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { // TODO: Instantiate an IComponent as described above return Steinberg::kNotImplemented; From 5eb1fe2de21ee872baa6ceb53a8a6d45e799aa64 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 8 Dec 2020 17:33:51 +0100 Subject: [PATCH 129/456] Redesign how interface instantiation works Transferring some argument pack is much easier than trying to deserialize into an existing object when you also have to transfer more information than just that object. --- meson.build | 2 + src/common/logging/vst3.cpp | 16 +++-- src/common/logging/vst3.h | 3 +- src/common/serialization/vst3/README.md | 6 ++ src/common/serialization/vst3/component.cpp | 29 ++++++--- src/common/serialization/vst3/component.h | 70 +++++++++++++++------ src/wine-host/bridges/vst3.cpp | 16 ++--- 7 files changed, 100 insertions(+), 42 deletions(-) diff --git a/meson.build b/meson.build index cca4136c..783c6428 100644 --- a/meson.build +++ b/meson.build @@ -77,6 +77,7 @@ vst3_plugin_sources = [ 'src/common/communication/common.cpp', 'src/common/logging/common.cpp', 'src/common/logging/vst3.cpp', + 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/configuration.cpp', 'src/common/plugins.cpp', @@ -108,6 +109,7 @@ host_sources = [ if with_vst3 host_sources += [ 'src/common/logging/vst3.cpp', + 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/wine-host/bridges/vst3.cpp', 'src/wine-host/bridges/vst3-impls.cpp', diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 19e30adb..f75010be 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -56,20 +56,26 @@ void Vst3Logger::log_request(bool is_host_vst, const WantsPluginFactory&) { } } -void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { +void Vst3Logger::log_response( + bool is_host_vst, + const std::optional& args) { if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { std::ostringstream message; - message << get_log_prefix(is_host_vst) << " "; + if (args) { + message << get_log_prefix(is_host_vst) << " instance_id << ">"; + } else { + message << get_log_prefix(is_host_vst) << " "; + } log(message.str()); } } -void Vst3Logger::log_response(bool is_host_vst, const YaComponent&) { +void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { std::ostringstream message; - // TODO: Add the instance ID after we implement that - message << get_log_prefix(is_host_vst) << " "; + message << get_log_prefix(is_host_vst) << " "; log(message.str()); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 48b1d0b3..11d75739 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -49,8 +49,9 @@ class Vst3Logger { void log_request(bool is_host_vst, const WantsConfiguration&); void log_request(bool is_host_vst, const WantsPluginFactory&); + void log_response(bool is_host_vst, + const std::optional&); void log_response(bool is_host_vst, const Configuration&); - void log_response(bool is_host_vst, const YaComponent&); void log_response(bool is_host_vst, const YaPluginFactory&); Logger& logger; diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 0757e74e..94d6f9c2 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -3,6 +3,12 @@ TODO: Once this is more fleshed out, move this document to `docs/`, and perhaps replace this readme with a link to that document. +TODO: There are now two approaches in use: the factory takes an interface +pointer for serialization and deserializes into an object directly, and the +component uses an args struct because the alternative involving pointers is just +too unsafe (as we also have to communicate additional payload data). This should +probably be unified into only using the latter appraoch. + The VST3 SDK uses an architecture where every object inherits from an interface, and every interface inherits from `FUnknown` which offers a dynamic casting interface through `queryInterface()`. Every interface gets a unique identifier. diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/component.cpp index efe19abf..9e521646 100644 --- a/src/common/serialization/vst3/component.cpp +++ b/src/common/serialization/vst3/component.cpp @@ -16,14 +16,19 @@ #include "component.h" -YaComponent::YaComponent(){FUNKNOWN_CTOR} - -YaComponent::YaComponent( - Steinberg::IPtr component) { - FUNKNOWN_CTOR - +YaComponent::Arguments::Arguments( + Steinberg::IPtr component, + size_t instance_id) + : instance_id(instance_id) { // `IComponent::getControllerClassId` - component->getControllerClassId(edit_controller_cid); + Steinberg::TUID cid; + if (component->getControllerClassId(cid) == Steinberg::kResultOk) { + edit_controller_cid = std::to_array(cid); + } +} + +YaComponent::YaComponent(const Arguments&& args) : arguments(std::move(args)) { + FUNKNOWN_CTOR // Everything else is handled directly through callbacks to minimize the // potential for errors @@ -49,3 +54,13 @@ tresult PLUGIN_API YaComponent::queryInterface(Steinberg::FIDString _iid, *obj = nullptr; return Steinberg::kNoInterface; } + +tresult PLUGIN_API YaComponent::getControllerClassId(Steinberg::TUID classId) { + if (arguments.edit_controller_cid) { + std::copy(arguments.edit_controller_cid->begin(), + arguments.edit_controller_cid->end(), classId); + return Steinberg::kResultOk; + } else { + return Steinberg::kNotImplemented; + } +} diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 41f6718a..2d285e69 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -16,9 +16,15 @@ #pragma once +#include +#include "src/common/serialization/common.h" + +#include +#include +#include #include -using Steinberg::TBool, Steinberg::int32, Steinberg::tresult; +using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::tresult; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -36,14 +42,44 @@ using Steinberg::TBool, Steinberg::int32, Steinberg::tresult; class YaComponent : public Steinberg::Vst::IComponent { public: /** - * Request the Wine plugin host to instantiate a new IComponent to pass - * through a call to `IPluginFactory::createInstance(cid, IComponent::iid, + * These are the arguments for creating a `YaComponentPluginImpl`. + */ + struct Arguments { + /** + * Read arguments from an existing implementation. + */ + Arguments(Steinberg::IPtr component, + size_t isntance_id); + + /** + * The unique identifier for this specific instance. + */ + native_size_t instance_id; + + /** + * The class ID of this component's corresponding editor controller. You + * can't use C-style array in `std::optional`s. + */ + std::optional>> + edit_controller_cid; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.ext(edit_controller_cid, bitsery::ext::StdOptional{}, + [](S& s, auto& cid) { s.container1b(cid); }); + } + }; + + /** + * Message to request the Wine plugin host to instantiate a new IComponent + * to pass through a call to `IPluginFactory::createInstance(cid, + * IComponent::iid, * ...)`. */ struct Create { - // TODO: This should be `std::optional`, and we need a way - // to deserialize that into an existing YaComponent. - using Response = YaComponent&; + // TODO: Create a `native_tvalue` wrapper, and then also add them here + using Response = std::optional; Steinberg::TUID cid; @@ -53,12 +89,11 @@ class YaComponent : public Steinberg::Vst::IComponent { } }; - YaComponent(); - /** - * Create a copy of an existing component. + * Instantiate this instance with arguments read from another interface + * implementation. */ - explicit YaComponent(Steinberg::IPtr component); + YaComponent(const Arguments&& args); /** * @remark The plugin side implementation should send a control message to @@ -97,19 +132,16 @@ class YaComponent : public Steinberg::Vst::IComponent { virtual tresult PLUGIN_API getState(Steinberg::IBStream* state) override = 0; - template - void serialize(S& s) { - s.container1b(edit_controller_cid); - } - private: - /** - * The class ID of this component's corresponding editor controller. - */ - Steinberg::TUID edit_controller_cid; + Arguments arguments; // TODO: As explained in a few other places, `YaComponent` objects should be // assigned a unique ID for identification }; +template +void serialize(S& s, std::optional& args) { + s.ext(args, bitsery::ext::StdOptional{}); +} + #pragma GCC diagnostic pop diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index f62f1068..791810b8 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -66,17 +66,13 @@ void Vst3Bridge::run() { // ID to that. // - Add that ID to `YaComponent` and set it in the object // we create here. - // - In case `factory` is a null pointer, allow returning - // `nullopt`. Not sure how that is going to work with - // the deserialization. - if (!component) { - // TODO: Handle + if (component) { + // TODO: Generate a unique instance ID + return std::make_optional( + component, 420691337); + } else { + return std::nullopt; } - - // TODO: Implement `YaComponentHostImpl` and create an instance - // based on `component` - YaComponent* removeme = nullptr; - return *removeme; }, [&](const WantsPluginFactory&) -> WantsPluginFactory::Response { return *plugin_factory; From e099255b92634d2d50b54056ec2bf068d078d566 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 8 Dec 2020 17:59:59 +0100 Subject: [PATCH 130/456] Generate a unique ID and store the new component --- src/wine-host/bridges/vst3.cpp | 22 ++++++++++------------ src/wine-host/bridges/vst3.h | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 791810b8..618b41c9 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -56,20 +56,14 @@ void Vst3Bridge::run() { Steinberg::IPtr component = module->getFactory() .createInstance(args.cid); - - // TODO: Next steps are: - // - Generate a new unique ID using an atomic size_t and - // fetch-and-add. - // - Add an `std::map` - // to this class and add `component` with the generated - // ID to that. - // - Add that ID to `YaComponent` and set it in the object - // we create here. if (component) { - // TODO: Generate a unique instance ID + std::lock_guard lock(component_instances_mutex); + + const size_t instance_id = generate_instance_id(); + component_instances[instance_id] = std::move(component); + return std::make_optional( - component, 420691337); + component, instance_id); } else { return std::nullopt; } @@ -78,3 +72,7 @@ void Vst3Bridge::run() { return *plugin_factory; }}); } + +size_t Vst3Bridge::generate_instance_id() { + return current_instance_id.fetch_add(1); +} diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index c5f52d5c..161e030c 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -59,6 +59,13 @@ class Vst3Bridge : public HostBridge { void run() override; private: + /** + * Generate a nique instance identifier using an atomic fetch-and-add. This + * is used to be able to refer to specific instances created for + * `IPluginFactory::createInstance()`. + */ + size_t generate_instance_id(); + /** * The IO context used for event handling so that all events and window * message handling can be performed from a single thread, even when hosting @@ -90,4 +97,22 @@ class Vst3Bridge : public HostBridge { * information during its initialization. */ std::unique_ptr plugin_factory; + + /** + * Used to assign unique identifier to instances created for + * `IPluginFactory::createInstance()`. + * + * @related enerate_instance_id + */ + std::atomic_size_t current_instance_id; + + // Below are managed instances we created for + // `IPluginFactory::createInstance()`. The keys in all of these maps are the + // unique identifiers we generated for them so we can identify specific + // instances. The mutexes are used for operations that insert or remove + // items, and not for regular access. + + std::map> + component_instances; + std::mutex component_instances_mutex; }; From 7828fc7befe8a955ea2dd0615c6a2093a53f5d14 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 8 Dec 2020 18:22:44 +0100 Subject: [PATCH 131/456] Update the VST3 interface instantiation docs --- src/common/serialization/vst3/README.md | 26 +++++++++++-------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 94d6f9c2..f8635e99 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -54,7 +54,7 @@ instantiated and managed by the host. The model works as follows: that implements those functions through yabridge's `Vst3MessageHandler` callback interface. -## Plugin Factory +## Interface Instantiation Creating a new instance of an interface using the plugin factory wroks as follows: @@ -72,20 +72,16 @@ follows: this operation fails and returns a null pointer, we'll send an `std::nullopt` back to indicate that the instantiation was not successful and we relay this on the plugin side. -5. Using the newly created instance (which will be returned by the factory as an - `IPtr`), we will instantiate a `YaFoo` object using `YaFooHostImpl`. - This will read all simple data members from the `IFoo` smart pointer just - like described in the above section. The `YaFoo` object will also gen a - unique identifier which we generate on the Wine side using an atomic - fetch-and-add on a counter. This way we can refer to this specific isntance - when doing callbacks. -6. Still on the Wine side of things, the `IPtr` will be moved to an - `std::map>` with that unique identifier we generated - earlier as a key so we can refer to it later. -7. Finally on the plugin side we will create an `YaFooPluginImpl` object that - can send control messages to the Wine plugin host, and then we'll deserialize - the `YaFoo` object we receive into that. -8. A pointer to this `YaFooPluginImpl` then gets returned as the final step of +5. We will generate a unique instance identifier for the newly generated object + so we can refer to it later. We then serialize that identifier along with + what other static data is available in `IFoo` in a `YaFoo::Arguments` object. +6. We then move `IPtr` to an `std::map>` with that + unique identifier we generated earlier as a key so we can refer to it later + in later function calls. +7. On the plugin side we can now use the `YaFoo::Arguments` object we received + to create a `YaFooPluginImpl` object that can send control messages to the + Wine plugin host. +8. Finally a pointer to this `YaFooPluginImpl` gets returned as the last step of the initialization process. ## Safety notes From 2e6184171c11d48027321f262555431d433aa41f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 8 Dec 2020 23:00:44 +0100 Subject: [PATCH 132/456] Use std::array for serializing UIDs These are easily assignable and we have to convert between char pointers, char arrays and UID objects all the time anyways. --- src/common/serialization/vst3/base.h | 36 +++++++++++++++++++ src/common/serialization/vst3/component.h | 12 ++++--- .../serialization/vst3/plugin-factory.h | 3 +- src/wine-host/bridges/vst3.cpp | 4 ++- 4 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 src/common/serialization/vst3/base.h diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h new file mode 100644 index 00000000..e4836118 --- /dev/null +++ b/src/common/serialization/vst3/base.h @@ -0,0 +1,36 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include +#include + +// Yet Another layer of includes, but these are some VST3-specific typedefs that +// we'll need for all of our interfaces + +using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::tresult; + +/** + * Both `TUID` (`int8_t[16]`) and `FIDString` (`char*`) are hard to work with + * because you can't just copy them. So when serializing/deserializing them + * we'll use `std::array`. + */ +using ArrayUID = std::array< + std::remove_reference_t()[0])>, + std::extent_v>; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 2d285e69..3be2f83e 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -17,14 +17,14 @@ #pragma once #include -#include "src/common/serialization/common.h" #include #include #include #include -using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::tresult; +#include "../common.h" +#include "base.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -38,6 +38,9 @@ using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::tresult; * We might be able to do some caching here with the buss infos, but since that * sounds like a huge potential source of errors we'll just do pure callbacks * for everything other than the edit controller's class ID. + * + * TODO: I think it's expected that components also implement `IAudioProcessor` + * and `IConnectionPoint`. */ class YaComponent : public Steinberg::Vst::IComponent { public: @@ -60,8 +63,7 @@ class YaComponent : public Steinberg::Vst::IComponent { * The class ID of this component's corresponding editor controller. You * can't use C-style array in `std::optional`s. */ - std::optional>> - edit_controller_cid; + std::optional edit_controller_cid; template void serialize(S& s) { @@ -81,7 +83,7 @@ class YaComponent : public Steinberg::Vst::IComponent { // TODO: Create a `native_tvalue` wrapper, and then also add them here using Response = std::optional; - Steinberg::TUID cid; + ArrayUID cid; template void serialize(S& s) { diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index a0e87daf..5815e084 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -24,8 +24,7 @@ #include #include "../../bitsery/ext/vst3.h" - -using Steinberg::int32, Steinberg::tresult; +#include "base.h" // TODO: After implementing one or two more of these, abstract away some of the // nasty bits diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 618b41c9..060cfedf 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -53,9 +53,11 @@ void Vst3Bridge::run() { overload{ [&](const YaComponent::Create& args) -> YaComponent::Create::Response { + Steinberg::TUID cid; + std::copy(args.cid.begin(), args.cid.end(), cid); Steinberg::IPtr component = module->getFactory() - .createInstance(args.cid); + .createInstance(cid); if (component) { std::lock_guard lock(component_instances_mutex); From 92ea15bcb49b35646bc118691ca0640da931b8f4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 8 Dec 2020 23:01:50 +0100 Subject: [PATCH 133/456] Allow interface implementations to send messages --- src/plugin/bridges/vst3.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 1d6d6d69..944f4bb8 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -63,6 +63,17 @@ class Vst3PluginBridge : PluginBridge> { */ Steinberg::IPluginFactory* get_plugin_factory(); + /** + * Send a control message to the Wine plugin host return the response. This + * is a shorthand for `sockets.host_vst_control.send_message` for use in + * VST3 interface implementations. + */ + template + typename T::Response send_message(const T& object) { + return sockets.host_vst_control.send_message( + object, std::pair(logger, true)); + } + /** * The logging facility used for this instance of yabridge. Wraps around * `PluginBridge::generic_logger`. From 9b025052d2ac9b734497961c6058f1a79266a5a0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 8 Dec 2020 23:02:26 +0100 Subject: [PATCH 134/456] Add stubs for an IComponent implementation --- src/plugin/bridges/vst3-impls.cpp | 80 +++++++++++++++++++++++++++++++ src/plugin/bridges/vst3-impls.h | 48 ++++++++++++++++++- 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/src/plugin/bridges/vst3-impls.cpp b/src/plugin/bridges/vst3-impls.cpp index e8c78bc6..9a99b254 100644 --- a/src/plugin/bridges/vst3-impls.cpp +++ b/src/plugin/bridges/vst3-impls.cpp @@ -56,3 +56,83 @@ YaPluginFactoryPluginImpl::setHostContext(Steinberg::FUnknown* /*context*/) { // host. return Steinberg::kNotImplemented; } + +YaComponentPluginImpl::YaComponentPluginImpl(Vst3PluginBridge& bridge, + YaComponent::Arguments&& args) + : YaComponent(std::move(args)), bridge(bridge) {} + +YaComponentPluginImpl::~YaComponentPluginImpl() { + // TODO: Send a control message to destroy the instance on the Wine side +} + +tresult PLUGIN_API +YaComponentPluginImpl::queryInterface(const ::Steinberg::TUID _iid, + void** obj) { + // TODO: Log when this fails on debug level 1, and on debug level 2 also log + // successful queries. This behaviour should be implemented for all + // interfaces. + return YaComponent::queryInterface(_iid, obj); +} + +tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { + // TODO: Implement + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API YaComponentPluginImpl::terminate() { + // TODO: Implement + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +YaComponentPluginImpl::setIoMode(Steinberg::Vst::IoMode mode) { + // TODO: Implement + return Steinberg::kNotImplemented; +} + +int32 PLUGIN_API +YaComponentPluginImpl::getBusCount(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir) { + // TODO: Implement + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +YaComponentPluginImpl::getBusInfo(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::BusInfo& bus /*out*/) { + // TODO: Implement + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API YaComponentPluginImpl::getRoutingInfo( + Steinberg::Vst::RoutingInfo& inInfo, + Steinberg::Vst::RoutingInfo& outInfo /*out*/) { + // TODO: Implement + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +YaComponentPluginImpl::activateBus(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + TBool state) { + // TODO: Implement + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API YaComponentPluginImpl::setActive(TBool state) { + // TODO: Implement + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API YaComponentPluginImpl::setState(Steinberg::IBStream* state) { + // TODO: Implement + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { + // TODO: Implement + return Steinberg::kNotImplemented; +} diff --git a/src/plugin/bridges/vst3-impls.h b/src/plugin/bridges/vst3-impls.h index 39a058f8..303eab67 100644 --- a/src/plugin/bridges/vst3-impls.h +++ b/src/plugin/bridges/vst3-impls.h @@ -20,6 +20,8 @@ // These are implementation of the serialization clases in // `src/common/serialization/vst3/` to provide callback support +// TODO: Split this up in multiple headers. I hoped it might stay small and easy +// to oversee. It won't. class YaPluginFactoryPluginImpl : public YaPluginFactory { public: @@ -28,9 +30,53 @@ class YaPluginFactoryPluginImpl : public YaPluginFactory { tresult PLUGIN_API createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, void** obj) override; - tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override; private: Vst3PluginBridge& bridge; }; + +class YaComponentPluginImpl : public YaComponent { + public: + YaComponentPluginImpl(Vst3PluginBridge& bridge, + YaComponent::Arguments&& args); + + /** + * When the reference count reaches zero and this destructor is called, + * we'll send a request to the Wine plugin host to destroy the corresponding + * object. + */ + ~YaComponentPluginImpl(); + + /** + * We'll override the query interface to log queries for interfaces we do + * not (yet) support. + */ + tresult PLUGIN_API queryInterface(const ::Steinberg::TUID _iid, + void** obj) override; + + tresult PLUGIN_API initialize(FUnknown* context) override; + tresult PLUGIN_API terminate() override; + + tresult PLUGIN_API setIoMode(Steinberg::Vst::IoMode mode) override; + int32 PLUGIN_API getBusCount(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir) override; + tresult PLUGIN_API + getBusInfo(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::BusInfo& bus /*out*/) override; + tresult PLUGIN_API + getRoutingInfo(Steinberg::Vst::RoutingInfo& inInfo, + Steinberg::Vst::RoutingInfo& outInfo /*out*/) override; + tresult PLUGIN_API activateBus(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + TBool state) override; + tresult PLUGIN_API setActive(TBool state) override; + tresult PLUGIN_API setState(Steinberg::IBStream* state) override; + tresult PLUGIN_API getState(Steinberg::IBStream* state) override; + + private: + Vst3PluginBridge& bridge; +}; From 75ed978a1b8c1cee27c9dafac9cd32b04944742f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 8 Dec 2020 23:07:03 +0100 Subject: [PATCH 135/456] Fix pointer dereference after move --- src/wine-host/bridges/vst3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 060cfedf..060c80be 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -65,7 +65,7 @@ void Vst3Bridge::run() { component_instances[instance_id] = std::move(component); return std::make_optional( - component, instance_id); + component_instances[instance_id], instance_id); } else { return std::nullopt; } From 225056bcff771ab7399cb1983a317f326307a381 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 8 Dec 2020 23:02:49 +0100 Subject: [PATCH 136/456] Allow instantiating IComponents --- src/common/serialization/vst3/component.cpp | 2 ++ src/common/serialization/vst3/component.h | 2 ++ src/plugin/bridges/vst3-impls.cpp | 15 ++++++++++++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/component.cpp index 9e521646..1db52fcf 100644 --- a/src/common/serialization/vst3/component.cpp +++ b/src/common/serialization/vst3/component.cpp @@ -16,6 +16,8 @@ #include "component.h" +YaComponent::Arguments::Arguments() {} + YaComponent::Arguments::Arguments( Steinberg::IPtr component, size_t instance_id) diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 3be2f83e..b025a785 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -48,6 +48,8 @@ class YaComponent : public Steinberg::Vst::IComponent { * These are the arguments for creating a `YaComponentPluginImpl`. */ struct Arguments { + Arguments(); + /** * Read arguments from an existing implementation. */ diff --git a/src/plugin/bridges/vst3-impls.cpp b/src/plugin/bridges/vst3-impls.cpp index 9a99b254..fd0d683e 100644 --- a/src/plugin/bridges/vst3-impls.cpp +++ b/src/plugin/bridges/vst3-impls.cpp @@ -25,10 +25,19 @@ tresult PLUGIN_API YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, void** obj) { - // TODO: Implement as specified in `src/common/serialization/vst3/README.md` + // TODO: Do the same thing for other types + ArrayUID cid_array; + std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { - // TODO: Instantiate an IComponent as described above - return Steinberg::kNotImplemented; + std::optional args = + bridge.send_message(YaComponent::Create{.cid = cid_array}); + if (args) { + // I find all of these raw pointers scary + *obj = new YaComponentPluginImpl(bridge, std::move(*args)); + return Steinberg::kResultOk; + } else { + return Steinberg::kNotImplemented; + } } else { // When the host requests an interface we do not (yet) implement, we'll // print a recognizable log message. I don't think they include a safe From 4a056dcd3156795f3716b253a36e9a666ce925c8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 9 Dec 2020 22:11:39 +0100 Subject: [PATCH 137/456] Get rid of the VST3 logging boilerplate --- src/common/logging/vst3.cpp | 61 +++++++++++-------------------------- src/common/logging/vst3.h | 43 +++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 48 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index f75010be..77e9bdec 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -16,79 +16,54 @@ #include "vst3.h" -#include #include "src/common/serialization/vst3.h" // TODO: Reconsider the output format -// TODO: Maybe think of an alterantive that's a little less boilerplaty Vst3Logger::Vst3Logger(Logger& generic_logger) : logger(generic_logger) {} void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Create&) { - if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { - std::ostringstream message; + log_request_base(is_host_vst, [](auto& message) { // TODO: Log the cid in some readable way, if possible - message << get_log_prefix(is_host_vst) - << " >> IPluginFactory::createComponent(cid, IComponent::iid, " + message << "IPluginFactory::createComponent(cid, IComponent::iid, " "&obj)"; - - log(message.str()); - } + }); } void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { - if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { - std::ostringstream message; - message << get_log_prefix(is_host_vst) - << " >> Requesting "; - - log(message.str()); - } + log_request_base(is_host_vst, [](auto& message) { + message << "Requesting "; + }); } void Vst3Logger::log_request(bool is_host_vst, const WantsPluginFactory&) { - if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { - std::ostringstream message; - message << get_log_prefix(is_host_vst) - << " >> Requesting "; - - log(message.str()); - } + log_request_base(is_host_vst, [](auto& message) { + message << "Requesting "; + }); } void Vst3Logger::log_response( bool is_host_vst, const std::optional& args) { - if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { - std::ostringstream message; + log_response_base(is_host_vst, [&](auto& message) { if (args) { - message << get_log_prefix(is_host_vst) << " instance_id << ">"; + message << "instance_id << ">"; } else { - message << get_log_prefix(is_host_vst) << " "; + message << ""; } - - log(message.str()); - } + }); } void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { - if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { - std::ostringstream message; - message << get_log_prefix(is_host_vst) << " "; - - log(message.str()); - } + log_response_base(is_host_vst, + [](auto& message) { message << "= Logger::Verbosity::most_events)) { - std::ostringstream message; - message << get_log_prefix(is_host_vst) << " with " + log_response_base(is_host_vst, [&](auto& message) { + message << " with " << const_cast(factory).countClasses() << " registered classes"; - - log(message.str()); - } + }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 11d75739..529ce3ba 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -16,6 +16,8 @@ #pragma once +#include + #include "../serialization/vst3.h" #include "common.h" @@ -58,11 +60,42 @@ class Vst3Logger { private: /** - * Get the `host -> vst` or `vst -> host` prefix based on the boolean flag - * we pass to every logging function so we don't have to repeat it - * everywhere. + * Log a request with a standard prefix based on the boolean flag we pass to + * every logging function so we don't have to repeat it everywhere. */ - inline std::string get_log_prefix(bool is_host_vst) { - return is_host_vst ? "[host -> vst]" : "[vst -> host]"; + template F> + void log_request_base(bool is_host_vst, F callback) { + if (BOOST_UNLIKELY(logger.verbosity >= + Logger::Verbosity::most_events)) { + std::ostringstream message; + if (is_host_vst) { + message << "[host -> vst] >> "; + } else { + message << "[vst -> host] >> "; + } + + callback(message); + log(message.str()); + } + } + + /** + * Log a response with a standard prefix based on the boolean flag we pass + * to every logging function so we don't have to repeat it everywhere. + */ + template F> + void log_response_base(bool is_host_vst, F callback) { + if (BOOST_UNLIKELY(logger.verbosity >= + Logger::Verbosity::most_events)) { + std::ostringstream message; + if (is_host_vst) { + message << "[host -> vst] "; + } else { + message << "[vst -> host] "; + } + + callback(message); + log(message.str()); + } } }; From aae98d518c33bcc7151e3266233e8e4e5e54acfe Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 10 Dec 2020 19:16:53 +0100 Subject: [PATCH 138/456] Fix the with-winedbg option --- src/plugin/host-process.cpp | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/plugin/host-process.cpp b/src/plugin/host-process.cpp index a130e7fa..2ed9a9bf 100644 --- a/src/plugin/host-process.cpp +++ b/src/plugin/host-process.cpp @@ -93,25 +93,27 @@ IndividualHost::IndividualHost(boost::asio::io_context& io_context, host_path(find_vst_host(plugin_info.native_library_path, plugin_info.plugin_arch, false)), - host(launch_host(host_path, - plugin_type_to_string(host_request.plugin_type), + host(launch_host( + host_path, + plugin_type_to_string(host_request.plugin_type), #ifdef WITH_WINEDBG - host_request.plugin_path.filename(), + plugin_info.windows_plugin_path.filename(), #else - host_request.plugin_path, + host_request.plugin_path, #endif - host_request.endpoint_base_dir, - bp::env = plugin_info.create_host_env(), - bp::std_out = stdout_pipe, - bp::std_err = stderr_pipe + host_request.endpoint_base_dir, + bp::env = plugin_info.create_host_env(), + bp::std_out = stdout_pipe, + bp::std_err = stderr_pipe #ifdef WITH_WINEDBG - , // winedbg has no reliable way to escape spaces, so - // we'll start the process in the plugin's directory - bp::start_dir = plugin_path.parent_path() + , // winedbg has no reliable way to escape spaces, so + // we'll start the process in the plugin's directory + bp::start_dir = plugin_info.windows_plugin_path.parent_path() #endif - )) { + )) { #ifdef WITH_WINEDBG - if (plugin_path.filename().string().find(' ') != std::string::npos) { + if (plugin_info.windows_plugin_path.filename().string().find(' ') != + std::string::npos) { logger.log("Warning: winedbg does not support paths containing spaces"); } #endif @@ -143,7 +145,8 @@ GroupHost::GroupHost(boost::asio::io_context& io_context, true)), sockets(sockets) { #ifdef WITH_WINEDBG - if (plugin_path.string().find(' ') != std::string::npos) { + if (plugin_info.windows_plugin_path.string().find(' ') != + std::string::npos) { logger.log("Warning: winedbg does not support paths containing spaces"); } #endif From 9439a62d94ffc0843591d9b3eae463814ef7f52f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 11 Dec 2020 16:56:46 +0100 Subject: [PATCH 139/456] Fix formatting --- src/plugin/bridges/common.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugin/bridges/common.h b/src/plugin/bridges/common.h index 6df18c44..32f5ea2e 100644 --- a/src/plugin/bridges/common.h +++ b/src/plugin/bridges/common.h @@ -228,8 +228,7 @@ class PluginBridge { if (!plugin_host->running()) { generic_logger.log( "The Wine host process has exited unexpectedly. Check " - "the " - "output above for more information."); + "the output above for more information."); std::terminate(); } From d1d85711f0847008bad82a23c72155f2f1daa57f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 11 Dec 2020 22:37:20 +0100 Subject: [PATCH 140/456] Split up the VST3 class implementations --- meson.build | 3 +- .../component.cpp} | 52 +------------- .../{vst3-impls.h => vst3-impls/component.h} | 20 +----- .../bridges/vst3-impls/plugin-factory.cpp | 69 +++++++++++++++++++ .../bridges/vst3-impls/plugin-factory.h | 32 +++++++++ src/plugin/bridges/vst3.cpp | 2 +- 6 files changed, 106 insertions(+), 72 deletions(-) rename src/plugin/bridges/{vst3-impls.cpp => vst3-impls/component.cpp} (60%) rename src/plugin/bridges/{vst3-impls.h => vst3-impls/component.h} (78%) create mode 100644 src/plugin/bridges/vst3-impls/plugin-factory.cpp create mode 100644 src/plugin/bridges/vst3-impls/plugin-factory.h diff --git a/meson.build b/meson.build index 648911d6..20f455a6 100644 --- a/meson.build +++ b/meson.build @@ -83,7 +83,8 @@ vst3_plugin_sources = [ 'src/common/plugins.cpp', 'src/common/utils.cpp', 'src/plugin/bridges/vst3.cpp', - 'src/plugin/bridges/vst3-impls.cpp', + 'src/plugin/bridges/vst3-impls/component.cpp', + 'src/plugin/bridges/vst3-impls/plugin-factory.cpp', 'src/plugin/host-process.cpp', 'src/plugin/utils.cpp', 'src/plugin/vst3-plugin.cpp', diff --git a/src/plugin/bridges/vst3-impls.cpp b/src/plugin/bridges/vst3-impls/component.cpp similarity index 60% rename from src/plugin/bridges/vst3-impls.cpp rename to src/plugin/bridges/vst3-impls/component.cpp index fd0d683e..1b8b0db1 100644 --- a/src/plugin/bridges/vst3-impls.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -14,57 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "vst3-impls.h" - -#include - -YaPluginFactoryPluginImpl::YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge) - : bridge(bridge) {} - -tresult PLUGIN_API -YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, - Steinberg::FIDString _iid, - void** obj) { - // TODO: Do the same thing for other types - ArrayUID cid_array; - std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); - if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { - std::optional args = - bridge.send_message(YaComponent::Create{.cid = cid_array}); - if (args) { - // I find all of these raw pointers scary - *obj = new YaComponentPluginImpl(bridge, std::move(*args)); - return Steinberg::kResultOk; - } else { - return Steinberg::kNotImplemented; - } - } else { - // When the host requests an interface we do not (yet) implement, we'll - // print a recognizable log message. I don't think they include a safe - // way to convert a `FIDString/char*` into a `FUID`, so this will have - // to do. - char iid_string[128] = ""; - constexpr size_t uid_size = sizeof(Steinberg::TUID); - if (_iid && strnlen(_iid, uid_size + 1) == uid_size) { - Steinberg::FUID iid = Steinberg::FUID::fromTUID( - *reinterpret_cast(&_iid)); - iid.print(iid_string, Steinberg::FUID::UIDPrintStyle::kCLASS_UID); - } - - bridge.logger.log("[Unknown interface] " + std::string(iid_string)); - - return Steinberg::kNotImplemented; - } -} - -tresult PLUGIN_API -YaPluginFactoryPluginImpl::setHostContext(Steinberg::FUnknown* /*context*/) { - // TODO: The docs don't clearly specify what this should be doing, but from - // what I've seen this is only used to pass a `IHostApplication` - // instance. That's used to allow the plugin to create objects in the - // host. - return Steinberg::kNotImplemented; -} +#include "component.h" YaComponentPluginImpl::YaComponentPluginImpl(Vst3PluginBridge& bridge, YaComponent::Arguments&& args) diff --git a/src/plugin/bridges/vst3-impls.h b/src/plugin/bridges/vst3-impls/component.h similarity index 78% rename from src/plugin/bridges/vst3-impls.h rename to src/plugin/bridges/vst3-impls/component.h index 303eab67..a9f34b84 100644 --- a/src/plugin/bridges/vst3-impls.h +++ b/src/plugin/bridges/vst3-impls/component.h @@ -16,25 +16,7 @@ #pragma once -#include "vst3.h" - -// These are implementation of the serialization clases in -// `src/common/serialization/vst3/` to provide callback support -// TODO: Split this up in multiple headers. I hoped it might stay small and easy -// to oversee. It won't. - -class YaPluginFactoryPluginImpl : public YaPluginFactory { - public: - YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge); - - tresult PLUGIN_API createInstance(Steinberg::FIDString cid, - Steinberg::FIDString _iid, - void** obj) override; - tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override; - - private: - Vst3PluginBridge& bridge; -}; +#include "../vst3.h" class YaComponentPluginImpl : public YaComponent { public: diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp new file mode 100644 index 00000000..07ac1c19 --- /dev/null +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -0,0 +1,69 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "plugin-factory.h" + +#include + +#include "component.h" + +YaPluginFactoryPluginImpl::YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge) + : bridge(bridge) {} + +tresult PLUGIN_API +YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, + Steinberg::FIDString _iid, + void** obj) { + // TODO: Do the same thing for other types + ArrayUID cid_array; + std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); + if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { + std::optional args = + bridge.send_message(YaComponent::Create{.cid = cid_array}); + if (args) { + // I find all of these raw pointers scary + *obj = new YaComponentPluginImpl(bridge, std::move(*args)); + return Steinberg::kResultOk; + } else { + return Steinberg::kNotImplemented; + } + } else { + // When the host requests an interface we do not (yet) implement, we'll + // print a recognizable log message. I don't think they include a safe + // way to convert a `FIDString/char*` into a `FUID`, so this will have + // to do. + char iid_string[128] = ""; + constexpr size_t uid_size = sizeof(Steinberg::TUID); + if (_iid && strnlen(_iid, uid_size + 1) == uid_size) { + Steinberg::FUID iid = Steinberg::FUID::fromTUID( + *reinterpret_cast(&_iid)); + iid.print(iid_string, Steinberg::FUID::UIDPrintStyle::kCLASS_UID); + } + + bridge.logger.log("[Unknown interface] " + std::string(iid_string)); + + return Steinberg::kNotImplemented; + } +} + +tresult PLUGIN_API +YaPluginFactoryPluginImpl::setHostContext(Steinberg::FUnknown* /*context*/) { + // TODO: The docs don't clearly specify what this should be doing, but from + // what I've seen this is only used to pass a `IHostApplication` + // instance. That's used to allow the plugin to create objects in the + // host. + return Steinberg::kNotImplemented; +} diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.h b/src/plugin/bridges/vst3-impls/plugin-factory.h new file mode 100644 index 00000000..8d877640 --- /dev/null +++ b/src/plugin/bridges/vst3-impls/plugin-factory.h @@ -0,0 +1,32 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "../vst3.h" + +class YaPluginFactoryPluginImpl : public YaPluginFactory { + public: + YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge); + + tresult PLUGIN_API createInstance(Steinberg::FIDString cid, + Steinberg::FIDString _iid, + void** obj) override; + tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override; + + private: + Vst3PluginBridge& bridge; +}; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index ae2887e5..e7815fa9 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -17,7 +17,7 @@ #include "vst3.h" #include "src/common/serialization/vst3.h" -#include "vst3-impls.h" +#include "vst3-impls/plugin-factory.h" // There are still some design decisions that need some more thought // TODO: Check whether `IPlugView::isPlatformTypeSupported` needs special From cdb9dae2dfec498b9e30d62f07bde9c924d9c2b2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 11 Dec 2020 22:43:12 +0100 Subject: [PATCH 141/456] Rename YaComponent::Arguments to CreateArgs --- src/common/logging/vst3.cpp | 2 +- src/common/logging/vst3.h | 2 +- src/common/serialization/vst3/component.cpp | 6 +++--- src/common/serialization/vst3/component.h | 16 ++++++++-------- src/plugin/bridges/vst3-impls/component.cpp | 2 +- src/plugin/bridges/vst3-impls/component.h | 2 +- src/plugin/bridges/vst3-impls/plugin-factory.cpp | 2 +- src/wine-host/bridges/vst3.cpp | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 77e9bdec..61c67816 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -44,7 +44,7 @@ void Vst3Logger::log_request(bool is_host_vst, const WantsPluginFactory&) { void Vst3Logger::log_response( bool is_host_vst, - const std::optional& args) { + const std::optional& args) { log_response_base(is_host_vst, [&](auto& message) { if (args) { message << "instance_id << ">"; diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 529ce3ba..78b69176 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -52,7 +52,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const WantsPluginFactory&); void log_response(bool is_host_vst, - const std::optional&); + const std::optional&); void log_response(bool is_host_vst, const Configuration&); void log_response(bool is_host_vst, const YaPluginFactory&); diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/component.cpp index 1db52fcf..494b5f18 100644 --- a/src/common/serialization/vst3/component.cpp +++ b/src/common/serialization/vst3/component.cpp @@ -16,9 +16,9 @@ #include "component.h" -YaComponent::Arguments::Arguments() {} +YaComponent::CreateArgs::CreateArgs() {} -YaComponent::Arguments::Arguments( +YaComponent::CreateArgs::CreateArgs( Steinberg::IPtr component, size_t instance_id) : instance_id(instance_id) { @@ -29,7 +29,7 @@ YaComponent::Arguments::Arguments( } } -YaComponent::YaComponent(const Arguments&& args) : arguments(std::move(args)) { +YaComponent::YaComponent(const CreateArgs&& args) : arguments(std::move(args)) { FUNKNOWN_CTOR // Everything else is handled directly through callbacks to minimize the diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index b025a785..f1fb4802 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -47,14 +47,14 @@ class YaComponent : public Steinberg::Vst::IComponent { /** * These are the arguments for creating a `YaComponentPluginImpl`. */ - struct Arguments { - Arguments(); + struct CreateArgs { + CreateArgs(); /** * Read arguments from an existing implementation. */ - Arguments(Steinberg::IPtr component, - size_t isntance_id); + CreateArgs(Steinberg::IPtr component, + size_t isntance_id); /** * The unique identifier for this specific instance. @@ -83,7 +83,7 @@ class YaComponent : public Steinberg::Vst::IComponent { */ struct Create { // TODO: Create a `native_tvalue` wrapper, and then also add them here - using Response = std::optional; + using Response = std::optional; ArrayUID cid; @@ -97,7 +97,7 @@ class YaComponent : public Steinberg::Vst::IComponent { * Instantiate this instance with arguments read from another interface * implementation. */ - YaComponent(const Arguments&& args); + YaComponent(const CreateArgs&& args); /** * @remark The plugin side implementation should send a control message to @@ -137,14 +137,14 @@ class YaComponent : public Steinberg::Vst::IComponent { getState(Steinberg::IBStream* state) override = 0; private: - Arguments arguments; + CreateArgs arguments; // TODO: As explained in a few other places, `YaComponent` objects should be // assigned a unique ID for identification }; template -void serialize(S& s, std::optional& args) { +void serialize(S& s, std::optional& args) { s.ext(args, bitsery::ext::StdOptional{}); } diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 1b8b0db1..52ff91ad 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -17,7 +17,7 @@ #include "component.h" YaComponentPluginImpl::YaComponentPluginImpl(Vst3PluginBridge& bridge, - YaComponent::Arguments&& args) + YaComponent::CreateArgs&& args) : YaComponent(std::move(args)), bridge(bridge) {} YaComponentPluginImpl::~YaComponentPluginImpl() { diff --git a/src/plugin/bridges/vst3-impls/component.h b/src/plugin/bridges/vst3-impls/component.h index a9f34b84..afe2edfe 100644 --- a/src/plugin/bridges/vst3-impls/component.h +++ b/src/plugin/bridges/vst3-impls/component.h @@ -21,7 +21,7 @@ class YaComponentPluginImpl : public YaComponent { public: YaComponentPluginImpl(Vst3PluginBridge& bridge, - YaComponent::Arguments&& args); + YaComponent::CreateArgs&& args); /** * When the reference count reaches zero and this destructor is called, diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 07ac1c19..e77c3191 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -31,7 +31,7 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, ArrayUID cid_array; std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { - std::optional args = + std::optional args = bridge.send_message(YaComponent::Create{.cid = cid_array}); if (args) { // I find all of these raw pointers scary diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 060c80be..1fc887e4 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -64,7 +64,7 @@ void Vst3Bridge::run() { const size_t instance_id = generate_instance_id(); component_instances[instance_id] = std::move(component); - return std::make_optional( + return std::make_optional( component_instances[instance_id], instance_id); } else { return std::nullopt; From 699ddfd2ea7b4ec7896481e6df15218c6166e31b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 11 Dec 2020 22:59:32 +0100 Subject: [PATCH 142/456] Implement YaComponentPluginImpl destructor When the object gets dropped through the reference counting system, the object should also be dropped in the Wine plugin host. --- src/common/logging/vst3.cpp | 12 ++++++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 3 ++- src/common/serialization/vst3/base.h | 9 +++++++++ src/common/serialization/vst3/component.h | 21 +++++++++++++++++---- src/plugin/bridges/vst3-impls/component.cpp | 3 ++- src/wine-host/bridges/vst3.cpp | 7 +++++++ 7 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 61c67816..7807a23c 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -30,6 +30,14 @@ void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Create&) { }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::Destroy& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::~IPluginFactory()"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { log_request_base(is_host_vst, [](auto& message) { message << "Requesting "; @@ -42,6 +50,10 @@ void Vst3Logger::log_request(bool is_host_vst, const WantsPluginFactory&) { }); } +void Vst3Logger::log_response(bool is_host_vst, const Ack&) { + log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); +} + void Vst3Logger::log_response( bool is_host_vst, const std::optional& args) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 78b69176..66a0a14f 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -48,9 +48,11 @@ class Vst3Logger { // (what we'll call a control message). void log_request(bool is_host_vst, const YaComponent::Create&); + void log_request(bool is_host_vst, const YaComponent::Destroy&); void log_request(bool is_host_vst, const WantsConfiguration&); void log_request(bool is_host_vst, const WantsPluginFactory&); + void log_response(bool is_host_vst, const Ack&); void log_response(bool is_host_vst, const std::optional&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 74157ec9..f037a44c 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -68,7 +68,8 @@ struct WantsPluginFactory { * encodes the information we request or the operation we want to perform. A * request of type `ControlRequest(T)` should send back a `T::Response`. */ -using ControlRequest = std::variant; +using ControlRequest = + std::variant; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index e4836118..c9d8a591 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -34,3 +34,12 @@ using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::tresult; using ArrayUID = std::array< std::remove_reference_t()[0])>, std::extent_v>; + +/** + * Empty struct for when we have send a response to some operation without any + * result values. + */ +struct Ack { + template + void serialize(S&) {} +}; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index f1fb4802..c31932de 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -93,6 +93,22 @@ class YaComponent : public Steinberg::Vst::IComponent { } }; + /** + * Message to request the Wine plugin host to destroy the IComponent + * instance with the given instance ID. Sent from the destructor of + * `YaComponentPluginImpl`. + */ + struct Destroy { + using Response = Ack; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + /** * Instantiate this instance with arguments read from another interface * implementation. @@ -136,11 +152,8 @@ class YaComponent : public Steinberg::Vst::IComponent { virtual tresult PLUGIN_API getState(Steinberg::IBStream* state) override = 0; - private: + protected: CreateArgs arguments; - - // TODO: As explained in a few other places, `YaComponent` objects should be - // assigned a unique ID for identification }; template diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 52ff91ad..3b4062fb 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -21,7 +21,8 @@ YaComponentPluginImpl::YaComponentPluginImpl(Vst3PluginBridge& bridge, : YaComponent(std::move(args)), bridge(bridge) {} YaComponentPluginImpl::~YaComponentPluginImpl() { - // TODO: Send a control message to destroy the instance on the Wine side + bridge.send_message( + YaComponent::Destroy{.instance_id = arguments.instance_id}); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 1fc887e4..dd34e711 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -70,6 +70,13 @@ void Vst3Bridge::run() { return std::nullopt; } }, + [&](const YaComponent::Destroy& request) + -> YaComponent::Destroy::Response { + std::lock_guard lock(component_instances_mutex); + component_instances.erase(request.instance_id); + + return Ack{}; + }, [&](const WantsPluginFactory&) -> WantsPluginFactory::Response { return *plugin_factory; }}); From 91a47a466ca822bc2385a7aa8d7412686dad5cf1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 11 Dec 2020 23:26:02 +0100 Subject: [PATCH 143/456] Create a universal wrapper around tvalue --- meson.build | 2 + src/common/serialization/vst3/base.cpp | 91 ++++++++++++++++++++++++++ src/common/serialization/vst3/base.h | 43 ++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 src/common/serialization/vst3/base.cpp diff --git a/meson.build b/meson.build index 20f455a6..01bb3857 100644 --- a/meson.build +++ b/meson.build @@ -77,6 +77,7 @@ vst3_plugin_sources = [ 'src/common/communication/common.cpp', 'src/common/logging/common.cpp', 'src/common/logging/vst3.cpp', + 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/configuration.cpp', @@ -110,6 +111,7 @@ host_sources = [ if with_vst3 host_sources += [ 'src/common/logging/vst3.cpp', + 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/wine-host/bridges/vst3.cpp', diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp new file mode 100644 index 00000000..afbdceff --- /dev/null +++ b/src/common/serialization/vst3/base.cpp @@ -0,0 +1,91 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "base.h" + +UniversalTResult::UniversalTResult(tresult native_result) + : universal_result(to_universal_result(native_result)) { + // +} + +tresult UniversalTResult::native() const { + static_assert(Steinberg::kResultOk == Steinberg::kResultTrue); + switch (universal_result) { + case Value::kNoInterface: + return Steinberg::kNoInterface; + break; + case Value::kResultOk: + return Steinberg::kResultOk; + break; + case Value::kResultFalse: + return Steinberg::kResultFalse; + break; + case Value::kInvalidArgument: + return Steinberg::kInvalidArgument; + break; + case Value::kNotImplemented: + return Steinberg::kNotImplemented; + break; + case Value::kInternalError: + return Steinberg::kInternalError; + break; + case Value::kNotInitialized: + return Steinberg::kNotInitialized; + break; + case Value::kOutOfMemory: + return Steinberg::kOutOfMemory; + break; + default: + // Shouldn't be happening + return Steinberg::kInvalidArgument; + break; + } +} + +UniversalTResult::Value UniversalTResult::to_universal_result( + tresult native_result) { + static_assert(Steinberg::kResultOk == Steinberg::kResultTrue); + switch (native_result) { + case Steinberg::kNoInterface: + return Value::kNoInterface; + break; + case Steinberg::kResultOk: + return Value::kResultOk; + break; + case Steinberg::kResultFalse: + return Value::kResultFalse; + break; + case Steinberg::kInvalidArgument: + return Value::kInvalidArgument; + break; + case Steinberg::kNotImplemented: + return Value::kNotImplemented; + break; + case Steinberg::kInternalError: + return Value::kInternalError; + break; + case Steinberg::kNotInitialized: + return Value::kNotInitialized; + break; + case Steinberg::kOutOfMemory: + return Value::kOutOfMemory; + break; + default: + // Shouldn't be happening + return Value::kInvalidArgument; + break; + } +} diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index c9d8a591..2dd19f30 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -43,3 +43,46 @@ struct Ack { template void serialize(S&) {} }; + +/** + * A wrapper around `Steinberg::tresult` that we can safely share between the + * native plugin and the Wine process. Depending on the platform and on whether + * or not the VST3 SDK is compiled to be COM compatible, the result codes may + * have three different values for the same meaning. + */ +class UniversalTResult { + public: + UniversalTResult(tresult native_result); + + /** + * Get the native equivalent for the wrapped `tresult` value. + */ + tresult native() const; + + template + void serialize(S& s) { + s.value4b(universal_result); + } + + private: + /** + * These are the non-COM compatible values copied from + * ` The actual values h ere don't matter + * but hopefully the compiler can be a bit smarter about it this way. + */ + enum class Value { + kNoInterface = -1, + kResultOk, + kResultTrue = kResultOk, + kResultFalse, + kInvalidArgument, + kNotImplemented, + kInternalError, + kNotInitialized, + kOutOfMemory + }; + + static Value to_universal_result(tresult native_result); + + Value universal_result; +}; From e24cecc6d796d5498e3af57182009f8deb24a333 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 11 Dec 2020 23:54:30 +0100 Subject: [PATCH 144/456] Use the univeral tresult in IComponent creation --- src/common/logging/vst3.cpp | 17 +++++++++++------ src/common/logging/vst3.h | 5 +++-- src/common/serialization/vst3/base.cpp | 6 +++--- src/common/serialization/vst3/base.h | 9 +++++++++ src/common/serialization/vst3/component.h | 17 ++++++++++------- .../bridges/vst3-impls/plugin-factory.cpp | 18 ++++++++++-------- src/wine-host/bridges/vst3.cpp | 5 +++-- 7 files changed, 49 insertions(+), 28 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 7807a23c..299bf473 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -56,13 +56,18 @@ void Vst3Logger::log_response(bool is_host_vst, const Ack&) { void Vst3Logger::log_response( bool is_host_vst, - const std::optional& args) { + const std::variant& result) { log_response_base(is_host_vst, [&](auto& message) { - if (args) { - message << "instance_id << ">"; - } else { - message << ""; - } + std::visit(overload{[&](const YaComponent::CreateArgs& args) { + message << ""; + }, + [&](const UniversalTResult& code) { + // TODO: Add a `UniversalTResult::string()` for + // the human readable representation + message << code.native(); + }}, + result); }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 66a0a14f..f958d0b6 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -53,8 +53,9 @@ class Vst3Logger { void log_request(bool is_host_vst, const WantsPluginFactory&); void log_response(bool is_host_vst, const Ack&); - void log_response(bool is_host_vst, - const std::optional&); + void log_response( + bool is_host_vst, + const std::variant&); void log_response(bool is_host_vst, const Configuration&); void log_response(bool is_host_vst, const YaPluginFactory&); diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index afbdceff..218ce166 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -16,10 +16,10 @@ #include "base.h" +UniversalTResult::UniversalTResult() : universal_result(Value::kResultFalse) {} + UniversalTResult::UniversalTResult(tresult native_result) - : universal_result(to_universal_result(native_result)) { - // -} + : universal_result(to_universal_result(native_result)) {} tresult UniversalTResult::native() const { static_assert(Steinberg::kResultOk == Steinberg::kResultTrue); diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 2dd19f30..4221da61 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -52,6 +52,15 @@ struct Ack { */ class UniversalTResult { public: + /** + * The default constructor will initialize the value to `kResutlFalse` and + * should only ever be used by bitsery in the serialization process. + */ + UniversalTResult(); + + /** + * Convert a native tresult into a univeral one. + */ UniversalTResult(tresult native_result); /** diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index c31932de..ed9b9781 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -17,9 +17,11 @@ #pragma once #include +#include #include #include +#include #include #include @@ -82,8 +84,7 @@ class YaComponent : public Steinberg::Vst::IComponent { * ...)`. */ struct Create { - // TODO: Create a `native_tvalue` wrapper, and then also add them here - using Response = std::optional; + using Response = std::variant; ArrayUID cid; @@ -156,9 +157,11 @@ class YaComponent : public Steinberg::Vst::IComponent { CreateArgs arguments; }; -template -void serialize(S& s, std::optional& args) { - s.ext(args, bitsery::ext::StdOptional{}); -} - #pragma GCC diagnostic pop + +template +void serialize( + S& s, + std::variant& result) { + s.ext(result, bitsery::ext::StdVariant{}); +} diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index e77c3191..fad34cb4 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -31,15 +31,17 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, ArrayUID cid_array; std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { - std::optional args = + std::variant result = bridge.send_message(YaComponent::Create{.cid = cid_array}); - if (args) { - // I find all of these raw pointers scary - *obj = new YaComponentPluginImpl(bridge, std::move(*args)); - return Steinberg::kResultOk; - } else { - return Steinberg::kNotImplemented; - } + return std::visit( + overload{ + [&](YaComponent::CreateArgs&& args) -> tresult { + // I find all of these raw pointers scary + *obj = new YaComponentPluginImpl(bridge, std::move(args)); + return Steinberg::kResultOk; + }, + [&](const UniversalTResult& code) { return code.native(); }}, + std::move(result)); } else { // When the host requests an interface we do not (yet) implement, we'll // print a recognizable log message. I don't think they include a safe diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index dd34e711..01d86b4a 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -64,10 +64,11 @@ void Vst3Bridge::run() { const size_t instance_id = generate_instance_id(); component_instances[instance_id] = std::move(component); - return std::make_optional( + return YaComponent::CreateArgs( component_instances[instance_id], instance_id); } else { - return std::nullopt; + // The actual result is lost here + return UniversalTResult(Steinberg::kNotImplemented); } }, [&](const YaComponent::Destroy& request) From f526ae3e396ad053a7eae374268d65d5e8f26ba8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 11 Dec 2020 23:57:10 +0100 Subject: [PATCH 145/456] Add a string representation for universal tresult --- src/common/logging/vst3.cpp | 4 +--- src/common/serialization/vst3/base.cpp | 33 ++++++++++++++++++++++++++ src/common/serialization/vst3/base.h | 6 +++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 299bf473..4c51ce5c 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -63,9 +63,7 @@ void Vst3Logger::log_response( << ">"; }, [&](const UniversalTResult& code) { - // TODO: Add a `UniversalTResult::string()` for - // the human readable representation - message << code.native(); + message << code.string(); }}, result); }); diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index 218ce166..24ef9618 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -55,6 +55,39 @@ tresult UniversalTResult::native() const { } } +std::string UniversalTResult::string() const { + static_assert(Steinberg::kResultOk == Steinberg::kResultTrue); + switch (universal_result) { + case Value::kNoInterface: + return "kNoInterface"; + break; + case Value::kResultOk: + return "kResultOk"; + break; + case Value::kResultFalse: + return "kResultFalse"; + break; + case Value::kInvalidArgument: + return "kInvalidArgument"; + break; + case Value::kNotImplemented: + return "kNotImplemented"; + break; + case Value::kInternalError: + return "kInternalError"; + break; + case Value::kNotInitialized: + return "kNotInitialized"; + break; + case Value::kOutOfMemory: + return "kOutOfMemory"; + break; + default: + return ""; + break; + } +} + UniversalTResult::Value UniversalTResult::to_universal_result( tresult native_result) { static_assert(Steinberg::kResultOk == Steinberg::kResultTrue); diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 4221da61..f1e4a5fc 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include @@ -68,6 +69,11 @@ class UniversalTResult { */ tresult native() const; + /** + * Get the original name for the result, e.g. `kResultOk`. + */ + std::string string() const; + template void serialize(S& s) { s.value4b(universal_result); From 0214221c3a10f2ccb3fca34f505c4453246d6a4f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 12 Dec 2020 13:34:30 +0100 Subject: [PATCH 146/456] Rename handle_plugin_dispatch to *_run Since run() is now the general `HostBridge()` function to listen for incoming events. --- src/wine-host/bridges/group.cpp | 4 ++-- src/wine-host/bridges/group.h | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index 65f13c8b..a544a827 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -111,7 +111,7 @@ GroupBridge::~GroupBridge() { stdio_context.stop(); } -void GroupBridge::handle_plugin_dispatch(size_t plugin_id, HostBridge* bridge) { +void GroupBridge::handle_plugin_run(size_t plugin_id, HostBridge* bridge) { // Blocks this thread until the plugin shuts down bridge->run(); logger.log("'" + bridge->plugin_path.string() + "' has exited"); @@ -236,7 +236,7 @@ void GroupBridge::accept_requests() { const size_t plugin_id = next_plugin_id.fetch_add(1); active_plugins[plugin_id] = std::pair( Win32Thread([this, plugin_id, plugin_ptr = bridge.get()]() { - handle_plugin_dispatch(plugin_id, plugin_ptr); + handle_plugin_run(plugin_id, plugin_ptr); }), std::move(bridge)); } catch (const std::runtime_error& error) { diff --git a/src/wine-host/bridges/group.h b/src/wine-host/bridges/group.h index f1837022..47a1c759 100644 --- a/src/wine-host/bridges/group.h +++ b/src/wine-host/bridges/group.h @@ -148,7 +148,7 @@ class GroupBridge { * then the process will never exit on its own. This should not happen * though. */ - void handle_plugin_dispatch(size_t plugin_id, HostBridge* bridge); + void handle_plugin_run(size_t plugin_id, HostBridge* bridge); /** * Listen for new requests to spawn plugins within this process and handle @@ -164,14 +164,14 @@ class GroupBridge { * the yabridge instance can tell if the plugin crashed during * initialization, and it will then try to initialize the plugin. After * intialization the plugin handling will be handed over to a new thread - * running `handle_plugin_dispatch()`. Because of the way the Win32 API - * works, all plugins have to be initialized from the same thread, and all - * event handling and message loop interaction also has to be done from that + * running `handle_plugin_run()`. Because of the way the Win32 API works, + * all plugins have to be initialized from the same thread, and all event + * handling and message loop interaction also has to be done from that * thread, which is why we initialize the plugin here and use the * `handle_dispatch()` function to run events within the same * `main_context`. * - * @see handle_plugin_dispatch + * @see handle_plugin_run */ void accept_requests(); @@ -263,7 +263,7 @@ class GroupBridge { std::atomic_size_t next_plugin_id; /** * A mutex to prevent two threads from simultaneously accessing the plugins - * map, and also to prevent `handle_plugin_dispatch()` from terminating the + * map, and also to prevent `handle_plugin_run()` from terminating the * process because it thinks there are no active plugins left just as a new * plugin is being spawned. */ @@ -274,7 +274,7 @@ class GroupBridge { * scanning without having to start a new group host process for each * plugin. * - * @see handle_plugin_dispatch + * @see handle_plugin_run */ boost::asio::steady_timer shutdown_timer; /** From f5a90eb892fdb08c6623dbeea99344bf2e50303f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 12 Dec 2020 14:59:59 +0100 Subject: [PATCH 147/456] Fix typo in YaComponent destructor message --- src/common/logging/vst3.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 4c51ce5c..299043c2 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -33,8 +33,8 @@ void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Create&) { void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Destroy& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::~IPluginFactory()"; + message << "::~IComponent()"; }); } From d80ba10f066d096d07b49a193a79f1571ff90654 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 12 Dec 2020 16:04:35 +0100 Subject: [PATCH 148/456] Interleave structs with their related functions Might make it a bit more organized when this is fully implemented. --- src/common/serialization/vst3/component.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index ed9b9781..0c7e7ade 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -94,6 +94,12 @@ class YaComponent : public Steinberg::Vst::IComponent { } }; + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaComponent(const CreateArgs&& args); + /** * Message to request the Wine plugin host to destroy the IComponent * instance with the given instance ID. Sent from the destructor of @@ -110,12 +116,6 @@ class YaComponent : public Steinberg::Vst::IComponent { } }; - /** - * Instantiate this instance with arguments read from another interface - * implementation. - */ - YaComponent(const CreateArgs&& args); - /** * @remark The plugin side implementation should send a control message to * clean up the instance on the Wine side in its destructor. From 68084bc555d0e9718da79003cccdbeb1cb4448da Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 12 Dec 2020 16:11:48 +0100 Subject: [PATCH 149/456] Implement IComponent::terminate() --- src/common/logging/vst3.cpp | 7 +++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 6 ++++-- src/common/serialization/vst3/component.h | 18 +++++++++++++++++- src/plugin/bridges/vst3-impls/component.cpp | 6 ++++-- src/wine-host/bridges/vst3.cpp | 4 ++++ 6 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 299043c2..1fa0a09a 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -38,6 +38,13 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::Terminate& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::terminate()"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { log_request_base(is_host_vst, [](auto& message) { message << "Requesting "; diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index f958d0b6..a4dfe316 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -49,6 +49,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::Create&); void log_request(bool is_host_vst, const YaComponent::Destroy&); + void log_request(bool is_host_vst, const YaComponent::Terminate&); void log_request(bool is_host_vst, const WantsConfiguration&); void log_request(bool is_host_vst, const WantsPluginFactory&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index f037a44c..8c34e6fa 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -68,8 +68,10 @@ struct WantsPluginFactory { * encodes the information we request or the operation we want to perform. A * request of type `ControlRequest(T)` should send back a `T::Response`. */ -using ControlRequest = - std::variant; +using ControlRequest = std::variant; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 0c7e7ade..9d6d9923 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -79,7 +79,7 @@ class YaComponent : public Steinberg::Vst::IComponent { /** * Message to request the Wine plugin host to instantiate a new IComponent - * to pass through a call to `IPluginFactory::createInstance(cid, + * to pass through a call to `IComponent::createInstance(cid, * IComponent::iid, * ...)`. */ @@ -126,6 +126,22 @@ class YaComponent : public Steinberg::Vst::IComponent { // From `IPluginBase` virtual tresult PLUGIN_API initialize(FUnknown* context) override = 0; + + /** + * Message to pass through a call to `IComponent::terminate()` to the Wine + * plugin host. + */ + struct Terminate { + using Response = UniversalTResult; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + virtual tresult PLUGIN_API terminate() override = 0; // From `IComponent` diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 3b4062fb..3d035a03 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -40,8 +40,10 @@ tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { } tresult PLUGIN_API YaComponentPluginImpl::terminate() { - // TODO: Implement - return Steinberg::kNotImplemented; + return bridge + .send_message( + YaComponent::Terminate{.instance_id = arguments.instance_id}) + .native(); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 01d86b4a..0e321644 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -78,6 +78,10 @@ void Vst3Bridge::run() { return Ack{}; }, + [&](const YaComponent::Terminate& request) + -> YaComponent::Terminate::Response { + return component_instances[request.instance_id]->terminate(); + }, [&](const WantsPluginFactory&) -> WantsPluginFactory::Response { return *plugin_factory; }}); From 1088483f15b6f4e4052689dd6d96e383f068a61a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 12 Dec 2020 16:15:35 +0100 Subject: [PATCH 150/456] Mention destructors in VST3 implementation docs --- src/common/serialization/vst3/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index f8635e99..7056c51b 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -53,6 +53,11 @@ instantiated and managed by the host. The model works as follows: would be on the side of the native plugin) should then provide a `YaFoo{Plugin,Host}Impl` that implements those functions through yabridge's `Vst3MessageHandler` callback interface. +7. If the `IFoo` has side effects and thus needs a corresonding 'real' isntance + on the other side to communicate to, then `YaFoo{Plugin,Host}Impl` should + implement a destructor that destroys the 'real' object when `YaFoo` proxy + gets destroyed. See [interface instantiation](#interface-instantiation) for + more information. ## Interface Instantiation @@ -65,7 +70,7 @@ follows: the interface, we'll log a message about it and return that we do not support the itnerface. 3. If we determine that `_iid` matches `IFoo`, then we'll send a - `YaFoo::Create{cid}` to the Wine plugin host process. + `YaFoo::Construct{cid}` to the Wine plugin host process. 4. The Wine plugin host will then call `module->getFactory().createInstance(cid)` using the Windows VST3 plugin's plugin factory to ask it to create an instance of that interface. If From f637e6ad1867f6641cca5ef53143834f808badcd Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 12 Dec 2020 16:16:18 +0100 Subject: [PATCH 151/456] Rename Create/Destroy to Construct/Destruct This is less likely to clash with names used by interfaces and it's a bit clearer what's going on (since they are basically proxies for constructors and destructors). --- src/common/logging/vst3.cpp | 8 ++++---- src/common/logging/vst3.h | 6 +++--- src/common/serialization/vst3.h | 4 ++-- src/common/serialization/vst3/component.cpp | 6 +++--- src/common/serialization/vst3/component.h | 20 +++++++++---------- src/plugin/bridges/vst3-impls/component.cpp | 4 ++-- src/plugin/bridges/vst3-impls/component.h | 2 +- .../bridges/vst3-impls/plugin-factory.cpp | 6 +++--- src/wine-host/bridges/vst3.cpp | 10 +++++----- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 1fa0a09a..0c809c80 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -22,7 +22,7 @@ Vst3Logger::Vst3Logger(Logger& generic_logger) : logger(generic_logger) {} -void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Create&) { +void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Construct&) { log_request_base(is_host_vst, [](auto& message) { // TODO: Log the cid in some readable way, if possible message << "IPluginFactory::createComponent(cid, IComponent::iid, " @@ -31,7 +31,7 @@ void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Create&) { } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::Destroy& request) { + const YaComponent::Destruct& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::~IComponent()"; @@ -63,9 +63,9 @@ void Vst3Logger::log_response(bool is_host_vst, const Ack&) { void Vst3Logger::log_response( bool is_host_vst, - const std::variant& result) { + const std::variant& result) { log_response_base(is_host_vst, [&](auto& message) { - std::visit(overload{[&](const YaComponent::CreateArgs& args) { + std::visit(overload{[&](const YaComponent::ConstructArgs& args) { message << ""; }, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index a4dfe316..cac56886 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -47,8 +47,8 @@ class Vst3Logger { // flag here indicates whether the request was initiated on the host side // (what we'll call a control message). - void log_request(bool is_host_vst, const YaComponent::Create&); - void log_request(bool is_host_vst, const YaComponent::Destroy&); + void log_request(bool is_host_vst, const YaComponent::Construct&); + void log_request(bool is_host_vst, const YaComponent::Destruct&); void log_request(bool is_host_vst, const YaComponent::Terminate&); void log_request(bool is_host_vst, const WantsConfiguration&); void log_request(bool is_host_vst, const WantsPluginFactory&); @@ -56,7 +56,7 @@ class Vst3Logger { void log_response(bool is_host_vst, const Ack&); void log_response( bool is_host_vst, - const std::variant&); + const std::variant&); void log_response(bool is_host_vst, const Configuration&); void log_response(bool is_host_vst, const YaPluginFactory&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 8c34e6fa..5dde5922 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -68,8 +68,8 @@ struct WantsPluginFactory { * encodes the information we request or the operation we want to perform. A * request of type `ControlRequest(T)` should send back a `T::Response`. */ -using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/component.cpp index 494b5f18..974743a4 100644 --- a/src/common/serialization/vst3/component.cpp +++ b/src/common/serialization/vst3/component.cpp @@ -16,9 +16,9 @@ #include "component.h" -YaComponent::CreateArgs::CreateArgs() {} +YaComponent::ConstructArgs::ConstructArgs() {} -YaComponent::CreateArgs::CreateArgs( +YaComponent::ConstructArgs::ConstructArgs( Steinberg::IPtr component, size_t instance_id) : instance_id(instance_id) { @@ -29,7 +29,7 @@ YaComponent::CreateArgs::CreateArgs( } } -YaComponent::YaComponent(const CreateArgs&& args) : arguments(std::move(args)) { +YaComponent::YaComponent(const ConstructArgs&& args) : arguments(std::move(args)) { FUNKNOWN_CTOR // Everything else is handled directly through callbacks to minimize the diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 9d6d9923..1066b577 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -49,14 +49,14 @@ class YaComponent : public Steinberg::Vst::IComponent { /** * These are the arguments for creating a `YaComponentPluginImpl`. */ - struct CreateArgs { - CreateArgs(); + struct ConstructArgs { + ConstructArgs(); /** * Read arguments from an existing implementation. */ - CreateArgs(Steinberg::IPtr component, - size_t isntance_id); + ConstructArgs(Steinberg::IPtr component, + size_t isntance_id); /** * The unique identifier for this specific instance. @@ -83,8 +83,8 @@ class YaComponent : public Steinberg::Vst::IComponent { * IComponent::iid, * ...)`. */ - struct Create { - using Response = std::variant; + struct Construct { + using Response = std::variant; ArrayUID cid; @@ -98,14 +98,14 @@ class YaComponent : public Steinberg::Vst::IComponent { * Instantiate this instance with arguments read from another interface * implementation. */ - YaComponent(const CreateArgs&& args); + YaComponent(const ConstructArgs&& args); /** * Message to request the Wine plugin host to destroy the IComponent * instance with the given instance ID. Sent from the destructor of * `YaComponentPluginImpl`. */ - struct Destroy { + struct Destruct { using Response = Ack; native_size_t instance_id; @@ -170,7 +170,7 @@ class YaComponent : public Steinberg::Vst::IComponent { getState(Steinberg::IBStream* state) override = 0; protected: - CreateArgs arguments; + ConstructArgs arguments; }; #pragma GCC diagnostic pop @@ -178,6 +178,6 @@ class YaComponent : public Steinberg::Vst::IComponent { template void serialize( S& s, - std::variant& result) { + std::variant& result) { s.ext(result, bitsery::ext::StdVariant{}); } diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 3d035a03..4978b9b5 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -17,12 +17,12 @@ #include "component.h" YaComponentPluginImpl::YaComponentPluginImpl(Vst3PluginBridge& bridge, - YaComponent::CreateArgs&& args) + YaComponent::ConstructArgs&& args) : YaComponent(std::move(args)), bridge(bridge) {} YaComponentPluginImpl::~YaComponentPluginImpl() { bridge.send_message( - YaComponent::Destroy{.instance_id = arguments.instance_id}); + YaComponent::Destruct{.instance_id = arguments.instance_id}); } tresult PLUGIN_API diff --git a/src/plugin/bridges/vst3-impls/component.h b/src/plugin/bridges/vst3-impls/component.h index afe2edfe..dddd1b88 100644 --- a/src/plugin/bridges/vst3-impls/component.h +++ b/src/plugin/bridges/vst3-impls/component.h @@ -21,7 +21,7 @@ class YaComponentPluginImpl : public YaComponent { public: YaComponentPluginImpl(Vst3PluginBridge& bridge, - YaComponent::CreateArgs&& args); + YaComponent::ConstructArgs&& args); /** * When the reference count reaches zero and this destructor is called, diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index fad34cb4..28c77be8 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -31,11 +31,11 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, ArrayUID cid_array; std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { - std::variant result = - bridge.send_message(YaComponent::Create{.cid = cid_array}); + std::variant result = + bridge.send_message(YaComponent::Construct{.cid = cid_array}); return std::visit( overload{ - [&](YaComponent::CreateArgs&& args) -> tresult { + [&](YaComponent::ConstructArgs&& args) -> tresult { // I find all of these raw pointers scary *obj = new YaComponentPluginImpl(bridge, std::move(args)); return Steinberg::kResultOk; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 0e321644..8fcc2318 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -51,8 +51,8 @@ void Vst3Bridge::run() { sockets.host_vst_control.receive_messages( std::nullopt, overload{ - [&](const YaComponent::Create& args) - -> YaComponent::Create::Response { + [&](const YaComponent::Construct& args) + -> YaComponent::Construct::Response { Steinberg::TUID cid; std::copy(args.cid.begin(), args.cid.end(), cid); Steinberg::IPtr component = @@ -64,15 +64,15 @@ void Vst3Bridge::run() { const size_t instance_id = generate_instance_id(); component_instances[instance_id] = std::move(component); - return YaComponent::CreateArgs( + return YaComponent::ConstructArgs( component_instances[instance_id], instance_id); } else { // The actual result is lost here return UniversalTResult(Steinberg::kNotImplemented); } }, - [&](const YaComponent::Destroy& request) - -> YaComponent::Destroy::Response { + [&](const YaComponent::Destruct& request) + -> YaComponent::Destruct::Response { std::lock_guard lock(component_instances_mutex); component_instances.erase(request.instance_id); From efc840a51afa44c703b73d4e7135c7b0044efa80 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 12 Dec 2020 16:53:44 +0100 Subject: [PATCH 152/456] Log unknown interfaces in IComponent::initialize() --- src/common/serialization/vst3/component.h | 2 +- src/plugin/bridges/vst3-impls/component.cpp | 22 +++++++++++++++++++ src/plugin/bridges/vst3-impls/component.h | 9 ++++++++ .../bridges/vst3-impls/plugin-factory.cpp | 4 +++- 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 1066b577..6068aaca 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -128,7 +128,7 @@ class YaComponent : public Steinberg::Vst::IComponent { virtual tresult PLUGIN_API initialize(FUnknown* context) override = 0; /** - * Message to pass through a call to `IComponent::terminate()` to the Wine + * Message to pass through a call to `IPluginBase::terminate()` to the Wine * plugin host. */ struct Terminate { diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 4978b9b5..bf209c44 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -35,6 +35,28 @@ YaComponentPluginImpl::queryInterface(const ::Steinberg::TUID _iid, } tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { + // This `context` will likely be an `IHostApplication`. If it is, we will + // store it here, and we'll proxy through all calls to it made from the Wine + // side. Otherwise we'll still call `IPluginBase::initialize()` but with a + // null pointer instead. + host_application_context = context; + if (host_application_context) { + // TODO: Init with `YaHostApplication` + } else { + context->iid; + + char iid_string[128] = ""; + if (context) { + context->iid.print(iid_string, + Steinberg::FUID::UIDPrintStyle::kCLASS_UID); + } + + bridge.logger.log("[Unknown interface] In IPluginBase::initialize(): " + + std::string(iid_string)); + + // TODO: Init with null pointer + } + // TODO: Implement return Steinberg::kNotImplemented; } diff --git a/src/plugin/bridges/vst3-impls/component.h b/src/plugin/bridges/vst3-impls/component.h index dddd1b88..2b53178f 100644 --- a/src/plugin/bridges/vst3-impls/component.h +++ b/src/plugin/bridges/vst3-impls/component.h @@ -16,6 +16,8 @@ #pragma once +#include + #include "../vst3.h" class YaComponentPluginImpl : public YaComponent { @@ -61,4 +63,11 @@ class YaComponentPluginImpl : public YaComponent { private: Vst3PluginBridge& bridge; + + /** + * An `IHostApplication` instance if we get one through + * `IPluginBase::initialize()`. + */ + Steinberg::FUnknownPtr + host_application_context; }; diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 28c77be8..4e5c6095 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -55,7 +55,9 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, iid.print(iid_string, Steinberg::FUID::UIDPrintStyle::kCLASS_UID); } - bridge.logger.log("[Unknown interface] " + std::string(iid_string)); + bridge.logger.log( + "[Unknown interface] In IPluginFactory::createInstance(): " + + std::string(iid_string)); return Steinberg::kNotImplemented; } From 1b300001477994fa2cfd220e719f2fbc12ab156a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 12 Dec 2020 21:24:11 +0100 Subject: [PATCH 153/456] Keep track of active YaComponentPluginImpls So we can do host callbacks later. --- src/plugin/bridges/vst3-impls/component.cpp | 5 ++- src/plugin/bridges/vst3.cpp | 12 ++++++ src/plugin/bridges/vst3.h | 48 +++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index bf209c44..dae8e0b3 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -18,11 +18,14 @@ YaComponentPluginImpl::YaComponentPluginImpl(Vst3PluginBridge& bridge, YaComponent::ConstructArgs&& args) - : YaComponent(std::move(args)), bridge(bridge) {} + : YaComponent(std::move(args)), bridge(bridge) { + bridge.register_component(arguments.instance_id, *this); +} YaComponentPluginImpl::~YaComponentPluginImpl() { bridge.send_message( YaComponent::Destruct{.instance_id = arguments.instance_id}); + bridge.unregister_component(arguments.instance_id); } tresult PLUGIN_API diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index e7815fa9..9a16cdf6 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -105,3 +105,15 @@ Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { return plugin_factory; } + +void Vst3PluginBridge::register_component(size_t instance_id, + YaComponentPluginImpl& component) { + std::lock_guard lock(component_instances_mutex); + component_instances.emplace(instance_id, + std::ref(component)); +} + +void Vst3PluginBridge::unregister_component(size_t instance_id) { + std::lock_guard lock(component_instances_mutex); + component_instances.erase(instance_id); +} diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 944f4bb8..249bfc9e 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -23,6 +23,9 @@ #include "../../common/logging/vst3.h" #include "common.h" +// Forward declaration +class YaComponentPluginImpl; + /** * This handles the communication between the native host and a VST3 plugin * hosted in our Wine plugin host. VST3 is handled very differently from VST2 @@ -63,6 +66,35 @@ class Vst3PluginBridge : PluginBridge> { */ Steinberg::IPluginFactory* get_plugin_factory(); + /** + * Add a `YaComponentPluginImpl` to the list of registered components so we + * can handle host callbacks, called during its constructor. + * + * @param instance_id The instance ID generated by the plugin host when + * instantiating the `IComponent`. Used as a stable name to refer to + * these. + * @param component The actual component instance so we can use its host + * context. + * + * @see component_instances + */ + void register_component(size_t instance_id, + YaComponentPluginImpl& component); + + /** + * Remove a previously registered `YaComponentPluginImpl` from the list of + * registered components so we can handle host callbacks, called during its + * destructor after asking the Wine plugin host to destroy the component on + * its side.. + * + * @param instance_id The instance ID generated by the plugin host when + * instantiating the `IComponent`. Used as a stable name to refer to + * these. + * + * @see component_instances + */ + void unregister_component(size_t instance_id); + /** * Send a control message to the Wine plugin host return the response. This * is a shorthand for `sockets.host_vst_control.send_message` for use in @@ -98,4 +130,20 @@ class Vst3PluginBridge : PluginBridge> { * @related get_plugin_factory */ YaPluginFactory* plugin_factory; + + private: + /** + * All `IComponent` instances we created from this plugin. We only need to + * keep track of them in case the 'real' `IComponent` instance tries to do a + * callback through the host context. We store the copy of the host context + * passed during `initialize()` in the `YaComponentPluginImpl`. The IDs here + * are the same IDs as generated by the Wine plugin host. We store raw + * pointers instead of smart pointers because this shouldn't affect the + * reference counting. An instance is added here through a call by + * `register_component()` in the constractor, and an instance is then + * removed through a call to `unregister_component()` in the destructor. + */ + std::map> + component_instances; + std::mutex component_instances_mutex; }; From 39984ad442aa29e45da48533aff38a50f4bf4f65 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 12 Dec 2020 21:51:06 +0100 Subject: [PATCH 154/456] Use the new approach for creating plugin factory Directly serializing and deserializing into objects was and more boilerplate heavy (since we now need two implementations even though we only use one), and also much less flexible because we can't wrap payloads in structs or provide optional values that way. --- meson.build | 1 - src/common/logging/vst3.cpp | 29 ++-- src/common/logging/vst3.h | 4 +- src/common/serialization/vst3.h | 13 +- src/common/serialization/vst3/README.md | 11 +- .../serialization/vst3/plugin-factory.cpp | 42 ++--- .../serialization/vst3/plugin-factory.h | 151 +++++++++++------- .../bridges/vst3-impls/plugin-factory.cpp | 6 +- .../bridges/vst3-impls/plugin-factory.h | 3 +- src/plugin/bridges/vst3.cpp | 10 +- src/wine-host/bridges/vst3-impls.cpp | 35 ---- src/wine-host/bridges/vst3-impls.h | 31 ---- src/wine-host/bridges/vst3.cpp | 12 +- src/wine-host/bridges/vst3.h | 7 - 14 files changed, 151 insertions(+), 204 deletions(-) delete mode 100644 src/wine-host/bridges/vst3-impls.cpp delete mode 100644 src/wine-host/bridges/vst3-impls.h diff --git a/meson.build b/meson.build index c3b08eec..5d12b3d5 100644 --- a/meson.build +++ b/meson.build @@ -115,7 +115,6 @@ if with_vst3 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/wine-host/bridges/vst3.cpp', - 'src/wine-host/bridges/vst3-impls.cpp', ] endif diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 0c809c80..21b50249 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -45,18 +45,18 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaPluginFactory::Construct&) { + log_request_base(is_host_vst, + [](auto& message) { message << "GetPluginFactory()"; }); +} + void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { log_request_base(is_host_vst, [](auto& message) { message << "Requesting "; }); } -void Vst3Logger::log_request(bool is_host_vst, const WantsPluginFactory&) { - log_request_base(is_host_vst, [](auto& message) { - message << "Requesting "; - }); -} - void Vst3Logger::log_response(bool is_host_vst, const Ack&) { log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); } @@ -76,16 +76,15 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response(bool is_host_vst, + const YaPluginFactory::ConstructArgs& args) { + log_response_base(is_host_vst, [&](auto& message) { + message << " with " << args.num_classes + << " registered classes"; + }); +} + void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { log_response_base(is_host_vst, [](auto& message) { message << " with " - << const_cast(factory).countClasses() - << " registered classes"; - }); -} diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index cac56886..f853cacb 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -50,15 +50,15 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::Construct&); void log_request(bool is_host_vst, const YaComponent::Destruct&); void log_request(bool is_host_vst, const YaComponent::Terminate&); + void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const WantsConfiguration&); - void log_request(bool is_host_vst, const WantsPluginFactory&); void log_response(bool is_host_vst, const Ack&); void log_response( bool is_host_vst, const std::variant&); + void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); - void log_response(bool is_host_vst, const YaPluginFactory&); Logger& logger; diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 5dde5922..06c43977 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -52,17 +52,6 @@ struct WantsConfiguration { void serialize(S&) {} }; -/** - * Marker struct to indicate the other side (the Wine plugin host) should send a - * copy of the hosted Windows VST3 plugin's `IPluginFactory{,2,3}` interface. - */ -struct WantsPluginFactory { - using Response = YaPluginFactory&; - - template - void serialize(S&) {} -}; - /** * When we send a control message from the plugin to the Wine VST host, this * encodes the information we request or the operation we want to perform. A @@ -71,7 +60,7 @@ struct WantsPluginFactory { using ControlRequest = std::variant; + YaPluginFactory::Construct>; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 7056c51b..10742a63 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -3,12 +3,6 @@ TODO: Once this is more fleshed out, move this document to `docs/`, and perhaps replace this readme with a link to that document. -TODO: There are now two approaches in use: the factory takes an interface -pointer for serialization and deserializes into an object directly, and the -component uses an args struct because the alternative involving pointers is just -too unsafe (as we also have to communicate additional payload data). This should -probably be unified into only using the latter appraoch. - The VST3 SDK uses an architecture where every object inherits from an interface, and every interface inherits from `FUnknown` which offers a dynamic casting interface through `queryInterface()`. Every interface gets a unique identifier. @@ -30,6 +24,11 @@ Yabridge's serialization and communication model for VST3 is thus a lot more complicated than for VST2 since all of these objects are loosely coupled and are instantiated and managed by the host. The model works as follows: +TODO: This is now slightly out of date. Instead of serializing and deserializing +directly into interface implementations through references, we now only pass +structs with payload data around to make the receiving process much more +flexible. + 1. For an interface `IFoo`, we provide a possibly abstract implementation called `YaFoo`. 2. This class has a constructor that takes an `IPtr` interface pointer and diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index 1465a82b..831c1c36 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -21,12 +21,10 @@ #include -YaPluginFactory::YaPluginFactory(){FUNKNOWN_CTOR} +YaPluginFactory::ConstructArgs::ConstructArgs() {} -YaPluginFactory::YaPluginFactory( +YaPluginFactory::ConstructArgs::ConstructArgs( Steinberg::IPtr factory) { - FUNKNOWN_CTOR - known_iids.insert(factory->iid); // `IPluginFactory::getFactoryInfo` if (Steinberg::PFactoryInfo info; @@ -75,7 +73,11 @@ YaPluginFactory::YaPluginFactory( } } -YaPluginFactory::~YaPluginFactory() { +YaPluginFactory::YaPluginFactory(const ConstructArgs&& args) + : arguments(std::move(args)){FUNKNOWN_CTOR} + + // clang-format just doesn't understand these macros, I guess + YaPluginFactory::~YaPluginFactory() { FUNKNOWN_DTOR } @@ -88,15 +90,15 @@ tresult PLUGIN_API YaPluginFactory::queryInterface(Steinberg::FIDString _iid, void** obj) { QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, Steinberg::IPluginFactory) - if (known_iids.contains(Steinberg::IPluginFactory::iid)) { + if (arguments.known_iids.contains(Steinberg::IPluginFactory::iid)) { QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory::iid, Steinberg::IPluginFactory) } - if (known_iids.contains(Steinberg::IPluginFactory2::iid)) { + if (arguments.known_iids.contains(Steinberg::IPluginFactory2::iid)) { QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory2::iid, Steinberg::IPluginFactory2) } - if (known_iids.contains(Steinberg::IPluginFactory3::iid)) { + if (arguments.known_iids.contains(Steinberg::IPluginFactory3::iid)) { QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory3::iid, Steinberg::IPluginFactory3) } @@ -107,8 +109,8 @@ tresult PLUGIN_API YaPluginFactory::queryInterface(Steinberg::FIDString _iid, tresult PLUGIN_API YaPluginFactory::getFactoryInfo(Steinberg::PFactoryInfo* info) { - if (info && factory_info) { - *info = *factory_info; + if (info && arguments.factory_info) { + *info = *arguments.factory_info; return Steinberg::kResultOk; } else { return Steinberg::kNotInitialized; @@ -116,17 +118,17 @@ YaPluginFactory::getFactoryInfo(Steinberg::PFactoryInfo* info) { } int32 PLUGIN_API YaPluginFactory::countClasses() { - return num_classes; + return arguments.num_classes; } tresult PLUGIN_API YaPluginFactory::getClassInfo(Steinberg::int32 index, Steinberg::PClassInfo* info) { - if (index >= static_cast(class_infos_unicode.size())) { + if (index >= static_cast(arguments.class_infos_unicode.size())) { return Steinberg::kInvalidArgument; } - if (class_infos_1[index]) { - *info = *class_infos_1[index]; + if (arguments.class_infos_1[index]) { + *info = *arguments.class_infos_1[index]; return Steinberg::kResultOk; } else { return Steinberg::kResultFalse; @@ -135,12 +137,12 @@ tresult PLUGIN_API YaPluginFactory::getClassInfo(Steinberg::int32 index, tresult PLUGIN_API YaPluginFactory::getClassInfo2(int32 index, Steinberg::PClassInfo2* info) { - if (index >= static_cast(class_infos_1.size())) { + if (index >= static_cast(arguments.class_infos_1.size())) { return Steinberg::kInvalidArgument; } - if (class_infos_2[index]) { - *info = *class_infos_2[index]; + if (arguments.class_infos_2[index]) { + *info = *arguments.class_infos_2[index]; return Steinberg::kResultOk; } else { return Steinberg::kResultFalse; @@ -150,12 +152,12 @@ YaPluginFactory::getClassInfo2(int32 index, Steinberg::PClassInfo2* info) { tresult PLUGIN_API YaPluginFactory::getClassInfoUnicode(int32 index, Steinberg::PClassInfoW* info) { - if (index >= static_cast(class_infos_unicode.size())) { + if (index >= static_cast(arguments.class_infos_unicode.size())) { return Steinberg::kInvalidArgument; } - if (class_infos_unicode[index]) { - *info = *class_infos_unicode[index]; + if (arguments.class_infos_unicode[index]) { + *info = *arguments.class_infos_unicode[index]; return Steinberg::kResultOk; } else { return Steinberg::kResultFalse; diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 5815e084..118eb305 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -28,8 +28,6 @@ // TODO: After implementing one or two more of these, abstract away some of the // nasty bits -// TODO: Should we have some clearer way to indicate to us which fields are -// going to return copied results directly and which make a callback? #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -40,16 +38,99 @@ */ class YaPluginFactory : public Steinberg::IPluginFactory3 { public: - YaPluginFactory(); + /** + * These are the arguments for creating a `YaPluginFactoryPluginImpl`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Create a copy of an existing plugin factory. Depending on the + * supported interface function more or less of this struct will be left + * empty, and `iid` will be set accordingly. + */ + ConstructArgs(Steinberg::IPtr factory); + + /** + * The IIDs that the interface we serialized supports. + */ + std::set known_iids; + + /** + * For `IPluginFactory::getFactoryInfo`. + */ + std::optional factory_info; + + /** + * For `IPluginFactory::countClasses`. + */ + int num_classes; + + /** + * For `IPluginFactory::getClassInfo`. We need to store all four class + * info versions if the plugin can provide them since we don't know + * which version of the interface the host will use. Will be + * `std::nullopt` if the plugin doesn't return a class info. + */ + std::vector> class_infos_1; + + /** + * For `IPluginFactory2::getClassInfo2`, works the same way as the + * above. + */ + std::vector> class_infos_2; + + /** + * For `IPluginFactory3::getClassInfoUnicode`, works the same way as the + * above. + */ + std::vector> class_infos_unicode; + + template + void serialize(S& s) { + s.ext(known_iids, bitsery::ext::StdSet{32}, + [](S& s, Steinberg::FUID& iid) { + s.ext(iid, bitsery::ext::FUID{}); + }); + s.ext(factory_info, bitsery::ext::StdOptional{}); + s.value4b(num_classes); + s.container(class_infos_1, 2048, + [](S& s, std::optional& info) { + s.ext(info, bitsery::ext::StdOptional{}); + }); + s.container(class_infos_2, 2048, + [](S& s, std::optional& info) { + s.ext(info, bitsery::ext::StdOptional{}); + }); + s.container(class_infos_unicode, 2048, + [](S& s, std::optional& info) { + s.ext(info, bitsery::ext::StdOptional{}); + }); + } + }; /** - * Create a copy of an existing plugin factory. Depending on the supported - * interface function more or less of this struct will be left empty, and - * `iid` will be set accordingly. + * Message to request the `IPluginFactory{,2,3}`'s information from the Wine + * plugin host. */ - explicit YaPluginFactory( - Steinberg::IPtr factory); + struct Construct { + using Response = ConstructArgs; + template + void serialize(S&) {} + }; + + /** + * Instantiate this instance with arguments read from the Windows VST3 + * plugin's plugin factory. + */ + YaPluginFactory(const ConstructArgs&& args); + + /** + * We do not need to implement the destructor, since when the sockets are + * closed, RAII will clean up the Windows VST3 module we loaded along with + * its factory for us. + */ virtual ~YaPluginFactory(); DECLARE_FUNKNOWN_METHODS @@ -81,58 +162,8 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { virtual tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override = 0; - template - void serialize(S& s) { - s.ext(known_iids, bitsery::ext::StdSet{32}, - [](S& s, Steinberg::FUID& iid) { - s.ext(iid, bitsery::ext::FUID{}); - }); - s.ext(factory_info, bitsery::ext::StdOptional{}); - s.value4b(num_classes); - s.container(class_infos_1, 2048, - [](S& s, std::optional& info) { - s.ext(info, bitsery::ext::StdOptional{}); - }); - s.container(class_infos_2, 2048, - [](S& s, std::optional& info) { - s.ext(info, bitsery::ext::StdOptional{}); - }); - s.container(class_infos_unicode, 2048, - [](S& s, std::optional& info) { - s.ext(info, bitsery::ext::StdOptional{}); - }); - } - - private: - /** - * The IIDs that the interface we serialized supports. - */ - std::set known_iids; - - /** - * For `IPluginFactory::getFactoryInfo`. - */ - std::optional factory_info; - /** - * For `IPluginFactory::countClasses`. - */ - int num_classes; - /** - * For `IPluginFactory::getClassInfo`. We need to store all four class info - * versions if the plugin can provide them since we don't know which version - * of the interface the host will use. Will be `std::nullopt` if the plugin - * doesn't return a class info. - */ - std::vector> class_infos_1; - /** - * For `IPluginFactory2::getClassInfo2`, works the same way as the above. - */ - std::vector> class_infos_2; - /** - * For `IPluginFactory3::getClassInfoUnicode`, works the same way as the - * above. - */ - std::vector> class_infos_unicode; + protected: + ConstructArgs arguments; }; #pragma GCC diagnostic pop diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 4e5c6095..123e960b 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -20,8 +20,10 @@ #include "component.h" -YaPluginFactoryPluginImpl::YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge) - : bridge(bridge) {} +YaPluginFactoryPluginImpl::YaPluginFactoryPluginImpl( + Vst3PluginBridge& bridge, + YaPluginFactory::ConstructArgs&& args) + : YaPluginFactory(std::move(args)), bridge(bridge) {} tresult PLUGIN_API YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.h b/src/plugin/bridges/vst3-impls/plugin-factory.h index 8d877640..aee5a149 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.h +++ b/src/plugin/bridges/vst3-impls/plugin-factory.h @@ -20,7 +20,8 @@ class YaPluginFactoryPluginImpl : public YaPluginFactory { public: - YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge); + YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge, + YaPluginFactory::ConstructArgs&& args); tresult PLUGIN_API createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 9a16cdf6..7c1073a3 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -97,10 +97,12 @@ Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { // will request after loading the module. Host callback handlers should // have started before this since the Wine plugin host will request a // copy of the configuration during its initialization. - plugin_factory = new YaPluginFactoryPluginImpl(*this); - sockets.host_vst_control.receive_into( - WantsPluginFactory{}, *plugin_factory, - std::pair(logger, true)); + YaPluginFactory::ConstructArgs factory_args = + sockets.host_vst_control.send_message( + YaPluginFactory::Construct{}, + std::pair(logger, true)); + plugin_factory = + new YaPluginFactoryPluginImpl(*this, std::move(factory_args)); } return plugin_factory; diff --git a/src/wine-host/bridges/vst3-impls.cpp b/src/wine-host/bridges/vst3-impls.cpp deleted file mode 100644 index d52d8cc6..00000000 --- a/src/wine-host/bridges/vst3-impls.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// yabridge: a Wine VST bridge -// Copyright (C) 2020 Robbert van der Helm -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include "vst3-impls.h" - -YaPluginFactoryHostImpl::YaPluginFactoryHostImpl( - Steinberg::IPtr factory) - : YaPluginFactory(factory) {} - -tresult PLUGIN_API -YaPluginFactoryHostImpl::createInstance(Steinberg::FIDString /*cid*/, - Steinberg::FIDString /*_iid*/, - void** /*obj*/) { - throw std::runtime_error( - "Unexpected call to 'IPluginFactory::createInstance()'"); -} - -tresult PLUGIN_API -YaPluginFactoryHostImpl::setHostContext(Steinberg::FUnknown* /*context*/) { - throw std::runtime_error( - "Unexpected call to 'IPluginFactory3::setHostContext()'"); -} diff --git a/src/wine-host/bridges/vst3-impls.h b/src/wine-host/bridges/vst3-impls.h deleted file mode 100644 index 59328696..00000000 --- a/src/wine-host/bridges/vst3-impls.h +++ /dev/null @@ -1,31 +0,0 @@ -// yabridge: a Wine VST bridge -// Copyright (C) 2020 Robbert van der Helm -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#pragma once - -#include "vst3.h" - -class YaPluginFactoryHostImpl : public YaPluginFactory { - public: - YaPluginFactoryHostImpl(Steinberg::IPtr factory); - - tresult PLUGIN_API createInstance(Steinberg::FIDString cid, - Steinberg::FIDString _iid, - void** obj) override; - - tresult PLUGIN_API - setHostContext(Steinberg::FUnknown* /*context*/) override; -}; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 8fcc2318..02ed53fd 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -17,7 +17,6 @@ #include "vst3.h" #include "../boost-fix.h" -#include "vst3-impls.h" #include @@ -36,11 +35,6 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context, sockets.connect(); - // Serialize the plugin's plugin factory. The native VST3 plugin will - // request a copy of this during its initialization. - plugin_factory = - std::make_unique(module->getFactory().get()); - // Fetch this instance's configuration from the plugin to finish the setup // process config = sockets.vst_host_callback.send_message(WantsConfiguration{}, @@ -82,8 +76,10 @@ void Vst3Bridge::run() { -> YaComponent::Terminate::Response { return component_instances[request.instance_id]->terminate(); }, - [&](const WantsPluginFactory&) -> WantsPluginFactory::Response { - return *plugin_factory; + [&](const YaPluginFactory::Construct&) + -> YaPluginFactory::Construct::Response { + return YaPluginFactory::ConstructArgs( + module->getFactory().get()); }}); } diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 161e030c..7c638e88 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -91,13 +91,6 @@ class Vst3Bridge : public HostBridge { */ Vst3Sockets sockets; - /** - * A plugin factory copied from the Windows VST3 plugin during - * initialization. The native VST3 plugin will request a copy of this - * information during its initialization. - */ - std::unique_ptr plugin_factory; - /** * Used to assign unique identifier to instances created for * `IPluginFactory::createInstance()`. From 0ad56874e2736c6514cc9dc2d3664a3178746ee6 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 12 Dec 2020 22:22:20 +0100 Subject: [PATCH 155/456] Abstract away logging unknown interfaces --- src/common/logging/vst3.cpp | 18 ++++++++++++++++-- src/common/logging/vst3.h | 9 +++++++++ src/plugin/bridges/vst3-impls/component.cpp | 16 ++++------------ src/plugin/bridges/vst3-impls/component.h | 6 ++++-- .../bridges/vst3-impls/plugin-factory.cpp | 10 ++++------ 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 21b50249..bd3dcd6d 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -18,10 +18,24 @@ #include "src/common/serialization/vst3.h" -// TODO: Reconsider the output format - Vst3Logger::Vst3Logger(Logger& generic_logger) : logger(generic_logger) {} +void Vst3Logger::log_unknown_interface( + const std::string& where, + const std::optional& uid) { + if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { + char uid_string[128] = ""; + if (uid) { + uid->print(uid_string, Steinberg::FUID::UIDPrintStyle::kCLASS_UID); + } + + std::ostringstream message; + message << "[unknown interface] " << where << ": " << uid_string; + + log(message.str()); + } +} + void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Construct&) { log_request_base(is_host_vst, [](auto& message) { // TODO: Log the cid in some readable way, if possible diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index f853cacb..326704ff 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -42,6 +42,15 @@ class Vst3Logger { logger.log_trace(message); } + /** + * Log about encountering an unknown interface. The location and the UID + * will be printed when the verbosity level is set to `most_events` or + * higher. In case we could not get a FUID (because of null pointers, for + * instance), `std::nullopt` should be passed. + */ + void log_unknown_interface(const std::string& where, + const std::optional& uid); + // For every object we send using `Vst3MessageHandler` we have overloads // that print information about the request and the response. The boolean // flag here indicates whether the request was initiated on the host side diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index dae8e0b3..8e5189ab 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -29,8 +29,7 @@ YaComponentPluginImpl::~YaComponentPluginImpl() { } tresult PLUGIN_API -YaComponentPluginImpl::queryInterface(const ::Steinberg::TUID _iid, - void** obj) { +YaComponentPluginImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { // TODO: Log when this fails on debug level 1, and on debug level 2 also log // successful queries. This behaviour should be implemented for all // interfaces. @@ -46,16 +45,9 @@ tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { if (host_application_context) { // TODO: Init with `YaHostApplication` } else { - context->iid; - - char iid_string[128] = ""; - if (context) { - context->iid.print(iid_string, - Steinberg::FUID::UIDPrintStyle::kCLASS_UID); - } - - bridge.logger.log("[Unknown interface] In IPluginBase::initialize(): " + - std::string(iid_string)); + bridge.logger.log_unknown_interface( + "In IPluginBase::initialize()", + context ? std::optional(context->iid) : std::nullopt); // TODO: Init with null pointer } diff --git a/src/plugin/bridges/vst3-impls/component.h b/src/plugin/bridges/vst3-impls/component.h index 2b53178f..5e989b98 100644 --- a/src/plugin/bridges/vst3-impls/component.h +++ b/src/plugin/bridges/vst3-impls/component.h @@ -36,7 +36,7 @@ class YaComponentPluginImpl : public YaComponent { * We'll override the query interface to log queries for interfaces we do * not (yet) support. */ - tresult PLUGIN_API queryInterface(const ::Steinberg::TUID _iid, + tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, void** obj) override; tresult PLUGIN_API initialize(FUnknown* context) override; @@ -66,7 +66,9 @@ class YaComponentPluginImpl : public YaComponent { /** * An `IHostApplication` instance if we get one through - * `IPluginBase::initialize()`. + * `IPluginBase::initialize()`. This should be the same for all plugin + * instances so we should not have to store it here separately, but for the + * sake of correctness we will. */ Steinberg::FUnknownPtr host_application_context; diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 123e960b..0f971f39 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -49,17 +49,15 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, // print a recognizable log message. I don't think they include a safe // way to convert a `FIDString/char*` into a `FUID`, so this will have // to do. - char iid_string[128] = ""; + std::optional uid; constexpr size_t uid_size = sizeof(Steinberg::TUID); if (_iid && strnlen(_iid, uid_size + 1) == uid_size) { - Steinberg::FUID iid = Steinberg::FUID::fromTUID( + uid = Steinberg::FUID::fromTUID( *reinterpret_cast(&_iid)); - iid.print(iid_string, Steinberg::FUID::UIDPrintStyle::kCLASS_UID); } - bridge.logger.log( - "[Unknown interface] In IPluginFactory::createInstance(): " + - std::string(iid_string)); + bridge.logger.log_unknown_interface( + "In IPluginFactory::createInstance()", uid); return Steinberg::kNotImplemented; } From cd92b7a976c599c999bdb285bc46265601657d5c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 12 Dec 2020 22:25:25 +0100 Subject: [PATCH 156/456] Log failed calls to IComponent::queryInterface() --- src/plugin/bridges/vst3-impls/component.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 8e5189ab..e3bbe257 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -30,10 +30,13 @@ YaComponentPluginImpl::~YaComponentPluginImpl() { tresult PLUGIN_API YaComponentPluginImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { - // TODO: Log when this fails on debug level 1, and on debug level 2 also log - // successful queries. This behaviour should be implemented for all - // interfaces. - return YaComponent::queryInterface(_iid, obj); + const tresult result = YaComponent::queryInterface(_iid, obj); + if (result != Steinberg::kResultOk) { + bridge.logger.log_unknown_interface("In IComponent::queryInterface()", + Steinberg::FUID::fromTUID(_iid)); + } + + return result; } tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { From d9989fe8561dceef3ba33c2908a6d0d3f8900f46 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 12:32:49 +0100 Subject: [PATCH 157/456] Update the VST3 architecture document --- src/common/serialization/vst3/README.md | 109 ++++++++++++++---------- 1 file changed, 62 insertions(+), 47 deletions(-) diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 10742a63..d4a22cf9 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -3,12 +3,15 @@ TODO: Once this is more fleshed out, move this document to `docs/`, and perhaps replace this readme with a link to that document. -The VST3 SDK uses an architecture where every object inherits from an interface, -and every interface inherits from `FUnknown` which offers a dynamic casting -interface through `queryInterface()`. Every interface gets a unique identifier. -It then uses a smart pointer system (`FUnknownPtr`) that queries whether the -`FUnknown` matches a certain interface by checking whether the IDs match up, -allowing casts to that interface if the `FUnkonwn` matches. +The VST3 SDK uses an architecture where every concrete object inherits from an +interface, and every interface inherits from `FUnknown`. `FUnkonwn` offers a +dynamic casting interface through `queryInterface()` and a reference counting +mechanism that calls `delete this;` when the reference count reaches 0. Every +interface gets a unique identifier. It then uses a smart pointer system +(`FUnknownPtr`) that queries whether the `FUnknown` matches a certain +interface by checking whether the IDs match up, allowing casts to that interface +if the `FUnkonwn` matches. Those smart pointers also use that reference counting +mechanism to destroy the object when the last pointer gets dropped. Another important part of this system is interface versioning. Old interfaces cannot be changed, so when the SDK adds new functionality to an existing @@ -16,52 +19,63 @@ interface it defines a new interface that inherits from the old one. The `queryInterface()` implementation should then allow casts to all of the implemented interface versions. -Lastly, the interfaces mostly provided a lot of getters for data, but some of -the interfaces also provide callback functions that should perform some -operation on the component implementing the interface. +Lastly, the interfaces provide both getters for static, non-chancing data (such +as the classes registered in a plugin factory) as well as functions that perform +side effects or return dynamically changing data (such as the input/output +configuration for an audio processor). Yabridge's serialization and communication model for VST3 is thus a lot more complicated than for VST2 since all of these objects are loosely coupled and are -instantiated and managed by the host. The model works as follows: - -TODO: This is now slightly out of date. Instead of serializing and deserializing -directly into interface implementations through references, we now only pass -structs with payload data around to make the receiving process much more -flexible. +instantiated and managed by the host. The basic model works as follows: 1. For an interface `IFoo`, we provide a possibly abstract implementation called `YaFoo`. -2. This class has a constructor that takes an `IPtr` interface pointer and - copies all of the data from the interface's functions that do not perform any - side effects. -3. `YaFoo` then implements all the boilerplate required for `FUnknown`. This - includes the constructor, destructor and methods required for reference - counting, as well as the query interface. -4. If `IFoo` is a versioned interface such as `IPluginFactory3`, the above two - steps work slightly differently. When copying the data for a plugin factory, - we'll start copying from `IPluginFactory`, and we'll copy data from each - newer version of the interface that the `IPtr` supports. +2. When we want to _proxy_ an interface from one side to the other (let's assume + we want to allow the native VST3 host to call functions on the `IFoo` + provided by the Windows VST3 plugin), we need to provide a `YaFoo` + implementation on the native plugin side that can do callbacks to the + corresponding `IFoo` object in the Wine plugin host. For most objects, this + works by first generating a unique identifier to be able to refer to this + specific `IFoo` instance, and then serializing that identifier together with + any static payload data into a `YaFoo::ConstructArgs` object. This + `YaFoo::ConstructArgs` copies this data through a `IPtr` smart pointer + to the original object we're proxying. This object can be serialized and + transmitted to the other side using bitsery. +3. The original `IFoo` we are proxying gets added to an + `std::map>` (in our assumed scenario, this happens on the + Wine plugin host's side) with the key being that unique instance identifier + we generated so we can refer to it later on. +4. `YaFoo` implements all the boilerplate required for `FUnknown`. This includes + the constructor, destructor and methods required for reference counting, as + well as the query interface. It also implements any static lookup functions + that can be performed using the data contained in a `YaFoo::ConstructArgs` + object. Any functions that perform side effects or return dynamic data and + thus require a callback or control message are marked as pure virtual. These + callbacks can be performed through yabridge's `Vst3MessageHandler` message + handling interface. For the sake of clarity, we use the term _callback_ for + `plugin -> host` function calls and _control message_ for `host -> plugin` + function calls. +5. The side that requested the object (which we assume to be the native plugin + here), creates a _proxy object_ called `YaFoo{Plugin,Host}Impl`, so + `YaFooPluginImpl` in this case. This is an instance of `YaFoo` and thus + `IFoo`, so we can pass it as an `IFoo` pointer to the host. This object takes + those `YaFoo::ConstructArgs` and a reference to the bridge instance so it can + do callbacks or send control messages. +6. If `IFoo` is a versioned interface such as `IPluginFactory{,2,3}`, the + creation of `YaFoo::ConstrctArgs` and the definition of `YaFoo`'s query + interface work slightly differently. When copying the data for a plugin + factory, we'll start copying from `IPluginFactory`, and we'll copy data from + each newer version of the interface that the `IPtr` supports. During this process we keep track of which interfaces were supported by the - native plugin. In our query interface method we then only report support for - the same itnerfaces that were supported by `IPtrgetFactory().createInstance(cid)` using the Windows VST3 plugin's plugin factory to ask it to create an instance of that interface. If - this operation fails and returns a null pointer, we'll send an `std::nullopt` - back to indicate that the instantiation was not successful and we relay this - on the plugin side. -5. We will generate a unique instance identifier for the newly generated object - so we can refer to it later. We then serialize that identifier along with - what other static data is available in `IFoo` in a `YaFoo::Arguments` object. + this operation fails and returns a null pointer, we'll send a + `kNotImplemented` result code back to indicate that the instantiation was not + successful and we relay this on the plugin side. +5. As mentioned above, we will generate a unique instance identifier for the + newly generated object so we can refer to it later. We then serialize that + identifier along with what other static data is available in `IFoo` in a + `YaFoo::ConstructArgs` object. 6. We then move `IPtr` to an `std::map>` with that unique identifier we generated earlier as a key so we can refer to it later in later function calls. From ca91cf041de6ca8670a9acc3b16a60efcb199c68 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 12:46:58 +0100 Subject: [PATCH 158/456] List the progress of interface implementations --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a15bb90d..6e2e4005 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,15 @@ highly compatible, while also staying easy to debug and maintain. This branch is still very far removed from being in a usable state. Below is an imcomplete list of things that still have to be done before this can be used: -- Actually start implementing VST3 support. +- Left to implement: + - `IHostApplication` for both `IPluginBase::initialize()` as well as for + `IPluginFactory3::setHostContext()`. + - The rest of `IComponent`'s functions after implementing `intialize()` + - `IPluginFactory3::setHostContext()` + - All other mandatory interfaces + - All other optional interfaces +- Fully implemented: + - Nothing yet - Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and From 919987298ceac1196f3a380500e78b0712c333db Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 13:45:30 +0100 Subject: [PATCH 159/456] Add the base for YaHostApplication --- README.md | 4 +- meson.build | 2 + .../serialization/vst3/host-application.cpp | 69 +++++++++++ .../serialization/vst3/host-application.h | 111 ++++++++++++++++++ .../serialization/vst3/plugin-factory.h | 6 +- 5 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 src/common/serialization/vst3/host-application.cpp create mode 100644 src/common/serialization/vst3/host-application.h diff --git a/README.md b/README.md index 6e2e4005..75fbfbed 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ This branch is still very far removed from being in a usable state. Below is an imcomplete list of things that still have to be done before this can be used: - Left to implement: - - `IHostApplication` for both `IPluginBase::initialize()` as well as for - `IPluginFactory3::setHostContext()`. + - `IHostApplication` implementations for both `IPluginBase::initialize()` as + well as for `IPluginFactory3::setHostContext()`. - The rest of `IComponent`'s functions after implementing `intialize()` - `IPluginFactory3::setHostContext()` - All other mandatory interfaces diff --git a/meson.build b/meson.build index 5d12b3d5..b6b09d2a 100644 --- a/meson.build +++ b/meson.build @@ -79,6 +79,7 @@ vst3_plugin_sources = [ 'src/common/logging/vst3.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component.cpp', + 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/configuration.cpp', 'src/common/plugins.cpp', @@ -113,6 +114,7 @@ if with_vst3 'src/common/logging/vst3.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component.cpp', + 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/wine-host/bridges/vst3.cpp', ] diff --git a/src/common/serialization/vst3/host-application.cpp b/src/common/serialization/vst3/host-application.cpp new file mode 100644 index 00000000..d270e277 --- /dev/null +++ b/src/common/serialization/vst3/host-application.cpp @@ -0,0 +1,69 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "host-application.h" + +YaHostApplication::ConstructArgs::ConstructArgs() {} + +YaHostApplication::ConstructArgs::ConstructArgs( + Steinberg::IPtr context, + size_t component_instance_id) + : component_instance_id(component_instance_id) { + Steinberg::Vst::String128 name_array; + if (context->getName(name_array) == Steinberg::kResultOk) { +#ifdef __WINE__ + // Who even invented UTF-16 + static_assert(sizeof(wchar_t) == sizeof(char16_t)); +#endif + name = std::u16string(reinterpret_cast(name_array)); + } +} + +YaHostApplication::YaHostApplication(const ConstructArgs&& args) + : arguments(std::move(args)){FUNKNOWN_CTOR} + + YaHostApplication::~YaHostApplication() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(YaHostApplication) +#pragma GCC diagnostic pop + +tresult PLUGIN_API YaHostApplication::queryInterface(Steinberg::FIDString _iid, + void** obj) { + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, + Steinberg::Vst::IHostApplication); + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IHostApplication::iid, + Steinberg::Vst::IHostApplication) + + *obj = nullptr; + return Steinberg::kNoInterface; +} + +tresult PLUGIN_API YaHostApplication::getName(Steinberg::Vst::String128 name) { + if (arguments.name) { + // Terminate with a null byte. There are no nice functions for copying + // UTF-16 strings (because who would use those?). + std::copy(arguments.name->begin(), arguments.name->end(), name); + name[arguments.name->size()] = 0; + + return Steinberg::kResultOk; + } else { + return Steinberg::kNotImplemented; + } +} diff --git a/src/common/serialization/vst3/host-application.h b/src/common/serialization/vst3/host-application.h new file mode 100644 index 00000000..0589feee --- /dev/null +++ b/src/common/serialization/vst3/host-application.h @@ -0,0 +1,111 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include +#include +#include + +#include "../common.h" +#include "base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IHostApplication` for serialization purposes. See `README.md` + * for more information on how this works. This is used both to proxy the host + * application context passed during `IPluginBase::intialize()` as well as for + * `IPluginFactory3::setHostContext()`. This interface is thus implemented on + * both the native plugin side as well as the Wine plugin host side. + */ +class YaHostApplication : public Steinberg::Vst::IHostApplication { + public: + /** + * These are the arguments for creating a + * `YaYaHostApplication{Plugin,Host}Impl`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Read arguments from an existing implementation. + */ + ConstructArgs(Steinberg::IPtr context, + size_t component_isntance_id); + + /** + * The unique instance identifier of the component this host context has + * been passed to and thus belongs to. + * + * TODO: When we implement `IPluginFactory3::setHostContext()` this + * should be made optional. + */ + native_size_t component_instance_id; + + /** + * For `IHostApplication::getName`. + */ + std::optional name; + + template + void serialize(S& s) { + s.value8b(component_instance_id); + s.ext(name, bitsery::ext::StdOptional{}, + [](S& s, std::string& name) { + s.text2b(name, std::extent_v); + }); + } + }; + + /** + * Instantiate this instance with arguments read from an actual host + * context. + * + * @note Since this is passed as part of ``IPluginBase::intialize()` and + * `IPluginFactory3::setHostContext()``, there are no direct `Construct` + * or `Destruct` messages. This object's lifetime is bound to that of the + * objects they are passed to. If those objects get dropped, then the host + * contexts should also be dropped. + * + * TODO: Check if this ends up working out this way + */ + YaHostApplication(const ConstructArgs&& args); + + /** + * The lifetime of this object should be bound to the object we created it + * for. When for instance the `IComponent` instance with id `x` gets dropped + * and we also track a `YaHostApplicationHostImpl` for the component with + * instance id `x`, then that should also be dropped. + */ + ~YaHostApplication(); + + DECLARE_FUNKNOWN_METHODS + + // From `IHostApplication` + tresult PLUGIN_API getName(Steinberg::Vst::String128 name) override; + virtual tresult PLUGIN_API createInstance(Steinberg::TUID cid, + Steinberg::TUID _iid, + void** obj) override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 118eb305..979038ce 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -127,9 +127,9 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { YaPluginFactory(const ConstructArgs&& args); /** - * We do not need to implement the destructor, since when the sockets are - * closed, RAII will clean up the Windows VST3 module we loaded along with - * its factory for us. + * We do not need to implement the destructor in + * `YaPluginFactoryPluginImpl`, since when the sockets are closed, RAII will + * clean up the Windows VST3 module we loaded along with its factory for us. */ virtual ~YaPluginFactory(); From e8929e5e43fa57ef7fb75afca42f5598455c9bc8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 13:53:53 +0100 Subject: [PATCH 160/456] Make the UTF-16 conversion a bit safer --- src/common/serialization/vst3/host-application.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/serialization/vst3/host-application.cpp b/src/common/serialization/vst3/host-application.cpp index d270e277..30f9a14e 100644 --- a/src/common/serialization/vst3/host-application.cpp +++ b/src/common/serialization/vst3/host-application.cpp @@ -26,9 +26,11 @@ YaHostApplication::ConstructArgs::ConstructArgs( if (context->getName(name_array) == Steinberg::kResultOk) { #ifdef __WINE__ // Who even invented UTF-16 - static_assert(sizeof(wchar_t) == sizeof(char16_t)); -#endif + static_assert(sizeof(Steinberg::Vst::TChar) == sizeof(char16_t)); name = std::u16string(reinterpret_cast(name_array)); +#else + name = std::u16string(static_cast(name_array)); +#endif } } From e21ee31ee85c88fdfacab9d3793ab4a0025d3cd3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 14:30:51 +0100 Subject: [PATCH 161/456] Implement plugin side of IPluginBase::initialize() --- src/common/logging/vst3.cpp | 13 ++++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/component.h | 24 +++++++++++++++++++ .../serialization/vst3/host-application.h | 8 +++---- src/plugin/bridges/vst3-impls/component.cpp | 16 +++++++++---- src/wine-host/bridges/vst3.cpp | 8 +++++++ 7 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index bd3dcd6d..43ee3330 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -52,6 +52,19 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::Initialize& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::initialize("; + if (request.host_application_context_args) { + message << "IHostApplication*"; + } else { + message << "nullptr"; + } + message << ")"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Terminate& request) { log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 326704ff..324186bf 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -58,6 +58,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::Construct&); void log_request(bool is_host_vst, const YaComponent::Destruct&); + void log_request(bool is_host_vst, const YaComponent::Initialize&); void log_request(bool is_host_vst, const YaComponent::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const WantsConfiguration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 06c43977..449547b8 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -59,6 +59,7 @@ struct WantsConfiguration { */ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 6068aaca..b370be25 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -27,6 +27,7 @@ #include "../common.h" #include "base.h" +#include "host-application.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -125,6 +126,29 @@ class YaComponent : public Steinberg::Vst::IComponent { DECLARE_FUNKNOWN_METHODS // From `IPluginBase` + + /** + * Message to pass through a call to `IPluginBase::initialize()` to the Wine + * plugin host. if we pass an `IHostApplication` instance, then a proxy + * `YaHostApplication` should be created and passed as an argument to + * `IPluginBase::initialize()`. If this is absent a null pointer should be + * passed. The lifetime of this `YaHostApplication` object should be bound + * to the `IComponent` we are proxying. + */ + struct Initialize { + using Response = UniversalTResult; + + native_size_t instance_id; + std::optional + host_application_context_args; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.ext(host_application_context_args, bitsery::ext::StdOptional{}); + } + }; + virtual tresult PLUGIN_API initialize(FUnknown* context) override = 0; /** diff --git a/src/common/serialization/vst3/host-application.h b/src/common/serialization/vst3/host-application.h index 0589feee..9f2e989a 100644 --- a/src/common/serialization/vst3/host-application.h +++ b/src/common/serialization/vst3/host-application.h @@ -68,7 +68,7 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { void serialize(S& s) { s.value8b(component_instance_id); s.ext(name, bitsery::ext::StdOptional{}, - [](S& s, std::string& name) { + [](S& s, std::u16string& name) { s.text2b(name, std::extent_v); }); } @@ -78,9 +78,9 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { * Instantiate this instance with arguments read from an actual host * context. * - * @note Since this is passed as part of ``IPluginBase::intialize()` and - * `IPluginFactory3::setHostContext()``, there are no direct `Construct` - * or `Destruct` messages. This object's lifetime is bound to that of the + * @note Since this is passed as part of `IPluginBase::intialize()` and + * `IPluginFactory3::setHostContext()`, there are no direct `Construct` or + * `Destruct` messages. This object's lifetime is bound to that of the * objects they are passed to. If those objects get dropped, then the host * contexts should also be dropped. * diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index e3bbe257..ba6dfee3 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -45,18 +45,24 @@ tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { // side. Otherwise we'll still call `IPluginBase::initialize()` but with a // null pointer instead. host_application_context = context; + + std::optional + host_application_context_args = std::nullopt; if (host_application_context) { - // TODO: Init with `YaHostApplication` + host_application_context_args = YaHostApplication::ConstructArgs( + host_application_context, arguments.instance_id); } else { bridge.logger.log_unknown_interface( "In IPluginBase::initialize()", context ? std::optional(context->iid) : std::nullopt); - - // TODO: Init with null pointer } - // TODO: Implement - return Steinberg::kNotImplemented; + return bridge + .send_message(YaComponent::Initialize{ + .instance_id = arguments.instance_id, + .host_application_context_args = + std::move(host_application_context_args)}) + .native(); } tresult PLUGIN_API YaComponentPluginImpl::terminate() { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 02ed53fd..ffd0e29d 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -72,6 +72,14 @@ void Vst3Bridge::run() { return Ack{}; }, + [&](const YaComponent::Initialize& request) + -> YaComponent::Initialize::Response { + // TODO: If `request.host_context_args` has a value, we should + // initialize a proxy object and pass a pointer to that + // instead + return component_instances[request.instance_id]->initialize( + nullptr); + }, [&](const YaComponent::Terminate& request) -> YaComponent::Terminate::Response { return component_instances[request.instance_id]->terminate(); From 0c64aabeea0d5c22fea9df9f173a947f716ff98a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 14:46:17 +0100 Subject: [PATCH 162/456] Add a partial YaHostApplication implementation --- README.md | 4 +- meson.build | 1 + .../serialization/vst3/host-application.h | 2 +- .../bridges/vst3-impls/host-application.cpp | 47 +++++++++++++++++++ .../bridges/vst3-impls/host-application.h | 41 ++++++++++++++++ 5 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 src/wine-host/bridges/vst3-impls/host-application.cpp create mode 100644 src/wine-host/bridges/vst3-impls/host-application.h diff --git a/README.md b/README.md index 75fbfbed..4a3f0937 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ This branch is still very far removed from being in a usable state. Below is an imcomplete list of things that still have to be done before this can be used: - Left to implement: - - `IHostApplication` implementations for both `IPluginBase::initialize()` as - well as for `IPluginFactory3::setHostContext()`. + - `YaHostApplicationHostImpl::createComponent`. + - `IPluginFactory3::setHostContext()` using the same host application context proxy method. - The rest of `IComponent`'s functions after implementing `intialize()` - `IPluginFactory3::setHostContext()` - All other mandatory interfaces diff --git a/meson.build b/meson.build index b6b09d2a..17f46217 100644 --- a/meson.build +++ b/meson.build @@ -116,6 +116,7 @@ if with_vst3 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', + 'src/wine-host/bridges/vst3-impls/host-application.cpp', 'src/wine-host/bridges/vst3.cpp', ] endif diff --git a/src/common/serialization/vst3/host-application.h b/src/common/serialization/vst3/host-application.h index 9f2e989a..6a88644f 100644 --- a/src/common/serialization/vst3/host-application.h +++ b/src/common/serialization/vst3/host-application.h @@ -94,7 +94,7 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { * and we also track a `YaHostApplicationHostImpl` for the component with * instance id `x`, then that should also be dropped. */ - ~YaHostApplication(); + virtual ~YaHostApplication(); DECLARE_FUNKNOWN_METHODS diff --git a/src/wine-host/bridges/vst3-impls/host-application.cpp b/src/wine-host/bridges/vst3-impls/host-application.cpp new file mode 100644 index 00000000..b53e5b73 --- /dev/null +++ b/src/wine-host/bridges/vst3-impls/host-application.cpp @@ -0,0 +1,47 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "host-application.h" + +#include + +YaHostApplicationHostImpl::YaHostApplicationHostImpl( + Vst3Bridge& bridge, + YaHostApplication::ConstructArgs&& args) + : YaHostApplication(std::move(args)), bridge(bridge) { + // The lifecycle is thos object is managed together with that of the + // `IComponent` instance this belongs to +} + +tresult PLUGIN_API +YaHostApplicationHostImpl::queryInterface(const Steinberg::TUID _iid, + void** obj) { + const tresult result = YaHostApplication::queryInterface(_iid, obj); + if (result != Steinberg::kResultOk) { + std::cerr << "TODO: Implement unknown interface logging on Wine side" + << std::endl; + } + + return result; +} + +tresult PLUGIN_API +YaHostApplicationHostImpl::createInstance(Steinberg::TUID cid, + Steinberg::TUID _iid, + void** obj) { + // TODO: Implement + return Steinberg::kNotImplemented; +} diff --git a/src/wine-host/bridges/vst3-impls/host-application.h b/src/wine-host/bridges/vst3-impls/host-application.h new file mode 100644 index 00000000..c9c302f9 --- /dev/null +++ b/src/wine-host/bridges/vst3-impls/host-application.h @@ -0,0 +1,41 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "../vst3.h" + +class YaHostApplicationHostImpl : public YaHostApplication { + public: + YaHostApplicationHostImpl(Vst3Bridge& bridge, + YaHostApplication::ConstructArgs&& args); + + /** + * We'll override the query interface to log queries for interfaces we do + * not (yet) support. + */ + tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, + void** obj) override; + + tresult PLUGIN_API createInstance(Steinberg::TUID cid, + Steinberg::TUID _iid, + void** obj) override; + + private: + Vst3Bridge& bridge; +}; From 7c5f7a2e0e28eb09b82893264d5175351ec6b749 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 15:16:11 +0100 Subject: [PATCH 163/456] Fully implement IPluginBase::initialize() We can now pass host contexts around. --- README.md | 2 +- src/common/communication/vst3.h | 2 +- src/wine-host/bridges/vst3.cpp | 35 +++++++++++++++++++++++++++------ src/wine-host/bridges/vst3.h | 15 ++++++++++++++ 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4a3f0937..be611af7 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ imcomplete list of things that still have to be done before this can be used: - Left to implement: - `YaHostApplicationHostImpl::createComponent`. - `IPluginFactory3::setHostContext()` using the same host application context proxy method. - - The rest of `IComponent`'s functions after implementing `intialize()` + - The rest of `IComponent`'s functions - `IPluginFactory3::setHostContext()` - All other mandatory interfaces - All other optional interfaces diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 24601100..3f73f95a 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -180,7 +180,7 @@ class Vst3MessageHandler : public AdHocSocketHandler { // always know for sure that the function returns the correct // type, and we can scrap a lot of boilerplate elsewhere. std::visit( - [&](const T object) { + [&](T object) { typename T::Response response = callback(object); if (logging) { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index ffd0e29d..1696a6d8 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -20,6 +20,8 @@ #include +#include "vst3-impls/host-application.h" + Vst3Bridge::Vst3Bridge(MainContext& main_context, std::string plugin_dll_path, std::string endpoint_base_dir) @@ -67,18 +69,39 @@ void Vst3Bridge::run() { }, [&](const YaComponent::Destruct& request) -> YaComponent::Destruct::Response { - std::lock_guard lock(component_instances_mutex); + std::scoped_lock lock( + component_instances_mutex, + component_host_application_context_instance_mutex); component_instances.erase(request.instance_id); + if (component_host_application_context_instances.contains( + request.instance_id)) { + component_host_application_context_instances.erase( + request.instance_id); + } return Ack{}; }, - [&](const YaComponent::Initialize& request) + [&](YaComponent::Initialize& request) -> YaComponent::Initialize::Response { - // TODO: If `request.host_context_args` has a value, we should - // initialize a proxy object and pass a pointer to that - // instead + // If we got passed a host context, we'll create a proxy object + // and pass that to the initialize function. This object should + // be cleaned up again during `YaComponent::Destruct`. + Steinberg::FUnknown* context = nullptr; + if (request.host_application_context_args) { + // TODO: Is this safe? These Steinberg smart pointers are + // scary + component_host_application_context_instances + [request.instance_id] = + Steinberg::owned(new YaHostApplicationHostImpl( + *this, + std::move( + *request.host_application_context_args))); + context = component_host_application_context_instances + [request.instance_id]; + } + return component_instances[request.instance_id]->initialize( - nullptr); + context); }, [&](const YaComponent::Terminate& request) -> YaComponent::Terminate::Response { diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 7c638e88..9bfd01e4 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -108,4 +108,19 @@ class Vst3Bridge : public HostBridge { std::map> component_instances; std::mutex component_instances_mutex; + + /** + * If an `IHostApplication` was passed during + * `{IPluginBase,IComponent}::initialize()`, then we'll store a proxy object + * here. The instance ID is the same as the corresponding instance ID in + * `component_instances`. When an instance gets removed from + * `component_instances`, it should also be removed from here. + * + * XXX: If it turns out we need to keep track of more of these kinds of + * objects, then we should create a wrapper so that everything can stay + * in `component_instances` + */ + std::map> + component_host_application_context_instances; + std::mutex component_host_application_context_instance_mutex; }; From 32c1028736a08fd2c544f3296fb0570d7e644fbb Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 15:50:41 +0100 Subject: [PATCH 164/456] Implement IPluginFactory3::setHostContext() Now the plugin factories are fully implemented (at least, functionality wise, we still can't create most kinds of objects). --- README.md | 4 +-- src/common/communication/vst2.h | 2 +- src/common/logging/vst3.cpp | 7 +++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 3 +- src/common/serialization/vst3/component.h | 5 ++-- .../serialization/vst3/host-application.cpp | 2 +- .../serialization/vst3/host-application.h | 16 +++++----- .../serialization/vst3/plugin-factory.h | 21 ++++++++++++-- .../bridges/vst3-impls/plugin-factory.cpp | 29 +++++++++++++++---- .../bridges/vst3-impls/plugin-factory.h | 7 +++++ src/wine-host/bridges/vst2.cpp | 2 +- src/wine-host/bridges/vst3.cpp | 13 +++++++++ src/wine-host/bridges/vst3.h | 7 +++++ 14 files changed, 95 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index be611af7..7bd354e0 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,11 @@ imcomplete list of things that still have to be done before this can be used: - Left to implement: - `YaHostApplicationHostImpl::createComponent`. - - `IPluginFactory3::setHostContext()` using the same host application context proxy method. - The rest of `IComponent`'s functions - - `IPluginFactory3::setHostContext()` - All other mandatory interfaces - All other optional interfaces - Fully implemented: - - Nothing yet + - `GetPluginFactory()` and `IPluginFactory{,2,3}` - Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index 26c038a6..60bb1e95 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -380,7 +380,7 @@ class Vst2Sockets : public Sockets { * @param plugin The `AEffect` instance that should be passed to the callback * function. During `WantsAEffect` we'll send back a copy of this, and when we * get sent an `AEffect` instance (e.g. during `audioMasterIOChanged()`) we'll - * write the updated values to this isntance. + * write the updated values to this instance. * @param callback The function to call with the arguments received from the * socket, either `AEffect::dispatcher()` or `audioMasterCallback()`. * diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 43ee3330..3241ac8e 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -78,6 +78,13 @@ void Vst3Logger::log_request(bool is_host_vst, [](auto& message) { message << "GetPluginFactory()"; }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaPluginFactory::SetHostContext&) { + log_request_base(is_host_vst, [](auto& message) { + message << "IPluginFactory3::setHostContext(IHostApplication*)"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { log_request_base(is_host_vst, [](auto& message) { message << "Requesting "; diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 324186bf..a580575f 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -61,6 +61,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::Initialize&); void log_request(bool is_host_vst, const YaComponent::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); + void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); void log_response(bool is_host_vst, const Ack&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 449547b8..60683920 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -61,7 +61,8 @@ using ControlRequest = std::variant; + YaPluginFactory::Construct, + YaPluginFactory::SetHostContext>; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index b370be25..11ecf078 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -43,7 +43,8 @@ * for everything other than the edit controller's class ID. * * TODO: I think it's expected that components also implement `IAudioProcessor` - * and `IConnectionPoint`. + * and `IConnectionPoint`. We should use the same approach as in the + * plugin factory to implement multiple, possibly optional, interfaces. */ class YaComponent : public Steinberg::Vst::IComponent { public: @@ -57,7 +58,7 @@ class YaComponent : public Steinberg::Vst::IComponent { * Read arguments from an existing implementation. */ ConstructArgs(Steinberg::IPtr component, - size_t isntance_id); + size_t instance_id); /** * The unique identifier for this specific instance. diff --git a/src/common/serialization/vst3/host-application.cpp b/src/common/serialization/vst3/host-application.cpp index 30f9a14e..b5695028 100644 --- a/src/common/serialization/vst3/host-application.cpp +++ b/src/common/serialization/vst3/host-application.cpp @@ -20,7 +20,7 @@ YaHostApplication::ConstructArgs::ConstructArgs() {} YaHostApplication::ConstructArgs::ConstructArgs( Steinberg::IPtr context, - size_t component_instance_id) + std::optional component_instance_id) : component_instance_id(component_instance_id) { Steinberg::Vst::String128 name_array; if (context->getName(name_array) == Steinberg::kResultOk) { diff --git a/src/common/serialization/vst3/host-application.h b/src/common/serialization/vst3/host-application.h index 6a88644f..80845748 100644 --- a/src/common/serialization/vst3/host-application.h +++ b/src/common/serialization/vst3/host-application.h @@ -48,16 +48,15 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { * Read arguments from an existing implementation. */ ConstructArgs(Steinberg::IPtr context, - size_t component_isntance_id); + std::optional component_instance_id); /** * The unique instance identifier of the component this host context has - * been passed to and thus belongs to. - * - * TODO: When we implement `IPluginFactory3::setHostContext()` this - * should be made optional. + * been passed to and thus belongs to, if we are handling + * `IpluginBase::initialize()`. When handling + * `IPluginFactory::setHostContext()` this will be empty. */ - native_size_t component_instance_id; + std::optional component_instance_id; /** * For `IHostApplication::getName`. @@ -66,7 +65,10 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { template void serialize(S& s) { - s.value8b(component_instance_id); + s.ext(component_instance_id, bitsery::ext::StdOptional{}, + [](S& s, native_size_t& instance_id) { + s.value8b(instance_id); + }); s.ext(name, bitsery::ext::StdOptional{}, [](S& s, std::u16string& name) { s.text2b(name, std::extent_v); diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 979038ce..532a9bb9 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -25,6 +25,7 @@ #include "../../bitsery/ext/vst3.h" #include "base.h" +#include "host-application.h" // TODO: After implementing one or two more of these, abstract away some of the // nasty bits @@ -155,10 +156,26 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { // From `IPluginFactory3` tresult PLUGIN_API getClassInfoUnicode(int32 index, Steinberg::PClassInfoW* info) override; + /** - * We'll pass a `IHostApplication` to the Windows VST3 plugin's factory when - * this is called so it can send messages. + * Message to pass through a call to `IPluginFactory3::setHostContext()` to + * the Wine plugin host. A proxy `YaHostApplication` should be created on + * the Wine plugin host and then passed as an argument to + * `IPluginFactory3::setHostContext()`. If the host called + * `IPluginFactory3::setHostContext()` with something other than an + * `IHostApplication*`, we return an error immediately and log the call. */ + struct SetHostContext { + using Response = UniversalTResult; + + YaHostApplication::ConstructArgs host_application_context_args; + + template + void serialize(S& s) { + s.object(host_application_context_args); + } + }; + virtual tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 0f971f39..27b31ba3 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -64,10 +64,27 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, } tresult PLUGIN_API -YaPluginFactoryPluginImpl::setHostContext(Steinberg::FUnknown* /*context*/) { - // TODO: The docs don't clearly specify what this should be doing, but from - // what I've seen this is only used to pass a `IHostApplication` - // instance. That's used to allow the plugin to create objects in the - // host. - return Steinberg::kNotImplemented; +YaPluginFactoryPluginImpl::setHostContext(Steinberg::FUnknown* context) { + // This `context` will likely be an `IHostApplication`. If it is, we will + // store it for future calls, create a proxy object on the Wine side, and + // then pass it to the Windows VST3 plugin's plugin factory using the same + // function. If we get passed anything else we'll just return instead since + // there's nothing we can do with it. + host_application_context = context; + + if (host_application_context) { + YaHostApplication::ConstructArgs host_application_context_args( + host_application_context, std::nullopt); + + return bridge + .send_message(YaPluginFactory::SetHostContext{ + .host_application_context_args = + std::move(host_application_context_args)}) + .native(); + } else { + bridge.logger.log_unknown_interface( + "In IPluginFactory3::setHostContext(), ignoring", + context ? std::optional(context->iid) : std::nullopt); + return Steinberg::kNotImplemented; + } } diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.h b/src/plugin/bridges/vst3-impls/plugin-factory.h index aee5a149..963ff26e 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.h +++ b/src/plugin/bridges/vst3-impls/plugin-factory.h @@ -30,4 +30,11 @@ class YaPluginFactoryPluginImpl : public YaPluginFactory { private: Vst3PluginBridge& bridge; + + /** + * An `IHostApplication` instance if we get one through + * `IPluginFactory3::setHostContext()`. + */ + Steinberg::FUnknownPtr + host_application_context; }; diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index ac349839..ff16659d 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -100,7 +100,7 @@ Vst2Bridge::Vst2Bridge(MainContext& main_context, sockets.connect(); // Initialize after communication has been set up - // We'll try to do the same `get_bridge_isntance` trick as in + // We'll try to do the same `get_bridge_instance` 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. { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 1696a6d8..8e8798e8 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -111,6 +111,19 @@ void Vst3Bridge::run() { -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( module->getFactory().get()); + }, + [&](YaPluginFactory::SetHostContext& request) + -> YaPluginFactory::SetHostContext::Response { + plugin_factory_host_application_context = + Steinberg::owned(new YaHostApplicationHostImpl( + *this, + std::move(request.host_application_context_args))); + + Steinberg::FUnknownPtr factory_3( + module->getFactory().get()); + assert(factory_3); + return factory_3->setHostContext( + plugin_factory_host_application_context); }}); } diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 9bfd01e4..b871155a 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -99,6 +99,13 @@ class Vst3Bridge : public HostBridge { */ std::atomic_size_t current_instance_id; + /** + * The host application context proxy object if we got passed a host + * application context during a call to `IPluginFactory3::setHostContext()` + * by the host. + */ + Steinberg::IPtr plugin_factory_host_application_context; + // Below are managed instances we created for // `IPluginFactory::createInstance()`. The keys in all of these maps are the // unique identifiers we generated for them so we can identify specific From 3f3759e5fc7c5df78d28cc5611f4afa2e3d0073b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 16:01:20 +0100 Subject: [PATCH 165/456] Log calls to unimplemented functions --- src/plugin/bridges/vst3-impls/component.cpp | 7 +++++++ src/wine-host/bridges/vst3-impls/host-application.cpp | 1 + 2 files changed, 8 insertions(+) diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index ba6dfee3..91a7ee93 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -75,6 +75,7 @@ tresult PLUGIN_API YaComponentPluginImpl::terminate() { tresult PLUGIN_API YaComponentPluginImpl::setIoMode(Steinberg::Vst::IoMode mode) { // TODO: Implement + bridge.logger.log("TODO: IComponent::setIoMode()"); return Steinberg::kNotImplemented; } @@ -82,6 +83,7 @@ int32 PLUGIN_API YaComponentPluginImpl::getBusCount(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir) { // TODO: Implement + bridge.logger.log("TODO: IComponent::getBusCount()"); return Steinberg::kNotImplemented; } @@ -98,6 +100,7 @@ tresult PLUGIN_API YaComponentPluginImpl::getRoutingInfo( Steinberg::Vst::RoutingInfo& inInfo, Steinberg::Vst::RoutingInfo& outInfo /*out*/) { // TODO: Implement + bridge.logger.log("TODO: IComponent::getRoutingInfo()"); return Steinberg::kNotImplemented; } @@ -107,20 +110,24 @@ YaComponentPluginImpl::activateBus(Steinberg::Vst::MediaType type, int32 index, TBool state) { // TODO: Implement + bridge.logger.log("TODO: IComponent::activateBus()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API YaComponentPluginImpl::setActive(TBool state) { // TODO: Implement + bridge.logger.log("TODO: IComponent::setActive()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API YaComponentPluginImpl::setState(Steinberg::IBStream* state) { // TODO: Implement + bridge.logger.log("TODO: IComponent::setState()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { // TODO: Implement + bridge.logger.log("TODO: IComponent::getState()"); return Steinberg::kNotImplemented; } diff --git a/src/wine-host/bridges/vst3-impls/host-application.cpp b/src/wine-host/bridges/vst3-impls/host-application.cpp index b53e5b73..dc62563c 100644 --- a/src/wine-host/bridges/vst3-impls/host-application.cpp +++ b/src/wine-host/bridges/vst3-impls/host-application.cpp @@ -43,5 +43,6 @@ YaHostApplicationHostImpl::createInstance(Steinberg::TUID cid, Steinberg::TUID _iid, void** obj) { // TODO: Implement + std::cerr << "TODO: IHostApplication::createInstance()" << std::endl; return Steinberg::kNotImplemented; } From 116b618ac58f2b8075a4a6c9407736ab1c5e61d3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 16:12:30 +0100 Subject: [PATCH 166/456] Implement IComponent::setIoMode() --- src/common/logging/vst3.cpp | 8 ++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/component.h | 19 +++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 7 ++++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 3241ac8e..f9a2c25a 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -72,6 +72,14 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::SetIoMode& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::setIoMode(" + << request.mode << ")"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index a580575f..468887c6 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -60,6 +60,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::Destruct&); void log_request(bool is_host_vst, const YaComponent::Initialize&); void log_request(bool is_host_vst, const YaComponent::Terminate&); + void log_request(bool is_host_vst, const YaComponent::SetIoMode&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 60683920..d5f875ad 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -61,6 +61,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 11ecf078..8bb5cb2b 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -171,6 +171,25 @@ class YaComponent : public Steinberg::Vst::IComponent { // From `IComponent` tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override; + + /** + * Message to pass through a call to `IComponent::setIoMode(IoMode)` to the + * Wine plugin host. + */ + struct SetIoMode { + using Response = UniversalTResult; + + native_size_t instance_id; + + Steinberg::Vst::IoMode mode; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(mode); + } + }; + virtual tresult PLUGIN_API setIoMode(Steinberg::Vst::IoMode mode) override = 0; virtual int32 PLUGIN_API diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 91a7ee93..09c056b2 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -74,9 +74,10 @@ tresult PLUGIN_API YaComponentPluginImpl::terminate() { tresult PLUGIN_API YaComponentPluginImpl::setIoMode(Steinberg::Vst::IoMode mode) { - // TODO: Implement - bridge.logger.log("TODO: IComponent::setIoMode()"); - return Steinberg::kNotImplemented; + return bridge + .send_message(YaComponent::SetIoMode{ + .instance_id = arguments.instance_id, .mode = mode}) + .native(); } int32 PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 8e8798e8..4611d348 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -107,6 +107,11 @@ void Vst3Bridge::run() { -> YaComponent::Terminate::Response { return component_instances[request.instance_id]->terminate(); }, + [&](const YaComponent::SetIoMode& request) + -> YaComponent::SetIoMode::Response { + return component_instances[request.instance_id]->setIoMode( + request.mode); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From 34b2fa8905fb9592a72ea74e9100a1e39f2d27de Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 16:18:13 +0100 Subject: [PATCH 167/456] Implement IComponent::getBusCount() --- src/common/logging/vst3.cpp | 8 ++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/component.h | 21 +++++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 7 ++++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index f9a2c25a..98c3ec0e 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -80,6 +80,14 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetBusCount& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getBusCount(" + << request.type << ", " << request.dir << ")"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 468887c6..a272f1f6 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -61,6 +61,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::Initialize&); void log_request(bool is_host_vst, const YaComponent::Terminate&); void log_request(bool is_host_vst, const YaComponent::SetIoMode&); + void log_request(bool is_host_vst, const YaComponent::GetBusCount&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index d5f875ad..5996ebe6 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -62,6 +62,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 8bb5cb2b..9545d59a 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -192,6 +192,27 @@ class YaComponent : public Steinberg::Vst::IComponent { virtual tresult PLUGIN_API setIoMode(Steinberg::Vst::IoMode mode) override = 0; + + /** + * Message to pass through a call to `IComponent::getBusCount(type, dir)` to + * the Wine plugin host. + */ + struct GetBusCount { + using Response = UniversalTResult; + + native_size_t instance_id; + + Steinberg::Vst::BusType type; + Steinberg::Vst::BusDirection dir; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(type); + s.value4b(dir); + } + }; + virtual int32 PLUGIN_API getBusCount(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir) override = 0; diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 09c056b2..5f6b9c24 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -83,9 +83,10 @@ YaComponentPluginImpl::setIoMode(Steinberg::Vst::IoMode mode) { int32 PLUGIN_API YaComponentPluginImpl::getBusCount(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir) { - // TODO: Implement - bridge.logger.log("TODO: IComponent::getBusCount()"); - return Steinberg::kNotImplemented; + return bridge + .send_message(YaComponent::GetBusCount{ + .instance_id = arguments.instance_id, .type = type, .dir = dir}) + .native(); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 4611d348..97e817e4 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -112,6 +112,11 @@ void Vst3Bridge::run() { return component_instances[request.instance_id]->setIoMode( request.mode); }, + [&](const YaComponent::GetBusCount& request) + -> YaComponent::GetBusCount::Response { + return component_instances[request.instance_id]->getBusCount( + request.type, request.dir); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From 9df812952e190625550f49403f6d945f6896ddce Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 16:56:09 +0100 Subject: [PATCH 168/456] Implement IComponent::GetBusInfo() --- src/common/logging/vst3.cpp | 19 ++++++++ src/common/logging/vst3.h | 2 + src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/component.h | 53 +++++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 11 ++++- src/wine-host/bridges/vst3.cpp | 9 ++++ 6 files changed, 93 insertions(+), 2 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 98c3ec0e..8b41dc23 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -88,6 +88,15 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetBusInfo& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getBusInfo(" + << request.type << ", " << request.dir << ", " << request.index + << ", &bus)"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, @@ -126,6 +135,16 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response(bool is_host_vst, + const YaComponent::GetBusInfoResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result.native() == Steinberg::kResultOk) { + message << ", "; + } + }); +} + void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs& args) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index a272f1f6..2e983e68 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -62,6 +62,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::Terminate&); void log_request(bool is_host_vst, const YaComponent::SetIoMode&); void log_request(bool is_host_vst, const YaComponent::GetBusCount&); + void log_request(bool is_host_vst, const YaComponent::GetBusInfo&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); @@ -70,6 +71,7 @@ class Vst3Logger { void log_response( bool is_host_vst, const std::variant&); + void log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 5996ebe6..5a8d0845 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -63,6 +63,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 9545d59a..8e086017 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -216,6 +216,45 @@ class YaComponent : public Steinberg::Vst::IComponent { virtual int32 PLUGIN_API getBusCount(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir) override = 0; + + /** + * The response code and returned bus information for a call to + * `IComponent::getBusInfo(type, dir, index, bus )`. + */ + struct GetBusInfoResponse { + UniversalTResult result; + Steinberg::Vst::BusInfo updated_bus; + + template + void serialize(S& s) { + s.object(result); + s.object(updated_bus); + } + }; + + /** + * Message to pass through a call to `IComponent::getBusInfo(type, dir, + * index, bus )` to the Wine plugin host. + */ + struct GetBusInfo { + using Response = GetBusInfoResponse; + + native_size_t instance_id; + + Steinberg::Vst::BusType type; + Steinberg::Vst::BusDirection dir; + int32 index; + Steinberg::Vst::BusInfo bus; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(type); + s.value4b(dir); + s.object(bus); + } + }; + virtual tresult PLUGIN_API getBusInfo(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir, @@ -246,3 +285,17 @@ void serialize( std::variant& result) { s.ext(result, bitsery::ext::StdVariant{}); } + +namespace Steinberg { +namespace Vst { +template +void serialize(S& s, Steinberg::Vst::BusInfo& info) { + s.value4b(info.mediaType); + s.value4b(info.direction); + s.value4b(info.channelCount); + s.container2b(info.name); + s.value4b(info.busType); + s.value4b(info.flags); +} +} // namespace Vst +} // namespace Steinberg diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 5f6b9c24..c570a313 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -94,8 +94,15 @@ YaComponentPluginImpl::getBusInfo(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir, int32 index, Steinberg::Vst::BusInfo& bus /*out*/) { - // TODO: Implement - return Steinberg::kNotImplemented; + const GetBusInfoResponse response = bridge.send_message( + YaComponent::GetBusInfo{.instance_id = arguments.instance_id, + .type = type, + .dir = dir, + .index = index, + .bus = bus}); + + bus = response.updated_bus; + return response.result.native(); } tresult PLUGIN_API YaComponentPluginImpl::getRoutingInfo( diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 97e817e4..a5be655b 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -117,6 +117,15 @@ void Vst3Bridge::run() { return component_instances[request.instance_id]->getBusCount( request.type, request.dir); }, + [&](YaComponent::GetBusInfo& request) + -> YaComponent::GetBusInfo::Response { + const tresult result = + component_instances[request.instance_id]->getBusInfo( + request.type, request.dir, request.index, request.bus); + + return YaComponent::GetBusInfoResponse{ + .result = result, .updated_bus = request.bus}; + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From 6adeb1987d5e7883cb460bd0bfca03d0291d77c6 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 16:59:16 +0100 Subject: [PATCH 169/456] Log function arguments kwargs-style This would make everything a bit clearer since the SDK uses bitfields everywhere. --- src/common/logging/vst3.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 8b41dc23..7026922f 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -75,24 +75,26 @@ void Vst3Logger::log_request(bool is_host_vst, void Vst3Logger::log_request(bool is_host_vst, const YaComponent::SetIoMode& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::setIoMode(" - << request.mode << ")"; + message << "::setIoMode(mode = " << request.mode << ")"; }); } void Vst3Logger::log_request(bool is_host_vst, const YaComponent::GetBusCount& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::getBusCount(" - << request.type << ", " << request.dir << ")"; + message << "::getBusCount(type = " << request.type + << ", dir = " << request.dir << ")"; }); } void Vst3Logger::log_request(bool is_host_vst, const YaComponent::GetBusInfo& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::getBusInfo(" - << request.type << ", " << request.dir << ", " << request.index + message << "::getBusInfo(type = " << request.type + << ", dir = " << request.dir << ", index = " << request.index << ", &bus)"; }); } From 583645bb46dc070ecfa76272e9b51b5cc7723562 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 21:31:59 +0100 Subject: [PATCH 170/456] Add a holder for component contexts and pointers When implementing the other component interfaces we want to be able to store the `FUnknownPtrs`. --- src/wine-host/bridges/vst3.cpp | 60 ++++++++++++++++++---------------- src/wine-host/bridges/vst3.h | 46 ++++++++++++++++---------- 2 files changed, 61 insertions(+), 45 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index a5be655b..f37f6bd8 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -22,6 +22,12 @@ #include "vst3-impls/host-application.h" +ComponentInstance::ComponentInstance() {} + +ComponentInstance::ComponentInstance( + Steinberg::IPtr component) + : component(component) {} + Vst3Bridge::Vst3Bridge(MainContext& main_context, std::string plugin_dll_path, std::string endpoint_base_dir) @@ -44,6 +50,11 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context, } void Vst3Bridge::run() { + // XXX: In theory all of thise should be safe assuming the host doesn't do + // anything weird. We're using mutexes when inserting and removing + // things, but for correctness we should have a multiple-readers + // single-writer style lock since concurrent reads and writes can also + // be unsafe. sockets.host_vst_control.receive_messages( std::nullopt, overload{ @@ -61,7 +72,8 @@ void Vst3Bridge::run() { component_instances[instance_id] = std::move(component); return YaComponent::ConstructArgs( - component_instances[instance_id], instance_id); + component_instances[instance_id].component, + instance_id); } else { // The actual result is lost here return UniversalTResult(Steinberg::kNotImplemented); @@ -69,15 +81,8 @@ void Vst3Bridge::run() { }, [&](const YaComponent::Destruct& request) -> YaComponent::Destruct::Response { - std::scoped_lock lock( - component_instances_mutex, - component_host_application_context_instance_mutex); + std::lock_guard lock(component_instances_mutex); component_instances.erase(request.instance_id); - if (component_host_application_context_instances.contains( - request.instance_id)) { - component_host_application_context_instances.erase( - request.instance_id); - } return Ack{}; }, @@ -88,40 +93,39 @@ void Vst3Bridge::run() { // be cleaned up again during `YaComponent::Destruct`. Steinberg::FUnknown* context = nullptr; if (request.host_application_context_args) { - // TODO: Is this safe? These Steinberg smart pointers are - // scary - component_host_application_context_instances - [request.instance_id] = - Steinberg::owned(new YaHostApplicationHostImpl( - *this, - std::move( - *request.host_application_context_args))); - context = component_host_application_context_instances - [request.instance_id]; + component_instances[request.instance_id] + .hsot_application_context = + Steinberg::owned(new YaHostApplicationHostImpl( + *this, + std::move(*request.host_application_context_args))); + context = component_instances[request.instance_id] + .hsot_application_context; } - return component_instances[request.instance_id]->initialize( - context); + return component_instances[request.instance_id] + .component->initialize(context); }, [&](const YaComponent::Terminate& request) -> YaComponent::Terminate::Response { - return component_instances[request.instance_id]->terminate(); + return component_instances[request.instance_id] + .component->terminate(); }, [&](const YaComponent::SetIoMode& request) -> YaComponent::SetIoMode::Response { - return component_instances[request.instance_id]->setIoMode( - request.mode); + return component_instances[request.instance_id] + .component->setIoMode(request.mode); }, [&](const YaComponent::GetBusCount& request) -> YaComponent::GetBusCount::Response { - return component_instances[request.instance_id]->getBusCount( - request.type, request.dir); + return component_instances[request.instance_id] + .component->getBusCount(request.type, request.dir); }, [&](YaComponent::GetBusInfo& request) -> YaComponent::GetBusInfo::Response { const tresult result = - component_instances[request.instance_id]->getBusInfo( - request.type, request.dir, request.index, request.bus); + component_instances[request.instance_id] + .component->getBusInfo(request.type, request.dir, + request.index, request.bus); return YaComponent::GetBusInfoResponse{ .result = result, .updated_bus = request.bus}; diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index b871155a..fc1e4106 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -24,6 +24,34 @@ #include "../../common/configuration.h" #include "common.h" +/** + * A holder for an `IComponent` instance created from the factory along with any + * host context proxy objects belonging to it, and several predefined + * `FUnknownPtrs` so we don't have to do these dynamic casts all the times.. + */ +struct ComponentInstance { + ComponentInstance(); + + ComponentInstance(Steinberg::IPtr component); + + /** + * If the host passes an `IHostApplication` during + * `IPluginBase::initialize()`, we'll store a proxy object here and then + * pass it to `component->initialize()`. + */ + Steinberg::IPtr hsot_application_context; + + /** + * The `IComponent` instance we created. + */ + Steinberg::IPtr component; + + // All smart pointers below are created from `component`. They will be null + // pointers if `component` did not implement the interface. + + // TODO: Implement things like `IConnectionPoint` and `IAudioProcessor` +}; + /** * This hosts a Windows VST3 plugin, forwards messages sent by the Linux VST * plugin and provides host callback function for the plugin to talk back. @@ -112,22 +140,6 @@ class Vst3Bridge : public HostBridge { // instances. The mutexes are used for operations that insert or remove // items, and not for regular access. - std::map> - component_instances; + std::map component_instances; std::mutex component_instances_mutex; - - /** - * If an `IHostApplication` was passed during - * `{IPluginBase,IComponent}::initialize()`, then we'll store a proxy object - * here. The instance ID is the same as the corresponding instance ID in - * `component_instances`. When an instance gets removed from - * `component_instances`, it should also be removed from here. - * - * XXX: If it turns out we need to keep track of more of these kinds of - * objects, then we should create a wrapper so that everything can stay - * in `component_instances` - */ - std::map> - component_host_application_context_instances; - std::mutex component_host_application_context_instance_mutex; }; From 5b6a8ebfac840b979e14cebf9e4f767860220f8a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 21:51:56 +0100 Subject: [PATCH 171/456] Implement IComponent::getRoutingInfo() --- src/common/logging/vst3.cpp | 29 +++++++++++++ src/common/logging/vst3.h | 3 ++ src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/component.h | 45 +++++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 11 +++-- src/wine-host/bridges/vst3.cpp | 12 ++++++ 6 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 7026922f..cc43a3f3 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -99,6 +99,19 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetRoutingInfo& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getRoutingInfo(inInfo = , outInfo = )"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, @@ -147,6 +160,22 @@ void Vst3Logger::log_response(bool is_host_vst, }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaComponent::GetRoutingInfoResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result.native() == Steinberg::kResultOk) { + message << ", "; + } + }); +} + void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs& args) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 2e983e68..b9a6ea9c 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -63,6 +63,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::SetIoMode&); void log_request(bool is_host_vst, const YaComponent::GetBusCount&); void log_request(bool is_host_vst, const YaComponent::GetBusInfo&); + void log_request(bool is_host_vst, const YaComponent::GetRoutingInfo&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); @@ -72,6 +73,8 @@ class Vst3Logger { bool is_host_vst, const std::variant&); void log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse&); + void log_response(bool is_host_vst, + const YaComponent::GetRoutingInfoResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 5a8d0845..5c1abb87 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -64,6 +64,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 8e086017..a92c9a63 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -260,6 +260,44 @@ class YaComponent : public Steinberg::Vst::IComponent { Steinberg::Vst::BusDirection dir, int32 index, Steinberg::Vst::BusInfo& bus /*out*/) override = 0; + + /** + * The response code and returned routing information for a call to + * `IComponent::getRoutingInfo(inInfo, outInfo )`. + */ + struct GetRoutingInfoResponse { + UniversalTResult result; + Steinberg::Vst::RoutingInfo updated_in_info; + Steinberg::Vst::RoutingInfo updated_out_info; + + template + void serialize(S& s) { + s.object(result); + s.object(updated_in_info); + s.object(updated_out_info); + } + }; + + /** + * Message to pass through a call to `IComponent::getRoutingInfo(inInfo, + * outInfo )` to the Wine plugin host. + */ + struct GetRoutingInfo { + using Response = GetRoutingInfoResponse; + + native_size_t instance_id; + + Steinberg::Vst::RoutingInfo in_info; + Steinberg::Vst::RoutingInfo out_info; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.object(in_info); + s.object(out_info); + } + }; + virtual tresult PLUGIN_API getRoutingInfo(Steinberg::Vst::RoutingInfo& inInfo, Steinberg::Vst::RoutingInfo& outInfo /*out*/) override = 0; @@ -297,5 +335,12 @@ void serialize(S& s, Steinberg::Vst::BusInfo& info) { s.value4b(info.busType); s.value4b(info.flags); } + +template +void serialize(S& s, Steinberg::Vst::RoutingInfo& info) { + s.value4b(info.mediaType); + s.value4b(info.busIndex); + s.value4b(info.channel); +} } // namespace Vst } // namespace Steinberg diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index c570a313..d362db06 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -108,9 +108,14 @@ YaComponentPluginImpl::getBusInfo(Steinberg::Vst::MediaType type, tresult PLUGIN_API YaComponentPluginImpl::getRoutingInfo( Steinberg::Vst::RoutingInfo& inInfo, Steinberg::Vst::RoutingInfo& outInfo /*out*/) { - // TODO: Implement - bridge.logger.log("TODO: IComponent::getRoutingInfo()"); - return Steinberg::kNotImplemented; + const GetRoutingInfoResponse response = bridge.send_message( + YaComponent::GetRoutingInfo{.instance_id = arguments.instance_id, + .in_info = inInfo, + .out_info = outInfo}); + + inInfo = response.updated_in_info; + outInfo = response.updated_out_info; + return response.result.native(); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index f37f6bd8..8480ec65 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -130,6 +130,18 @@ void Vst3Bridge::run() { return YaComponent::GetBusInfoResponse{ .result = result, .updated_bus = request.bus}; }, + [&](YaComponent::GetRoutingInfo& request) + -> YaComponent::GetRoutingInfo::Response { + const tresult result = + component_instances[request.instance_id] + .component->getRoutingInfo(request.in_info, + request.out_info); + + return YaComponent::GetRoutingInfoResponse{ + .result = result, + .updated_in_info = request.in_info, + .updated_out_info = request.out_info}; + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From 16b949bccfb475a0a532d7e7561585c9f74895ba Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 21:53:08 +0100 Subject: [PATCH 172/456] Log all function calls kwargs-style --- src/common/logging/vst3.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index cc43a3f3..b2035a32 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -39,7 +39,8 @@ void Vst3Logger::log_unknown_interface( void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Construct&) { log_request_base(is_host_vst, [](auto& message) { // TODO: Log the cid in some readable way, if possible - message << "IPluginFactory::createComponent(cid, IComponent::iid, " + message << "IPluginFactory::createComponent(cid = ..., _iid = " + "IComponent::iid, " "&obj)"; }); } @@ -55,7 +56,8 @@ void Vst3Logger::log_request(bool is_host_vst, void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::initialize("; + message << "::initialize(context = "; if (request.host_application_context_args) { message << "IHostApplication*"; } else { From 43296675f6a65e5bd68c5c2e4ebd7a1b2cc51f00 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 22:07:19 +0100 Subject: [PATCH 173/456] Implement IComponent::activateBus() --- src/common/logging/vst3.cpp | 10 +++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/component.h | 25 +++++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 11 ++++++--- src/wine-host/bridges/vst3.cpp | 6 +++++ 6 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index b2035a32..4ef9e8e5 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -114,6 +114,16 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::ActivateBus& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::activateBus(type = " << request.type + << ", dir = " << request.dir << ", index = " << request.index + << ", state = " << (request.state ? "true" : "false") << ">)"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index b9a6ea9c..ffcd7002 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -64,6 +64,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::GetBusCount&); void log_request(bool is_host_vst, const YaComponent::GetBusInfo&); void log_request(bool is_host_vst, const YaComponent::GetRoutingInfo&); + void log_request(bool is_host_vst, const YaComponent::ActivateBus&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 5c1abb87..11cf13f3 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -65,6 +65,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index a92c9a63..0ec6f177 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -301,6 +301,31 @@ class YaComponent : public Steinberg::Vst::IComponent { virtual tresult PLUGIN_API getRoutingInfo(Steinberg::Vst::RoutingInfo& inInfo, Steinberg::Vst::RoutingInfo& outInfo /*out*/) override = 0; + + /** + * Message to pass through a call to `IComponent::activateBus(type, dir, + * index, state)` to the Wine plugin host. + */ + struct ActivateBus { + using Response = UniversalTResult; + + native_size_t instance_id; + + Steinberg::Vst::MediaType type; + Steinberg::Vst::BusDirection dir; + int32 index; + TBool state; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(type); + s.value4b(dir); + s.value4b(index); + s.value1b(state); + } + }; + virtual tresult PLUGIN_API activateBus(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir, int32 index, diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index d362db06..206b89c9 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -123,9 +123,14 @@ YaComponentPluginImpl::activateBus(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir, int32 index, TBool state) { - // TODO: Implement - bridge.logger.log("TODO: IComponent::activateBus()"); - return Steinberg::kNotImplemented; + return bridge + .send_message( + YaComponent::ActivateBus{.instance_id = arguments.instance_id, + .type = type, + .dir = dir, + .index = index, + .state = state}) + .native(); } tresult PLUGIN_API YaComponentPluginImpl::setActive(TBool state) { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 8480ec65..8baee5ae 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -142,6 +142,12 @@ void Vst3Bridge::run() { .updated_in_info = request.in_info, .updated_out_info = request.out_info}; }, + [&](const YaComponent::ActivateBus& request) + -> YaComponent::ActivateBus::Response { + return component_instances[request.instance_id] + .component->activateBus(request.type, request.dir, + request.index, request.state); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From 7341fab0ead638866944be6b2acbc76de42ffc28 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 22:17:06 +0100 Subject: [PATCH 174/456] Implement IComponent::setActive() --- src/common/logging/vst3.cpp | 11 ++++++++++- src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/component.h | 19 +++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 7 ++++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 4ef9e8e5..d7387559 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -120,7 +120,16 @@ void Vst3Logger::log_request(bool is_host_vst, message << "::activateBus(type = " << request.type << ", dir = " << request.dir << ", index = " << request.index - << ", state = " << (request.state ? "true" : "false") << ">)"; + << ", state = " << (request.state ? "true" : "false") << ")"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::SetActive& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::setActive(state = " << (request.state ? "true" : "false") + << ")"; }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index ffcd7002..bba8ff86 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -65,6 +65,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::GetBusInfo&); void log_request(bool is_host_vst, const YaComponent::GetRoutingInfo&); void log_request(bool is_host_vst, const YaComponent::ActivateBus&); + void log_request(bool is_host_vst, const YaComponent::SetActive&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 11cf13f3..490851dd 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -66,6 +66,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 0ec6f177..6e505634 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -330,6 +330,25 @@ class YaComponent : public Steinberg::Vst::IComponent { Steinberg::Vst::BusDirection dir, int32 index, TBool state) override = 0; + + /** + * Message to pass through a call to `IComponent::setActive(state)` to the + * Wine plugin host. + */ + struct SetActive { + using Response = UniversalTResult; + + native_size_t instance_id; + + TBool state; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value1b(state); + } + }; + virtual tresult PLUGIN_API setActive(TBool state) override = 0; virtual tresult PLUGIN_API setState(Steinberg::IBStream* state) override = 0; diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 206b89c9..15e8686d 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -134,9 +134,10 @@ YaComponentPluginImpl::activateBus(Steinberg::Vst::MediaType type, } tresult PLUGIN_API YaComponentPluginImpl::setActive(TBool state) { - // TODO: Implement - bridge.logger.log("TODO: IComponent::setActive()"); - return Steinberg::kNotImplemented; + return bridge + .send_message(YaComponent::SetActive{ + .instance_id = arguments.instance_id, .state = state}) + .native(); } tresult PLUGIN_API YaComponentPluginImpl::setState(Steinberg::IBStream* state) { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 8baee5ae..b1c236dc 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -148,6 +148,11 @@ void Vst3Bridge::run() { .component->activateBus(request.type, request.dir, request.index, request.state); }, + [&](const YaComponent::SetActive& request) + -> YaComponent::SetActive::Response { + return component_instances[request.instance_id] + .component->setActive(request.state); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From 42664be3788d55722394a7324df142f7e9475ff9 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 13 Dec 2020 23:19:37 +0100 Subject: [PATCH 175/456] Use std::copy_n instead of pointer arithmetic --- src/plugin/bridges/vst2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index e4a07e60..15a9bca8 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -444,8 +444,8 @@ void Vst2PluginBridge::do_process(T** inputs, T** outputs, int sample_frames) { std::vector> input_buffers(plugin.numInputs, std::vector(sample_frames)); for (int channel = 0; channel < plugin.numInputs; channel++) { - std::copy(inputs[channel], inputs[channel] + sample_frames, - input_buffers[channel].begin()); + std::copy_n(inputs[channel], sample_frames, + input_buffers[channel].begin()); } const AudioBuffers request{input_buffers, sample_frames}; From c463543ac97a1ee5048dddb37f28f9df3698b60c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 00:02:31 +0100 Subject: [PATCH 176/456] Implement a serializable vector based IBStream We can now use this for implementing reading and writing preset data. --- src/common/serialization/vst2.h | 6 +- src/common/serialization/vst3/base.cpp | 137 +++++++++++++++++++++++++ src/common/serialization/vst3/base.h | 63 +++++++++++- 3 files changed, 202 insertions(+), 4 deletions(-) diff --git a/src/common/serialization/vst2.h b/src/common/serialization/vst2.h index eae8f1d2..e44dfc16 100644 --- a/src/common/serialization/vst2.h +++ b/src/common/serialization/vst2.h @@ -53,9 +53,9 @@ constexpr size_t max_midi_events = max_buffer_size / sizeof(size_t); [[maybe_unused]] constexpr size_t max_string_length = 64; /** - * The size for a buffer in which we're receiving chunks. Allow for up to 50 MB - * chunks. Hopefully no plugin will come anywhere near this limit, but it will - * add up when plugins start to audio samples in their presets. + * The maximum size for the buffer we're receiving chunks in. Allows for up to + * 50 MB chunks. Hopefully no plugin will come anywhere near this limit, but it + * will add up when plugins start to audio include samples in their presets. */ constexpr size_t binary_buffer_size = 50 << 20; diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index 24ef9618..1145dfb0 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -14,6 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include +#include + #include "base.h" UniversalTResult::UniversalTResult() : universal_result(Value::kResultFalse) {} @@ -122,3 +125,137 @@ UniversalTResult::Value UniversalTResult::to_universal_result( break; } } + +VectorStream::VectorStream(){FUNKNOWN_CTOR} + +VectorStream::VectorStream(Steinberg::IBStream* stream) { + FUNKNOWN_CTOR + + if (!stream) { + throw std::runtime_error("Null pointer passed to VectorStream()"); + } + + if (stream->seek(0, Steinberg::IBStream::IStreamSeekMode::kIBSeekEnd) != + Steinberg::kResultOk) { + throw std::runtime_error( + "IBStream passed to VectorStream() does not suport seeking to end"); + } + + // Now that we're at the end of the stream we know how large the buffer + // should be + int64 size; + assert(stream->tell(&size) == Steinberg::kResultOk); + + int32 num_bytes_read; + buffer.resize(size); + assert(stream->seek(0, Steinberg::IBStream::IStreamSeekMode::kIBSeekSet) != + Steinberg::kResultOk); + assert(stream->read(buffer.data(), size, &num_bytes_read) == + Steinberg::kResultOk); + assert(num_bytes_read == 0 || num_bytes_read == size); +} + +VectorStream::~VectorStream() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(VectorStream) +#pragma GCC diagnostic pop + +tresult PLUGIN_API VectorStream::queryInterface(Steinberg::FIDString _iid, + void** obj) { + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, Steinberg::IBStream) + QUERY_INTERFACE(_iid, obj, Steinberg::IBStream::iid, Steinberg::IBStream) + QUERY_INTERFACE(_iid, obj, Steinberg::ISizeableStream::iid, + Steinberg::ISizeableStream) + + *obj = nullptr; + return Steinberg::kNoInterface; +} + +tresult PLUGIN_API VectorStream::read(void* buffer, + int32 numBytes, + int32* numBytesRead) { + if (!buffer || numBytes < 0) { + return Steinberg::kInvalidArgument; + } + + size_t bytes_to_read = std::min(static_cast(numBytes), + this->buffer.size() - seek_position); + + std::copy_n(&this->buffer[seek_position], bytes_to_read, + reinterpret_cast(buffer)); + + seek_position += bytes_to_read; + if (numBytesRead) { + *numBytesRead = bytes_to_read; + } + + return Steinberg::kResultOk; +} + +tresult PLUGIN_API VectorStream::write(void* buffer, + int32 numBytes, + int32* numBytesWritten) { + if (!buffer || numBytes < 0) { + return Steinberg::kInvalidArgument; + } + + if (seek_position + numBytes > this->buffer.size()) { + this->buffer.resize(seek_position + numBytes); + } + + std::copy_n(reinterpret_cast(buffer), numBytes, + &this->buffer[seek_position]); + + seek_position += numBytes; + if (numBytesWritten) { + *numBytesWritten = numBytes; + } + + return Steinberg::kResultOk; +} + +tresult PLUGIN_API VectorStream::seek(int64 pos, int32 mode, int64* result) { + switch (mode) { + case kIBSeekSet: + seek_position = pos; + break; + case kIBSeekCur: + seek_position += pos; + break; + case kIBSeekEnd: + seek_position = this->buffer.size() + pos; + break; + default: + return Steinberg::kInvalidArgument; + break; + } + + if (result) { + *result = seek_position; + } + + return Steinberg::kResultOk; +} + +tresult PLUGIN_API VectorStream::tell(int64* pos) { + if (pos) { + *pos = seek_position; + return Steinberg::kResultOk; + } else { + return Steinberg::kInvalidArgument; + } +} + +tresult PLUGIN_API VectorStream::getStreamSize(int64& size) { + size = seek_position; + return Steinberg::kResultOk; +} + +tresult PLUGIN_API VectorStream::setStreamSize(int64 size) { + buffer.resize(size); + return Steinberg::kResultOk; +} diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index f1e4a5fc..1cdeccae 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -18,14 +18,17 @@ #include #include +#include #include #include +#include // Yet Another layer of includes, but these are some VST3-specific typedefs that // we'll need for all of our interfaces -using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::tresult; +using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::int64, + Steinberg::tresult; /** * Both `TUID` (`int8_t[16]`) and `FIDString` (`char*`) are hard to work with @@ -36,6 +39,13 @@ using ArrayUID = std::array< std::remove_reference_t()[0])>, std::extent_v>; +/** + * The maximum size for an `IBStream` we can serialize. Allows for up to 50 MB + * of preset data. Hopefully no plugin will come anywhere near this limit, but + * it will add up when plugins start to include audio samples in their presets. + */ +constexpr size_t max_vector_stream_size = 50 << 20; + /** * Empty struct for when we have send a response to some operation without any * result values. @@ -101,3 +111,54 @@ class UniversalTResult { Value universal_result; }; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Serialize an `IBStream` into an `std::vector`, and allow the + * receiving side to use it as an `IBStream` again. `ISizeableStream` is defined + * but then for whatever reason never used, but we'll implement it anyways. + */ +class VectorStream : public Steinberg::IBStream, + public Steinberg::ISizeableStream { + public: + VectorStream(); + + /** + * Read an existing stream. + */ + VectorStream(Steinberg::IBStream* stream); + + virtual ~VectorStream(); + + DECLARE_FUNKNOWN_METHODS + + // From `IBstream` + tresult PLUGIN_API read(void* buffer, + int32 numBytes, + int32* numBytesRead = nullptr) override; + tresult PLUGIN_API write(void* buffer, + int32 numBytes, + int32* numBytesWritten = nullptr) override; + tresult PLUGIN_API seek(int64 pos, + int32 mode, + int64* result = nullptr) override; + tresult PLUGIN_API tell(int64* pos) override; + + // From `ISizeableStream` + tresult PLUGIN_API getStreamSize(int64& size) override; + tresult PLUGIN_API setStreamSize(int64 size) override; + + template + void serialize(S& s) { + s.container1b(buffer, max_vector_stream_size); + // The seek position should always be initialized at 0 + } + + private: + std::vector buffer; + size_t seek_position; +}; + +#pragma GCC diagnostic pop From 816d1c150140c346ed116fd9e8475a8d3310ec3f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 12:09:47 +0100 Subject: [PATCH 177/456] Implement IComponent::setState() --- src/common/logging/vst3.cpp | 9 +++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/base.cpp | 4 ++++ src/common/serialization/vst3/base.h | 5 +++++ src/common/serialization/vst3/component.h | 19 +++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 7 ++++--- .../bridges/vst3-impls/plugin-factory.cpp | 9 +++++++-- src/wine-host/bridges/vst2.cpp | 2 +- src/wine-host/bridges/vst3.cpp | 5 +++++ 10 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index d7387559..83ee6f7e 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -133,6 +133,15 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::SetState& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::setState(state = )"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index bba8ff86..29d45d93 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -66,6 +66,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::GetRoutingInfo&); void log_request(bool is_host_vst, const YaComponent::ActivateBus&); void log_request(bool is_host_vst, const YaComponent::SetActive&); + void log_request(bool is_host_vst, const YaComponent::SetState&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 490851dd..3d63e5ab 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -67,6 +67,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index 1145dfb0..a45e1773 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -175,6 +175,10 @@ tresult PLUGIN_API VectorStream::queryInterface(Steinberg::FIDString _iid, return Steinberg::kNoInterface; } +size_t VectorStream::size() const { + return buffer.size(); +} + tresult PLUGIN_API VectorStream::read(void* buffer, int32 numBytes, int32* numBytesRead) { diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 1cdeccae..3bd11847 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -134,6 +134,11 @@ class VectorStream : public Steinberg::IBStream, DECLARE_FUNKNOWN_METHODS + /** + * Return the buffer's, used in the logging messages. + */ + size_t size() const; + // From `IBstream` tresult PLUGIN_API read(void* buffer, int32 numBytes, diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 6e505634..e73dd1c2 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -350,6 +350,25 @@ class YaComponent : public Steinberg::Vst::IComponent { }; virtual tresult PLUGIN_API setActive(TBool state) override = 0; + + /** + * Message to pass through a call to `IComponent::setState(state)` to the + * Wine plugin host. + */ + struct SetState { + using Response = UniversalTResult; + + native_size_t instance_id; + + VectorStream state; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.object(state); + } + }; + virtual tresult PLUGIN_API setState(Steinberg::IBStream* state) override = 0; virtual tresult PLUGIN_API diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 15e8686d..22800a6e 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -141,9 +141,10 @@ tresult PLUGIN_API YaComponentPluginImpl::setActive(TBool state) { } tresult PLUGIN_API YaComponentPluginImpl::setState(Steinberg::IBStream* state) { - // TODO: Implement - bridge.logger.log("TODO: IComponent::setState()"); - return Steinberg::kNotImplemented; + return bridge + .send_message(YaComponent::SetState{ + .instance_id = arguments.instance_id, .state = state}) + .native(); } tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 27b31ba3..d5522051 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -30,6 +30,11 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, void** obj) { // TODO: Do the same thing for other types + + // These arw pointers are scary. The idea here is that we return a newly + // initialized object (that initializes itself with a reference count of 1), + // and then the receiving side will use `Steinberg::owned()` to adopt it to + // an `IPtr`. ArrayUID cid_array; std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { @@ -38,8 +43,8 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, return std::visit( overload{ [&](YaComponent::ConstructArgs&& args) -> tresult { - // I find all of these raw pointers scary - *obj = new YaComponentPluginImpl(bridge, std::move(args)); + *obj = static_cast( + new YaComponentPluginImpl(bridge, std::move(args))); return Steinberg::kResultOk; }, [&](const UniversalTResult& code) { return code.native(); }}, diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index ff16659d..86f58ab9 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -441,7 +441,7 @@ class HostCallbackDataConverter : DefaultDataConverter { switch (opcode) { case audioMasterGetTime: // Write the returned `VstTimeInfo` struct into a field and - // make the function return a poitner to it in the function + // make the function return a pointer to it in the function // below. Depending on whether the host supported the // requested time information this operations returns either // a null pointer or a pointer to a `VstTimeInfo` object. diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index b1c236dc..b297733f 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -153,6 +153,11 @@ void Vst3Bridge::run() { return component_instances[request.instance_id] .component->setActive(request.state); }, + [&](YaComponent::SetState& request) + -> YaComponent::SetState::Response { + return component_instances[request.instance_id] + .component->setState(&request.state); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From 02e6fb1ba8becdeaf16626d4ee82a69591fb3009 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 12:24:09 +0100 Subject: [PATCH 178/456] Add a way to write a VectorStream back --- src/common/serialization/vst3/base.cpp | 16 ++++++++++++++++ src/common/serialization/vst3/base.h | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index a45e1773..ac29d4cb 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -175,6 +175,22 @@ tresult PLUGIN_API VectorStream::queryInterface(Steinberg::FIDString _iid, return Steinberg::kNoInterface; } +tresult VectorStream::write_back(Steinberg::IBStream* stream) { + if (!stream) { + return Steinberg::kInvalidArgument; + } + + int32 num_bytes_written; + assert(stream->seek(0, kIBSeekSet) == Steinberg::kResultOk); + assert(stream->write(buffer.data(), buffer.size(), &num_bytes_written) == + Steinberg::kResultOk); + + assert(num_bytes_written == 0 || + static_cast(num_bytes_written) == buffer.size()); + + return Steinberg::kResultOk; +} + size_t VectorStream::size() const { return buffer.size(); } diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 3bd11847..2f50174c 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -127,6 +127,8 @@ class VectorStream : public Steinberg::IBStream, /** * Read an existing stream. + * + * @throw std::runtime_error If we couldn't read from the stream. */ VectorStream(Steinberg::IBStream* stream); @@ -134,6 +136,12 @@ class VectorStream : public Steinberg::IBStream, DECLARE_FUNKNOWN_METHODS + /** + * Write the vector buffer back to an IBStream. After writing the seek + * position will be left at the end of the stream. + */ + tresult write_back(Steinberg::IBStream* stream); + /** * Return the buffer's, used in the logging messages. */ From e653142e458c851d0ec0ed3ef9ea1633480cfe99 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 12:33:35 +0100 Subject: [PATCH 179/456] Implement IComponent::getState() With this the basic IComponent interface is fully implemented. Next will be `IAudioProcessor` and `IConnectionPoint` as additions to IComponent. We'll use the same `known_iids` mechanism as used in the plugin factory. --- README.md | 4 ++- src/common/logging/vst3.cpp | 19 +++++++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/base.cpp | 6 ++-- src/common/serialization/vst3/base.h | 2 +- src/common/serialization/vst3/component.h | 31 +++++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 9 ++++-- src/wine-host/bridges/vst3.cpp | 9 ++++++ 9 files changed, 75 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7bd354e0..769fba67 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,13 @@ imcomplete list of things that still have to be done before this can be used: - Left to implement: - `YaHostApplicationHostImpl::createComponent`. - - The rest of `IComponent`'s functions + - `IAudioProcessor` and `IConnectionPoint` to supplement `IComponent` + - `IEditController{,2}` - All other mandatory interfaces - All other optional interfaces - Fully implemented: - `GetPluginFactory()` and `IPluginFactory{,2,3}` + - `IComponent` - Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 83ee6f7e..d77fcc49 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -142,6 +142,14 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetState& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getState(state = )"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, @@ -206,6 +214,17 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response(bool is_host_vst, + const YaComponent::GetStateResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result.native() == Steinberg::kResultOk) { + message << ", "; + } + }); +} + void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs& args) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 29d45d93..951466d1 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -67,6 +67,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::ActivateBus&); void log_request(bool is_host_vst, const YaComponent::SetActive&); void log_request(bool is_host_vst, const YaComponent::SetState&); + void log_request(bool is_host_vst, const YaComponent::GetState&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); @@ -78,6 +79,7 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse&); void log_response(bool is_host_vst, const YaComponent::GetRoutingInfoResponse&); + void log_response(bool is_host_vst, const YaComponent::GetStateResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 3d63e5ab..45ded5e4 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -68,6 +68,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index ac29d4cb..88542121 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -175,15 +175,15 @@ tresult PLUGIN_API VectorStream::queryInterface(Steinberg::FIDString _iid, return Steinberg::kNoInterface; } -tresult VectorStream::write_back(Steinberg::IBStream* stream) { +tresult VectorStream::write_back(Steinberg::IBStream* stream) const { if (!stream) { return Steinberg::kInvalidArgument; } int32 num_bytes_written; assert(stream->seek(0, kIBSeekSet) == Steinberg::kResultOk); - assert(stream->write(buffer.data(), buffer.size(), &num_bytes_written) == - Steinberg::kResultOk); + assert(stream->write(const_cast(buffer.data()), buffer.size(), + &num_bytes_written) == Steinberg::kResultOk); assert(num_bytes_written == 0 || static_cast(num_bytes_written) == buffer.size()); diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 2f50174c..2f5aef88 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -140,7 +140,7 @@ class VectorStream : public Steinberg::IBStream, * Write the vector buffer back to an IBStream. After writing the seek * position will be left at the end of the stream. */ - tresult write_back(Steinberg::IBStream* stream); + tresult write_back(Steinberg::IBStream* stream) const; /** * Return the buffer's, used in the logging messages. diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index e73dd1c2..99789178 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -371,6 +371,37 @@ class YaComponent : public Steinberg::Vst::IComponent { virtual tresult PLUGIN_API setState(Steinberg::IBStream* state) override = 0; + + /** + * The response code and written state for a call to + * `IComponent::getState(state)`. + */ + struct GetStateResponse { + UniversalTResult result; + VectorStream updated_state; + + template + void serialize(S& s) { + s.object(result); + s.object(updated_state); + } + }; + + /** + * Message to pass through a call to `IComponent::getState(state)` to the + * Wine plugin host. + */ + struct GetState { + using Response = GetStateResponse; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + virtual tresult PLUGIN_API getState(Steinberg::IBStream* state) override = 0; diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 22800a6e..f3c22279 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -148,7 +148,10 @@ tresult PLUGIN_API YaComponentPluginImpl::setState(Steinberg::IBStream* state) { } tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { - // TODO: Implement - bridge.logger.log("TODO: IComponent::getState()"); - return Steinberg::kNotImplemented; + const GetStateResponse response = bridge.send_message( + YaComponent::GetState{.instance_id = arguments.instance_id}); + + assert(response.updated_state.write_back(state) == Steinberg::kResultOk); + + return response.result.native(); } diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index b297733f..7da18669 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -158,6 +158,15 @@ void Vst3Bridge::run() { return component_instances[request.instance_id] .component->setState(&request.state); }, + [&](YaComponent::GetState& request) + -> YaComponent::GetState::Response { + VectorStream stream; + const tresult result = component_instances[request.instance_id] + .component->getState(&stream); + + return YaComponent::GetStateResponse{ + .result = result, .updated_state = std::move(stream)}; + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From 096171bb96ebe871920c44cfcdc3e69d8976ea09 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 12:48:21 +0100 Subject: [PATCH 180/456] Use the `known_iids` mechanism for IComponent --- src/common/serialization/vst3/README.md | 4 ++++ src/common/serialization/vst3/component.cpp | 14 ++++++++++---- src/common/serialization/vst3/component.h | 16 +++++++++++++++- src/common/serialization/vst3/plugin-factory.h | 2 +- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index d4a22cf9..12e801bf 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -70,6 +70,10 @@ instantiated and managed by the host. The basic model works as follows: native plugin in a `known_iids` set. In our query interface method we then only report support for the same interfaces that were supported by the original `IPtr component, size_t instance_id) : instance_id(instance_id) { + known_iids.insert(component->iid); // `IComponent::getControllerClassId` Steinberg::TUID cid; if (component->getControllerClassId(cid) == Steinberg::kResultOk) { edit_controller_cid = std::to_array(cid); } + + // TODO: Add support of IAudioProcessor } YaComponent::YaComponent(const ConstructArgs&& args) : arguments(std::move(args)) { @@ -48,10 +51,13 @@ IMPLEMENT_REFCOUNT(YaComponent) tresult PLUGIN_API YaComponent::queryInterface(Steinberg::FIDString _iid, void** obj) { QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, Steinberg::IPluginBase) - QUERY_INTERFACE(_iid, obj, Steinberg::IPluginBase::iid, - Steinberg::IPluginBase) - QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, - Steinberg::Vst::IComponent) + if (arguments.known_iids.contains(Steinberg::Vst::IComponent::iid)) { + QUERY_INTERFACE(_iid, obj, Steinberg::IPluginBase::iid, + Steinberg::IPluginBase) + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, + Steinberg::Vst::IComponent) + } + // TODO: Add IAudioProcessor *obj = nullptr; return Steinberg::kNoInterface; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 99789178..fe0216d2 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -17,14 +17,17 @@ #pragma once #include +#include #include #include #include +#include #include #include #include +#include "../../bitsery/ext/vst3.h" #include "../common.h" #include "base.h" #include "host-application.h" @@ -55,7 +58,9 @@ class YaComponent : public Steinberg::Vst::IComponent { ConstructArgs(); /** - * Read arguments from an existing implementation. + * Read arguments from an existing implementation. Depending on the + * supported interface function more or less of this struct will be left + * empty, and `known_iids` will be set accordingly. */ ConstructArgs(Steinberg::IPtr component, size_t instance_id); @@ -65,6 +70,11 @@ class YaComponent : public Steinberg::Vst::IComponent { */ native_size_t instance_id; + /** + * The IIDs that the interface we serialized supports. + */ + std::set known_iids; + /** * The class ID of this component's corresponding editor controller. You * can't use C-style array in `std::optional`s. @@ -74,6 +84,10 @@ class YaComponent : public Steinberg::Vst::IComponent { template void serialize(S& s) { s.value8b(instance_id); + s.ext(known_iids, bitsery::ext::StdSet{32}, + [](S& s, Steinberg::FUID& iid) { + s.ext(iid, bitsery::ext::FUID{}); + }); s.ext(edit_controller_cid, bitsery::ext::StdOptional{}, [](S& s, auto& cid) { s.container1b(cid); }); } diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 532a9bb9..6d1dfa35 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -48,7 +48,7 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { /** * Create a copy of an existing plugin factory. Depending on the * supported interface function more or less of this struct will be left - * empty, and `iid` will be set accordingly. + * empty, and `known_iids` will be set accordingly. */ ConstructArgs(Steinberg::IPtr factory); From 0668a785b0c17ed938ca0c1a01a4f440818f7957 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 13:06:02 +0100 Subject: [PATCH 181/456] Add stubs for implementing IAudioProcessor --- src/common/serialization/vst3/base.h | 2 +- src/common/serialization/vst3/component.h | 24 ++++++++- src/plugin/bridges/vst3-impls/component.cpp | 58 +++++++++++++++++++++ src/plugin/bridges/vst3-impls/component.h | 17 ++++++ 4 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 2f5aef88..b70749a0 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -28,7 +28,7 @@ // we'll need for all of our interfaces using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::int64, - Steinberg::tresult; + Steinberg::uint32, Steinberg::tresult; /** * Both `TUID` (`int8_t[16]`) and `FIDString` (`char*`) are hard to work with diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index fe0216d2..8b7ba161 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "../../bitsery/ext/vst3.h" @@ -49,7 +50,8 @@ * and `IConnectionPoint`. We should use the same approach as in the * plugin factory to implement multiple, possibly optional, interfaces. */ -class YaComponent : public Steinberg::Vst::IComponent { +class YaComponent : public Steinberg::Vst::IComponent, + public Steinberg::Vst::IAudioProcessor { public: /** * These are the arguments for creating a `YaComponentPluginImpl`. @@ -419,6 +421,26 @@ class YaComponent : public Steinberg::Vst::IComponent { virtual tresult PLUGIN_API getState(Steinberg::IBStream* state) override = 0; + // From `IAudioProcessor` + virtual tresult PLUGIN_API + setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, + int32 numIns, + Steinberg::Vst::SpeakerArrangement* outputs, + int32 numOuts) override = 0; + virtual tresult PLUGIN_API + getBusArrangement(Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::SpeakerArrangement& arr) override = 0; + virtual tresult PLUGIN_API + canProcessSampleSize(int32 symbolicSampleSize) override = 0; + virtual uint32 PLUGIN_API getLatencySamples() override = 0; + virtual tresult PLUGIN_API + setupProcessing(Steinberg::Vst::ProcessSetup& setup) override = 0; + virtual tresult PLUGIN_API setProcessing(TBool state) override = 0; + virtual tresult PLUGIN_API + process(Steinberg::Vst::ProcessData& data) override = 0; + virtual uint32 PLUGIN_API getTailSamples() override = 0; + protected: ConstructArgs arguments; }; diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index f3c22279..dfca2535 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -155,3 +155,61 @@ tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { return response.result.native(); } + +tresult PLUGIN_API YaComponentPluginImpl::setBusArrangements( + Steinberg::Vst::SpeakerArrangement* inputs, + int32 numIns, + Steinberg::Vst::SpeakerArrangement* outputs, + int32 numOuts) { + // TODO: Implement + bridge.logger.log("TODO: IAudioProcessor::setBusArrangements()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API YaComponentPluginImpl::getBusArrangement( + Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::SpeakerArrangement& arr) { + // TODO: Implement + bridge.logger.log("TODO: IAudioProcessor::getBusArrangement()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +YaComponentPluginImpl::canProcessSampleSize(int32 symbolicSampleSize) { + // TODO: Implement + bridge.logger.log("TODO: IAudioProcessor::canProcessSampleSize()"); + return Steinberg::kNotImplemented; +} + +uint32 PLUGIN_API YaComponentPluginImpl::getLatencySamples() { + // TODO: Implement + bridge.logger.log("TODO: IAudioProcessor::getLatencySamples()"); + return 0; +} + +tresult PLUGIN_API +YaComponentPluginImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { + // TODO: Implement + bridge.logger.log("TODO: IAudioProcessor::setupProcessing()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API YaComponentPluginImpl::setProcessing(TBool state) { + // TODO: Implement + bridge.logger.log("TODO: IAudioProcessor::setProcessing()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +YaComponentPluginImpl::process(Steinberg::Vst::ProcessData& data) { + // TODO: Implement + bridge.logger.log("TODO: IAudioProcessor::process()"); + return Steinberg::kNotImplemented; +} + +uint32 PLUGIN_API YaComponentPluginImpl::getTailSamples() { + // TODO: Implement + bridge.logger.log("TODO: IAudioProcessor::getTailSamples()"); + return 0; +} diff --git a/src/plugin/bridges/vst3-impls/component.h b/src/plugin/bridges/vst3-impls/component.h index 5e989b98..1c20d118 100644 --- a/src/plugin/bridges/vst3-impls/component.h +++ b/src/plugin/bridges/vst3-impls/component.h @@ -61,6 +61,23 @@ class YaComponentPluginImpl : public YaComponent { tresult PLUGIN_API setState(Steinberg::IBStream* state) override; tresult PLUGIN_API getState(Steinberg::IBStream* state) override; + tresult PLUGIN_API + setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, + int32 numIns, + Steinberg::Vst::SpeakerArrangement* outputs, + int32 numOuts) override; + tresult PLUGIN_API + getBusArrangement(Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::SpeakerArrangement& arr) override; + tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override; + uint32 PLUGIN_API getLatencySamples() override; + tresult PLUGIN_API + setupProcessing(Steinberg::Vst::ProcessSetup& setup) override; + tresult PLUGIN_API setProcessing(TBool state) override; + tresult PLUGIN_API process(Steinberg::Vst::ProcessData& data) override; + uint32 PLUGIN_API getTailSamples() override; + private: Vst3PluginBridge& bridge; From e0dd4ab22db27071af2c74b65b3d6a7262241639 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 13:17:10 +0100 Subject: [PATCH 182/456] Add IAudioProcessor query interface to YaComponent --- src/common/serialization/vst3/component.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/component.cpp index 1314c8b2..cbc9b2e5 100644 --- a/src/common/serialization/vst3/component.cpp +++ b/src/common/serialization/vst3/component.cpp @@ -29,7 +29,12 @@ YaComponent::ConstructArgs::ConstructArgs( edit_controller_cid = std::to_array(cid); } - // TODO: Add support of IAudioProcessor + // There's no static data we can copy from the audio processor + if (auto audio_processor = + Steinberg::FUnknownPtr( + component)) { + known_iids.insert(Steinberg::Vst::IAudioProcessor::iid); + } } YaComponent::YaComponent(const ConstructArgs&& args) : arguments(std::move(args)) { @@ -57,7 +62,10 @@ tresult PLUGIN_API YaComponent::queryInterface(Steinberg::FIDString _iid, QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, Steinberg::Vst::IComponent) } - // TODO: Add IAudioProcessor + if (arguments.known_iids.contains(Steinberg::Vst::IAudioProcessor::iid)) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IAudioProcessor::iid, + Steinberg::Vst::IAudioProcessor) + } *obj = nullptr; return Steinberg::kNoInterface; From e2ba9c13f14023f7778eaa9c3cc78c534a3992f0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 13:18:47 +0100 Subject: [PATCH 183/456] Add IAudioProcessor instance to ComponentInstance So we don't have to these expensive casts every time. --- src/wine-host/bridges/vst3.cpp | 2 +- src/wine-host/bridges/vst3.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 7da18669..2e1c8f85 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -26,7 +26,7 @@ ComponentInstance::ComponentInstance() {} ComponentInstance::ComponentInstance( Steinberg::IPtr component) - : component(component) {} + : component(component), audio_processor(component) {} Vst3Bridge::Vst3Bridge(MainContext& main_context, std::string plugin_dll_path, diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index fc1e4106..5e968efa 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -49,7 +49,7 @@ struct ComponentInstance { // All smart pointers below are created from `component`. They will be null // pointers if `component` did not implement the interface. - // TODO: Implement things like `IConnectionPoint` and `IAudioProcessor` + Steinberg::FUnknownPtr audio_processor; }; /** From 143d795c0b0f20697d231334cd67e4090c47c452 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 13:21:28 +0100 Subject: [PATCH 184/456] Unify log message format for initialize() --- src/common/logging/vst3.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index d77fcc49..e18f20a6 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -38,7 +38,8 @@ void Vst3Logger::log_unknown_interface( void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Construct&) { log_request_base(is_host_vst, [](auto& message) { - // TODO: Log the cid in some readable way, if possible + // TODO: Log the CID on verbosity level 2, and then also report all CIDs + // in the plugin factory message << "IPluginFactory::createComponent(cid = ..., _iid = " "IComponent::iid, " "&obj)"; @@ -59,9 +60,9 @@ void Vst3Logger::log_request(bool is_host_vst, message << "::initialize(context = "; if (request.host_application_context_args) { - message << "IHostApplication*"; + message << ""; } else { - message << "nullptr"; + message << ""; } message << ")"; }); From 49a87371bc555e7e7123bf25a47ff04120dbe46a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 13:23:27 +0100 Subject: [PATCH 185/456] Add todos for logging successful interface queries --- src/plugin/bridges/vst3-impls/component.cpp | 1 + src/wine-host/bridges/vst3-impls/host-application.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index dfca2535..b173b19b 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -30,6 +30,7 @@ YaComponentPluginImpl::~YaComponentPluginImpl() { tresult PLUGIN_API YaComponentPluginImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { + // TODO: Successful queries should also be logged const tresult result = YaComponent::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { bridge.logger.log_unknown_interface("In IComponent::queryInterface()", diff --git a/src/wine-host/bridges/vst3-impls/host-application.cpp b/src/wine-host/bridges/vst3-impls/host-application.cpp index dc62563c..7d77d917 100644 --- a/src/wine-host/bridges/vst3-impls/host-application.cpp +++ b/src/wine-host/bridges/vst3-impls/host-application.cpp @@ -29,6 +29,7 @@ YaHostApplicationHostImpl::YaHostApplicationHostImpl( tresult PLUGIN_API YaHostApplicationHostImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { + // TODO: Successful queries should also be logged const tresult result = YaHostApplication::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { std::cerr << "TODO: Implement unknown interface logging on Wine side" From b87c3fe7908ae9d001ea18624ee222efb262a50c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 16:10:56 +0100 Subject: [PATCH 186/456] Add a todo about non-separated controllers --- src/common/serialization/vst3/component.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 8b7ba161..85326be8 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -46,9 +46,12 @@ * sounds like a huge potential source of errors we'll just do pure callbacks * for everything other than the edit controller's class ID. * - * TODO: I think it's expected that components also implement `IAudioProcessor` - * and `IConnectionPoint`. We should use the same approach as in the - * plugin factory to implement multiple, possibly optional, interfaces. + * TODO: Amplement IConnectionPoint + * TODO: How should we support IComponents without a seperate edit controller? + * Can we just use a separate `YaEditController` that just points to the + * same implementation (with the same CID)? Check the reference + * implementation in the framework to see how this is initialized, make + * sure we support the reference w workflow. */ class YaComponent : public Steinberg::Vst::IComponent, public Steinberg::Vst::IAudioProcessor { From e3b442de5703f2e006c661913a5a1829a41bd123 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 16:40:40 +0100 Subject: [PATCH 187/456] Implement IAudioProcessor::setBusArrangements() --- src/common/logging/vst3.cpp | 11 +++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/base.h | 5 +++ src/common/serialization/vst3/component.h | 35 ++++++++++++++++++--- src/plugin/bridges/vst3-impls/component.cpp | 15 +++++++-- src/wine-host/bridges/vst3.cpp | 8 +++++ 7 files changed, 69 insertions(+), 7 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index e18f20a6..075609c1 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -151,6 +151,17 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::SetBusArrangements& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::setBusArrangements(inputs = [SpeakerArrangement; " + << request.inputs.size() << "], numIns = " << request.num_ins + << ", outputs = [SpeakerArrangement; " << request.outputs.size() + << "], numOuts = " << request.num_outs << ")"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 951466d1..7541abb2 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -68,6 +68,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::SetActive&); void log_request(bool is_host_vst, const YaComponent::SetState&); void log_request(bool is_host_vst, const YaComponent::GetState&); + void log_request(bool is_host_vst, const YaComponent::SetBusArrangements&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 45ded5e4..5a3f01c4 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -69,6 +69,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index b70749a0..d1155e35 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -39,6 +39,11 @@ using ArrayUID = std::array< std::remove_reference_t()[0])>, std::extent_v>; +/** + * The maximum number of speakers or busses we support. + */ +constexpr size_t max_num_speakers = 16384; + /** * The maximum size for an `IBStream` we can serialize. Allows for up to 50 MB * of preset data. Hopefully no plugin will come anywhere near this limit, but diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 85326be8..d51cca3d 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -192,7 +192,7 @@ class YaComponent : public Steinberg::Vst::IComponent, tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override; /** - * Message to pass through a call to `IComponent::setIoMode(IoMode)` to the + * Message to pass through a call to `IComponent::setIoMode(mode)` to the * Wine plugin host. */ struct SetIoMode { @@ -282,7 +282,7 @@ class YaComponent : public Steinberg::Vst::IComponent, /** * The response code and returned routing information for a call to - * `IComponent::getRoutingInfo(inInfo, outInfo )`. + * `IComponent::getRoutingInfo(in_info, out_info )`. */ struct GetRoutingInfoResponse { UniversalTResult result; @@ -298,8 +298,8 @@ class YaComponent : public Steinberg::Vst::IComponent, }; /** - * Message to pass through a call to `IComponent::getRoutingInfo(inInfo, - * outInfo )` to the Wine plugin host. + * Message to pass through a call to `IComponent::getRoutingInfo(in_info, + * out_info )` to the Wine plugin host. */ struct GetRoutingInfo { using Response = GetRoutingInfoResponse; @@ -425,6 +425,33 @@ class YaComponent : public Steinberg::Vst::IComponent, getState(Steinberg::IBStream* state) override = 0; // From `IAudioProcessor` + + /** + * Message to pass through a call to + * `IAudioProcessor::setBusArrangements(inputs, num_ins, outputs, num_outs)` + * to the Wine plugin host. + */ + struct SetBusArrangements { + using Response = UniversalTResult; + + native_size_t instance_id; + + // These are orginally C-style heap arrays, not normal pointers + std::vector inputs; + int32 num_ins; + std::vector outputs; + int32 num_outs; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.container8b(inputs, max_num_speakers); + s.value4b(num_ins); + s.container8b(outputs, max_num_speakers); + s.value4b(num_outs); + } + }; + virtual tresult PLUGIN_API setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, int32 numIns, diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index b173b19b..34863df9 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -162,9 +162,18 @@ tresult PLUGIN_API YaComponentPluginImpl::setBusArrangements( int32 numIns, Steinberg::Vst::SpeakerArrangement* outputs, int32 numOuts) { - // TODO: Implement - bridge.logger.log("TODO: IAudioProcessor::setBusArrangements()"); - return Steinberg::kNotImplemented; + assert(inputs && outputs); + return bridge + .send_message(YaComponent::SetBusArrangements{ + .instance_id = arguments.instance_id, + .inputs = std::vector( + inputs, &inputs[numIns]), + .num_ins = numIns, + .outputs = std::vector( + outputs, &outputs[numOuts]), + .num_outs = numOuts, + }) + .native(); } tresult PLUGIN_API YaComponentPluginImpl::getBusArrangement( diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 2e1c8f85..ef291995 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -167,6 +167,14 @@ void Vst3Bridge::run() { return YaComponent::GetStateResponse{ .result = result, .updated_state = std::move(stream)}; }, + [&](YaComponent::SetBusArrangements& request) + -> YaComponent::SetBusArrangements::Response { + VectorStream stream; + return component_instances[request.instance_id] + .audio_processor->setBusArrangements( + request.inputs.data(), request.num_ins, + request.outputs.data(), request.num_outs); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From b26c2e08a7c5d0134ec4aa431f0506eb08df9bcf Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 16:55:21 +0100 Subject: [PATCH 188/456] Implement IAudioProcessor::getBusArrangement() --- src/common/logging/vst3.cpp | 20 +++++++++++ src/common/logging/vst3.h | 3 ++ src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/component.h | 39 +++++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 12 +++++-- src/wine-host/bridges/vst3.cpp | 10 ++++++ 6 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 075609c1..91a43660 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -162,6 +162,15 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetBusArrangement& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getBusArrangement(dir = " << request.dir + << ", index = " << request.index << ", &arr"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, @@ -237,6 +246,17 @@ void Vst3Logger::log_response(bool is_host_vst, }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaComponent::GetBusArrangementResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result.native() == Steinberg::kResultOk) { + message << ", "; + } + }); +} + void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs& args) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 7541abb2..d2a13011 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -69,6 +69,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::SetState&); void log_request(bool is_host_vst, const YaComponent::GetState&); void log_request(bool is_host_vst, const YaComponent::SetBusArrangements&); + void log_request(bool is_host_vst, const YaComponent::GetBusArrangement&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); @@ -81,6 +82,8 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaComponent::GetRoutingInfoResponse&); void log_response(bool is_host_vst, const YaComponent::GetStateResponse&); + void log_response(bool is_host_vst, + const YaComponent::GetBusArrangementResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 5a3f01c4..f81e893a 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -70,6 +70,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index d51cca3d..e04ad807 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -457,6 +457,45 @@ class YaComponent : public Steinberg::Vst::IComponent, int32 numIns, Steinberg::Vst::SpeakerArrangement* outputs, int32 numOuts) override = 0; + + /** + * The response code and written state for a call to + * `IAudioProcessor::getBusArrangement(dir, index, arr)`. + */ + struct GetBusArrangementResponse { + UniversalTResult result; + Steinberg::Vst::SpeakerArrangement updated_arr; + + template + void serialize(S& s) { + s.object(result); + s.value8b(updated_arr); + } + }; + + /** + * Message to pass through a call to + * `IAudioProcessor::getBusArrangement(dir, index, arr)` to the Wine + * plugin host. + */ + struct GetBusArrangement { + using Response = GetBusArrangementResponse; + + native_size_t instance_id; + + Steinberg::Vst::BusDirection dir; + int32 index; + Steinberg::Vst::SpeakerArrangement arr; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(dir); + s.value4b(index); + s.value8b(arr); + } + }; + virtual tresult PLUGIN_API getBusArrangement(Steinberg::Vst::BusDirection dir, int32 index, diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 34863df9..34170d17 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -180,9 +180,15 @@ tresult PLUGIN_API YaComponentPluginImpl::getBusArrangement( Steinberg::Vst::BusDirection dir, int32 index, Steinberg::Vst::SpeakerArrangement& arr) { - // TODO: Implement - bridge.logger.log("TODO: IAudioProcessor::getBusArrangement()"); - return Steinberg::kNotImplemented; + const GetBusArrangementResponse response = bridge.send_message( + YaComponent::GetBusArrangement{.instance_id = arguments.instance_id, + .dir = dir, + .index = index, + .arr = arr}); + + arr = response.updated_arr; + + return response.result.native(); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index ef291995..c154a629 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -175,6 +175,16 @@ void Vst3Bridge::run() { request.inputs.data(), request.num_ins, request.outputs.data(), request.num_outs); }, + [&](YaComponent::GetBusArrangement& request) + -> YaComponent::GetBusArrangement::Response { + const tresult result = + component_instances[request.instance_id] + .audio_processor->getBusArrangement( + request.dir, request.index, request.arr); + + return YaComponent::GetBusArrangementResponse{ + .result = result, .updated_arr = request.arr}; + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From d9585fac7867a6b4760a59605696cc162d670a4e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 17:12:49 +0100 Subject: [PATCH 189/456] Implement IAudioProcessor::canProcessSampleSize() --- src/common/logging/vst3.cpp | 11 ++++++++++- src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/component.h | 20 ++++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 8 +++++--- src/wine-host/bridges/vst3.cpp | 7 ++++++- 6 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 91a43660..b0349f32 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -167,7 +167,16 @@ void Vst3Logger::log_request(bool is_host_vst, log_request_base(is_host_vst, [&](auto& message) { message << "::getBusArrangement(dir = " << request.dir - << ", index = " << request.index << ", &arr"; + << ", index = " << request.index << ", &arr)"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::CanProcessSampleSize& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::canProcessSampleSize(symbolicSampleSize = " + << request.symbolic_sample_size << ")"; }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index d2a13011..10ff9356 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -70,6 +70,8 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::GetState&); void log_request(bool is_host_vst, const YaComponent::SetBusArrangements&); void log_request(bool is_host_vst, const YaComponent::GetBusArrangement&); + void log_request(bool is_host_vst, + const YaComponent::CanProcessSampleSize&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index f81e893a..f038cf1a 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -71,6 +71,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index e04ad807..201ac9ee 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -500,6 +500,26 @@ class YaComponent : public Steinberg::Vst::IComponent, getBusArrangement(Steinberg::Vst::BusDirection dir, int32 index, Steinberg::Vst::SpeakerArrangement& arr) override = 0; + + /** + * Message to pass through a call to + * `IAudioProcessor::canProcessSampleSize(symbolic_sample_size)` to the Wine + * plugin host. + */ + struct CanProcessSampleSize { + using Response = UniversalTResult; + + native_size_t instance_id; + + int32 symbolic_sample_size; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(symbolic_sample_size); + } + }; + virtual tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override = 0; virtual uint32 PLUGIN_API getLatencySamples() override = 0; diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 34170d17..134a20f2 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -193,9 +193,11 @@ tresult PLUGIN_API YaComponentPluginImpl::getBusArrangement( tresult PLUGIN_API YaComponentPluginImpl::canProcessSampleSize(int32 symbolicSampleSize) { - // TODO: Implement - bridge.logger.log("TODO: IAudioProcessor::canProcessSampleSize()"); - return Steinberg::kNotImplemented; + return bridge + .send_message(YaComponent::CanProcessSampleSize{ + .instance_id = arguments.instance_id, + .symbolic_sample_size = symbolicSampleSize}) + .native(); } uint32 PLUGIN_API YaComponentPluginImpl::getLatencySamples() { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index c154a629..9ac44011 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -169,7 +169,6 @@ void Vst3Bridge::run() { }, [&](YaComponent::SetBusArrangements& request) -> YaComponent::SetBusArrangements::Response { - VectorStream stream; return component_instances[request.instance_id] .audio_processor->setBusArrangements( request.inputs.data(), request.num_ins, @@ -185,6 +184,12 @@ void Vst3Bridge::run() { return YaComponent::GetBusArrangementResponse{ .result = result, .updated_arr = request.arr}; }, + [&](YaComponent::CanProcessSampleSize& request) + -> YaComponent::CanProcessSampleSize::Response { + return component_instances[request.instance_id] + .audio_processor->canProcessSampleSize( + request.symbolic_sample_size); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From 6979dafa0694917cf01ad1ed054321c7f20e9ca3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 17:53:38 +0100 Subject: [PATCH 190/456] Add a wrapper for serializing primitives --- src/common/serialization/vst3/base.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index d1155e35..1d7057e9 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -60,6 +60,28 @@ struct Ack { void serialize(S&) {} }; +/** + * A simple wrapper around primitive values for serialization purposes. Bitsery + * doesn't seem to like serializing plain primitives using `s.object()` even if + * you define a serialization function. + */ +template +class PrimitiveWrapper { + public: + PrimitiveWrapper() {} + PrimitiveWrapper(T value) : value(value) {} + + operator T() { return value; } + + template + void serialize(S& s) { + s.template value(value); + } + + private: + T value; +}; + /** * A wrapper around `Steinberg::tresult` that we can safely share between the * native plugin and the Wine process. Depending on the platform and on whether From 0f59d6429dc98560434fb2f540f24b17bfd0dc9e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 17:53:50 +0100 Subject: [PATCH 191/456] Implement IAudioProcessor::getLatencySamples() --- src/common/logging/vst3.cpp | 12 ++++++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/component.h | 17 +++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index b0349f32..d242b43e 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -180,6 +180,14 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetLatencySamples& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getLatencySamples()"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, @@ -199,6 +207,10 @@ void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { }); } +void Vst3Logger::log_response(bool is_host_vst, const uint32& value) { + log_response_base(is_host_vst, [&](auto& message) { message << value; }); +} + void Vst3Logger::log_response(bool is_host_vst, const Ack&) { log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 10ff9356..813e6add 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -72,10 +72,12 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::GetBusArrangement&); void log_request(bool is_host_vst, const YaComponent::CanProcessSampleSize&); + void log_request(bool is_host_vst, const YaComponent::GetLatencySamples&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); + void log_response(bool is_host_vst, const uint32&); void log_response(bool is_host_vst, const Ack&); void log_response( bool is_host_vst, diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index f038cf1a..bb860cd5 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -72,6 +72,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 201ac9ee..d3ceeb63 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -522,6 +522,23 @@ class YaComponent : public Steinberg::Vst::IComponent, virtual tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override = 0; + + /** + * Message to pass through a call to + * `IAudioProcessor::getLatencySamples()` to the Wine + * plugin host. + */ + struct GetLatencySamples { + using Response = PrimitiveWrapper; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + virtual uint32 PLUGIN_API getLatencySamples() override = 0; virtual tresult PLUGIN_API setupProcessing(Steinberg::Vst::ProcessSetup& setup) override = 0; diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 134a20f2..02fab9e8 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -201,9 +201,8 @@ YaComponentPluginImpl::canProcessSampleSize(int32 symbolicSampleSize) { } uint32 PLUGIN_API YaComponentPluginImpl::getLatencySamples() { - // TODO: Implement - bridge.logger.log("TODO: IAudioProcessor::getLatencySamples()"); - return 0; + return bridge.send_message( + YaComponent::GetLatencySamples{.instance_id = arguments.instance_id}); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 9ac44011..5552b96d 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -190,6 +190,11 @@ void Vst3Bridge::run() { .audio_processor->canProcessSampleSize( request.symbolic_sample_size); }, + [&](YaComponent::GetLatencySamples& request) + -> YaComponent::GetLatencySamples::Response { + return component_instances[request.instance_id] + .audio_processor->getLatencySamples(); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From e282f8c5722f770058991a8bb3b1988802c99f5b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 18:02:51 +0100 Subject: [PATCH 192/456] Fix IComponent::getBusCount() --- src/common/serialization/vst3/component.h | 2 +- src/plugin/bridges/vst3-impls/component.cpp | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index d3ceeb63..a17af82d 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -217,7 +217,7 @@ class YaComponent : public Steinberg::Vst::IComponent, * the Wine plugin host. */ struct GetBusCount { - using Response = UniversalTResult; + using Response = PrimitiveWrapper; native_size_t instance_id; diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 02fab9e8..94ef7a20 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -84,10 +84,8 @@ YaComponentPluginImpl::setIoMode(Steinberg::Vst::IoMode mode) { int32 PLUGIN_API YaComponentPluginImpl::getBusCount(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir) { - return bridge - .send_message(YaComponent::GetBusCount{ - .instance_id = arguments.instance_id, .type = type, .dir = dir}) - .native(); + return bridge.send_message(YaComponent::GetBusCount{ + .instance_id = arguments.instance_id, .type = type, .dir = dir}); } tresult PLUGIN_API From bb110e8cbb5f0915f0a6b4e49d9c48a5a8c033b7 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 18:05:37 +0100 Subject: [PATCH 193/456] Add PrimitiveWrapper response logging --- src/common/logging/vst3.cpp | 4 ---- src/common/logging/vst3.h | 8 +++++++- src/common/serialization/vst3/base.h | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index d242b43e..db4fe48b 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -207,10 +207,6 @@ void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { }); } -void Vst3Logger::log_response(bool is_host_vst, const uint32& value) { - log_response_base(is_host_vst, [&](auto& message) { message << value; }); -} - void Vst3Logger::log_response(bool is_host_vst, const Ack&) { log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 813e6add..4634bfaa 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -77,7 +77,6 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); - void log_response(bool is_host_vst, const uint32&); void log_response(bool is_host_vst, const Ack&); void log_response( bool is_host_vst, @@ -91,6 +90,13 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); + template + void log_response(bool is_host_vst, const PrimitiveWrapper& value) { + // For logging all primitive return values other than `tresult` + log_response_base(is_host_vst, + [&](auto& message) { message << value; }); + } + Logger& logger; private: diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 1d7057e9..9798d9e3 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -71,7 +71,7 @@ class PrimitiveWrapper { PrimitiveWrapper() {} PrimitiveWrapper(T value) : value(value) {} - operator T() { return value; } + operator T() const { return value; } template void serialize(S& s) { From 2becd420b282084f297d77435a6e80f7348a8f79 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 18:22:12 +0100 Subject: [PATCH 194/456] Take PrimitiveWrapper value by rvalue --- src/common/serialization/vst3/base.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 9798d9e3..1023431c 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -69,7 +69,7 @@ template class PrimitiveWrapper { public: PrimitiveWrapper() {} - PrimitiveWrapper(T value) : value(value) {} + PrimitiveWrapper(T&& value) : value(value) {} operator T() const { return value; } From cb7413c5214598eb6d247a4b53775057147d5e3f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 18:32:18 +0100 Subject: [PATCH 195/456] Add implicit conversion for UniversalTResult --- src/common/logging/vst3.cpp | 8 +- src/common/serialization/vst3/base.cpp | 2 +- src/common/serialization/vst3/base.h | 2 +- src/plugin/bridges/vst3-impls/component.cpp | 84 ++++++++----------- .../bridges/vst3-impls/plugin-factory.cpp | 10 +-- 5 files changed, 44 insertions(+), 62 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index db4fe48b..c713e206 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -230,7 +230,7 @@ void Vst3Logger::log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse& response) { log_response_base(is_host_vst, [&](auto& message) { message << response.result.string(); - if (response.result.native() == Steinberg::kResultOk) { + if (response.result == Steinberg::kResultOk) { message << ", "; } }); @@ -241,7 +241,7 @@ void Vst3Logger::log_response( const YaComponent::GetRoutingInfoResponse& response) { log_response_base(is_host_vst, [&](auto& message) { message << response.result.string(); - if (response.result.native() == Steinberg::kResultOk) { + if (response.result == Steinberg::kResultOk) { message << ", "; } @@ -268,7 +268,7 @@ void Vst3Logger::log_response( const YaComponent::GetBusArrangementResponse& response) { log_response_base(is_host_vst, [&](auto& message) { message << response.result.string(); - if (response.result.native() == Steinberg::kResultOk) { + if (response.result == Steinberg::kResultOk) { message << ", "; } }); diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index 88542121..0704c5ac 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -24,7 +24,7 @@ UniversalTResult::UniversalTResult() : universal_result(Value::kResultFalse) {} UniversalTResult::UniversalTResult(tresult native_result) : universal_result(to_universal_result(native_result)) {} -tresult UniversalTResult::native() const { +UniversalTResult::operator tresult() const { static_assert(Steinberg::kResultOk == Steinberg::kResultTrue); switch (universal_result) { case Value::kNoInterface: diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 1023431c..7d452a02 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -104,7 +104,7 @@ class UniversalTResult { /** * Get the native equivalent for the wrapped `tresult` value. */ - tresult native() const; + operator tresult() const; /** * Get the original name for the result, e.g. `kResultOk`. diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 94ef7a20..180edd1f 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -58,27 +58,21 @@ tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { context ? std::optional(context->iid) : std::nullopt); } - return bridge - .send_message(YaComponent::Initialize{ - .instance_id = arguments.instance_id, - .host_application_context_args = - std::move(host_application_context_args)}) - .native(); + return bridge.send_message( + YaComponent::Initialize{.instance_id = arguments.instance_id, + .host_application_context_args = + std::move(host_application_context_args)}); } tresult PLUGIN_API YaComponentPluginImpl::terminate() { - return bridge - .send_message( - YaComponent::Terminate{.instance_id = arguments.instance_id}) - .native(); + return bridge.send_message( + YaComponent::Terminate{.instance_id = arguments.instance_id}); } tresult PLUGIN_API YaComponentPluginImpl::setIoMode(Steinberg::Vst::IoMode mode) { - return bridge - .send_message(YaComponent::SetIoMode{ - .instance_id = arguments.instance_id, .mode = mode}) - .native(); + return bridge.send_message(YaComponent::SetIoMode{ + .instance_id = arguments.instance_id, .mode = mode}); } int32 PLUGIN_API @@ -101,7 +95,7 @@ YaComponentPluginImpl::getBusInfo(Steinberg::Vst::MediaType type, .bus = bus}); bus = response.updated_bus; - return response.result.native(); + return response.result; } tresult PLUGIN_API YaComponentPluginImpl::getRoutingInfo( @@ -114,7 +108,7 @@ tresult PLUGIN_API YaComponentPluginImpl::getRoutingInfo( inInfo = response.updated_in_info; outInfo = response.updated_out_info; - return response.result.native(); + return response.result; } tresult PLUGIN_API @@ -122,28 +116,22 @@ YaComponentPluginImpl::activateBus(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir, int32 index, TBool state) { - return bridge - .send_message( - YaComponent::ActivateBus{.instance_id = arguments.instance_id, - .type = type, - .dir = dir, - .index = index, - .state = state}) - .native(); + return bridge.send_message( + YaComponent::ActivateBus{.instance_id = arguments.instance_id, + .type = type, + .dir = dir, + .index = index, + .state = state}); } tresult PLUGIN_API YaComponentPluginImpl::setActive(TBool state) { - return bridge - .send_message(YaComponent::SetActive{ - .instance_id = arguments.instance_id, .state = state}) - .native(); + return bridge.send_message(YaComponent::SetActive{ + .instance_id = arguments.instance_id, .state = state}); } tresult PLUGIN_API YaComponentPluginImpl::setState(Steinberg::IBStream* state) { - return bridge - .send_message(YaComponent::SetState{ - .instance_id = arguments.instance_id, .state = state}) - .native(); + return bridge.send_message(YaComponent::SetState{ + .instance_id = arguments.instance_id, .state = state}); } tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { @@ -152,7 +140,7 @@ tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { assert(response.updated_state.write_back(state) == Steinberg::kResultOk); - return response.result.native(); + return response.result; } tresult PLUGIN_API YaComponentPluginImpl::setBusArrangements( @@ -161,17 +149,15 @@ tresult PLUGIN_API YaComponentPluginImpl::setBusArrangements( Steinberg::Vst::SpeakerArrangement* outputs, int32 numOuts) { assert(inputs && outputs); - return bridge - .send_message(YaComponent::SetBusArrangements{ - .instance_id = arguments.instance_id, - .inputs = std::vector( - inputs, &inputs[numIns]), - .num_ins = numIns, - .outputs = std::vector( - outputs, &outputs[numOuts]), - .num_outs = numOuts, - }) - .native(); + return bridge.send_message(YaComponent::SetBusArrangements{ + .instance_id = arguments.instance_id, + .inputs = std::vector( + inputs, &inputs[numIns]), + .num_ins = numIns, + .outputs = std::vector( + outputs, &outputs[numOuts]), + .num_outs = numOuts, + }); } tresult PLUGIN_API YaComponentPluginImpl::getBusArrangement( @@ -186,16 +172,14 @@ tresult PLUGIN_API YaComponentPluginImpl::getBusArrangement( arr = response.updated_arr; - return response.result.native(); + return response.result; } tresult PLUGIN_API YaComponentPluginImpl::canProcessSampleSize(int32 symbolicSampleSize) { - return bridge - .send_message(YaComponent::CanProcessSampleSize{ - .instance_id = arguments.instance_id, - .symbolic_sample_size = symbolicSampleSize}) - .native(); + return bridge.send_message(YaComponent::CanProcessSampleSize{ + .instance_id = arguments.instance_id, + .symbolic_sample_size = symbolicSampleSize}); } uint32 PLUGIN_API YaComponentPluginImpl::getLatencySamples() { diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index d5522051..a39ac230 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -47,7 +47,7 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, new YaComponentPluginImpl(bridge, std::move(args))); return Steinberg::kResultOk; }, - [&](const UniversalTResult& code) { return code.native(); }}, + [&](const UniversalTResult& code) -> tresult { return code; }}, std::move(result)); } else { // When the host requests an interface we do not (yet) implement, we'll @@ -81,11 +81,9 @@ YaPluginFactoryPluginImpl::setHostContext(Steinberg::FUnknown* context) { YaHostApplication::ConstructArgs host_application_context_args( host_application_context, std::nullopt); - return bridge - .send_message(YaPluginFactory::SetHostContext{ - .host_application_context_args = - std::move(host_application_context_args)}) - .native(); + return bridge.send_message(YaPluginFactory::SetHostContext{ + .host_application_context_args = + std::move(host_application_context_args)}); } else { bridge.logger.log_unknown_interface( "In IPluginFactory3::setHostContext(), ignoring", From b1bcfd3873c5892d2b6c33e7729737d5e9ebd65b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 21:25:05 +0100 Subject: [PATCH 196/456] Implement IAudioProcessor::setupProcessing() --- src/common/logging/vst3.cpp | 12 ++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/component.h | 32 +++++++++++++++++++-- src/plugin/bridges/vst3-impls/component.cpp | 5 ++-- src/wine-host/bridges/vst3.cpp | 9 ++++-- 6 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index c713e206..37a4fff5 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -188,6 +188,18 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::SetupProcessing& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::setupProcessing(setup = )"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 4634bfaa..94eaff66 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -73,6 +73,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::CanProcessSampleSize&); void log_request(bool is_host_vst, const YaComponent::GetLatencySamples&); + void log_request(bool is_host_vst, const YaComponent::SetupProcessing&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index bb860cd5..57563b0c 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -73,6 +73,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index a17af82d..54a54cf1 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -524,9 +524,8 @@ class YaComponent : public Steinberg::Vst::IComponent, canProcessSampleSize(int32 symbolicSampleSize) override = 0; /** - * Message to pass through a call to - * `IAudioProcessor::getLatencySamples()` to the Wine - * plugin host. + * Message to pass through a call to `IAudioProcessor::getLatencySamples()` + * to the Wine plugin host. */ struct GetLatencySamples { using Response = PrimitiveWrapper; @@ -540,6 +539,25 @@ class YaComponent : public Steinberg::Vst::IComponent, }; virtual uint32 PLUGIN_API getLatencySamples() override = 0; + + /** + * Message to pass through a call to + * `IAudioProcessor::setupProcessing(setup)` to the Wine plugin host. + */ + struct SetupProcessing { + using Response = UniversalTResult; + + native_size_t instance_id; + + Steinberg::Vst::ProcessSetup setup; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.object(setup); + } + }; + virtual tresult PLUGIN_API setupProcessing(Steinberg::Vst::ProcessSetup& setup) override = 0; virtual tresult PLUGIN_API setProcessing(TBool state) override = 0; @@ -578,5 +596,13 @@ void serialize(S& s, Steinberg::Vst::RoutingInfo& info) { s.value4b(info.busIndex); s.value4b(info.channel); } + +template +void serialize(S& s, Steinberg::Vst::ProcessSetup& setup) { + s.value4b(setup.processMode); + s.value4b(setup.symbolicSampleSize); + s.value4b(setup.maxSamplesPerBlock); + s.value8b(setup.sampleRate); +} } // namespace Vst } // namespace Steinberg diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 180edd1f..9790b299 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -189,9 +189,8 @@ uint32 PLUGIN_API YaComponentPluginImpl::getLatencySamples() { tresult PLUGIN_API YaComponentPluginImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { - // TODO: Implement - bridge.logger.log("TODO: IAudioProcessor::setupProcessing()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaComponent::SetupProcessing{ + .instance_id = arguments.instance_id, .setup = setup}); } tresult PLUGIN_API YaComponentPluginImpl::setProcessing(TBool state) { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 5552b96d..e65e1cdb 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -184,17 +184,22 @@ void Vst3Bridge::run() { return YaComponent::GetBusArrangementResponse{ .result = result, .updated_arr = request.arr}; }, - [&](YaComponent::CanProcessSampleSize& request) + [&](const YaComponent::CanProcessSampleSize& request) -> YaComponent::CanProcessSampleSize::Response { return component_instances[request.instance_id] .audio_processor->canProcessSampleSize( request.symbolic_sample_size); }, - [&](YaComponent::GetLatencySamples& request) + [&](const YaComponent::GetLatencySamples& request) -> YaComponent::GetLatencySamples::Response { return component_instances[request.instance_id] .audio_processor->getLatencySamples(); }, + [&](YaComponent::SetupProcessing& request) + -> YaComponent::SetupProcessing::Response { + return component_instances[request.instance_id] + .audio_processor->setupProcessing(request.setup); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From 007aa1e7078eada1f49ae9fe0933ad24f58b065e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 21:31:50 +0100 Subject: [PATCH 197/456] Implement IAudioProcessor::setProcessing() --- src/common/logging/vst3.cpp | 9 +++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/component.h | 19 +++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 37a4fff5..4d548f5a 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -200,6 +200,15 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::SetProcessing& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::setProcessing(state = " + << (request.state ? "true" : "false") << ")"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 94eaff66..09bcf269 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -74,6 +74,7 @@ class Vst3Logger { const YaComponent::CanProcessSampleSize&); void log_request(bool is_host_vst, const YaComponent::GetLatencySamples&); void log_request(bool is_host_vst, const YaComponent::SetupProcessing&); + void log_request(bool is_host_vst, const YaComponent::SetProcessing&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 57563b0c..0b82b726 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -74,6 +74,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 54a54cf1..6c0285aa 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -560,6 +560,25 @@ class YaComponent : public Steinberg::Vst::IComponent, virtual tresult PLUGIN_API setupProcessing(Steinberg::Vst::ProcessSetup& setup) override = 0; + + /** + * Message to pass through a call to `IAudioProcessor::setProcessing(state)` + * to the Wine plugin host. + */ + struct SetProcessing { + using Response = UniversalTResult; + + native_size_t instance_id; + + TBool state; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value1b(state); + } + }; + virtual tresult PLUGIN_API setProcessing(TBool state) override = 0; virtual tresult PLUGIN_API process(Steinberg::Vst::ProcessData& data) override = 0; diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 9790b299..cbdb321d 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -194,9 +194,8 @@ YaComponentPluginImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { } tresult PLUGIN_API YaComponentPluginImpl::setProcessing(TBool state) { - // TODO: Implement - bridge.logger.log("TODO: IAudioProcessor::setProcessing()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaComponent::SetProcessing{ + .instance_id = arguments.instance_id, .state = state}); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index e65e1cdb..d42952f2 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -200,6 +200,11 @@ void Vst3Bridge::run() { return component_instances[request.instance_id] .audio_processor->setupProcessing(request.setup); }, + [&](const YaComponent::SetProcessing& request) + -> YaComponent::SetProcessing::Response { + return component_instances[request.instance_id] + .audio_processor->setProcessing(request.state); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From c815b3903b139f4caf0e3b5471dd6f1aeb410d58 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 21:43:38 +0100 Subject: [PATCH 198/456] Implement IAudioProcessor::getTailSamples() --- README.md | 4 +++- src/common/logging/vst3.cpp | 18 +++++++++++++----- src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 2 ++ src/common/serialization/vst3/component.h | 16 ++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 7 files changed, 42 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 769fba67..e0771d85 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,9 @@ imcomplete list of things that still have to be done before this can be used: - Left to implement: - `YaHostApplicationHostImpl::createComponent`. - - `IAudioProcessor` and `IConnectionPoint` to supplement `IComponent` + - `IAudioProcessor::process()` along with every interface and struct involved + in `ProcessData` + - `IConnectionPoint` to supplement `IComponent` - `IEditController{,2}` - All other mandatory interfaces - All other optional interfaces diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 4d548f5a..7e70c58e 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -37,7 +37,7 @@ void Vst3Logger::log_unknown_interface( } void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Construct&) { - log_request_base(is_host_vst, [](auto& message) { + log_request_base(is_host_vst, [&](auto& message) { // TODO: Log the CID on verbosity level 2, and then also report all CIDs // in the plugin factory message << "IPluginFactory::createComponent(cid = ..., _iid = " @@ -209,21 +209,29 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetTailSamples& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getTailSamples()"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, - [](auto& message) { message << "GetPluginFactory()"; }); + [&](auto& message) { message << "GetPluginFactory()"; }); } void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&) { - log_request_base(is_host_vst, [](auto& message) { + log_request_base(is_host_vst, [&](auto& message) { message << "IPluginFactory3::setHostContext(IHostApplication*)"; }); } void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { - log_request_base(is_host_vst, [](auto& message) { + log_request_base(is_host_vst, [&](auto& message) { message << "Requesting "; }); } @@ -305,5 +313,5 @@ void Vst3Logger::log_response(bool is_host_vst, void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { log_response_base(is_host_vst, - [](auto& message) { message << "; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 6c0285aa..23ce5e95 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -582,6 +582,22 @@ class YaComponent : public Steinberg::Vst::IComponent, virtual tresult PLUGIN_API setProcessing(TBool state) override = 0; virtual tresult PLUGIN_API process(Steinberg::Vst::ProcessData& data) override = 0; + + /** + * Message to pass through a call to `IAudioProcessor::getTailSamples()` + * to the Wine plugin host. + */ + struct GetTailSamples { + using Response = PrimitiveWrapper; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + virtual uint32 PLUGIN_API getTailSamples() override = 0; protected: diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index cbdb321d..5fbb6fe5 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -206,7 +206,6 @@ YaComponentPluginImpl::process(Steinberg::Vst::ProcessData& data) { } uint32 PLUGIN_API YaComponentPluginImpl::getTailSamples() { - // TODO: Implement - bridge.logger.log("TODO: IAudioProcessor::getTailSamples()"); - return 0; + return bridge.send_message( + YaComponent::GetTailSamples{.instance_id = arguments.instance_id}); } diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index d42952f2..3b787aeb 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -205,6 +205,11 @@ void Vst3Bridge::run() { return component_instances[request.instance_id] .audio_processor->setProcessing(request.state); }, + [&](const YaComponent::GetTailSamples& request) + -> YaComponent::GetTailSamples::Response { + return component_instances[request.instance_id] + .audio_processor->getTailSamples(); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( From 62f376d952f24fe0f9d6c0fb2a65bf446fd048a4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 14 Dec 2020 22:49:20 +0100 Subject: [PATCH 199/456] Allow the module to be properly unloaded Terminating the host process will cause all sockets to be closed so we can join the threads. --- src/plugin/bridges/vst2.cpp | 2 ++ src/plugin/bridges/vst3.cpp | 6 ++++++ src/plugin/bridges/vst3.h | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index 15a9bca8..54f394f0 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -396,6 +396,8 @@ intptr_t Vst2PluginBridge::dispatch(AEffect* /*plugin*/, // been caused by pipes and sockets being closed. io_context.stop(); + // TODO: For consistency with the VST3 version, move the above to + // the destructor delete this; return return_value; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 7c1073a3..c8e45e95 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -83,6 +83,12 @@ Vst3PluginBridge::Vst3PluginBridge() }); } +Vst3PluginBridge::~Vst3PluginBridge() { + // Drop all work make sure all sockets are closed + plugin_host->terminate(); + io_context.stop(); +} + Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { // Even though we're working with raw pointers here, we should pretend that // we're `IPtr` and do the reference counting diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 249bfc9e..86e54404 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -55,6 +55,12 @@ class Vst3PluginBridge : PluginBridge> { */ Vst3PluginBridge(); + /** + * Terminate the Wine plugin host process and drop all work when the module + * gets unloaded. + */ + ~Vst3PluginBridge(); + /** * When the host loads the module it will call `GetPluginFactory()` which * will in turn call this function. The idea is that we return an From e7d7317f60c8b51653fe770ccf105965f70d5071 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 13:07:25 +0100 Subject: [PATCH 200/456] Add a partial AudioBusBuffers implementation --- README.md | 2 +- src/common/serialization/vst3/base.h | 2 +- src/common/serialization/vst3/component.h | 1 + src/common/serialization/vst3/process-data.h | 216 +++++++++++++++++++ 4 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 src/common/serialization/vst3/process-data.h diff --git a/README.md b/README.md index e0771d85..e5857d99 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ imcomplete list of things that still have to be done before this can be used: - All other optional interfaces - Fully implemented: - `GetPluginFactory()` and `IPluginFactory{,2,3}` - - `IComponent` + - `IPluginBase` and `IComponent` - Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 7d452a02..049d6bac 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -28,7 +28,7 @@ // we'll need for all of our interfaces using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::int64, - Steinberg::uint32, Steinberg::tresult; + Steinberg::uint32, Steinberg::uint64, Steinberg::tresult; /** * Both `TUID` (`int8_t[16]`) and `FIDString` (`char*`) are hard to work with diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 23ce5e95..60b68340 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -32,6 +32,7 @@ #include "../common.h" #include "base.h" #include "host-application.h" +#include "process-data.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h new file mode 100644 index 00000000..b0e4a600 --- /dev/null +++ b/src/common/serialization/vst3/process-data.h @@ -0,0 +1,216 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include +#include + +#include "base.h" + +/** + * A serializable wrapper around `AudioBusBuffers` back by `std::vector`s. + * Data can be read from a `AudioBusBuffers` object provided by the host, and + * one the Wine plugin host side we can reconstruct the `AudioBusBuffers` object + * back from this object again. + * + * @see YaProcessData + */ +class YaAudioBusBuffers { + public: + /** + * A default constructor does not make any sense here since the actual data + * is a union, but we need a default constructor for bitsery. + */ + YaAudioBusBuffers(); + + /** + * Create a new, zero initialize audio bus buffers object. Used to + * reconstruct the output buffers during `YaProcessData::get()`. + */ + YaAudioBusBuffers(Steinberg::Vst::SymbolicSampleSizes sample_size, + size_t num_channels, + size_t num_samples); + + /** + * Copy data from a host provided `AudioBusBuffers` object during a process + * call. Constructed as part of `YaProcessData`. Since `AudioBusBuffers` + * contains an untagged union for storing single and double precision + * floating point values, the original `ProcessData`'s `symbolicSampleSize` + * field determines which variant of that union to use. + */ + YaAudioBusBuffers(Steinberg::Vst::SymbolicSampleSizes sample_size, + const Steinberg::Vst::AudioBusBuffers& data); + + /** + * Reconstruct the original `AudioBusBuffers` object passed to the + * constructor and return it. This is used as part of + * `YaProcessData::get()`. + */ + Steinberg::Vst::AudioBusBuffers& get(); + + template + void serialize(S& s) { + s.value8b(silence_flags); + s.ext(buffers, bitsery::ext::StdVariant{ + [](S& s, std::vector>& buffers) { + s.container(buffers, max_num_speakers, + [](S& s, auto& channel) { + s.container4b(channel, 1 << 16); + }); + }, + [](S& s, std::vector>& buffers) { + s.container(buffers, max_num_speakers, + [](S& s, auto& channel) { + s.container8b(channel, 1 << 16); + }); + }, + }); + } + + private: + /** + * The `AudioBusBuffers` object we reconstruct during `get()`. + */ + Steinberg::Vst::AudioBusBuffers reconstructed_buffers; + + /** + * A bitfield for silent channels copied directly from the input struct. + */ + uint64 silence_flags; + + /** + * The original implementation uses heap arrays and it stores a + * {float,double} array pointer per channel, with a separate field for the + * number of channels. We'll store this using a vector of vectors. + */ + std::variant>, + std::vector>> + buffers; +}; + +/** + * A serializable wrapper around the output fields of `ProcessData`. We send + * this back as a response to a process call so we can write those fields back + * to the host. It would be possible to just send `YaProcessData` back and have + * everything be in a single structure, but that would involve a lot of + * unnecessary copying (since, at least in theory, all the input audio buffers, + * events and context data shouldn't have been changed by the plugin). + * + * @see YaProcessData + */ +struct YaProcessDataResponse { + UniversalTResult result; + + // TODO: Add the output fields and a function to write these back to a + // `ProcessData&` + + template + void serialize(S& s) { + s.object(result); + } +}; + +/** + * A serializable wrapper around `ProcessData`. We'll read all information from + * the host so we can serialize it and provide an equivalent `ProcessData` + * struct to the plugin. Then we can create a `YaProcessDataResponse` object + * that contains all output values so we can write those back to the host. + */ +class YaProcessData { + public: + YaProcessData(); + + /** + * Copy data from a host provided `ProcessData` object during a process + * call. This struct can then be serialized, and `YaProcessData::get()` can + * then be used again to recreate the original `ProcessData` object. + */ + YaProcessData(const Steinberg::Vst::ProcessData& process_data); + + /** + * Reconstruct the original `ProcessData` object passed to the constructor + * and return it. This is used in the Wine plugin host when processing an + * `IAudioProcessor::process()` call. + */ + Steinberg::Vst::ProcessData& get(); + + /** + * **Move** all output written by the Windows VST3 plugin to a response + * object that can be used to write those results back to the host. + */ + YaProcessDataResponse move_outputs_to_response(); + + template + void serialize(S& s) { + s.value4b(process_mode); + s.value4b(symbolic_sample_size); + s.value4b(num_samples); + s.container( + inputs, max_num_speakers, + [](S& s, YaAudioBusBuffers& buffers) { s.object(buffers); }); + s.container4b(outputs_num_channels, max_num_speakers); + } + + private: + /** + * The process data we reconstruct from the other fields during `get()`. + */ + Steinberg::Vst::ProcessData reconstructed_process_data; + + /** + * The processing mode copied directly from the input struct. + */ + Steinberg::Vst::ProcessModes process_mode; + /** + * The symbolic sample size (see `Steinberg::Vst::SymbolicSampleSizes`) is + * important. The audio buffers are represented by as a C-style untagged + * union of array of either single or double precision floating point + * arrays. This field determines which of those variants should be used. + */ + Steinberg::Vst::SymbolicSampleSizes symbolic_sample_size; + /** + * The number of samples in each audio buffer. + */ + int32 num_samples; + /** + * In `ProcessData` they use C-style heap arrays, so they have to store the + * number of input/output busses, and then also store pointers to the first + * audio buffer object. We can combine these two into vectors. + */ + std::vector inputs; + /** + * For the outputs we only have to keep track of how many output channels + * each bus has. From this and from `num_samples` we can reconstruct the + * output buffers on the Wine side of the process call. + */ + std::vector outputs_num_channels; + + // TODO: Add these (but since these require interface implementations we'll + // do it in a second round) + /* + IParameterChanges* + inputParameterChanges; ///< incoming parameter changes for this block + IParameterChanges* outputParameterChanges; ///< outgoing parameter changes + ///< for this block (optional) + IEventList* inputEvents; ///< incoming events for this block (optional) + IEventList* outputEvents; ///< outgoing events for this block (optional) + ProcessContext* + processContext; ///< processing context (optional, but most welcome) + */ +}; From 026595d99f7a8a5c213ac3643cc2c369a020e5de Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 14:01:04 +0100 Subject: [PATCH 201/456] Implement IParamValueQueue --- README.md | 1 + meson.build | 2 + src/common/serialization/vst3/README.md | 8 ++ .../serialization/vst3/param-value-queue.cpp | 74 ++++++++++++++++ .../serialization/vst3/param-value-queue.h | 86 +++++++++++++++++++ 5 files changed, 171 insertions(+) create mode 100644 src/common/serialization/vst3/param-value-queue.cpp create mode 100644 src/common/serialization/vst3/param-value-queue.h diff --git a/README.md b/README.md index e5857d99..5f96bbb7 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ imcomplete list of things that still have to be done before this can be used: - Fully implemented: - `GetPluginFactory()` and `IPluginFactory{,2,3}` - `IPluginBase` and `IComponent` + - `IParamValueQueue` - Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and diff --git a/meson.build b/meson.build index 17f46217..79972446 100644 --- a/meson.build +++ b/meson.build @@ -80,6 +80,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/host-application.cpp', + 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/configuration.cpp', 'src/common/plugins.cpp', @@ -115,6 +116,7 @@ if with_vst3 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/host-application.cpp', + 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/wine-host/bridges/vst3-impls/host-application.cpp', 'src/wine-host/bridges/vst3.cpp', diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 12e801bf..5a6b4f8c 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -107,6 +107,14 @@ proxying is described in the section above. 8. Finally a pointer to this `YaFooPluginImpl` gets returned as the last step of the initialization process. +## Simple objects + +For serializing objects of interfaces that purely contain getters and setters +(and thus don't need to perform any host callbacks), we'll simply have a +constructor that takes the `IFoo` by `IPtr` or reference (depending on how it's +used in the SDK) and reads the data from it to create a serializable copy of +that object. + ## Safety notes - None of the destructors in the interfaces defined by the SDK are marked as diff --git a/src/common/serialization/vst3/param-value-queue.cpp b/src/common/serialization/vst3/param-value-queue.cpp new file mode 100644 index 00000000..02f1c75f --- /dev/null +++ b/src/common/serialization/vst3/param-value-queue.cpp @@ -0,0 +1,74 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "param-value-queue.h" + +YaParamValueQueue::YaParamValueQueue(){FUNKNOWN_CTOR} + +YaParamValueQueue::YaParamValueQueue( + Steinberg::Vst::IParamValueQueue& original_queue) + : parameter_id(original_queue.getParameterId()), + queue(original_queue.getPointCount()) { + FUNKNOWN_CTOR + + // Copy over all points to our vector + for (int i = 0; i < original_queue.getPointCount(); i++) { + // We're skipping the assertions here and just assume that the function + // returns `kResultOk` + original_queue.getPoint(i, queue[i].first, queue[i].second); + } +} + +YaParamValueQueue::~YaParamValueQueue() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_FUNKNOWN_METHODS(YaParamValueQueue, + Steinberg::Vst::IParamValueQueue, + Steinberg::Vst::IParamValueQueue::iid) +#pragma GCC diagnostic pop + +Steinberg::Vst::ParamID PLUGIN_API YaParamValueQueue::getParameterId() { + return parameter_id; +} + +int32 PLUGIN_API YaParamValueQueue::getPointCount() { + return queue.size(); +} + +tresult PLUGIN_API +YaParamValueQueue::getPoint(int32 index, + int32& sampleOffset /*out*/, + Steinberg::Vst::ParamValue& value /*out*/) { + if (index < static_cast(queue.size())) { + sampleOffset = queue[index].first; + value = queue[index].second; + + return Steinberg::kResultOk; + } else { + return Steinberg::kInvalidArgument; + } +} +tresult PLUGIN_API YaParamValueQueue::addPoint(int32 sampleOffset, + Steinberg::Vst::ParamValue value, + int32& index /*out*/) { + index = queue.size(); + queue.push_back({sampleOffset, value}); + + return Steinberg::kResultOk; +} diff --git a/src/common/serialization/vst3/param-value-queue.h b/src/common/serialization/vst3/param-value-queue.h new file mode 100644 index 00000000..49e0c1de --- /dev/null +++ b/src/common/serialization/vst3/param-value-queue.h @@ -0,0 +1,86 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include + +#include "base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IParamValueQueue` for serializing a queue containing changes to + * a single parameter during the current processing cycle. Used in + * `YaParameterChanges`. + */ +class YaParamValueQueue : public Steinberg::Vst::IParamValueQueue { + public: + /** + * Default constructor with an empty queue. + */ + YaParamValueQueue(); + + /** + * Read data from an existing `IParamValueQueue` object. + */ + YaParamValueQueue(Steinberg::Vst::IParamValueQueue&); + + ~YaParamValueQueue(); + + DECLARE_FUNKNOWN_METHODS + + // From `IParamValueQueue` + Steinberg::Vst::ParamID PLUGIN_API getParameterId() override; + int32 PLUGIN_API getPointCount() override; + tresult PLUGIN_API + getPoint(int32 index, + int32& sampleOffset /*out*/, + Steinberg::Vst::ParamValue& value /*out*/) override; + tresult PLUGIN_API addPoint(int32 sampleOffset, + Steinberg::Vst::ParamValue value, + int32& index /*out*/) override; + + template + void serialize(S& s) { + s.value4b(parameter_id); + // TODO: Does bitsery have a built in way to serialize pairs? + s.container(queue, 1 << 16, [](S& s, std::pair& pair) { + s.value4b(pair.first); + s.value8b(pair.second); + }); + } + + private: + /** + * For `IParamValueQueue::getParameterId`. + */ + Steinberg::Vst::ParamID parameter_id; + + /** + * The actual parameter changes queue. The specification doesn't mention + * that this should be a priority queue or something, but I'd assume both + * the plugin and the host will insert the values in chronological order + * (because, why would they not?). + * + * This contains pairs of `(sample_offset, value)`. + */ + std::vector> queue; +}; + +#pragma GCC diagnostic pop From 92c37f71db3e03c40e78900ec42ae8fcea66f5f3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 14:24:55 +0100 Subject: [PATCH 202/456] Implement IParameterChanges --- README.md | 2 +- meson.build | 2 + .../serialization/vst3/param-value-queue.cpp | 8 ++- .../serialization/vst3/param-value-queue.h | 8 ++- .../serialization/vst3/parameter-changes.cpp | 64 +++++++++++++++++ .../serialization/vst3/parameter-changes.h | 68 +++++++++++++++++++ 6 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 src/common/serialization/vst3/parameter-changes.cpp create mode 100644 src/common/serialization/vst3/parameter-changes.h diff --git a/README.md b/README.md index 5f96bbb7..a73135eb 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ imcomplete list of things that still have to be done before this can be used: - Fully implemented: - `GetPluginFactory()` and `IPluginFactory{,2,3}` - `IPluginBase` and `IComponent` - - `IParamValueQueue` + - `IParameterChanges` and `IParamValueQueue` - Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and diff --git a/meson.build b/meson.build index 79972446..d8c0fbb8 100644 --- a/meson.build +++ b/meson.build @@ -81,6 +81,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', + 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/configuration.cpp', 'src/common/plugins.cpp', @@ -117,6 +118,7 @@ if with_vst3 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', + 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/wine-host/bridges/vst3-impls/host-application.cpp', 'src/wine-host/bridges/vst3.cpp', diff --git a/src/common/serialization/vst3/param-value-queue.cpp b/src/common/serialization/vst3/param-value-queue.cpp index 02f1c75f..25e93375 100644 --- a/src/common/serialization/vst3/param-value-queue.cpp +++ b/src/common/serialization/vst3/param-value-queue.cpp @@ -18,8 +18,12 @@ YaParamValueQueue::YaParamValueQueue(){FUNKNOWN_CTOR} -YaParamValueQueue::YaParamValueQueue( - Steinberg::Vst::IParamValueQueue& original_queue) +YaParamValueQueue::YaParamValueQueue(Steinberg::Vst::ParamID parameter_id) + : parameter_id(parameter_id){FUNKNOWN_CTOR} + + // clang-format /really/ doesn't like these macros + YaParamValueQueue::YaParamValueQueue(Steinberg::Vst::IParamValueQueue & + original_queue) : parameter_id(original_queue.getParameterId()), queue(original_queue.getPointCount()) { FUNKNOWN_CTOR diff --git a/src/common/serialization/vst3/param-value-queue.h b/src/common/serialization/vst3/param-value-queue.h index 49e0c1de..9b3b02fe 100644 --- a/src/common/serialization/vst3/param-value-queue.h +++ b/src/common/serialization/vst3/param-value-queue.h @@ -36,10 +36,16 @@ class YaParamValueQueue : public Steinberg::Vst::IParamValueQueue { */ YaParamValueQueue(); + /** + * Create an empty queue for a specific parameter. Used in + * `YaParameterChanges::addParameterData`. + */ + YaParamValueQueue(Steinberg::Vst::ParamID parameter_id); + /** * Read data from an existing `IParamValueQueue` object. */ - YaParamValueQueue(Steinberg::Vst::IParamValueQueue&); + YaParamValueQueue(Steinberg::Vst::IParamValueQueue& original_queue); ~YaParamValueQueue(); diff --git a/src/common/serialization/vst3/parameter-changes.cpp b/src/common/serialization/vst3/parameter-changes.cpp new file mode 100644 index 00000000..0b78efee --- /dev/null +++ b/src/common/serialization/vst3/parameter-changes.cpp @@ -0,0 +1,64 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "parameter-changes.h" + +YaParameterChanges::YaParameterChanges(){FUNKNOWN_CTOR} + +YaParameterChanges::YaParameterChanges( + Steinberg::Vst::IParameterChanges& original_queues) { + FUNKNOWN_CTOR + + // Copy over all parameter changne queues. Everything gets converted to + // `YaParamValueQueue`s + queues.reserve(original_queues.getParameterCount()); + for (int i = 0; i < original_queues.getParameterCount(); i++) { + queues.push_back(*original_queues.getParameterData(i)); + } +} + +YaParameterChanges::~YaParameterChanges() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_FUNKNOWN_METHODS(YaParameterChanges, + Steinberg::Vst::IParameterChanges, + Steinberg::Vst::IParameterChanges::iid) +#pragma GCC diagnostic pop + +int32 PLUGIN_API YaParameterChanges::getParameterCount() { + return queues.size(); +} + +Steinberg::Vst::IParamValueQueue* PLUGIN_API +YaParameterChanges::getParameterData(int32 index) { + if (index < static_cast(queues.size())) { + return &queues[index]; + } else { + return nullptr; + } +} + +Steinberg::Vst::IParamValueQueue* PLUGIN_API +YaParameterChanges::addParameterData(const Steinberg::Vst::ParamID& id, + int32& index /*out*/) { + index = queues.size(); + queues.push_back(YaParamValueQueue(id)); + + return &queues[index]; +} diff --git a/src/common/serialization/vst3/parameter-changes.h b/src/common/serialization/vst3/parameter-changes.h new file mode 100644 index 00000000..0eabaa30 --- /dev/null +++ b/src/common/serialization/vst3/parameter-changes.h @@ -0,0 +1,68 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "base.h" +#include "param-value-queue.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IParameterChanges` for serialization purposes. Used in + * `YaProcessData`. + */ +class YaParameterChanges : public Steinberg::Vst::IParameterChanges { + public: + /** + * Default constructor with an empty parameter changes list. The plugin can + * use this to output data. + */ + YaParameterChanges(); + + /** + * Read data from an existing `IParameterChanges` object. + */ + YaParameterChanges(Steinberg::Vst::IParameterChanges& original_queues); + + ~YaParameterChanges(); + + DECLARE_FUNKNOWN_METHODS + + // From `IParameterChanges` + int32 PLUGIN_API getParameterCount() override; + Steinberg::Vst::IParamValueQueue* PLUGIN_API + getParameterData(int32 index) override; + Steinberg::Vst::IParamValueQueue* PLUGIN_API + addParameterData(const Steinberg::Vst::ParamID& id, + int32& index /*out*/) override; + + template + void serialize(S& s) { + s.container(queues, 1 << 16); + } + + private: + /** + * The parameter value changes queues. + */ + std::vector queues; +}; + +#pragma GCC diagnostic pop From ce09d604470d1cdb55456f3735a70750b02e4e0c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 14:30:41 +0100 Subject: [PATCH 203/456] Add parameter changes to YaProcessData --- src/common/serialization/vst3/process-data.h | 31 +++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h index b0e4a600..93377a73 100644 --- a/src/common/serialization/vst3/process-data.h +++ b/src/common/serialization/vst3/process-data.h @@ -22,6 +22,9 @@ #include #include "base.h" +#include "parameter-changes.h" + +// This header provides serialization wrappers around `ProcessData` /** * A serializable wrapper around `AudioBusBuffers` back by `std::vector`s. @@ -115,14 +118,16 @@ class YaAudioBusBuffers { * @see YaProcessData */ struct YaProcessDataResponse { - UniversalTResult result; + std::vector outputs; + // TODO: Don't forget to check for null before writing these back + YaParameterChanges output_parameter_changes; - // TODO: Add the output fields and a function to write these back to a - // `ProcessData&` + // TODO: Add events template void serialize(S& s) { - s.object(result); + s.container(outputs, max_num_speakers); + s.container(output_parameter_changes, 1 << 16); } }; @@ -161,10 +166,9 @@ class YaProcessData { s.value4b(process_mode); s.value4b(symbolic_sample_size); s.value4b(num_samples); - s.container( - inputs, max_num_speakers, - [](S& s, YaAudioBusBuffers& buffers) { s.object(buffers); }); + s.container(inputs, max_num_speakers); s.container4b(outputs_num_channels, max_num_speakers); + s.container(input_parameter_changes, 1 << 16); } private: @@ -177,6 +181,7 @@ class YaProcessData { * The processing mode copied directly from the input struct. */ Steinberg::Vst::ProcessModes process_mode; + /** * The symbolic sample size (see `Steinberg::Vst::SymbolicSampleSizes`) is * important. The audio buffers are represented by as a C-style untagged @@ -184,16 +189,19 @@ class YaProcessData { * arrays. This field determines which of those variants should be used. */ Steinberg::Vst::SymbolicSampleSizes symbolic_sample_size; + /** * The number of samples in each audio buffer. */ int32 num_samples; + /** * In `ProcessData` they use C-style heap arrays, so they have to store the * number of input/output busses, and then also store pointers to the first * audio buffer object. We can combine these two into vectors. */ std::vector inputs; + /** * For the outputs we only have to keep track of how many output channels * each bus has. From this and from `num_samples` we can reconstruct the @@ -201,13 +209,14 @@ class YaProcessData { */ std::vector outputs_num_channels; + /** + * Incoming parameter changes. + */ + YaParameterChanges input_parameter_changes; + // TODO: Add these (but since these require interface implementations we'll // do it in a second round) /* - IParameterChanges* - inputParameterChanges; ///< incoming parameter changes for this block - IParameterChanges* outputParameterChanges; ///< outgoing parameter changes - ///< for this block (optional) IEventList* inputEvents; ///< incoming events for this block (optional) IEventList* outputEvents; ///< outgoing events for this block (optional) ProcessContext* From 6fc54d80fb7e55c6ac9d3b4b7505e9600329e5b4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 17:01:57 +0100 Subject: [PATCH 204/456] Move TChar* -> std::u16string conversion to base.h We're going to need it for VST3 events. --- src/common/serialization/vst3/base.cpp | 10 ++++++++++ src/common/serialization/vst3/base.h | 7 +++++++ src/common/serialization/vst3/host-application.cpp | 8 +------- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index 0704c5ac..31b00998 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -19,6 +19,16 @@ #include "base.h" +std::u16string tchar_string_to_u16string(const Steinberg::Vst::TChar* string) { +#ifdef __WINE__ + // This is great, thanks Steinberg + static_assert(sizeof(Steinberg::Vst::TChar) == sizeof(char16_t)); + return std::u16string(reinterpret_cast(string)); +#else + return std::u16string(static_cast(string)); +#endif +} + UniversalTResult::UniversalTResult() : universal_result(Value::kResultFalse) {} UniversalTResult::UniversalTResult(tresult native_result) diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 049d6bac..a2437457 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -23,6 +23,7 @@ #include #include #include +#include // Yet Another layer of includes, but these are some VST3-specific typedefs that // we'll need for all of our interfaces @@ -51,6 +52,12 @@ constexpr size_t max_num_speakers = 16384; */ constexpr size_t max_vector_stream_size = 50 << 20; +/** + * Convert a UTF-16 C-style string to an `std::u16string`. Who event invented + * UTF-16? + */ +std::u16string tchar_string_to_u16string(const Steinberg::Vst::TChar* string); + /** * Empty struct for when we have send a response to some operation without any * result values. diff --git a/src/common/serialization/vst3/host-application.cpp b/src/common/serialization/vst3/host-application.cpp index b5695028..00c2e637 100644 --- a/src/common/serialization/vst3/host-application.cpp +++ b/src/common/serialization/vst3/host-application.cpp @@ -24,13 +24,7 @@ YaHostApplication::ConstructArgs::ConstructArgs( : component_instance_id(component_instance_id) { Steinberg::Vst::String128 name_array; if (context->getName(name_array) == Steinberg::kResultOk) { -#ifdef __WINE__ - // Who even invented UTF-16 - static_assert(sizeof(Steinberg::Vst::TChar) == sizeof(char16_t)); - name = std::u16string(reinterpret_cast(name_array)); -#else - name = std::u16string(static_cast(name_array)); -#endif + name = tchar_string_to_u16string(name_array); } } From d8c51d885be45cfa8d3415b06b05c3ae7c23685b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 17:10:54 +0100 Subject: [PATCH 205/456] Partially implement IEventList and all event types --- src/common/serialization/vst3/base.h | 5 +- src/common/serialization/vst3/event-list.h | 294 +++++++++++++++++++++ 2 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 src/common/serialization/vst3/event-list.h diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index a2437457..5a1b12d0 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -28,8 +28,9 @@ // Yet Another layer of includes, but these are some VST3-specific typedefs that // we'll need for all of our interfaces -using Steinberg::TBool, Steinberg::int8, Steinberg::int32, Steinberg::int64, - Steinberg::uint32, Steinberg::uint64, Steinberg::tresult; +using Steinberg::TBool, Steinberg::int8, Steinberg::int16, Steinberg::int32, + Steinberg::int64, Steinberg::uint8, Steinberg::uint32, Steinberg::uint64, + Steinberg::tresult; /** * Both `TUID` (`int8_t[16]`) and `FIDString` (`char*`) are hard to work with diff --git a/src/common/serialization/vst3/event-list.h b/src/common/serialization/vst3/event-list.h new file mode 100644 index 00000000..ac3e93d7 --- /dev/null +++ b/src/common/serialization/vst3/event-list.h @@ -0,0 +1,294 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include + +#include "base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * A wrapper around `DataEvent` for serialization purposes, as this event + * contains a heap array. + */ +struct YaDataEvent { + YaDataEvent(); + + /** + * Copy data from an existing `DataEvent`. + */ + YaDataEvent(const Steinberg::Vst::DataEvent& event); + + /** + * Reconstruct a `DataEvent` from this object. + * + * @note This object may contain pointers to data stored in this object, and + * must thus not outlive it. + */ + Steinberg::Vst::DataEvent get() const; + + uint32 type; + std::vector data; + + template + void serialize(S& s) { + s.value4b(type); + s.container1b(data, 1 << 16); + } +}; + +/** + * A wrapper around `NoteExpressionTextEvent` for serialization purposes, as + * this event contains a heap array. + */ +struct YaNoteExpressionTextEvent { + YaNoteExpressionTextEvent(); + + /** + * Copy data from an existing `NoteExpressionTextEvent`. + */ + YaNoteExpressionTextEvent( + const Steinberg::Vst::NoteExpressionTextEvent& event); + + /** + * Reconstruct a `NoteExpressionTextEvent` from this object. + * + * @note This object may contain pointers to data stored in this object, and + * must thus not outlive it. + */ + Steinberg::Vst::NoteExpressionTextEvent get() const; + + Steinberg::Vst::NoteExpressionTypeID type_id; + int32 note_id; + + std::u16string text; + + template + void serialize(S& s) { + s.value4b(type_id); + s.value4b(note_id); + s.container2b(text, 1 << 16); + } +}; + +/** + * A wrapper around `ChordEvent` for serialization purposes, as this event + * contains a heap array. + */ +struct YaChordEvent { + YaChordEvent(); + + /** + * Copy data from an existing `ChordEvent`. + */ + YaChordEvent(const Steinberg::Vst::ChordEvent& event); + + /** + * Reconstruct a `ChordEvent` from this object. + * + * @note This object may contain pointers to data stored in this object, and + * must thus not outlive it. + */ + Steinberg::Vst::ChordEvent get() const; + + int16 root; + int16 bass_note; + int16 mask; + + std::u16string text; + + template + void serialize(S& s) { + s.value2b(root); + s.value2b(bass_note); + s.value2b(mask); + s.container2b(text, 1 << 16); + } +}; + +/** + * A wrapper around `ScaleEvent` for serialization purposes, as this event + * contains a heap array. + */ +struct YaScaleEvent { + YaScaleEvent(); + + /** + * Copy data from an existing `ScaleEvent`. + */ + YaScaleEvent(const Steinberg::Vst::ScaleEvent& event); + + /** + * Reconstruct a `ScaleEvent` from this object. + * + * @note This object may contain pointers to data stored in this object, and + * must thus not outlive it. + */ + Steinberg::Vst::ScaleEvent get() const; + + int16 root; + int16 mask; + + std::u16string text; + + template + void serialize(S& s) { + s.value2b(root); + s.value2b(mask); + s.container2b(text, 1 << 16); + } +}; + +/** + * A wrapper around `Event` for serialization purposes, as some event types + * include heap pointers. + */ +struct YaEvent { + YaEvent(); + + /** + * Copy data from an `Event`. + */ + YaEvent(const Steinberg::Vst::Event& event); + + /** + * Reconstruct an `Event` from this object. + * + * @note This object may contain pointers to data stored in this object, and + * must thus not outlive it. + */ + Steinberg::Vst::Event get() const; + + // These fields directly reflect those from `Event` + int32 bus_index; + int32 sample_offset; + Steinberg::Vst::TQuarterNotes ppq_position; + Steinberg::Vst::Event::EventFlags flags; + + // `Event` stores an event type and a union, we'll encode both in a variant. + // We can use simple types directly, and we need serializable wrappers + // around move event types with heap pointers. + std::variant + payload; + + template + void serialize(S& s) { + s.value4b(bus_index); + s.value4b(sample_offset); + s.value8b(ppq_position); + s.value2b(flags); + s.ext(payload, bitsery::ext::StdVariant{}); + } +}; + +/** + * Wraps around `IEventList` for serialization purposes. Used in + * `YaProcessData`. + */ +class YaEventList : public Steinberg::Vst::IEventList { + public: + /** + * Default constructor with an empty event list. The plugin can use this to + * output data. + */ + YaEventList(); + + /** + * Read data from an existing `IEventList` object. + */ + YaEventList(Steinberg::Vst::IEventList& original_events); + + ~YaEventList(); + + DECLARE_FUNKNOWN_METHODS + + // From `IEventList` + virtual int32 PLUGIN_API getEventCount() override; + // We're making the assumption here that events are immutable (which should + // be the case, but it's never mentioned anywhere) + virtual tresult PLUGIN_API + getEvent(int32 index, Steinberg::Vst::Event& e /*out*/) override; + virtual tresult PLUGIN_API + addEvent(Steinberg::Vst::Event& e /*in*/) override; + + template + void serialize(S& s) { + s.container(events, 1 << 16); + } + + private: + std::vector events; +}; + +namespace Steinberg { +namespace Vst { +template +void serialize(S& s, NoteOnEvent& event) { + s.value2b(event.channel); + s.value2b(event.pitch); + s.value4b(event.tuning); + s.value4b(event.velocity); + s.value4b(event.length); + s.value4b(event.noteId); +} + +template +void serialize(S& s, NoteOffEvent& event) { + s.value2b(event.channel); + s.value2b(event.pitch); + s.value4b(event.velocity); + s.value4b(event.noteId); + s.value4b(event.tuning); +} + +template +void serialize(S& s, PolyPressureEvent& event) { + s.value2b(event.channel); + s.value2b(event.pitch); + s.value4b(event.pressure); + s.value4b(event.noteId); +} + +template +void serialize(S& s, NoteExpressionValueEvent& event) { + s.value4b(event.typeId); + s.value4b(event.noteId); + s.value8b(event.value); +} + +template +void serialize(S& s, LegacyMIDICCOutEvent& event) { + s.value1b(event.controlNumber); + s.value1b(event.channel); + s.value1b(event.value); + s.value1b(event.value2); +} +} // namespace Vst +} // namespace Steinberg + +#pragma GCC diagnostic pop From f33b749172d605d82d44a9dfa40c86930d58a8df Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 17:44:26 +0100 Subject: [PATCH 206/456] Implement all event types --- meson.build | 2 + src/common/serialization/vst3/base.cpp | 22 +++++- src/common/serialization/vst3/base.h | 20 +++++- src/common/serialization/vst3/event-list.cpp | 72 +++++++++++++++++++ src/common/serialization/vst3/event-list.h | 4 +- .../serialization/vst3/host-application.cpp | 2 +- 6 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 src/common/serialization/vst3/event-list.cpp diff --git a/meson.build b/meson.build index d8c0fbb8..7e514e0b 100644 --- a/meson.build +++ b/meson.build @@ -79,6 +79,7 @@ vst3_plugin_sources = [ 'src/common/logging/vst3.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component.cpp', + 'src/common/serialization/vst3/event-list.cpp', 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', @@ -116,6 +117,7 @@ if with_vst3 'src/common/logging/vst3.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component.cpp', + 'src/common/serialization/vst3/event-list.cpp', 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index 31b00998..d8f21041 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -19,7 +19,7 @@ #include "base.h" -std::u16string tchar_string_to_u16string(const Steinberg::Vst::TChar* string) { +std::u16string tchar_pointer_to_u16string(const Steinberg::Vst::TChar* string) { #ifdef __WINE__ // This is great, thanks Steinberg static_assert(sizeof(Steinberg::Vst::TChar) == sizeof(char16_t)); @@ -29,6 +29,26 @@ std::u16string tchar_string_to_u16string(const Steinberg::Vst::TChar* string) { #endif } +std::u16string tchar_pointer_to_u16string(const Steinberg::Vst::TChar* string, + uint32 length) { +#ifdef __WINE__ + static_assert(sizeof(Steinberg::Vst::TChar) == sizeof(char16_t)); + return std::u16string(reinterpret_cast(string), length); +#else + return std::u16string(static_cast(string), length); +#endif +} + +const Steinberg::Vst::TChar* u16string_to_tchar_pointer( + const std::u16string& string) { +#ifdef __WINE__ + static_assert(sizeof(Steinberg::Vst::TChar) == sizeof(char16_t)); + return reinterpret_cast(string.c_str()); +#else + return static_cast(string.c_str()); +#endif +} + UniversalTResult::UniversalTResult() : universal_result(Value::kResultFalse) {} UniversalTResult::UniversalTResult(tresult native_result) diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 5a1b12d0..97504639 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -29,8 +29,8 @@ // we'll need for all of our interfaces using Steinberg::TBool, Steinberg::int8, Steinberg::int16, Steinberg::int32, - Steinberg::int64, Steinberg::uint8, Steinberg::uint32, Steinberg::uint64, - Steinberg::tresult; + Steinberg::int64, Steinberg::uint8, Steinberg::uint16, Steinberg::uint32, + Steinberg::uint64, Steinberg::tresult; /** * Both `TUID` (`int8_t[16]`) and `FIDString` (`char*`) are hard to work with @@ -57,7 +57,21 @@ constexpr size_t max_vector_stream_size = 50 << 20; * Convert a UTF-16 C-style string to an `std::u16string`. Who event invented * UTF-16? */ -std::u16string tchar_string_to_u16string(const Steinberg::Vst::TChar* string); +std::u16string tchar_pointer_to_u16string(const Steinberg::Vst::TChar* string); + +/** + * Same as the above, but with a fixed string length. + * + * @overload + */ +std::u16string tchar_pointer_to_u16string(const Steinberg::Vst::TChar* string, + uint32 length); + +/** + * Convert an `std::u16string` back to a null terminated `TChar*` string. + */ +const Steinberg::Vst::TChar* u16string_to_tchar_pointer( + const std::u16string& string); /** * Empty struct for when we have send a response to some operation without any diff --git a/src/common/serialization/vst3/event-list.cpp b/src/common/serialization/vst3/event-list.cpp new file mode 100644 index 00000000..58359588 --- /dev/null +++ b/src/common/serialization/vst3/event-list.cpp @@ -0,0 +1,72 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "event-list.h" + +YaDataEvent::YaDataEvent() {} + +YaDataEvent::YaDataEvent(const Steinberg::Vst::DataEvent& event) + : type(event.type), buffer(event.bytes, event.bytes + event.size) {} + +Steinberg::Vst::DataEvent YaDataEvent::get() const { + return Steinberg::Vst::DataEvent{.size = static_cast(buffer.size()), + .type = type, + .bytes = buffer.data()}; +} + +YaNoteExpressionTextEvent::YaNoteExpressionTextEvent() {} + +YaNoteExpressionTextEvent::YaNoteExpressionTextEvent( + const Steinberg::Vst::NoteExpressionTextEvent& event) + : type_id(event.typeId), + note_id(event.noteId), + text(tchar_pointer_to_u16string(event.text, event.textLen)) {} + +Steinberg::Vst::NoteExpressionTextEvent YaNoteExpressionTextEvent::get() const { + return Steinberg::Vst::NoteExpressionTextEvent{ + .typeId = type_id, + .noteId = note_id, + .textLen = static_cast(text.size()), + .text = u16string_to_tchar_pointer(text)}; +} + +YaChordEvent::YaChordEvent() {} + +YaChordEvent::YaChordEvent(const Steinberg::Vst::ChordEvent& event) + : root(event.root), + bass_note(event.bassNote), + text(tchar_pointer_to_u16string(event.text, event.textLen)) {} + +Steinberg::Vst::ChordEvent YaChordEvent::get() const { + return Steinberg::Vst::ChordEvent{ + .root = root, + .bassNote = bass_note, + .textLen = static_cast(text.size()), + .text = u16string_to_tchar_pointer(text)}; +} + +YaScaleEvent::YaScaleEvent() {} + +YaScaleEvent::YaScaleEvent(const Steinberg::Vst::ScaleEvent& event) + : root(event.root), + text(tchar_pointer_to_u16string(event.text, event.textLen)) {} + +Steinberg::Vst::ScaleEvent YaScaleEvent::get() const { + return Steinberg::Vst::ScaleEvent{ + .root = root, + .textLen = static_cast(text.size()), + .text = u16string_to_tchar_pointer(text)}; +} diff --git a/src/common/serialization/vst3/event-list.h b/src/common/serialization/vst3/event-list.h index ac3e93d7..21bba146 100644 --- a/src/common/serialization/vst3/event-list.h +++ b/src/common/serialization/vst3/event-list.h @@ -45,12 +45,12 @@ struct YaDataEvent { Steinberg::Vst::DataEvent get() const; uint32 type; - std::vector data; + std::vector buffer; template void serialize(S& s) { s.value4b(type); - s.container1b(data, 1 << 16); + s.container1b(buffer, 1 << 16); } }; diff --git a/src/common/serialization/vst3/host-application.cpp b/src/common/serialization/vst3/host-application.cpp index 00c2e637..044f4d7a 100644 --- a/src/common/serialization/vst3/host-application.cpp +++ b/src/common/serialization/vst3/host-application.cpp @@ -24,7 +24,7 @@ YaHostApplication::ConstructArgs::ConstructArgs( : component_instance_id(component_instance_id) { Steinberg::Vst::String128 name_array; if (context->getName(name_array) == Steinberg::kResultOk) { - name = tchar_string_to_u16string(name_array); + name = tchar_pointer_to_u16string(name_array); } } From 0137bb349967ae6fb7c3ec219c2b7f1f2756bb69 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 18:02:26 +0100 Subject: [PATCH 207/456] Fully implement YaEvent --- src/common/serialization/vst3/event-list.cpp | 95 ++++++++++++++++++++ src/common/serialization/vst3/event-list.h | 2 +- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/common/serialization/vst3/event-list.cpp b/src/common/serialization/vst3/event-list.cpp index 58359588..8257ea47 100644 --- a/src/common/serialization/vst3/event-list.cpp +++ b/src/common/serialization/vst3/event-list.cpp @@ -16,6 +16,8 @@ #include "event-list.h" +#include "src/common/utils.h" + YaDataEvent::YaDataEvent() {} YaDataEvent::YaDataEvent(const Steinberg::Vst::DataEvent& event) @@ -70,3 +72,96 @@ Steinberg::Vst::ScaleEvent YaScaleEvent::get() const { .textLen = static_cast(text.size()), .text = u16string_to_tchar_pointer(text)}; } + +YaEvent::YaEvent() {} + +YaEvent::YaEvent(const Steinberg::Vst::Event& event) + : bus_index(event.busIndex), + sample_offset(event.sampleOffset), + ppq_position(event.ppqPosition), + flags(event.flags) { + // Now we need the correct event type + switch (event.type) { + case Steinberg::Vst::Event::kNoteOnEvent: + payload = event.noteOn; + break; + case Steinberg::Vst::Event::kNoteOffEvent: + payload = event.noteOff; + break; + case Steinberg::Vst::Event::kDataEvent: + payload = YaDataEvent(event.data); + break; + case Steinberg::Vst::Event::kPolyPressureEvent: + payload = event.polyPressure; + break; + case Steinberg::Vst::Event::kNoteExpressionValueEvent: + payload = event.noteExpressionValue; + break; + case Steinberg::Vst::Event::kNoteExpressionTextEvent: + payload = YaNoteExpressionTextEvent(event.noteExpressionText); + break; + case Steinberg::Vst::Event::kChordEvent: + payload = YaChordEvent(event.chord); + break; + case Steinberg::Vst::Event::kScaleEvent: + payload = YaScaleEvent(event.scale); + break; + case Steinberg::Vst::Event::kLegacyMIDICCOutEvent: + payload = event.midiCCOut; + break; + default: + // XXX: When encountering something we don't know about, should we + // throw or silently ignore it? We can't properly log about + // this directly from here. + break; + } +} + +Steinberg::Vst::Event YaEvent::get() const { + Steinberg::Vst::Event event{.busIndex = bus_index, + .sampleOffset = sample_offset, + .ppqPosition = ppq_position, + .flags = flags}; + std::visit( + overload{ + [&](const Steinberg::Vst::NoteOnEvent& specific_event) { + event.type = Steinberg::Vst::Event::kNoteOnEvent; + event.noteOn = specific_event; + }, + [&](const Steinberg::Vst::NoteOffEvent& specific_event) { + event.type = Steinberg::Vst::Event::kNoteOffEvent; + event.noteOff = specific_event; + }, + [&](const YaDataEvent& specific_event) { + event.type = Steinberg::Vst::Event::kDataEvent; + event.data = specific_event.get(); + }, + [&](const Steinberg::Vst::PolyPressureEvent& specific_event) { + event.type = Steinberg::Vst::Event::kPolyPressureEvent; + event.polyPressure = specific_event; + }, + [&](const Steinberg::Vst::NoteExpressionValueEvent& + specific_event) { + event.type = Steinberg::Vst::Event::kNoteExpressionValueEvent; + event.noteExpressionValue = specific_event; + }, + [&](const YaNoteExpressionTextEvent& specific_event) { + event.type = Steinberg::Vst::Event::kNoteExpressionTextEvent; + event.noteExpressionText = specific_event.get(); + }, + [&](const YaChordEvent& specific_event) { + event.type = Steinberg::Vst::Event::kChordEvent; + event.chord = specific_event.get(); + }, + [&](const YaScaleEvent& specific_event) { + event.type = Steinberg::Vst::Event::kScaleEvent; + event.scale = specific_event.get(); + }, + [&](const Steinberg::Vst::LegacyMIDICCOutEvent& specific_event) { + event.type = Steinberg::Vst::Event::kLegacyMIDICCOutEvent; + event.midiCCOut = specific_event; + }}, + payload); + + return event; +} diff --git a/src/common/serialization/vst3/event-list.h b/src/common/serialization/vst3/event-list.h index 21bba146..e9e0bab7 100644 --- a/src/common/serialization/vst3/event-list.h +++ b/src/common/serialization/vst3/event-list.h @@ -180,7 +180,7 @@ struct YaEvent { int32 bus_index; int32 sample_offset; Steinberg::Vst::TQuarterNotes ppq_position; - Steinberg::Vst::Event::EventFlags flags; + uint16 flags; // `Event` stores an event type and a union, we'll encode both in a variant. // We can use simple types directly, and we need serializable wrappers From a516309d17cb09f40ec9675d787708a483c37595 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 18:32:43 +0100 Subject: [PATCH 208/456] Implement IEventList --- README.md | 3 +- src/common/serialization/vst3/event-list.cpp | 60 +++++++++++++++++++ src/common/serialization/vst3/event-list.h | 10 +++- .../serialization/vst3/parameter-changes.cpp | 2 +- 4 files changed, 70 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a73135eb..94ad4dc1 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ imcomplete list of things that still have to be done before this can be used: - Fully implemented: - `GetPluginFactory()` and `IPluginFactory{,2,3}` - `IPluginBase` and `IComponent` - - `IParameterChanges` and `IParamValueQueue` + - `IParameterChanges`, `IParamValueQueue`, `IEventList`, and all event types + in VST 3.7.1 - Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and diff --git a/src/common/serialization/vst3/event-list.cpp b/src/common/serialization/vst3/event-list.cpp index 8257ea47..4fa1c6e9 100644 --- a/src/common/serialization/vst3/event-list.cpp +++ b/src/common/serialization/vst3/event-list.cpp @@ -165,3 +165,63 @@ Steinberg::Vst::Event YaEvent::get() const { return event; } + +YaEventList::YaEventList(){FUNKNOWN_CTOR} + +YaEventList::YaEventList(Steinberg::Vst::IEventList& event_list) { + FUNKNOWN_CTOR + + events.reserve(event_list.getEventCount()); + + // Copy over all events. Everything gets converted to `YaEvent`s. + Steinberg::Vst::Event event; + for (int i = 0; i < event_list.getEventCount(); i++) { + // We're skipping the `kResultOk` assertions here + event_list.getEvent(i, event); + events.push_back(event); + } +} + +YaEventList::~YaEventList() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_FUNKNOWN_METHODS(YaEventList, + Steinberg::Vst::IEventList, + Steinberg::Vst::IEventList::iid) +#pragma GCC diagnostic pop + +int32 PLUGIN_API YaEventList::getEventCount() { + return events.size(); +} + +tresult PLUGIN_API YaEventList::getEvent(int32 index, + Steinberg::Vst::Event& e /*out*/) { + if (index < 0 || index >= static_cast(events.size())) { + return Steinberg::kInvalidArgument; + } + + // On the first call to this, we'll reconstruct `Event` objects out of our + // `YaEvent`s all at once. This is also done if for whatever reason the + // plugin `getEvent()`s an event it just added. + const size_t num_already_reconstructed_events = reconstructed_events.size(); + if (index >= static_cast(num_already_reconstructed_events)) { + reconstructed_events.resize(events.size()); + std::transform( + events.begin() + num_already_reconstructed_events, events.end(), + reconstructed_events.begin() + num_already_reconstructed_events, + [](const YaEvent& event) { return event.get(); }); + } + + e = reconstructed_events[index]; + + return Steinberg::kResultOk; +} + +tresult PLUGIN_API YaEventList::addEvent(Steinberg::Vst::Event& e /*in*/) { + events.push_back(e); + + return Steinberg::kResultOk; +} diff --git a/src/common/serialization/vst3/event-list.h b/src/common/serialization/vst3/event-list.h index e9e0bab7..0d5459cf 100644 --- a/src/common/serialization/vst3/event-list.h +++ b/src/common/serialization/vst3/event-list.h @@ -221,7 +221,7 @@ class YaEventList : public Steinberg::Vst::IEventList { /** * Read data from an existing `IEventList` object. */ - YaEventList(Steinberg::Vst::IEventList& original_events); + YaEventList(Steinberg::Vst::IEventList& event_list); ~YaEventList(); @@ -229,8 +229,6 @@ class YaEventList : public Steinberg::Vst::IEventList { // From `IEventList` virtual int32 PLUGIN_API getEventCount() override; - // We're making the assumption here that events are immutable (which should - // be the case, but it's never mentioned anywhere) virtual tresult PLUGIN_API getEvent(int32 index, Steinberg::Vst::Event& e /*out*/) override; virtual tresult PLUGIN_API @@ -243,6 +241,12 @@ class YaEventList : public Steinberg::Vst::IEventList { private: std::vector events; + + /** + * On the first `getEvent()` call we'll reconstruct these from `events` all + * at once. These event objects may not outlive this event list. + */ + std::vector reconstructed_events; }; namespace Steinberg { diff --git a/src/common/serialization/vst3/parameter-changes.cpp b/src/common/serialization/vst3/parameter-changes.cpp index 0b78efee..bb6d27ce 100644 --- a/src/common/serialization/vst3/parameter-changes.cpp +++ b/src/common/serialization/vst3/parameter-changes.cpp @@ -23,7 +23,7 @@ YaParameterChanges::YaParameterChanges( FUNKNOWN_CTOR // Copy over all parameter changne queues. Everything gets converted to - // `YaParamValueQueue`s + // `YaParamValueQueue`s. queues.reserve(original_queues.getParameterCount()); for (int i = 0; i < original_queues.getParameterCount(); i++) { queues.push_back(*original_queues.getParameterData(i)); From 7e40d70ff4fe0b09289937edd50de1e15a26013a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 20:01:43 +0100 Subject: [PATCH 209/456] Update the TODO list in the readme --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 94ad4dc1..ae07010a 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,13 @@ communication through [plugin groups](#plugin-groups). Its modern concurrent architecture and focus on transparency allows yabridge to be both fast and highly compatible, while also staying easy to debug and maintain. -## TODO +## TODOs This branch is still very far removed from being in a usable state. Below is an -imcomplete list of things that still have to be done before this can be used: +incomplete list of things that still have to be done before this can be used: -- Left to implement: - - `YaHostApplicationHostImpl::createComponent`. +- Interfaces left to implement: + - `YaHostApplicationHostImpl::createComponent()` - `IAudioProcessor::process()` along with every interface and struct involved in `ProcessData` - `IConnectionPoint` to supplement `IComponent` @@ -28,15 +28,19 @@ imcomplete list of things that still have to be done before this can be used: - `IPluginBase` and `IComponent` - `IParameterChanges`, `IParamValueQueue`, `IEventList`, and all event types in VST 3.7.1 + - `IBStream` +- Finalize the VST3 [design + document](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/src/common/serialization/vst3/README.md) + and move it to `docs/`. - Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and figure out how to do this in the least confusing way possible. - Mention that this update will break all existing symlinks and that the old `libyabridge.so` file should be removed when upgrading. -- Pay close attention to the plugin groups section, since VST3 plugins by design - cannot be hosted completely individually (as in, each plugin is basically in - its own group). +- Pay close attention when updating the plugin groups section of the readme, + since VST3 plugins by design cannot be hosted completely individually (as in, + each plugin is basically in its own group). - Update all the AUR packages. - Test the binaries built on GitHub on plain Ubuntu 18.04, are we missing any static linking? From e4b97d90c967d5aef3e7f7da2c07afc41e8bf6aa Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 21:32:50 +0100 Subject: [PATCH 210/456] Add the events to YaProcessData --- src/common/serialization/vst3/process-data.h | 21 +++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h index 93377a73..8b70e59c 100644 --- a/src/common/serialization/vst3/process-data.h +++ b/src/common/serialization/vst3/process-data.h @@ -18,10 +18,12 @@ #include +#include #include #include #include "base.h" +#include "event-list.h" #include "parameter-changes.h" // This header provides serialization wrappers around `ProcessData` @@ -119,15 +121,16 @@ class YaAudioBusBuffers { */ struct YaProcessDataResponse { std::vector outputs; - // TODO: Don't forget to check for null before writing these back - YaParameterChanges output_parameter_changes; + std::optional output_parameter_changes; + std::optional output_events; - // TODO: Add events + // TODO: Add function to write these back to the host's `ProcessData` template void serialize(S& s) { s.container(outputs, max_num_speakers); - s.container(output_parameter_changes, 1 << 16); + s.ext(output_parameter_changes, bitsery::ext::StdOptional{}); + s.ext(output_events, bitsery::ext::StdOptional{}); } }; @@ -168,7 +171,8 @@ class YaProcessData { s.value4b(num_samples); s.container(inputs, max_num_speakers); s.container4b(outputs_num_channels, max_num_speakers); - s.container(input_parameter_changes, 1 << 16); + s.object(input_parameter_changes); + s.ext(input_events, bitsery::ext::StdOptional{}); } private: @@ -214,11 +218,14 @@ class YaProcessData { */ YaParameterChanges input_parameter_changes; + /** + * Incoming events. + */ + std::optional input_events; + // TODO: Add these (but since these require interface implementations we'll // do it in a second round) /* - IEventList* inputEvents; ///< incoming events for this block (optional) - IEventList* outputEvents; ///< outgoing events for this block (optional) ProcessContext* processContext; ///< processing context (optional, but most welcome) */ From 487e6eac98d997657603664d3da085d303d5d908 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 21:49:30 +0100 Subject: [PATCH 211/456] Add the process context to YaProcessData --- src/common/serialization/vst3/process-data.h | 54 +++++++++++++++++--- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h index 8b70e59c..ccc07c24 100644 --- a/src/common/serialization/vst3/process-data.h +++ b/src/common/serialization/vst3/process-data.h @@ -173,6 +173,7 @@ class YaProcessData { s.container4b(outputs_num_channels, max_num_speakers); s.object(input_parameter_changes); s.ext(input_events, bitsery::ext::StdOptional{}); + s.ext(process_context, bitsery::ext::StdOptional{}); } private: @@ -223,10 +224,51 @@ class YaProcessData { */ std::optional input_events; - // TODO: Add these (but since these require interface implementations we'll - // do it in a second round) - /* - ProcessContext* - processContext; ///< processing context (optional, but most welcome) - */ + /** + * Some more information about the project and transport. + */ + std::optional process_context; }; + +namespace Steinberg { +namespace Vst { +template +void serialize(S& s, Steinberg::Vst::ProcessContext& process_context) { + // The docs don't mention that things ever got added to this context (and + // that some fields thus may not exist for all hosts), so we'll just + // directly serialize everything. If it does end up being the case that new + // fields were added here we should serialize based on the bits set in the + // flags bitfield. + s.value4b(process_context.state); + s.value8b(process_context.sampleRate); + s.value8b(process_context.projectTimeSamples); + s.value8b(process_context.systemTime); + s.value8b(process_context.continousTimeSamples); + s.value8b(process_context.projectTimeMusic); + s.value8b(process_context.barPositionMusic); + s.value8b(process_context.cycleStartMusic); + s.value8b(process_context.cycleEndMusic); + s.value8b(process_context.tempo); + s.value4b(process_context.timeSigNumerator); + s.value4b(process_context.timeSigDenominator); + s.object(process_context.chord); + s.value4b(process_context.smpteOffsetSubframes); + s.value4b(process_context.smpteOffsetSubframes); + s.object(process_context.frameRate); + s.value4b(process_context.samplesToNextClock); +} + +template +void serialize(S& s, Steinberg::Vst::Chord& chord) { + s.value1b(chord.keyNote); + s.value1b(chord.rootNote); + s.value2b(chord.chordMask); +} + +template +void serialize(S& s, Steinberg::Vst::FrameRate& frame_rate) { + s.value1b(frame_rate.framesPerSecond); + s.value1b(frame_rate.flags); +} +} // namespace Vst +} // namespace Steinberg From f1aefc0a9dd0a4106a564bc7d571ad286f17a317 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 22:32:42 +0100 Subject: [PATCH 212/456] Finish implementing YaAudioBusBuffers --- meson.build | 2 + .../serialization/vst3/process-data.cpp | 93 +++++++++++++++++++ src/common/serialization/vst3/process-data.h | 12 ++- 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/common/serialization/vst3/process-data.cpp diff --git a/meson.build b/meson.build index 7e514e0b..93443962 100644 --- a/meson.build +++ b/meson.build @@ -84,6 +84,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', + 'src/common/serialization/vst3/process-data.cpp', 'src/common/configuration.cpp', 'src/common/plugins.cpp', 'src/common/utils.cpp', @@ -122,6 +123,7 @@ if with_vst3 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', + 'src/common/serialization/vst3/process-data.cpp', 'src/wine-host/bridges/vst3-impls/host-application.cpp', 'src/wine-host/bridges/vst3.cpp', ] diff --git a/src/common/serialization/vst3/process-data.cpp b/src/common/serialization/vst3/process-data.cpp new file mode 100644 index 00000000..0fd0662e --- /dev/null +++ b/src/common/serialization/vst3/process-data.cpp @@ -0,0 +1,93 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "process-data.h" + +#include "src/common/utils.h" + +YaAudioBusBuffers::YaAudioBusBuffers() {} + +YaAudioBusBuffers::YaAudioBusBuffers( + Steinberg::Vst::SymbolicSampleSizes sample_size, + size_t num_channels, + size_t num_samples) + : buffers(sample_size == Steinberg::Vst::SymbolicSampleSizes::kSample64 + ? decltype(buffers)(std::vector>( + num_channels, + std::vector(num_samples, 0.0))) + : decltype(buffers)(std::vector>( + num_channels, + std::vector(num_samples, 0.0)))) {} + +YaAudioBusBuffers::YaAudioBusBuffers( + Steinberg::Vst::SymbolicSampleSizes sample_size, + int32 num_samples, + const Steinberg::Vst::AudioBusBuffers& data) + : silence_flags(data.silenceFlags) { + switch (sample_size) { + case Steinberg::Vst::kSample64: { + std::vector> vector_buffers(data.numChannels); + for (int channel = 0; channel < data.numChannels; channel++) { + vector_buffers[channel].assign( + &data.channelBuffers64[channel][0], + &data.channelBuffers64[channel][num_samples]); + } + + buffers = std::move(vector_buffers); + } break; + case Steinberg::Vst::kSample32: + // I don't think they'll add any other sample sizes any time soon + default: { + std::vector> vector_buffers(data.numChannels); + for (int channel = 0; channel < data.numChannels; channel++) { + vector_buffers[channel].assign( + &data.channelBuffers32[channel][0], + &data.channelBuffers32[channel][num_samples]); + } + + buffers = std::move(vector_buffers); + } break; + } +} + +Steinberg::Vst::AudioBusBuffers& YaAudioBusBuffers::get() { + reconstructed_buffers.silenceFlags = silence_flags; + std::visit(overload{ + [&](std::vector>& buffers) { + double_buffer_pointers.clear(); + for (auto& buffer : buffers) { + double_buffer_pointers.push_back(buffer.data()); + } + + reconstructed_buffers.numChannels = buffers.size(); + reconstructed_buffers.channelBuffers64 = + double_buffer_pointers.data(); + }, + [&](std::vector>& buffers) { + float_buffer_pointers.clear(); + for (auto& buffer : buffers) { + float_buffer_pointers.push_back(buffer.data()); + } + + reconstructed_buffers.numChannels = buffers.size(); + reconstructed_buffers.channelBuffers32 = + float_buffer_pointers.data(); + }, + }, + buffers); + + return reconstructed_buffers; +} diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h index ccc07c24..b441452e 100644 --- a/src/common/serialization/vst3/process-data.h +++ b/src/common/serialization/vst3/process-data.h @@ -57,9 +57,11 @@ class YaAudioBusBuffers { * call. Constructed as part of `YaProcessData`. Since `AudioBusBuffers` * contains an untagged union for storing single and double precision * floating point values, the original `ProcessData`'s `symbolicSampleSize` - * field determines which variant of that union to use. + * field determines which variant of that union to use. Similarly the + * `ProcessData`' `numSamples` field determines the extent of these arrays. */ YaAudioBusBuffers(Steinberg::Vst::SymbolicSampleSizes sample_size, + int32 num_samples, const Steinberg::Vst::AudioBusBuffers& data); /** @@ -94,6 +96,14 @@ class YaAudioBusBuffers { */ Steinberg::Vst::AudioBusBuffers reconstructed_buffers; + // Used in reconstructed_buffers, because we need to store pointers to the + // inner vectors in `buffers`. We're using a union instead of void pointers + // here to have at least some resemblance of type safety. + union { + std::vector float_buffer_pointers; + std::vector double_buffer_pointers; + }; + /** * A bitfield for silent channels copied directly from the input struct. */ From d6b7ef38e248c1fe9afb47c4deb31e9c2533d897 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 22:39:54 +0100 Subject: [PATCH 213/456] Add missing fields in events Thanks GCC. --- src/common/serialization/vst3/event-list.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/common/serialization/vst3/event-list.cpp b/src/common/serialization/vst3/event-list.cpp index 4fa1c6e9..ea084f95 100644 --- a/src/common/serialization/vst3/event-list.cpp +++ b/src/common/serialization/vst3/event-list.cpp @@ -50,12 +50,14 @@ YaChordEvent::YaChordEvent() {} YaChordEvent::YaChordEvent(const Steinberg::Vst::ChordEvent& event) : root(event.root), bass_note(event.bassNote), + mask(event.mask), text(tchar_pointer_to_u16string(event.text, event.textLen)) {} Steinberg::Vst::ChordEvent YaChordEvent::get() const { return Steinberg::Vst::ChordEvent{ .root = root, .bassNote = bass_note, + .mask = mask, .textLen = static_cast(text.size()), .text = u16string_to_tchar_pointer(text)}; } @@ -64,11 +66,13 @@ YaScaleEvent::YaScaleEvent() {} YaScaleEvent::YaScaleEvent(const Steinberg::Vst::ScaleEvent& event) : root(event.root), + mask(event.mask), text(tchar_pointer_to_u16string(event.text, event.textLen)) {} Steinberg::Vst::ScaleEvent YaScaleEvent::get() const { return Steinberg::Vst::ScaleEvent{ .root = root, + .mask = mask, .textLen = static_cast(text.size()), .text = u16string_to_tchar_pointer(text)}; } @@ -118,10 +122,14 @@ YaEvent::YaEvent(const Steinberg::Vst::Event& event) } Steinberg::Vst::Event YaEvent::get() const { + // We of course can't fully initialize a field with an untagged union +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" Steinberg::Vst::Event event{.busIndex = bus_index, .sampleOffset = sample_offset, .ppqPosition = ppq_position, .flags = flags}; +#pragma GCC diagnostic pop std::visit( overload{ [&](const Steinberg::Vst::NoteOnEvent& specific_event) { From d1c5d4c4ac920c5452cce290b59e1397733ebf88 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 15 Dec 2020 23:11:59 +0100 Subject: [PATCH 214/456] Implement YaProcessData reading --- .../serialization/vst3/process-data.cpp | 50 +++++++++++++++---- src/common/serialization/vst3/process-data.h | 20 ++++---- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/common/serialization/vst3/process-data.cpp b/src/common/serialization/vst3/process-data.cpp index 0fd0662e..1e7fea8b 100644 --- a/src/common/serialization/vst3/process-data.cpp +++ b/src/common/serialization/vst3/process-data.cpp @@ -20,10 +20,9 @@ YaAudioBusBuffers::YaAudioBusBuffers() {} -YaAudioBusBuffers::YaAudioBusBuffers( - Steinberg::Vst::SymbolicSampleSizes sample_size, - size_t num_channels, - size_t num_samples) +YaAudioBusBuffers::YaAudioBusBuffers(int32 sample_size, + size_t num_channels, + size_t num_samples) : buffers(sample_size == Steinberg::Vst::SymbolicSampleSizes::kSample64 ? decltype(buffers)(std::vector>( num_channels, @@ -33,7 +32,7 @@ YaAudioBusBuffers::YaAudioBusBuffers( std::vector(num_samples, 0.0)))) {} YaAudioBusBuffers::YaAudioBusBuffers( - Steinberg::Vst::SymbolicSampleSizes sample_size, + int32 sample_size, int32 num_samples, const Steinberg::Vst::AudioBusBuffers& data) : silence_flags(data.silenceFlags) { @@ -67,27 +66,56 @@ Steinberg::Vst::AudioBusBuffers& YaAudioBusBuffers::get() { reconstructed_buffers.silenceFlags = silence_flags; std::visit(overload{ [&](std::vector>& buffers) { - double_buffer_pointers.clear(); + buffer_pointers.clear(); for (auto& buffer : buffers) { - double_buffer_pointers.push_back(buffer.data()); + buffer_pointers.push_back(buffer.data()); } reconstructed_buffers.numChannels = buffers.size(); reconstructed_buffers.channelBuffers64 = - double_buffer_pointers.data(); + reinterpret_cast(buffer_pointers.data()); }, [&](std::vector>& buffers) { - float_buffer_pointers.clear(); + buffer_pointers.clear(); for (auto& buffer : buffers) { - float_buffer_pointers.push_back(buffer.data()); + buffer_pointers.push_back(buffer.data()); } reconstructed_buffers.numChannels = buffers.size(); reconstructed_buffers.channelBuffers32 = - float_buffer_pointers.data(); + reinterpret_cast(buffer_pointers.data()); }, }, buffers); return reconstructed_buffers; } + +YaProcessData::YaProcessData() {} + +YaProcessData::YaProcessData(const Steinberg::Vst::ProcessData& process_data) + : process_mode(process_data.processMode), + symbolic_sample_size(process_data.symbolicSampleSize), + num_samples(process_data.numSamples), + outputs_num_channels(process_data.numOutputs), + input_parameter_changes(*process_data.inputParameterChanges), + input_events(process_data.inputEvents ? std::make_optional( + *process_data.inputEvents) + : std::nullopt), + process_context(process_data.processContext + ? std::make_optional( + *process_data.processContext) + : std::nullopt) { + for (int i = 0; i < process_data.numInputs; i++) { + inputs.emplace_back(symbolic_sample_size, num_samples, + process_data.inputs[i]); + } + + // Fetch the number of channels for each output so we can recreate these + // buffers in the Wine plugin host + for (int i = 0; i < process_data.numOutputs; i++) { + outputs_num_channels[i] = process_data.outputs[i].numChannels; + } +} + +// TODO: Reconstruction diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h index b441452e..76e754c3 100644 --- a/src/common/serialization/vst3/process-data.h +++ b/src/common/serialization/vst3/process-data.h @@ -48,7 +48,7 @@ class YaAudioBusBuffers { * Create a new, zero initialize audio bus buffers object. Used to * reconstruct the output buffers during `YaProcessData::get()`. */ - YaAudioBusBuffers(Steinberg::Vst::SymbolicSampleSizes sample_size, + YaAudioBusBuffers(int32 sample_size, size_t num_channels, size_t num_samples); @@ -60,7 +60,7 @@ class YaAudioBusBuffers { * field determines which variant of that union to use. Similarly the * `ProcessData`' `numSamples` field determines the extent of these arrays. */ - YaAudioBusBuffers(Steinberg::Vst::SymbolicSampleSizes sample_size, + YaAudioBusBuffers(int32 sample_size, int32 num_samples, const Steinberg::Vst::AudioBusBuffers& data); @@ -96,13 +96,11 @@ class YaAudioBusBuffers { */ Steinberg::Vst::AudioBusBuffers reconstructed_buffers; - // Used in reconstructed_buffers, because we need to store pointers to the - // inner vectors in `buffers`. We're using a union instead of void pointers - // here to have at least some resemblance of type safety. - union { - std::vector float_buffer_pointers; - std::vector double_buffer_pointers; - }; + /** + * We need these during the reconstruction process to provide a pointer to + * an array of pointers to the actual buffers. + */ + std::vector buffer_pointers; /** * A bitfield for silent channels copied directly from the input struct. @@ -195,7 +193,7 @@ class YaProcessData { /** * The processing mode copied directly from the input struct. */ - Steinberg::Vst::ProcessModes process_mode; + int32 process_mode; /** * The symbolic sample size (see `Steinberg::Vst::SymbolicSampleSizes`) is @@ -203,7 +201,7 @@ class YaProcessData { * union of array of either single or double precision floating point * arrays. This field determines which of those variants should be used. */ - Steinberg::Vst::SymbolicSampleSizes symbolic_sample_size; + int32 symbolic_sample_size; /** * The number of samples in each audio buffer. From 3771ed6870b3c8043c1634a66d0280e0e31199cf Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 16 Dec 2020 17:11:43 +0100 Subject: [PATCH 215/456] Add more YaProcessData boilerplate --- .../serialization/vst3/process-data.cpp | 2 + src/common/serialization/vst3/process-data.h | 69 +++++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/common/serialization/vst3/process-data.cpp b/src/common/serialization/vst3/process-data.cpp index 1e7fea8b..469e60bd 100644 --- a/src/common/serialization/vst3/process-data.cpp +++ b/src/common/serialization/vst3/process-data.cpp @@ -99,9 +99,11 @@ YaProcessData::YaProcessData(const Steinberg::Vst::ProcessData& process_data) num_samples(process_data.numSamples), outputs_num_channels(process_data.numOutputs), input_parameter_changes(*process_data.inputParameterChanges), + output_parameter_changes_supported(process_data.outputParameterChanges), input_events(process_data.inputEvents ? std::make_optional( *process_data.inputEvents) : std::nullopt), + output_events_supported(process_data.outputEvents), process_context(process_data.processContext ? std::make_optional( *process_data.processContext) diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h index 76e754c3..51601895 100644 --- a/src/common/serialization/vst3/process-data.h +++ b/src/common/serialization/vst3/process-data.h @@ -104,6 +104,10 @@ class YaAudioBusBuffers { /** * A bitfield for silent channels copied directly from the input struct. + * + * We could have done some optimizations to avoid unnecessary copying when + * these silence flags are set, but since it's an optional feature we + * shouldn't risk it. */ uint64 silence_flags; @@ -128,6 +132,8 @@ class YaAudioBusBuffers { * @see YaProcessData */ struct YaProcessDataResponse { + // These fields are directly moved from a `YaProcessData` object. See the + // docstrings there for more information std::vector outputs; std::optional output_parameter_changes; std::optional output_events; @@ -168,7 +174,9 @@ class YaProcessData { /** * **Move** all output written by the Windows VST3 plugin to a response - * object that can be used to write those results back to the host. + * object that can be used to write those results back to the host. This + * should of course be done after making a call to the `IAudioProcessor`'s + * `process()` function with the object obtained using `get()`. */ YaProcessDataResponse move_outputs_to_response(); @@ -180,15 +188,18 @@ class YaProcessData { s.container(inputs, max_num_speakers); s.container4b(outputs_num_channels, max_num_speakers); s.object(input_parameter_changes); + s.value1b(output_parameter_changes_supported); s.ext(input_events, bitsery::ext::StdOptional{}); + s.value1b(output_events_supported); s.ext(process_context, bitsery::ext::StdOptional{}); + + // We of course won't serialize the `reconstructed_process_data` and all + // of the `output*` fields defined below it } private: - /** - * The process data we reconstruct from the other fields during `get()`. - */ - Steinberg::Vst::ProcessData reconstructed_process_data; + // These fields are input and context data read from the original + // `ProcessData` object /** * The processing mode copied directly from the input struct. @@ -227,15 +238,63 @@ class YaProcessData { */ YaParameterChanges input_parameter_changes; + /** + * Whether the host supports output parameter changes (depending on whether + * `outputParameterChanges` was a null pointer or not). + */ + bool output_parameter_changes_supported; + /** * Incoming events. */ std::optional input_events; + /** + * Whether the host supports output events (depending on whether + * `outputEvents` was a null pointer or not). + */ + bool output_events_supported; + /** * Some more information about the project and transport. */ std::optional process_context; + + // These last few members are used on the Wine plugin host side to + // reconstruct the original `ProcessData` object. Here we also initialize + // these `output*` fields so the Windows VST3 plugin can write to them + // though a regular `ProcessData` object. Finally we can wrap these output + // fields back into a `YaProcessDataResponse` using + // `move_outputs_to_response()`. so they can be serialized and written back + // to the host's `ProcessData` object. + + /** + * The process data we reconstruct from the other fields during `get()`. + */ + Steinberg::Vst::ProcessData reconstructed_process_data; + + // These are the same fields as in `YaProcessDataResponse`. We'll generate + // these as part of creating `reconstructed_process_data`, and they will be + // moved into a response object during `move_outputs_to_response()`. + + /** + * The outputs. Will be created based on `outputs_num_channels` (which + * determines how many output busses there are and how many channels each + * bus has) and `num_samples`. + */ + std::vector outputs; + + /** + * The output parameter changes. Will be initialized depending on + * `output_parameter_changes_supported`. + */ + std::optional output_parameter_changes; + + /** + * The output events. Will be initialized depending on + * `output_events_supported`. + */ + std::optional output_events; }; namespace Steinberg { From 95a4ef8eedc016d2f8fb4529d00e351524a50631 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 16 Dec 2020 17:36:49 +0100 Subject: [PATCH 216/456] Implement reconstructing ProcessData --- .../serialization/vst3/process-data.cpp | 67 ++++++++++++++++++- src/common/serialization/vst3/process-data.h | 53 +++++++++------ 2 files changed, 98 insertions(+), 22 deletions(-) diff --git a/src/common/serialization/vst3/process-data.cpp b/src/common/serialization/vst3/process-data.cpp index 469e60bd..80b84722 100644 --- a/src/common/serialization/vst3/process-data.cpp +++ b/src/common/serialization/vst3/process-data.cpp @@ -62,7 +62,8 @@ YaAudioBusBuffers::YaAudioBusBuffers( } } -Steinberg::Vst::AudioBusBuffers& YaAudioBusBuffers::get() { +Steinberg::Vst::AudioBusBuffers YaAudioBusBuffers::get() { + Steinberg::Vst::AudioBusBuffers reconstructed_buffers; reconstructed_buffers.silenceFlags = silence_flags; std::visit(overload{ [&](std::vector>& buffers) { @@ -120,4 +121,66 @@ YaProcessData::YaProcessData(const Steinberg::Vst::ProcessData& process_data) } } -// TODO: Reconstruction +Steinberg::Vst::ProcessData& YaProcessData::get() { + // We'll have to transform out `YaAudioBusBuffers` objects into an array of + // `AudioBusBuffers` object so the plugin can deal with them. These objects + // contain pointers to those original objects and thus don't store any + // buffer data themselves. + inputs_audio_bus_buffers.clear(); + for (auto& buffers : inputs) { + inputs_audio_bus_buffers.push_back(buffers.get()); + } + + // We'll do the same with with the outputs, but we'll first have to + // initialize zeroed out buffers for the plugin to work with since we didn't + // serialize those directly + outputs.clear(); + outputs_audio_bus_buffers.clear(); + for (auto& num_channels : outputs_num_channels) { + YaAudioBusBuffers& buffers = outputs.emplace_back( + symbolic_sample_size, num_channels, num_samples); + outputs_audio_bus_buffers.push_back(buffers.get()); + } + + reconstructed_process_data.processMode = process_mode; + reconstructed_process_data.symbolicSampleSize = symbolic_sample_size; + reconstructed_process_data.numSamples = num_samples; + reconstructed_process_data.numInputs = inputs.size(); + reconstructed_process_data.numOutputs = outputs_num_channels.size(); + reconstructed_process_data.inputs = inputs_audio_bus_buffers.data(); + reconstructed_process_data.outputs = outputs_audio_bus_buffers.data(); + + reconstructed_process_data.inputParameterChanges = &input_parameter_changes; + if (output_parameter_changes_supported) { + output_parameter_changes.emplace(); + reconstructed_process_data.outputParameterChanges = + &*output_parameter_changes; + } else { + output_parameter_changes.reset(); + reconstructed_process_data.outputParameterChanges = nullptr; + } + + if (input_events) { + reconstructed_process_data.inputEvents = &*input_events; + } else { + reconstructed_process_data.inputEvents = nullptr; + } + + if (output_events_supported) { + output_events.emplace(); + reconstructed_process_data.outputEvents = &*output_events; + } else { + output_events.reset(); + reconstructed_process_data.outputEvents = nullptr; + } + + if (process_context) { + reconstructed_process_data.processContext = &*process_context; + } else { + reconstructed_process_data.processContext = nullptr; + } + + return reconstructed_process_data; +} + +// TODO: Response creation diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h index 51601895..c3a10167 100644 --- a/src/common/serialization/vst3/process-data.h +++ b/src/common/serialization/vst3/process-data.h @@ -67,9 +67,10 @@ class YaAudioBusBuffers { /** * Reconstruct the original `AudioBusBuffers` object passed to the * constructor and return it. This is used as part of - * `YaProcessData::get()`. + * `YaProcessData::get()`. The object contains pointers to `buffers`, so it + * may not outlive this object. */ - Steinberg::Vst::AudioBusBuffers& get(); + Steinberg::Vst::AudioBusBuffers get(); template void serialize(S& s) { @@ -91,11 +92,6 @@ class YaAudioBusBuffers { } private: - /** - * The `AudioBusBuffers` object we reconstruct during `get()`. - */ - Steinberg::Vst::AudioBusBuffers reconstructed_buffers; - /** * We need these during the reconstruction process to provide a pointer to * an array of pointers to the actual buffers. @@ -260,19 +256,6 @@ class YaProcessData { */ std::optional process_context; - // These last few members are used on the Wine plugin host side to - // reconstruct the original `ProcessData` object. Here we also initialize - // these `output*` fields so the Windows VST3 plugin can write to them - // though a regular `ProcessData` object. Finally we can wrap these output - // fields back into a `YaProcessDataResponse` using - // `move_outputs_to_response()`. so they can be serialized and written back - // to the host's `ProcessData` object. - - /** - * The process data we reconstruct from the other fields during `get()`. - */ - Steinberg::Vst::ProcessData reconstructed_process_data; - // These are the same fields as in `YaProcessDataResponse`. We'll generate // these as part of creating `reconstructed_process_data`, and they will be // moved into a response object during `move_outputs_to_response()`. @@ -295,6 +278,36 @@ class YaProcessData { * `output_events_supported`. */ std::optional output_events; + + // These last few members are used on the Wine plugin host side to + // reconstruct the original `ProcessData` object. Here we also initialize + // these `output*` fields so the Windows VST3 plugin can write to them + // though a regular `ProcessData` object. Finally we can wrap these output + // fields back into a `YaProcessDataResponse` using + // `move_outputs_to_response()`. so they can be serialized and written back + // to the host's `ProcessData` object. + + /** + * Obtained by calling `.get()` on every `YaAudioBusBuffers` object in + * `intputs`. These objects contain pointers to the data in `inputs` and may + * thus not outlive them. + */ + std::vector inputs_audio_bus_buffers; + + /** + * Obtained by calling `.get()` on every `YaAudioBusBuffers` object in + * `outputs`. These objects contain pointers to the data in `outputs` and + * may thus not outlive them. These are created in a two step process, since + * we first have to create `outputs` from `outputs_num_channels` before we + * can transform it into a structure the Windows VST3 plugin can work with. + * Hooray for heap arrays. + */ + std::vector outputs_audio_bus_buffers; + + /** + * The process data we reconstruct from the other fields during `get()`. + */ + Steinberg::Vst::ProcessData reconstructed_process_data; }; namespace Steinberg { From 6f38f8400cb9bc908a36a99bdfa725c189d18dc6 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 16 Dec 2020 17:42:12 +0100 Subject: [PATCH 217/456] Implement process call creation --- src/common/serialization/vst3/process-data.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/common/serialization/vst3/process-data.cpp b/src/common/serialization/vst3/process-data.cpp index 80b84722..50e4cc77 100644 --- a/src/common/serialization/vst3/process-data.cpp +++ b/src/common/serialization/vst3/process-data.cpp @@ -183,4 +183,9 @@ Steinberg::Vst::ProcessData& YaProcessData::get() { return reconstructed_process_data; } -// TODO: Response creation +YaProcessDataResponse YaProcessData::move_outputs_to_response() { + return YaProcessDataResponse{ + .outputs = std::move(outputs), + .output_parameter_changes = std::move(output_parameter_changes), + .output_events = std::move(output_events)}; +} From 2bf98d0a97e2e3d823f98ea52912f2fbcaab7c30 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 16 Dec 2020 18:14:28 +0100 Subject: [PATCH 218/456] Implement writing back YaProcessDataResponse Everything around `ProcessData` is now fully implemented and should in theory work. --- README.md | 2 + src/common/serialization/vst3/event-list.cpp | 11 ++++++ src/common/serialization/vst3/event-list.h | 6 +++ .../serialization/vst3/param-value-queue.cpp | 10 +++++ .../serialization/vst3/param-value-queue.h | 9 ++++- .../serialization/vst3/parameter-changes.cpp | 13 +++++++ .../serialization/vst3/parameter-changes.h | 7 ++++ .../serialization/vst3/process-data.cpp | 39 +++++++++++++++++++ src/common/serialization/vst3/process-data.h | 12 +++++- 9 files changed, 107 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ae07010a..7a981a79 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ incomplete list of things that still have to be done before this can be used: - Fully implemented: - `GetPluginFactory()` and `IPluginFactory{,2,3}` - `IPluginBase` and `IComponent` + - Everything surrounding `ProcessData`, `ProcessingContext` and + `AudioBusBuffers` - `IParameterChanges`, `IParamValueQueue`, `IEventList`, and all event types in VST 3.7.1 - `IBStream` diff --git a/src/common/serialization/vst3/event-list.cpp b/src/common/serialization/vst3/event-list.cpp index ea084f95..a60f7023 100644 --- a/src/common/serialization/vst3/event-list.cpp +++ b/src/common/serialization/vst3/event-list.cpp @@ -194,6 +194,17 @@ YaEventList::~YaEventList() { FUNKNOWN_DTOR } +void YaEventList::write_back_outputs( + Steinberg::Vst::IEventList& output_events) const { + // TODO: I assume the host is responsible for directly copying heap data + // (e.g. text) in these events and they're not supposed to stay + // around, right? If not, then we'll find out very quickly. + for (auto& event : events) { + Steinberg::Vst::Event reconstructed_event = event.get(); + output_events.addEvent(reconstructed_event); + } +} + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" IMPLEMENT_FUNKNOWN_METHODS(YaEventList, diff --git a/src/common/serialization/vst3/event-list.h b/src/common/serialization/vst3/event-list.h index 0d5459cf..84ae6059 100644 --- a/src/common/serialization/vst3/event-list.h +++ b/src/common/serialization/vst3/event-list.h @@ -227,6 +227,12 @@ class YaEventList : public Steinberg::Vst::IEventList { DECLARE_FUNKNOWN_METHODS + /** + * Write these events an output events queue on the `ProcessData` object + * provided by the host. + */ + void write_back_outputs(Steinberg::Vst::IEventList& output_events) const; + // From `IEventList` virtual int32 PLUGIN_API getEventCount() override; virtual tresult PLUGIN_API diff --git a/src/common/serialization/vst3/param-value-queue.cpp b/src/common/serialization/vst3/param-value-queue.cpp index 25e93375..45c4897a 100644 --- a/src/common/serialization/vst3/param-value-queue.cpp +++ b/src/common/serialization/vst3/param-value-queue.cpp @@ -47,6 +47,16 @@ IMPLEMENT_FUNKNOWN_METHODS(YaParamValueQueue, Steinberg::Vst::IParamValueQueue::iid) #pragma GCC diagnostic pop +void YaParamValueQueue::write_back_outputs( + Steinberg::Vst::IParamValueQueue& output_queue) const { + // We don't need this value + int32 index; + for (const auto& [sample_offset, value] : queue) { + // We don't check for `kResultOk` here + output_queue.addPoint(sample_offset, value, index); + } +} + Steinberg::Vst::ParamID PLUGIN_API YaParamValueQueue::getParameterId() { return parameter_id; } diff --git a/src/common/serialization/vst3/param-value-queue.h b/src/common/serialization/vst3/param-value-queue.h index 9b3b02fe..dfb13464 100644 --- a/src/common/serialization/vst3/param-value-queue.h +++ b/src/common/serialization/vst3/param-value-queue.h @@ -51,6 +51,13 @@ class YaParamValueQueue : public Steinberg::Vst::IParamValueQueue { DECLARE_FUNKNOWN_METHODS + /** + * Write this queue back the output parameter changes object on the + * `ProcessData` object provided by the host. + */ + void write_back_outputs( + Steinberg::Vst::IParamValueQueue& output_queue) const; + // From `IParamValueQueue` Steinberg::Vst::ParamID PLUGIN_API getParameterId() override; int32 PLUGIN_API getPointCount() override; @@ -72,12 +79,12 @@ class YaParamValueQueue : public Steinberg::Vst::IParamValueQueue { }); } - private: /** * For `IParamValueQueue::getParameterId`. */ Steinberg::Vst::ParamID parameter_id; + private: /** * The actual parameter changes queue. The specification doesn't mention * that this should be a priority queue or something, but I'd assume both diff --git a/src/common/serialization/vst3/parameter-changes.cpp b/src/common/serialization/vst3/parameter-changes.cpp index bb6d27ce..44c4bb62 100644 --- a/src/common/serialization/vst3/parameter-changes.cpp +++ b/src/common/serialization/vst3/parameter-changes.cpp @@ -41,6 +41,19 @@ IMPLEMENT_FUNKNOWN_METHODS(YaParameterChanges, Steinberg::Vst::IParameterChanges::iid) #pragma GCC diagnostic pop +void YaParameterChanges::write_back_outputs( + Steinberg::Vst::IParameterChanges& output_queues) const { + for (auto& queue : queues) { + // We don't need this, but the SDK requires us to need this + int32 output_queue_index; + if (Steinberg::Vst::IParamValueQueue* output_queue = + output_queues.addParameterData(queue.parameter_id, + output_queue_index)) { + queue.write_back_outputs(*output_queue); + } + } +} + int32 PLUGIN_API YaParameterChanges::getParameterCount() { return queues.size(); } diff --git a/src/common/serialization/vst3/parameter-changes.h b/src/common/serialization/vst3/parameter-changes.h index 0eabaa30..600dd08b 100644 --- a/src/common/serialization/vst3/parameter-changes.h +++ b/src/common/serialization/vst3/parameter-changes.h @@ -45,6 +45,13 @@ class YaParameterChanges : public Steinberg::Vst::IParameterChanges { DECLARE_FUNKNOWN_METHODS + /** + * Write these changes back to an output parameter changes queue on the + * `ProcessData` object provided by the host. + */ + void write_back_outputs( + Steinberg::Vst::IParameterChanges& output_queues) const; + // From `IParameterChanges` int32 PLUGIN_API getParameterCount() override; Steinberg::Vst::IParamValueQueue* PLUGIN_API diff --git a/src/common/serialization/vst3/process-data.cpp b/src/common/serialization/vst3/process-data.cpp index 50e4cc77..b733ee4c 100644 --- a/src/common/serialization/vst3/process-data.cpp +++ b/src/common/serialization/vst3/process-data.cpp @@ -92,6 +92,45 @@ Steinberg::Vst::AudioBusBuffers YaAudioBusBuffers::get() { return reconstructed_buffers; } +void YaAudioBusBuffers::write_back_outputs( + Steinberg::Vst::AudioBusBuffers& output_buffers) const { + output_buffers.silenceFlags = silence_flags; + std::visit( + overload{ + [&](const std::vector>& buffers) { + for (int channel = 0; channel < output_buffers.numChannels; + channel++) { + std::copy(buffers[channel].begin(), buffers[channel].end(), + output_buffers.channelBuffers64[channel]); + } + }, + [&](const std::vector>& buffers) { + for (int channel = 0; channel < output_buffers.numChannels; + channel++) { + std::copy(buffers[channel].begin(), buffers[channel].end(), + output_buffers.channelBuffers32[channel]); + } + }, + }, + buffers); +} + +void YaProcessDataResponse::write_back_outputs( + Steinberg::Vst::ProcessData& process_data) { + for (int i = 0; i < process_data.numOutputs; i++) { + outputs[i].write_back_outputs(process_data.outputs[i]); + } + + if (output_parameter_changes && process_data.outputParameterChanges) { + output_parameter_changes->write_back_outputs( + *process_data.outputParameterChanges); + } + + if (output_events && process_data.outputEvents) { + output_events->write_back_outputs(*process_data.outputEvents); + } +} + YaProcessData::YaProcessData() {} YaProcessData::YaProcessData(const Steinberg::Vst::ProcessData& process_data) diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h index c3a10167..6216d672 100644 --- a/src/common/serialization/vst3/process-data.h +++ b/src/common/serialization/vst3/process-data.h @@ -72,6 +72,13 @@ class YaAudioBusBuffers { */ Steinberg::Vst::AudioBusBuffers get(); + /** + * Write these buffers and the silence flag back to an `AudioBusBuffers + * object provided by the host. + */ + void write_back_outputs( + Steinberg::Vst::AudioBusBuffers& output_buffers) const; + template void serialize(S& s) { s.value8b(silence_flags); @@ -134,7 +141,10 @@ struct YaProcessDataResponse { std::optional output_parameter_changes; std::optional output_events; - // TODO: Add function to write these back to the host's `ProcessData` + /** + * Write all of this output data back to the host's `ProcessData` object. + */ + void write_back_outputs(Steinberg::Vst::ProcessData& process_data); template void serialize(S& s) { From 6d0a38f720afebfdde7d4c69542bc8986e07fdb8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 16 Dec 2020 18:55:07 +0100 Subject: [PATCH 219/456] Null initialize the plugin factory pointer --- src/plugin/bridges/vst3.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 86e54404..402ad38b 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -135,7 +135,7 @@ class Vst3PluginBridge : PluginBridge> { * * @related get_plugin_factory */ - YaPluginFactory* plugin_factory; + YaPluginFactory* plugin_factory = nullptr; private: /** From 1dd575e4a7bfb62c3eaef7c34976ee46250a85e3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 16 Dec 2020 18:38:17 +0100 Subject: [PATCH 220/456] Implement IAudioProcessor::process() With this the entire `IAudioProcessor` interface has been implemented and in theory it should now be possible to process audio and events. Logging for these requests still has to be implemented separately. --- README.md | 13 +++---- src/common/logging/vst3.cpp | 20 ++++++++++ src/common/logging/vst3.h | 2 + src/common/serialization/vst3.h | 2 +- src/common/serialization/vst3/component.h | 40 ++++++++++++++++++++ src/common/serialization/vst3/process-data.h | 4 +- src/plugin/bridges/vst3-impls/component.cpp | 9 +++-- src/wine-host/bridges/vst3.cpp | 10 +++++ 8 files changed, 87 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7a981a79..4e53f1ff 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,6 @@ incomplete list of things that still have to be done before this can be used: - Interfaces left to implement: - `YaHostApplicationHostImpl::createComponent()` - - `IAudioProcessor::process()` along with every interface and struct involved - in `ProcessData` - `IConnectionPoint` to supplement `IComponent` - `IEditController{,2}` - All other mandatory interfaces @@ -26,11 +24,12 @@ incomplete list of things that still have to be done before this can be used: - Fully implemented: - `GetPluginFactory()` and `IPluginFactory{,2,3}` - `IPluginBase` and `IComponent` - - Everything surrounding `ProcessData`, `ProcessingContext` and - `AudioBusBuffers` - - `IParameterChanges`, `IParamValueQueue`, `IEventList`, and all event types - in VST 3.7.1 - - `IBStream` + - `IBStream` + - `IAudioProcessor` + - Everything surrounding `ProcessData`, `ProcessingContext` and + `AudioBusBuffers` + - `IParameterChanges`, `IParamValueQueue`, `IEventList`, and all event types + in VST 3.7.1 - Finalize the VST3 [design document](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/src/common/serialization/vst3/README.md) and move it to `docs/`. diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 7e70c58e..b3a11c15 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -209,6 +209,16 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::Process& request) { + // TODO: Only log this on log level 2 + log_request_base(is_host_vst, [&](auto& message) { + // TODO: Log about the process data + message << "::process(TODO)"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaComponent::GetTailSamples& request) { log_request_base(is_host_vst, [&](auto& message) { @@ -303,6 +313,16 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response(bool is_host_vst, + const YaComponent::ProcessResponse& response) { + // TODO: Only log this on verbosity level 2 + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + + // TODO: Log response + }); +} + void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs& args) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index cf824906..9cab41f7 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -75,6 +75,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::GetLatencySamples&); void log_request(bool is_host_vst, const YaComponent::SetupProcessing&); void log_request(bool is_host_vst, const YaComponent::SetProcessing&); + void log_request(bool is_host_vst, const YaComponent::Process&); void log_request(bool is_host_vst, const YaComponent::GetTailSamples&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); @@ -90,6 +91,7 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaComponent::GetStateResponse&); void log_response(bool is_host_vst, const YaComponent::GetBusArrangementResponse&); + void log_response(bool is_host_vst, const YaComponent::ProcessResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index c6913bf8..b9049583 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -75,7 +75,7 @@ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 60b68340..a018af1a 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -581,6 +581,46 @@ class YaComponent : public Steinberg::Vst::IComponent, }; virtual tresult PLUGIN_API setProcessing(TBool state) override = 0; + + /** + * The response code and all the output data resulting from a call to + * `IAudioProcessor::process(data)`. + */ + struct ProcessResponse { + UniversalTResult result; + YaProcessDataResponse output_data; + + template + void serialize(S& s) { + s.object(result); + s.object(output_data); + } + }; + + /** + * Message to pass through a call to `IAudioProcessor::process(data)` to the + * Wine plugin host. This `YaProcessData` object wraps around all input + * audio buffers, parameter changes and events along with all context data + * provided by the host so we can send it to the Wine plugin host. We can + * then use `YaProcessData::get()` on the Wine plugin host side to + * reconstruct the original `ProcessData` object, and we then finally use + * `YaProcessData::move_outputs_to_response()` to create a response object + * that we can write back to the `ProcessData` object provided by the host. + */ + struct Process { + using Response = ProcessResponse; + + native_size_t instance_id; + + YaProcessData data; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.object(data); + } + }; + virtual tresult PLUGIN_API process(Steinberg::Vst::ProcessData& data) override = 0; diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h index 6216d672..2a38052b 100644 --- a/src/common/serialization/vst3/process-data.h +++ b/src/common/serialization/vst3/process-data.h @@ -357,8 +357,8 @@ void serialize(S& s, Steinberg::Vst::Chord& chord) { template void serialize(S& s, Steinberg::Vst::FrameRate& frame_rate) { - s.value1b(frame_rate.framesPerSecond); - s.value1b(frame_rate.flags); + s.value4b(frame_rate.framesPerSecond); + s.value4b(frame_rate.flags); } } // namespace Vst } // namespace Steinberg diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 5fbb6fe5..2a5a4c7f 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -200,9 +200,12 @@ tresult PLUGIN_API YaComponentPluginImpl::setProcessing(TBool state) { tresult PLUGIN_API YaComponentPluginImpl::process(Steinberg::Vst::ProcessData& data) { - // TODO: Implement - bridge.logger.log("TODO: IAudioProcessor::process()"); - return Steinberg::kNotImplemented; + ProcessResponse response = bridge.send_message(YaComponent::Process{ + .instance_id = arguments.instance_id, .data = data}); + + response.output_data.write_back_outputs(data); + + return response.result; } uint32 PLUGIN_API YaComponentPluginImpl::getTailSamples() { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 3b787aeb..25c6d0af 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -205,6 +205,16 @@ void Vst3Bridge::run() { return component_instances[request.instance_id] .audio_processor->setProcessing(request.state); }, + [&](YaComponent::Process& request) + -> YaComponent::Process::Response { + const tresult result = + component_instances[request.instance_id] + .audio_processor->process(request.data.get()); + + return YaComponent::ProcessResponse{ + .result = result, + .output_data = request.data.move_outputs_to_response()}; + }, [&](const YaComponent::GetTailSamples& request) -> YaComponent::GetTailSamples::Response { return component_instances[request.instance_id] From 7488d6f4828956331fa1fd794bc8f153dc5a0e30 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 16 Dec 2020 21:53:59 +0100 Subject: [PATCH 221/456] Move the VST3 design document to docs/ --- README.md | 3 --- src/common/serialization/vst3/README.md => docs/vst3.md | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) rename src/common/serialization/vst3/README.md => docs/vst3.md (98%) diff --git a/README.md b/README.md index 4e53f1ff..5d7e6a8c 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,6 @@ incomplete list of things that still have to be done before this can be used: `AudioBusBuffers` - `IParameterChanges`, `IParamValueQueue`, `IEventList`, and all event types in VST 3.7.1 -- Finalize the VST3 [design - document](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/src/common/serialization/vst3/README.md) - and move it to `docs/`. - Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and diff --git a/src/common/serialization/vst3/README.md b/docs/vst3.md similarity index 98% rename from src/common/serialization/vst3/README.md rename to docs/vst3.md index 5a6b4f8c..3b3cbcfa 100644 --- a/src/common/serialization/vst3/README.md +++ b/docs/vst3.md @@ -1,7 +1,6 @@ # VST3 serialization -TODO: Once this is more fleshed out, move this document to `docs/`, and perhaps -replace this readme with a link to that document. +TODO: Flesh this out further The VST3 SDK uses an architecture where every concrete object inherits from an interface, and every interface inherits from `FUnknown`. `FUnkonwn` offers a From 97570a47ba71a47c1fb1f6b3a2bd846566a6a959 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 16 Dec 2020 22:06:45 +0100 Subject: [PATCH 222/456] Add a document with all implemented interfaces --- README.md | 11 ++--------- src/common/serialization/vst3/README.md | 26 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 src/common/serialization/vst3/README.md diff --git a/README.md b/README.md index 5d7e6a8c..1c3e5d9f 100644 --- a/README.md +++ b/README.md @@ -21,15 +21,8 @@ incomplete list of things that still have to be done before this can be used: - `IEditController{,2}` - All other mandatory interfaces - All other optional interfaces -- Fully implemented: - - `GetPluginFactory()` and `IPluginFactory{,2,3}` - - `IPluginBase` and `IComponent` - - `IBStream` - - `IAudioProcessor` - - Everything surrounding `ProcessData`, `ProcessingContext` and - `AudioBusBuffers` - - `IParameterChanges`, `IParamValueQueue`, `IEventList`, and all event types - in VST 3.7.1 +- Fully implemented: see [this + document](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/src/common/serialization/vst3/README.md) - Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md new file mode 100644 index 00000000..e93902ec --- /dev/null +++ b/src/common/serialization/vst3/README.md @@ -0,0 +1,26 @@ +# VST3 interfaces + +TODO: After merging into master, update this link to just point to GitHub + +See [docs/vst3.md](../../../../docs/vst3.md) for more information on how the +serialization works. + +VST3 interfaces are implemented as follows: + +| Yabridge class | Interfaces | Notes | +| ------------------- | ------------------------------------------------------ | ---------------------------------------------------------- | +| `YaComponent` | `IComponent`, `IPluginBase`, `IAudioProcessor` | | +| `YaHostApplication` | `iHostAPplication` | Used as a 'context' to allow the plugin to maek callbacks. | +| `YaPluginFactory` | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | | + +The following interfaces are implemented purely fur serialization purposes: + +| Yabridge class | Interfaces | Notes | +| -------------------- | ------------------- | ---------------------------------------------------------------------- | +| `YaEventList` | `IEventList` | Comes with a lot of serialization wrappers around the related structs. | +| `YaParameterChanges` | `IParameterChanges` | | +| `YaParamValueQueue` | `IParamValueQueue` | | +| `VectorStream` | `IBStream` | Used for serializing data streams. | + +And finally `YaProcessData` uses the above along with `YaAudioBusBuffers` to +wrap around `ProcessData`. From 6809e73d6bb484ac7f6a0176125874aee120535e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 16 Dec 2020 23:46:47 +0100 Subject: [PATCH 223/456] Split IPluginBase from IComponent We're also going to need this for `IEditController`. Separating all of these classes will also keep everything much more maintainable with all of these associated structs. --- docs/vst3.md | 2 + meson.build | 2 + src/common/logging/vst3.cpp | 42 +++---- src/common/logging/vst3.h | 4 +- src/common/serialization/vst3.h | 4 +- src/common/serialization/vst3/README.md | 11 +- src/common/serialization/vst3/component.cpp | 45 ++++--- src/common/serialization/vst3/component.h | 70 ++--------- src/common/serialization/vst3/plugin-base.cpp | 26 ++++ src/common/serialization/vst3/plugin-base.h | 113 ++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 58 ++++----- src/plugin/bridges/vst3-impls/component.h | 8 +- src/wine-host/bridges/vst3.cpp | 16 +-- src/wine-host/bridges/vst3.h | 6 +- 14 files changed, 257 insertions(+), 150 deletions(-) create mode 100644 src/common/serialization/vst3/plugin-base.cpp create mode 100644 src/common/serialization/vst3/plugin-base.h diff --git a/docs/vst3.md b/docs/vst3.md index 3b3cbcfa..11253864 100644 --- a/docs/vst3.md +++ b/docs/vst3.md @@ -2,6 +2,8 @@ TODO: Flesh this out further +TODO: Link to `src/common/serialization/vst3/README.md` + The VST3 SDK uses an architecture where every concrete object inherits from an interface, and every interface inherits from `FUnknown`. `FUnkonwn` offers a dynamic casting interface through `queryInterface()` and a reference counting diff --git a/meson.build b/meson.build index 93443962..90ce16a3 100644 --- a/meson.build +++ b/meson.build @@ -83,6 +83,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', + 'src/common/serialization/vst3/plugin-base.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', 'src/common/configuration.cpp', @@ -122,6 +123,7 @@ if with_vst3 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', + 'src/common/serialization/vst3/plugin-base.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', 'src/wine-host/bridges/vst3-impls/host-application.cpp', diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index b3a11c15..26bd558d 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -54,27 +54,6 @@ void Vst3Logger::log_request(bool is_host_vst, }); } -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::Initialize& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::initialize(context = "; - if (request.host_application_context_args) { - message << ""; - } else { - message << ""; - } - message << ")"; - }); -} - -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::Terminate& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::terminate()"; - }); -} - void Vst3Logger::log_request(bool is_host_vst, const YaComponent::SetIoMode& request) { log_request_base(is_host_vst, [&](auto& message) { @@ -227,6 +206,27 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaPluginBase::Initialize& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::initialize(context = "; + if (request.host_application_context_args) { + message << ""; + } else { + message << ""; + } + message << ")"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaPluginBase::Terminate& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::terminate()"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::Construct&) { log_request_base(is_host_vst, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 9cab41f7..67912a6d 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -58,8 +58,6 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::Construct&); void log_request(bool is_host_vst, const YaComponent::Destruct&); - void log_request(bool is_host_vst, const YaComponent::Initialize&); - void log_request(bool is_host_vst, const YaComponent::Terminate&); void log_request(bool is_host_vst, const YaComponent::SetIoMode&); void log_request(bool is_host_vst, const YaComponent::GetBusCount&); void log_request(bool is_host_vst, const YaComponent::GetBusInfo&); @@ -77,6 +75,8 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::SetProcessing&); void log_request(bool is_host_vst, const YaComponent::Process&); void log_request(bool is_host_vst, const YaComponent::GetTailSamples&); + void log_request(bool is_host_vst, const YaPluginBase::Initialize&); + void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); void log_request(bool is_host_vst, const WantsConfiguration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index b9049583..304899fd 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -59,8 +59,6 @@ struct WantsConfiguration { */ using ControlRequest = std::variant; diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index e93902ec..da95009c 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -7,11 +7,12 @@ serialization works. VST3 interfaces are implemented as follows: -| Yabridge class | Interfaces | Notes | -| ------------------- | ------------------------------------------------------ | ---------------------------------------------------------- | -| `YaComponent` | `IComponent`, `IPluginBase`, `IAudioProcessor` | | -| `YaHostApplication` | `iHostAPplication` | Used as a 'context' to allow the plugin to maek callbacks. | -| `YaPluginFactory` | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | | +| Yabridge class | Included in | Interfaces | +| ------------------- | ------------- | ------------------------------------------------------ | +| `YaComponent` | | `IComponent`, `IAudioProcessor` | +| `YaHostApplication` | | `iHostAPplication` | +| `YaPluginBase` | `YaComponent` | `IPluginBase` | +| `YaPluginFactory` | | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | The following interfaces are implemented purely fur serialization purposes: diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/component.cpp index cbc9b2e5..754168ab 100644 --- a/src/common/serialization/vst3/component.cpp +++ b/src/common/serialization/vst3/component.cpp @@ -21,30 +21,21 @@ YaComponent::ConstructArgs::ConstructArgs() {} YaComponent::ConstructArgs::ConstructArgs( Steinberg::IPtr component, size_t instance_id) - : instance_id(instance_id) { - known_iids.insert(component->iid); + : instance_id(instance_id), + audio_processor_supported( + Steinberg::FUnknownPtr(component)) { // `IComponent::getControllerClassId` Steinberg::TUID cid; if (component->getControllerClassId(cid) == Steinberg::kResultOk) { edit_controller_cid = std::to_array(cid); } - - // There's no static data we can copy from the audio processor - if (auto audio_processor = - Steinberg::FUnknownPtr( - component)) { - known_iids.insert(Steinberg::Vst::IAudioProcessor::iid); - } } -YaComponent::YaComponent(const ConstructArgs&& args) : arguments(std::move(args)) { - FUNKNOWN_CTOR +YaComponent::YaComponent(const ConstructArgs&& args) + : YaPluginBase(std::move(args.plugin_base_args)), + arguments(std::move(args)){FUNKNOWN_CTOR} - // Everything else is handled directly through callbacks to minimize the - // potential for errors -} - -YaComponent::~YaComponent() { + YaComponent::~YaComponent() { FUNKNOWN_DTOR } @@ -55,14 +46,22 @@ IMPLEMENT_REFCOUNT(YaComponent) tresult PLUGIN_API YaComponent::queryInterface(Steinberg::FIDString _iid, void** obj) { - QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, Steinberg::IPluginBase) - if (arguments.known_iids.contains(Steinberg::Vst::IComponent::iid)) { - QUERY_INTERFACE(_iid, obj, Steinberg::IPluginBase::iid, - Steinberg::IPluginBase) - QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, - Steinberg::Vst::IComponent) + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, + Steinberg::Vst::IComponent) + if (YaPluginBase::supported()) { + // We had to expand the macro here because we need to cast through + // `YaPluginBase`, since `IpluginBase` is also a base of `IComponent` + if (Steinberg::FUnknownPrivate ::iidEqual( + _iid, Steinberg::IPluginBase::iid)) { + addRef(); + *obj = static_cast( + static_cast(this)); + return ::Steinberg ::kResultOk; + } } - if (arguments.known_iids.contains(Steinberg::Vst::IAudioProcessor::iid)) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, + Steinberg::Vst::IComponent) + if (arguments.audio_processor_supported) { QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IAudioProcessor::iid, Steinberg::Vst::IAudioProcessor) } diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index a018af1a..3899a4fd 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -32,6 +32,7 @@ #include "../common.h" #include "base.h" #include "host-application.h" +#include "plugin-base.h" #include "process-data.h" #pragma GCC diagnostic push @@ -43,18 +44,18 @@ * used for serialization, and on the plugin side have an implementation that * can send control messages. * + * This implements all interfaces that an `IComponent` might also implement. + * * We might be able to do some caching here with the buss infos, but since that * sounds like a huge potential source of errors we'll just do pure callbacks * for everything other than the edit controller's class ID. * - * TODO: Amplement IConnectionPoint - * TODO: How should we support IComponents without a seperate edit controller? - * Can we just use a separate `YaEditController` that just points to the - * same implementation (with the same CID)? Check the reference - * implementation in the framework to see how this is initialized, make - * sure we support the reference w workflow. + * TODO: Rework this into `YaPluginMonolith` + * TODO: Eventually this should (optionally) implement everything supported by + * the SDK's `AudioEffect` component. */ class YaComponent : public Steinberg::Vst::IComponent, + public YaPluginBase, public Steinberg::Vst::IAudioProcessor { public: /** @@ -76,10 +77,10 @@ class YaComponent : public Steinberg::Vst::IComponent, */ native_size_t instance_id; - /** - * The IIDs that the interface we serialized supports. - */ - std::set known_iids; + YaPluginBase::ConstructArgs plugin_base_args; + + // TODO: Remove, transitional + bool audio_processor_supported; /** * The class ID of this component's corresponding editor controller. You @@ -90,10 +91,8 @@ class YaComponent : public Steinberg::Vst::IComponent, template void serialize(S& s) { s.value8b(instance_id); - s.ext(known_iids, bitsery::ext::StdSet{32}, - [](S& s, Steinberg::FUID& iid) { - s.ext(iid, bitsery::ext::FUID{}); - }); + s.object(plugin_base_args); + s.value1b(audio_processor_supported); s.ext(edit_controller_cid, bitsery::ext::StdOptional{}, [](S& s, auto& cid) { s.container1b(cid); }); } @@ -146,49 +145,6 @@ class YaComponent : public Steinberg::Vst::IComponent, DECLARE_FUNKNOWN_METHODS - // From `IPluginBase` - - /** - * Message to pass through a call to `IPluginBase::initialize()` to the Wine - * plugin host. if we pass an `IHostApplication` instance, then a proxy - * `YaHostApplication` should be created and passed as an argument to - * `IPluginBase::initialize()`. If this is absent a null pointer should be - * passed. The lifetime of this `YaHostApplication` object should be bound - * to the `IComponent` we are proxying. - */ - struct Initialize { - using Response = UniversalTResult; - - native_size_t instance_id; - std::optional - host_application_context_args; - - template - void serialize(S& s) { - s.value8b(instance_id); - s.ext(host_application_context_args, bitsery::ext::StdOptional{}); - } - }; - - virtual tresult PLUGIN_API initialize(FUnknown* context) override = 0; - - /** - * Message to pass through a call to `IPluginBase::terminate()` to the Wine - * plugin host. - */ - struct Terminate { - using Response = UniversalTResult; - - native_size_t instance_id; - - template - void serialize(S& s) { - s.value8b(instance_id); - } - }; - - virtual tresult PLUGIN_API terminate() override = 0; - // From `IComponent` tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override; diff --git a/src/common/serialization/vst3/plugin-base.cpp b/src/common/serialization/vst3/plugin-base.cpp new file mode 100644 index 00000000..f595b6ab --- /dev/null +++ b/src/common/serialization/vst3/plugin-base.cpp @@ -0,0 +1,26 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "plugin-base.h" + +YaPluginBase::ConstructArgs::ConstructArgs() {} + +YaPluginBase::ConstructArgs::ConstructArgs( + Steinberg::IPtr component) + : supported(Steinberg::FUnknownPtr(component)) {} + +YaPluginBase::YaPluginBase(const ConstructArgs&& args) + : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin-base.h b/src/common/serialization/vst3/plugin-base.h new file mode 100644 index 00000000..47ee27c6 --- /dev/null +++ b/src/common/serialization/vst3/plugin-base.h @@ -0,0 +1,113 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include + +#include "../common.h" +#include "base.h" +#include "host-application.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IPluginBase` for serialization purposes. Both components and + * edit controllers inherit from this. This is instantiated as part of + * `YaComponent` or `YaEditController`. + */ +class YaPluginBase : public Steinberg::IPluginBase { + public: + /** + * These are the arguments for creating a `YaPluginBase`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Read arguments from an existing implementation. Depending on the + * supported interface function more or less of this struct will be left + * empty, and `known_iids` will be set accordingly. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + template + void serialize(S& s) { + s.value1b(supported); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaPluginBase(const ConstructArgs&& args); + + inline bool supported() { return arguments.supported; } + + /** + * Message to pass through a call to `IPluginBase::initialize()` to the Wine + * plugin host. if we pass an `IHostApplication` instance, then a proxy + * `YaHostApplication` should be created and passed as an argument to + * `IPluginBase::initialize()`. If this is absent a null pointer should be + * passed. The lifetime of this `YaHostApplication` object should be bound + * to the `IComponent` we are proxying. + */ + struct Initialize { + using Response = UniversalTResult; + + native_size_t instance_id; + std::optional + host_application_context_args; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.ext(host_application_context_args, bitsery::ext::StdOptional{}); + } + }; + + virtual tresult PLUGIN_API initialize(FUnknown* context) override = 0; + + /** + * Message to pass through a call to `IPluginBase::terminate()` to the Wine + * plugin host. + */ + struct Terminate { + using Response = UniversalTResult; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + + virtual tresult PLUGIN_API terminate() override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 2a5a4c7f..4a96aa50 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -40,35 +40,6 @@ YaComponentPluginImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { return result; } -tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { - // This `context` will likely be an `IHostApplication`. If it is, we will - // store it here, and we'll proxy through all calls to it made from the Wine - // side. Otherwise we'll still call `IPluginBase::initialize()` but with a - // null pointer instead. - host_application_context = context; - - std::optional - host_application_context_args = std::nullopt; - if (host_application_context) { - host_application_context_args = YaHostApplication::ConstructArgs( - host_application_context, arguments.instance_id); - } else { - bridge.logger.log_unknown_interface( - "In IPluginBase::initialize()", - context ? std::optional(context->iid) : std::nullopt); - } - - return bridge.send_message( - YaComponent::Initialize{.instance_id = arguments.instance_id, - .host_application_context_args = - std::move(host_application_context_args)}); -} - -tresult PLUGIN_API YaComponentPluginImpl::terminate() { - return bridge.send_message( - YaComponent::Terminate{.instance_id = arguments.instance_id}); -} - tresult PLUGIN_API YaComponentPluginImpl::setIoMode(Steinberg::Vst::IoMode mode) { return bridge.send_message(YaComponent::SetIoMode{ @@ -143,6 +114,35 @@ tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { return response.result; } +tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { + // This `context` will likely be an `IHostApplication`. If it is, we will + // store it here, and we'll proxy through all calls to it made from the Wine + // side. Otherwise we'll still call `IPluginBase::initialize()` but with a + // null pointer instead. + host_application_context = context; + + std::optional + host_application_context_args = std::nullopt; + if (host_application_context) { + host_application_context_args = YaHostApplication::ConstructArgs( + host_application_context, arguments.instance_id); + } else { + bridge.logger.log_unknown_interface( + "In IPluginBase::initialize()", + context ? std::optional(context->iid) : std::nullopt); + } + + return bridge.send_message( + YaPluginBase::Initialize{.instance_id = arguments.instance_id, + .host_application_context_args = + std::move(host_application_context_args)}); +} + +tresult PLUGIN_API YaComponentPluginImpl::terminate() { + return bridge.send_message( + YaPluginBase::Terminate{.instance_id = arguments.instance_id}); +} + tresult PLUGIN_API YaComponentPluginImpl::setBusArrangements( Steinberg::Vst::SpeakerArrangement* inputs, int32 numIns, diff --git a/src/plugin/bridges/vst3-impls/component.h b/src/plugin/bridges/vst3-impls/component.h index 1c20d118..fd9a4ca5 100644 --- a/src/plugin/bridges/vst3-impls/component.h +++ b/src/plugin/bridges/vst3-impls/component.h @@ -39,9 +39,7 @@ class YaComponentPluginImpl : public YaComponent { tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, void** obj) override; - tresult PLUGIN_API initialize(FUnknown* context) override; - tresult PLUGIN_API terminate() override; - + // From `IComponent` tresult PLUGIN_API setIoMode(Steinberg::Vst::IoMode mode) override; int32 PLUGIN_API getBusCount(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir) override; @@ -61,6 +59,10 @@ class YaComponentPluginImpl : public YaComponent { tresult PLUGIN_API setState(Steinberg::IBStream* state) override; tresult PLUGIN_API getState(Steinberg::IBStream* state) override; + // From `IPluginBase` + tresult PLUGIN_API initialize(FUnknown* context) override; + tresult PLUGIN_API terminate() override; + tresult PLUGIN_API setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, int32 numIns, diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 25c6d0af..6d60c3ce 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -26,7 +26,9 @@ ComponentInstance::ComponentInstance() {} ComponentInstance::ComponentInstance( Steinberg::IPtr component) - : component(component), audio_processor(component) {} + : component(component), + plugin_base(component), + audio_processor(component) {} Vst3Bridge::Vst3Bridge(MainContext& main_context, std::string plugin_dll_path, @@ -86,8 +88,8 @@ void Vst3Bridge::run() { return Ack{}; }, - [&](YaComponent::Initialize& request) - -> YaComponent::Initialize::Response { + [&](YaPluginBase::Initialize& request) + -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object // and pass that to the initialize function. This object should // be cleaned up again during `YaComponent::Destruct`. @@ -103,12 +105,12 @@ void Vst3Bridge::run() { } return component_instances[request.instance_id] - .component->initialize(context); + .plugin_base->initialize(context); }, - [&](const YaComponent::Terminate& request) - -> YaComponent::Terminate::Response { + [&](const YaPluginBase::Terminate& request) + -> YaPluginBase::Terminate::Response { return component_instances[request.instance_id] - .component->terminate(); + .plugin_base->terminate(); }, [&](const YaComponent::SetIoMode& request) -> YaComponent::SetIoMode::Response { diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 5e968efa..2dfa2c9a 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -28,6 +28,9 @@ * A holder for an `IComponent` instance created from the factory along with any * host context proxy objects belonging to it, and several predefined * `FUnknownPtrs` so we don't have to do these dynamic casts all the times.. + * + * TODO: When implementing `IEditController`, change this to use `IPluginBase` + * as the base interface. */ struct ComponentInstance { ComponentInstance(); @@ -37,7 +40,7 @@ struct ComponentInstance { /** * If the host passes an `IHostApplication` during * `IPluginBase::initialize()`, we'll store a proxy object here and then - * pass it to `component->initialize()`. + * pass it to `plugin_base->initialize()`. */ Steinberg::IPtr hsot_application_context; @@ -49,6 +52,7 @@ struct ComponentInstance { // All smart pointers below are created from `component`. They will be null // pointers if `component` did not implement the interface. + Steinberg::FUnknownPtr plugin_base; Steinberg::FUnknownPtr audio_processor; }; From 602bbc5d353fffa99d056510f9ba3819bc76b36e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 00:31:01 +0100 Subject: [PATCH 224/456] Add TODO about replacing known_iids with flags --- docs/vst3.md | 3 +++ src/common/serialization/vst3/plugin-factory.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/docs/vst3.md b/docs/vst3.md index 11253864..a5ef5413 100644 --- a/docs/vst3.md +++ b/docs/vst3.md @@ -4,6 +4,9 @@ TODO: Flesh this out further TODO: Link to `src/common/serialization/vst3/README.md` +TODO: Mention the new `Ya::supports()` mechanism for monolithic interfaces +through multiple inheritance + The VST3 SDK uses an architecture where every concrete object inherits from an interface, and every interface inherits from `FUnknown`. `FUnkonwn` offers a dynamic casting interface through `queryInterface()` and a reference counting diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 6d1dfa35..933f0bf8 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -54,6 +54,9 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { /** * The IIDs that the interface we serialized supports. + * + * TODO: Replace this with a set of boolean flags, just like we're doing + * with the other interfaces. */ std::set known_iids; From d6c28f48d9607b0f3ec1eba6ad74e9fc6d7b8f6d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 00:28:23 +0100 Subject: [PATCH 225/456] Split YaAudioProcessor from YaComponent Now all that's left is splitting YaComponent into the IComponent bits and separate YaPluginMonlith that implements everything. --- meson.build | 2 + src/common/logging/vst3.cpp | 195 +++++------ src/common/logging/vst3.h | 29 +- src/common/serialization/vst3/README.md | 5 +- .../serialization/vst3/audio-processor.cpp | 27 ++ .../serialization/vst3/audio-processor.h | 314 ++++++++++++++++++ src/common/serialization/vst3/component.cpp | 9 +- src/common/serialization/vst3/component.h | 262 +-------------- src/common/serialization/vst3/plugin-base.h | 5 +- src/plugin/bridges/vst3-impls/component.cpp | 141 ++++---- src/plugin/bridges/vst3-impls/component.h | 35 +- src/wine-host/bridges/vst3.cpp | 142 ++++---- 12 files changed, 634 insertions(+), 532 deletions(-) create mode 100644 src/common/serialization/vst3/audio-processor.cpp create mode 100644 src/common/serialization/vst3/audio-processor.h diff --git a/meson.build b/meson.build index 90ce16a3..ef56acd9 100644 --- a/meson.build +++ b/meson.build @@ -77,6 +77,7 @@ vst3_plugin_sources = [ 'src/common/communication/common.cpp', 'src/common/logging/common.cpp', 'src/common/logging/vst3.cpp', + 'src/common/serialization/vst3/audio-processor.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/event-list.cpp', @@ -117,6 +118,7 @@ host_sources = [ if with_vst3 host_sources += [ 'src/common/logging/vst3.cpp', + 'src/common/serialization/vst3/audio-processor.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/event-list.cpp', diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 26bd558d..18229a1f 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -36,6 +36,82 @@ void Vst3Logger::log_unknown_interface( } } +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::SetBusArrangements& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::setBusArrangements(inputs = [SpeakerArrangement; " + << request.inputs.size() << "], numIns = " << request.num_ins + << ", outputs = [SpeakerArrangement; " << request.outputs.size() + << "], numOuts = " << request.num_outs << ")"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetBusArrangement& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getBusArrangement(dir = " << request.dir + << ", index = " << request.index << ", &arr)"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::CanProcessSampleSize& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::canProcessSampleSize(symbolicSampleSize = " + << request.symbolic_sample_size << ")"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetLatencySamples& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getLatencySamples()"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::SetupProcessing& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::setupProcessing(setup = )"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::SetProcessing& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::setProcessing(state = " + << (request.state ? "true" : "false") << ")"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::Process& request) { + // TODO: Only log this on log level 2 + log_request_base(is_host_vst, [&](auto& message) { + // TODO: Log about the process data + message << "::process(TODO)"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetTailSamples& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getTailSamples()"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Construct&) { log_request_base(is_host_vst, [&](auto& message) { // TODO: Log the CID on verbosity level 2, and then also report all CIDs @@ -130,82 +206,6 @@ void Vst3Logger::log_request(bool is_host_vst, }); } -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::SetBusArrangements& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::setBusArrangements(inputs = [SpeakerArrangement; " - << request.inputs.size() << "], numIns = " << request.num_ins - << ", outputs = [SpeakerArrangement; " << request.outputs.size() - << "], numOuts = " << request.num_outs << ")"; - }); -} - -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetBusArrangement& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::getBusArrangement(dir = " << request.dir - << ", index = " << request.index << ", &arr)"; - }); -} - -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::CanProcessSampleSize& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::canProcessSampleSize(symbolicSampleSize = " - << request.symbolic_sample_size << ")"; - }); -} - -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetLatencySamples& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::getLatencySamples()"; - }); -} - -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::SetupProcessing& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::setupProcessing(setup = )"; - }); -} - -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::SetProcessing& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::setProcessing(state = " - << (request.state ? "true" : "false") << ")"; - }); -} - -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::Process& request) { - // TODO: Only log this on log level 2 - log_request_base(is_host_vst, [&](auto& message) { - // TODO: Log about the process data - message << "::process(TODO)"; - }); -} - -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetTailSamples& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::getTailSamples()"; - }); -} - void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { @@ -246,6 +246,28 @@ void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaAudioProcessor::GetBusArrangementResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", "; + } + }); +} + +void Vst3Logger::log_response( + bool is_host_vst, + const YaAudioProcessor::ProcessResponse& response) { + // TODO: Only log this on verbosity level 2 + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + + // TODO: Log response + }); +} + void Vst3Logger::log_response(bool is_host_vst, const Ack&) { log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); } @@ -302,27 +324,6 @@ void Vst3Logger::log_response(bool is_host_vst, }); } -void Vst3Logger::log_response( - bool is_host_vst, - const YaComponent::GetBusArrangementResponse& response) { - log_response_base(is_host_vst, [&](auto& message) { - message << response.result.string(); - if (response.result == Steinberg::kResultOk) { - message << ", "; - } - }); -} - -void Vst3Logger::log_response(bool is_host_vst, - const YaComponent::ProcessResponse& response) { - // TODO: Only log this on verbosity level 2 - log_response_base(is_host_vst, [&](auto& message) { - message << response.result.string(); - - // TODO: Log response - }); -} - void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs& args) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 67912a6d..53e92a65 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -56,6 +56,19 @@ class Vst3Logger { // flag here indicates whether the request was initiated on the host side // (what we'll call a control message). + void log_request(bool is_host_vst, + const YaAudioProcessor::SetBusArrangements&); + void log_request(bool is_host_vst, + const YaAudioProcessor::GetBusArrangement&); + void log_request(bool is_host_vst, + const YaAudioProcessor::CanProcessSampleSize&); + void log_request(bool is_host_vst, + const YaAudioProcessor::GetLatencySamples&); + void log_request(bool is_host_vst, + const YaAudioProcessor::SetupProcessing&); + void log_request(bool is_host_vst, const YaAudioProcessor::SetProcessing&); + void log_request(bool is_host_vst, const YaAudioProcessor::Process&); + void log_request(bool is_host_vst, const YaAudioProcessor::GetTailSamples&); void log_request(bool is_host_vst, const YaComponent::Construct&); void log_request(bool is_host_vst, const YaComponent::Destruct&); void log_request(bool is_host_vst, const YaComponent::SetIoMode&); @@ -66,15 +79,6 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::SetActive&); void log_request(bool is_host_vst, const YaComponent::SetState&); void log_request(bool is_host_vst, const YaComponent::GetState&); - void log_request(bool is_host_vst, const YaComponent::SetBusArrangements&); - void log_request(bool is_host_vst, const YaComponent::GetBusArrangement&); - void log_request(bool is_host_vst, - const YaComponent::CanProcessSampleSize&); - void log_request(bool is_host_vst, const YaComponent::GetLatencySamples&); - void log_request(bool is_host_vst, const YaComponent::SetupProcessing&); - void log_request(bool is_host_vst, const YaComponent::SetProcessing&); - void log_request(bool is_host_vst, const YaComponent::Process&); - void log_request(bool is_host_vst, const YaComponent::GetTailSamples&); void log_request(bool is_host_vst, const YaPluginBase::Initialize&); void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); @@ -82,6 +86,10 @@ class Vst3Logger { void log_request(bool is_host_vst, const WantsConfiguration&); void log_response(bool is_host_vst, const Ack&); + void log_response(bool is_host_vst, + const YaAudioProcessor::GetBusArrangementResponse&); + void log_response(bool is_host_vst, + const YaAudioProcessor::ProcessResponse&); void log_response( bool is_host_vst, const std::variant&); @@ -89,9 +97,6 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaComponent::GetRoutingInfoResponse&); void log_response(bool is_host_vst, const YaComponent::GetStateResponse&); - void log_response(bool is_host_vst, - const YaComponent::GetBusArrangementResponse&); - void log_response(bool is_host_vst, const YaComponent::ProcessResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index da95009c..fcc6531d 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -9,9 +9,10 @@ VST3 interfaces are implemented as follows: | Yabridge class | Included in | Interfaces | | ------------------- | ------------- | ------------------------------------------------------ | -| `YaComponent` | | `IComponent`, `IAudioProcessor` | -| `YaHostApplication` | | `iHostAPplication` | +| `YaComponent` | | `IComponent` | +| `YaAudioProcessor` | `YaComponent` | `IAudioProcessor` | | `YaPluginBase` | `YaComponent` | `IPluginBase` | +| `YaHostApplication` | | `iHostAPplication` | | `YaPluginFactory` | | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | The following interfaces are implemented purely fur serialization purposes: diff --git a/src/common/serialization/vst3/audio-processor.cpp b/src/common/serialization/vst3/audio-processor.cpp new file mode 100644 index 00000000..3bcb3c9d --- /dev/null +++ b/src/common/serialization/vst3/audio-processor.cpp @@ -0,0 +1,27 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "audio-processor.h" + +YaAudioProcessor::ConstructArgs::ConstructArgs() {} + +YaAudioProcessor::ConstructArgs::ConstructArgs( + Steinberg::IPtr component) + : supported( + Steinberg::FUnknownPtr(component)) {} + +YaAudioProcessor::YaAudioProcessor(const ConstructArgs&& args) + : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/audio-processor.h b/src/common/serialization/vst3/audio-processor.h new file mode 100644 index 00000000..140c014a --- /dev/null +++ b/src/common/serialization/vst3/audio-processor.h @@ -0,0 +1,314 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include + +#include "../common.h" +#include "base.h" +#include "host-application.h" +#include "process-data.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IAudioProcessor` for serialization purposes. This is + * instantiated as part of `YaComponent`. + */ +class YaAudioProcessor : public Steinberg::Vst::IAudioProcessor { + public: + /** + * These are the arguments for creating a `YaAudioProcessor`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Check whether an existing implementation implements `IPluginBase` and + * read arguments from it. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + template + void serialize(S& s) { + s.value1b(supported); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaAudioProcessor(const ConstructArgs&& args); + + inline bool supported() { return arguments.supported; } + + /** + * Message to pass through a call to + * `IAudioProcessor::setBusArrangements(inputs, num_ins, outputs, num_outs)` + * to the Wine plugin host. + */ + struct SetBusArrangements { + using Response = UniversalTResult; + + native_size_t instance_id; + + // These are orginally C-style heap arrays, not normal pointers + std::vector inputs; + int32 num_ins; + std::vector outputs; + int32 num_outs; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.container8b(inputs, max_num_speakers); + s.value4b(num_ins); + s.container8b(outputs, max_num_speakers); + s.value4b(num_outs); + } + }; + + virtual tresult PLUGIN_API + setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, + int32 numIns, + Steinberg::Vst::SpeakerArrangement* outputs, + int32 numOuts) override = 0; + + /** + * The response code and written state for a call to + * `IAudioProcessor::getBusArrangement(dir, index, arr)`. + */ + struct GetBusArrangementResponse { + UniversalTResult result; + Steinberg::Vst::SpeakerArrangement updated_arr; + + template + void serialize(S& s) { + s.object(result); + s.value8b(updated_arr); + } + }; + + /** + * Message to pass through a call to + * `IAudioProcessor::getBusArrangement(dir, index, arr)` to the Wine + * plugin host. + */ + struct GetBusArrangement { + using Response = GetBusArrangementResponse; + + native_size_t instance_id; + + Steinberg::Vst::BusDirection dir; + int32 index; + Steinberg::Vst::SpeakerArrangement arr; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(dir); + s.value4b(index); + s.value8b(arr); + } + }; + + virtual tresult PLUGIN_API + getBusArrangement(Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::SpeakerArrangement& arr) override = 0; + + /** + * Message to pass through a call to + * `IAudioProcessor::canProcessSampleSize(symbolic_sample_size)` to the Wine + * plugin host. + */ + struct CanProcessSampleSize { + using Response = UniversalTResult; + + native_size_t instance_id; + + int32 symbolic_sample_size; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(symbolic_sample_size); + } + }; + + virtual tresult PLUGIN_API + canProcessSampleSize(int32 symbolicSampleSize) override = 0; + + /** + * Message to pass through a call to `IAudioProcessor::getLatencySamples()` + * to the Wine plugin host. + */ + struct GetLatencySamples { + using Response = PrimitiveWrapper; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + + virtual uint32 PLUGIN_API getLatencySamples() override = 0; + + /** + * Message to pass through a call to + * `IAudioProcessor::setupProcessing(setup)` to the Wine plugin host. + */ + struct SetupProcessing { + using Response = UniversalTResult; + + native_size_t instance_id; + + Steinberg::Vst::ProcessSetup setup; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.object(setup); + } + }; + + virtual tresult PLUGIN_API + setupProcessing(Steinberg::Vst::ProcessSetup& setup) override = 0; + + /** + * Message to pass through a call to `IAudioProcessor::setProcessing(state)` + * to the Wine plugin host. + */ + struct SetProcessing { + using Response = UniversalTResult; + + native_size_t instance_id; + + TBool state; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value1b(state); + } + }; + + virtual tresult PLUGIN_API setProcessing(TBool state) override = 0; + + /** + * The response code and all the output data resulting from a call to + * `IAudioProcessor::process(data)`. + */ + struct ProcessResponse { + UniversalTResult result; + YaProcessDataResponse output_data; + + template + void serialize(S& s) { + s.object(result); + s.object(output_data); + } + }; + + /** + * Message to pass through a call to `IAudioProcessor::process(data)` to the + * Wine plugin host. This `YaProcessData` object wraps around all input + * audio buffers, parameter changes and events along with all context data + * provided by the host so we can send it to the Wine plugin host. We can + * then use `YaProcessData::get()` on the Wine plugin host side to + * reconstruct the original `ProcessData` object, and we then finally use + * `YaProcessData::move_outputs_to_response()` to create a response object + * that we can write back to the `ProcessData` object provided by the host. + */ + struct Process { + using Response = ProcessResponse; + + native_size_t instance_id; + + YaProcessData data; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.object(data); + } + }; + + virtual tresult PLUGIN_API + process(Steinberg::Vst::ProcessData& data) override = 0; + + /** + * Message to pass through a call to `IAudioProcessor::getTailSamples()` + * to the Wine plugin host. + */ + struct GetTailSamples { + using Response = PrimitiveWrapper; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + + virtual uint32 PLUGIN_API getTailSamples() override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop + +namespace Steinberg { +namespace Vst { +template +void serialize(S& s, Steinberg::Vst::BusInfo& info) { + s.value4b(info.mediaType); + s.value4b(info.direction); + s.value4b(info.channelCount); + s.container2b(info.name); + s.value4b(info.busType); + s.value4b(info.flags); +} + +template +void serialize(S& s, Steinberg::Vst::RoutingInfo& info) { + s.value4b(info.mediaType); + s.value4b(info.busIndex); + s.value4b(info.channel); +} + +template +void serialize(S& s, Steinberg::Vst::ProcessSetup& setup) { + s.value4b(setup.processMode); + s.value4b(setup.symbolicSampleSize); + s.value4b(setup.maxSamplesPerBlock); + s.value8b(setup.sampleRate); +} +} // namespace Vst +} // namespace Steinberg diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/component.cpp index 754168ab..1ae2c548 100644 --- a/src/common/serialization/vst3/component.cpp +++ b/src/common/serialization/vst3/component.cpp @@ -22,8 +22,8 @@ YaComponent::ConstructArgs::ConstructArgs( Steinberg::IPtr component, size_t instance_id) : instance_id(instance_id), - audio_processor_supported( - Steinberg::FUnknownPtr(component)) { + audio_processor_args(component), + plugin_base_args(component) { // `IComponent::getControllerClassId` Steinberg::TUID cid; if (component->getControllerClassId(cid) == Steinberg::kResultOk) { @@ -32,7 +32,8 @@ YaComponent::ConstructArgs::ConstructArgs( } YaComponent::YaComponent(const ConstructArgs&& args) - : YaPluginBase(std::move(args.plugin_base_args)), + : YaAudioProcessor(std::move(args.audio_processor_args)), + YaPluginBase(std::move(args.plugin_base_args)), arguments(std::move(args)){FUNKNOWN_CTOR} YaComponent::~YaComponent() { @@ -61,7 +62,7 @@ tresult PLUGIN_API YaComponent::queryInterface(Steinberg::FIDString _iid, } QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, Steinberg::Vst::IComponent) - if (arguments.audio_processor_supported) { + if (YaAudioProcessor::supported()) { QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IAudioProcessor::iid, Steinberg::Vst::IAudioProcessor) } diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 3899a4fd..09c4f714 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -25,15 +25,14 @@ #include #include #include -#include #include #include "../../bitsery/ext/vst3.h" #include "../common.h" +#include "audio-processor.h" #include "base.h" #include "host-application.h" #include "plugin-base.h" -#include "process-data.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -55,8 +54,8 @@ * the SDK's `AudioEffect` component. */ class YaComponent : public Steinberg::Vst::IComponent, - public YaPluginBase, - public Steinberg::Vst::IAudioProcessor { + public YaAudioProcessor, + public YaPluginBase { public: /** * These are the arguments for creating a `YaComponentPluginImpl`. @@ -77,11 +76,9 @@ class YaComponent : public Steinberg::Vst::IComponent, */ native_size_t instance_id; + YaAudioProcessor::ConstructArgs audio_processor_args; YaPluginBase::ConstructArgs plugin_base_args; - // TODO: Remove, transitional - bool audio_processor_supported; - /** * The class ID of this component's corresponding editor controller. You * can't use C-style array in `std::optional`s. @@ -91,8 +88,8 @@ class YaComponent : public Steinberg::Vst::IComponent, template void serialize(S& s) { s.value8b(instance_id); + s.object(audio_processor_args); s.object(plugin_base_args); - s.value1b(audio_processor_supported); s.ext(edit_controller_cid, bitsery::ext::StdOptional{}, [](S& s, auto& cid) { s.container1b(cid); }); } @@ -101,8 +98,7 @@ class YaComponent : public Steinberg::Vst::IComponent, /** * Message to request the Wine plugin host to instantiate a new IComponent * to pass through a call to `IComponent::createInstance(cid, - * IComponent::iid, - * ...)`. + * IComponent::iid, ...)`. */ struct Construct { using Response = std::variant; @@ -145,7 +141,6 @@ class YaComponent : public Steinberg::Vst::IComponent, DECLARE_FUNKNOWN_METHODS - // From `IComponent` tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override; /** @@ -381,222 +376,6 @@ class YaComponent : public Steinberg::Vst::IComponent, virtual tresult PLUGIN_API getState(Steinberg::IBStream* state) override = 0; - // From `IAudioProcessor` - - /** - * Message to pass through a call to - * `IAudioProcessor::setBusArrangements(inputs, num_ins, outputs, num_outs)` - * to the Wine plugin host. - */ - struct SetBusArrangements { - using Response = UniversalTResult; - - native_size_t instance_id; - - // These are orginally C-style heap arrays, not normal pointers - std::vector inputs; - int32 num_ins; - std::vector outputs; - int32 num_outs; - - template - void serialize(S& s) { - s.value8b(instance_id); - s.container8b(inputs, max_num_speakers); - s.value4b(num_ins); - s.container8b(outputs, max_num_speakers); - s.value4b(num_outs); - } - }; - - virtual tresult PLUGIN_API - setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, - int32 numIns, - Steinberg::Vst::SpeakerArrangement* outputs, - int32 numOuts) override = 0; - - /** - * The response code and written state for a call to - * `IAudioProcessor::getBusArrangement(dir, index, arr)`. - */ - struct GetBusArrangementResponse { - UniversalTResult result; - Steinberg::Vst::SpeakerArrangement updated_arr; - - template - void serialize(S& s) { - s.object(result); - s.value8b(updated_arr); - } - }; - - /** - * Message to pass through a call to - * `IAudioProcessor::getBusArrangement(dir, index, arr)` to the Wine - * plugin host. - */ - struct GetBusArrangement { - using Response = GetBusArrangementResponse; - - native_size_t instance_id; - - Steinberg::Vst::BusDirection dir; - int32 index; - Steinberg::Vst::SpeakerArrangement arr; - - template - void serialize(S& s) { - s.value8b(instance_id); - s.value4b(dir); - s.value4b(index); - s.value8b(arr); - } - }; - - virtual tresult PLUGIN_API - getBusArrangement(Steinberg::Vst::BusDirection dir, - int32 index, - Steinberg::Vst::SpeakerArrangement& arr) override = 0; - - /** - * Message to pass through a call to - * `IAudioProcessor::canProcessSampleSize(symbolic_sample_size)` to the Wine - * plugin host. - */ - struct CanProcessSampleSize { - using Response = UniversalTResult; - - native_size_t instance_id; - - int32 symbolic_sample_size; - - template - void serialize(S& s) { - s.value8b(instance_id); - s.value4b(symbolic_sample_size); - } - }; - - virtual tresult PLUGIN_API - canProcessSampleSize(int32 symbolicSampleSize) override = 0; - - /** - * Message to pass through a call to `IAudioProcessor::getLatencySamples()` - * to the Wine plugin host. - */ - struct GetLatencySamples { - using Response = PrimitiveWrapper; - - native_size_t instance_id; - - template - void serialize(S& s) { - s.value8b(instance_id); - } - }; - - virtual uint32 PLUGIN_API getLatencySamples() override = 0; - - /** - * Message to pass through a call to - * `IAudioProcessor::setupProcessing(setup)` to the Wine plugin host. - */ - struct SetupProcessing { - using Response = UniversalTResult; - - native_size_t instance_id; - - Steinberg::Vst::ProcessSetup setup; - - template - void serialize(S& s) { - s.value8b(instance_id); - s.object(setup); - } - }; - - virtual tresult PLUGIN_API - setupProcessing(Steinberg::Vst::ProcessSetup& setup) override = 0; - - /** - * Message to pass through a call to `IAudioProcessor::setProcessing(state)` - * to the Wine plugin host. - */ - struct SetProcessing { - using Response = UniversalTResult; - - native_size_t instance_id; - - TBool state; - - template - void serialize(S& s) { - s.value8b(instance_id); - s.value1b(state); - } - }; - - virtual tresult PLUGIN_API setProcessing(TBool state) override = 0; - - /** - * The response code and all the output data resulting from a call to - * `IAudioProcessor::process(data)`. - */ - struct ProcessResponse { - UniversalTResult result; - YaProcessDataResponse output_data; - - template - void serialize(S& s) { - s.object(result); - s.object(output_data); - } - }; - - /** - * Message to pass through a call to `IAudioProcessor::process(data)` to the - * Wine plugin host. This `YaProcessData` object wraps around all input - * audio buffers, parameter changes and events along with all context data - * provided by the host so we can send it to the Wine plugin host. We can - * then use `YaProcessData::get()` on the Wine plugin host side to - * reconstruct the original `ProcessData` object, and we then finally use - * `YaProcessData::move_outputs_to_response()` to create a response object - * that we can write back to the `ProcessData` object provided by the host. - */ - struct Process { - using Response = ProcessResponse; - - native_size_t instance_id; - - YaProcessData data; - - template - void serialize(S& s) { - s.value8b(instance_id); - s.object(data); - } - }; - - virtual tresult PLUGIN_API - process(Steinberg::Vst::ProcessData& data) override = 0; - - /** - * Message to pass through a call to `IAudioProcessor::getTailSamples()` - * to the Wine plugin host. - */ - struct GetTailSamples { - using Response = PrimitiveWrapper; - - native_size_t instance_id; - - template - void serialize(S& s) { - s.value8b(instance_id); - } - }; - - virtual uint32 PLUGIN_API getTailSamples() override = 0; - protected: ConstructArgs arguments; }; @@ -609,32 +388,3 @@ void serialize( std::variant& result) { s.ext(result, bitsery::ext::StdVariant{}); } - -namespace Steinberg { -namespace Vst { -template -void serialize(S& s, Steinberg::Vst::BusInfo& info) { - s.value4b(info.mediaType); - s.value4b(info.direction); - s.value4b(info.channelCount); - s.container2b(info.name); - s.value4b(info.busType); - s.value4b(info.flags); -} - -template -void serialize(S& s, Steinberg::Vst::RoutingInfo& info) { - s.value4b(info.mediaType); - s.value4b(info.busIndex); - s.value4b(info.channel); -} - -template -void serialize(S& s, Steinberg::Vst::ProcessSetup& setup) { - s.value4b(setup.processMode); - s.value4b(setup.symbolicSampleSize); - s.value4b(setup.maxSamplesPerBlock); - s.value8b(setup.sampleRate); -} -} // namespace Vst -} // namespace Steinberg diff --git a/src/common/serialization/vst3/plugin-base.h b/src/common/serialization/vst3/plugin-base.h index 47ee27c6..503fe2f4 100644 --- a/src/common/serialization/vst3/plugin-base.h +++ b/src/common/serialization/vst3/plugin-base.h @@ -40,9 +40,8 @@ class YaPluginBase : public Steinberg::IPluginBase { ConstructArgs(); /** - * Read arguments from an existing implementation. Depending on the - * supported interface function more or less of this struct will be left - * empty, and `known_iids` will be set accordingly. + * Check whether an existing implementation implements `IPluginBase` and + * read arguments from it. */ ConstructArgs(Steinberg::IPtr object); diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 4a96aa50..92329b35 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -40,6 +40,77 @@ YaComponentPluginImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { return result; } +tresult PLUGIN_API YaComponentPluginImpl::setBusArrangements( + Steinberg::Vst::SpeakerArrangement* inputs, + int32 numIns, + Steinberg::Vst::SpeakerArrangement* outputs, + int32 numOuts) { + assert(inputs && outputs); + return bridge.send_message(YaAudioProcessor::SetBusArrangements{ + .instance_id = arguments.instance_id, + .inputs = std::vector( + inputs, &inputs[numIns]), + .num_ins = numIns, + .outputs = std::vector( + outputs, &outputs[numOuts]), + .num_outs = numOuts, + }); +} + +tresult PLUGIN_API YaComponentPluginImpl::getBusArrangement( + Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::SpeakerArrangement& arr) { + const GetBusArrangementResponse response = + bridge.send_message(YaAudioProcessor::GetBusArrangement{ + .instance_id = arguments.instance_id, + .dir = dir, + .index = index, + .arr = arr}); + + arr = response.updated_arr; + + return response.result; +} + +tresult PLUGIN_API +YaComponentPluginImpl::canProcessSampleSize(int32 symbolicSampleSize) { + return bridge.send_message(YaAudioProcessor::CanProcessSampleSize{ + .instance_id = arguments.instance_id, + .symbolic_sample_size = symbolicSampleSize}); +} + +uint32 PLUGIN_API YaComponentPluginImpl::getLatencySamples() { + return bridge.send_message(YaAudioProcessor::GetLatencySamples{ + .instance_id = arguments.instance_id}); +} + +tresult PLUGIN_API +YaComponentPluginImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { + return bridge.send_message(YaAudioProcessor::SetupProcessing{ + .instance_id = arguments.instance_id, .setup = setup}); +} + +tresult PLUGIN_API YaComponentPluginImpl::setProcessing(TBool state) { + return bridge.send_message(YaAudioProcessor::SetProcessing{ + .instance_id = arguments.instance_id, .state = state}); +} + +tresult PLUGIN_API +YaComponentPluginImpl::process(Steinberg::Vst::ProcessData& data) { + ProcessResponse response = bridge.send_message(YaAudioProcessor::Process{ + .instance_id = arguments.instance_id, .data = data}); + + response.output_data.write_back_outputs(data); + + return response.result; +} + +uint32 PLUGIN_API YaComponentPluginImpl::getTailSamples() { + return bridge.send_message( + YaAudioProcessor::GetTailSamples{.instance_id = arguments.instance_id}); +} + tresult PLUGIN_API YaComponentPluginImpl::setIoMode(Steinberg::Vst::IoMode mode) { return bridge.send_message(YaComponent::SetIoMode{ @@ -142,73 +213,3 @@ tresult PLUGIN_API YaComponentPluginImpl::terminate() { return bridge.send_message( YaPluginBase::Terminate{.instance_id = arguments.instance_id}); } - -tresult PLUGIN_API YaComponentPluginImpl::setBusArrangements( - Steinberg::Vst::SpeakerArrangement* inputs, - int32 numIns, - Steinberg::Vst::SpeakerArrangement* outputs, - int32 numOuts) { - assert(inputs && outputs); - return bridge.send_message(YaComponent::SetBusArrangements{ - .instance_id = arguments.instance_id, - .inputs = std::vector( - inputs, &inputs[numIns]), - .num_ins = numIns, - .outputs = std::vector( - outputs, &outputs[numOuts]), - .num_outs = numOuts, - }); -} - -tresult PLUGIN_API YaComponentPluginImpl::getBusArrangement( - Steinberg::Vst::BusDirection dir, - int32 index, - Steinberg::Vst::SpeakerArrangement& arr) { - const GetBusArrangementResponse response = bridge.send_message( - YaComponent::GetBusArrangement{.instance_id = arguments.instance_id, - .dir = dir, - .index = index, - .arr = arr}); - - arr = response.updated_arr; - - return response.result; -} - -tresult PLUGIN_API -YaComponentPluginImpl::canProcessSampleSize(int32 symbolicSampleSize) { - return bridge.send_message(YaComponent::CanProcessSampleSize{ - .instance_id = arguments.instance_id, - .symbolic_sample_size = symbolicSampleSize}); -} - -uint32 PLUGIN_API YaComponentPluginImpl::getLatencySamples() { - return bridge.send_message( - YaComponent::GetLatencySamples{.instance_id = arguments.instance_id}); -} - -tresult PLUGIN_API -YaComponentPluginImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { - return bridge.send_message(YaComponent::SetupProcessing{ - .instance_id = arguments.instance_id, .setup = setup}); -} - -tresult PLUGIN_API YaComponentPluginImpl::setProcessing(TBool state) { - return bridge.send_message(YaComponent::SetProcessing{ - .instance_id = arguments.instance_id, .state = state}); -} - -tresult PLUGIN_API -YaComponentPluginImpl::process(Steinberg::Vst::ProcessData& data) { - ProcessResponse response = bridge.send_message(YaComponent::Process{ - .instance_id = arguments.instance_id, .data = data}); - - response.output_data.write_back_outputs(data); - - return response.result; -} - -uint32 PLUGIN_API YaComponentPluginImpl::getTailSamples() { - return bridge.send_message( - YaComponent::GetTailSamples{.instance_id = arguments.instance_id}); -} diff --git a/src/plugin/bridges/vst3-impls/component.h b/src/plugin/bridges/vst3-impls/component.h index fd9a4ca5..8d2a293c 100644 --- a/src/plugin/bridges/vst3-impls/component.h +++ b/src/plugin/bridges/vst3-impls/component.h @@ -39,6 +39,24 @@ class YaComponentPluginImpl : public YaComponent { tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, void** obj) override; + // From `IAudioProcessor` + tresult PLUGIN_API + setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, + int32 numIns, + Steinberg::Vst::SpeakerArrangement* outputs, + int32 numOuts) override; + tresult PLUGIN_API + getBusArrangement(Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::SpeakerArrangement& arr) override; + tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override; + uint32 PLUGIN_API getLatencySamples() override; + tresult PLUGIN_API + setupProcessing(Steinberg::Vst::ProcessSetup& setup) override; + tresult PLUGIN_API setProcessing(TBool state) override; + tresult PLUGIN_API process(Steinberg::Vst::ProcessData& data) override; + uint32 PLUGIN_API getTailSamples() override; + // From `IComponent` tresult PLUGIN_API setIoMode(Steinberg::Vst::IoMode mode) override; int32 PLUGIN_API getBusCount(Steinberg::Vst::MediaType type, @@ -63,23 +81,6 @@ class YaComponentPluginImpl : public YaComponent { tresult PLUGIN_API initialize(FUnknown* context) override; tresult PLUGIN_API terminate() override; - tresult PLUGIN_API - setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, - int32 numIns, - Steinberg::Vst::SpeakerArrangement* outputs, - int32 numOuts) override; - tresult PLUGIN_API - getBusArrangement(Steinberg::Vst::BusDirection dir, - int32 index, - Steinberg::Vst::SpeakerArrangement& arr) override; - tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override; - uint32 PLUGIN_API getLatencySamples() override; - tresult PLUGIN_API - setupProcessing(Steinberg::Vst::ProcessSetup& setup) override; - tresult PLUGIN_API setProcessing(TBool state) override; - tresult PLUGIN_API process(Steinberg::Vst::ProcessData& data) override; - uint32 PLUGIN_API getTailSamples() override; - private: Vst3PluginBridge& bridge; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 6d60c3ce..1c0e9c36 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -60,6 +60,59 @@ void Vst3Bridge::run() { sockets.host_vst_control.receive_messages( std::nullopt, overload{ + [&](YaAudioProcessor::SetBusArrangements& request) + -> YaAudioProcessor::SetBusArrangements::Response { + return component_instances[request.instance_id] + .audio_processor->setBusArrangements( + request.inputs.data(), request.num_ins, + request.outputs.data(), request.num_outs); + }, + [&](YaAudioProcessor::GetBusArrangement& request) + -> YaAudioProcessor::GetBusArrangement::Response { + const tresult result = + component_instances[request.instance_id] + .audio_processor->getBusArrangement( + request.dir, request.index, request.arr); + + return YaAudioProcessor::GetBusArrangementResponse{ + .result = result, .updated_arr = request.arr}; + }, + [&](const YaAudioProcessor::CanProcessSampleSize& request) + -> YaAudioProcessor::CanProcessSampleSize::Response { + return component_instances[request.instance_id] + .audio_processor->canProcessSampleSize( + request.symbolic_sample_size); + }, + [&](const YaAudioProcessor::GetLatencySamples& request) + -> YaAudioProcessor::GetLatencySamples::Response { + return component_instances[request.instance_id] + .audio_processor->getLatencySamples(); + }, + [&](YaAudioProcessor::SetupProcessing& request) + -> YaAudioProcessor::SetupProcessing::Response { + return component_instances[request.instance_id] + .audio_processor->setupProcessing(request.setup); + }, + [&](const YaAudioProcessor::SetProcessing& request) + -> YaAudioProcessor::SetProcessing::Response { + return component_instances[request.instance_id] + .audio_processor->setProcessing(request.state); + }, + [&](YaAudioProcessor::Process& request) + -> YaAudioProcessor::Process::Response { + const tresult result = + component_instances[request.instance_id] + .audio_processor->process(request.data.get()); + + return YaAudioProcessor::ProcessResponse{ + .result = result, + .output_data = request.data.move_outputs_to_response()}; + }, + [&](const YaAudioProcessor::GetTailSamples& request) + -> YaAudioProcessor::GetTailSamples::Response { + return component_instances[request.instance_id] + .audio_processor->getTailSamples(); + }, [&](const YaComponent::Construct& args) -> YaComponent::Construct::Response { Steinberg::TUID cid; @@ -88,30 +141,6 @@ void Vst3Bridge::run() { return Ack{}; }, - [&](YaPluginBase::Initialize& request) - -> YaPluginBase::Initialize::Response { - // If we got passed a host context, we'll create a proxy object - // and pass that to the initialize function. This object should - // be cleaned up again during `YaComponent::Destruct`. - Steinberg::FUnknown* context = nullptr; - if (request.host_application_context_args) { - component_instances[request.instance_id] - .hsot_application_context = - Steinberg::owned(new YaHostApplicationHostImpl( - *this, - std::move(*request.host_application_context_args))); - context = component_instances[request.instance_id] - .hsot_application_context; - } - - return component_instances[request.instance_id] - .plugin_base->initialize(context); - }, - [&](const YaPluginBase::Terminate& request) - -> YaPluginBase::Terminate::Response { - return component_instances[request.instance_id] - .plugin_base->terminate(); - }, [&](const YaComponent::SetIoMode& request) -> YaComponent::SetIoMode::Response { return component_instances[request.instance_id] @@ -169,58 +198,29 @@ void Vst3Bridge::run() { return YaComponent::GetStateResponse{ .result = result, .updated_state = std::move(stream)}; }, - [&](YaComponent::SetBusArrangements& request) - -> YaComponent::SetBusArrangements::Response { - return component_instances[request.instance_id] - .audio_processor->setBusArrangements( - request.inputs.data(), request.num_ins, - request.outputs.data(), request.num_outs); - }, - [&](YaComponent::GetBusArrangement& request) - -> YaComponent::GetBusArrangement::Response { - const tresult result = + [&](YaPluginBase::Initialize& request) + -> YaPluginBase::Initialize::Response { + // If we got passed a host context, we'll create a proxy object + // and pass that to the initialize function. This object should + // be cleaned up again during `YaComponent::Destruct`. + Steinberg::FUnknown* context = nullptr; + if (request.host_application_context_args) { component_instances[request.instance_id] - .audio_processor->getBusArrangement( - request.dir, request.index, request.arr); + .hsot_application_context = + Steinberg::owned(new YaHostApplicationHostImpl( + *this, + std::move(*request.host_application_context_args))); + context = component_instances[request.instance_id] + .hsot_application_context; + } - return YaComponent::GetBusArrangementResponse{ - .result = result, .updated_arr = request.arr}; - }, - [&](const YaComponent::CanProcessSampleSize& request) - -> YaComponent::CanProcessSampleSize::Response { return component_instances[request.instance_id] - .audio_processor->canProcessSampleSize( - request.symbolic_sample_size); + .plugin_base->initialize(context); }, - [&](const YaComponent::GetLatencySamples& request) - -> YaComponent::GetLatencySamples::Response { + [&](const YaPluginBase::Terminate& request) + -> YaPluginBase::Terminate::Response { return component_instances[request.instance_id] - .audio_processor->getLatencySamples(); - }, - [&](YaComponent::SetupProcessing& request) - -> YaComponent::SetupProcessing::Response { - return component_instances[request.instance_id] - .audio_processor->setupProcessing(request.setup); - }, - [&](const YaComponent::SetProcessing& request) - -> YaComponent::SetProcessing::Response { - return component_instances[request.instance_id] - .audio_processor->setProcessing(request.state); - }, - [&](YaComponent::Process& request) - -> YaComponent::Process::Response { - const tresult result = - component_instances[request.instance_id] - .audio_processor->process(request.data.get()); - - return YaComponent::ProcessResponse{ - .result = result, - .output_data = request.data.move_outputs_to_response()}; - }, - [&](const YaComponent::GetTailSamples& request) - -> YaComponent::GetTailSamples::Response { - return component_instances[request.instance_id] - .audio_processor->getTailSamples(); + .plugin_base->terminate(); }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { From d8b26465639d777ce87311e7a89cb14b8d012513 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 12:48:10 +0100 Subject: [PATCH 226/456] Split off IComponent and create a monolithic class We can now use implement all VST3 plugin interfaces through this class, check whether the object from the plugin also supports these classes, and then conditionally allow casting to the supported classes. This should give us a one-to-one proxy of the original object. --- docs/vst3.md | 2 + meson.build | 2 + src/common/logging/vst3.cpp | 99 ++++++------ src/common/logging/vst3.h | 10 +- src/common/serialization/vst3.h | 22 +-- src/common/serialization/vst3/README.md | 15 +- .../serialization/vst3/audio-processor.cpp | 4 +- .../serialization/vst3/audio-processor.h | 6 +- src/common/serialization/vst3/component.cpp | 59 ++----- src/common/serialization/vst3/component.h | 100 ++---------- .../serialization/vst3/host-application.h | 3 + src/common/serialization/vst3/plugin-base.cpp | 4 +- src/common/serialization/vst3/plugin-base.h | 2 +- .../serialization/vst3/plugin-monolith.cpp | 75 +++++++++ .../serialization/vst3/plugin-monolith.h | 151 ++++++++++++++++++ src/plugin/bridges/vst3-impls/component.cpp | 65 ++++---- src/plugin/bridges/vst3-impls/component.h | 8 +- .../bridges/vst3-impls/plugin-factory.cpp | 8 +- .../bridges/vst3-impls/plugin-factory.h | 1 + src/plugin/bridges/vst3.cpp | 4 +- src/plugin/bridges/vst3.h | 8 +- src/wine-host/bridges/vst3.cpp | 59 +++---- 22 files changed, 422 insertions(+), 285 deletions(-) create mode 100644 src/common/serialization/vst3/plugin-monolith.cpp create mode 100644 src/common/serialization/vst3/plugin-monolith.h diff --git a/docs/vst3.md b/docs/vst3.md index a5ef5413..df08998b 100644 --- a/docs/vst3.md +++ b/docs/vst3.md @@ -7,6 +7,8 @@ TODO: Link to `src/common/serialization/vst3/README.md` TODO: Mention the new `Ya::supports()` mechanism for monolithic interfaces through multiple inheritance +TODO: Explain the monolith. + The VST3 SDK uses an architecture where every concrete object inherits from an interface, and every interface inherits from `FUnknown`. `FUnkonwn` offers a dynamic casting interface through `queryInterface()` and a reference counting diff --git a/meson.build b/meson.build index ef56acd9..99c9644e 100644 --- a/meson.build +++ b/meson.build @@ -85,6 +85,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plugin-base.cpp', + 'src/common/serialization/vst3/plugin-monolith.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', 'src/common/configuration.cpp', @@ -126,6 +127,7 @@ if with_vst3 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plugin-base.cpp', + 'src/common/serialization/vst3/plugin-monolith.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', 'src/wine-host/bridges/vst3-impls/host-application.cpp', diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 18229a1f..51140247 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -37,7 +37,30 @@ void Vst3Logger::log_unknown_interface( } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::SetBusArrangements& request) { + const YaPluginMonolith::Construct&) { + log_request_base(is_host_vst, [&](auto& message) { + // TODO: Log the CID on verbosity level 2, and then also report all CIDs + // in the plugin factory + // TODO: When adding the enum class for instantiating different types, + // make sure to reflect those in the constructor and destructor + // logging + message << "IPluginFactory::createComponent(cid = ..., _iid = " + "IComponent::iid, " + "&obj)"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const YaPluginMonolith::Destruct& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::~IComponent()"; + }); +} + +void Vst3Logger::log_request( + bool is_host_vst, + const YaAudioProcessor::SetBusArrangements& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::setBusArrangements(inputs = [SpeakerArrangement; " @@ -48,7 +71,7 @@ void Vst3Logger::log_request(bool is_host_vst, } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetBusArrangement& request) { + const YaAudioProcessor::GetBusArrangement& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::getBusArrangement(dir = " << request.dir @@ -57,7 +80,7 @@ void Vst3Logger::log_request(bool is_host_vst, } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::CanProcessSampleSize& request) { + const YaAudioProcessor::CanProcessSampleSize& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::canProcessSampleSize(symbolicSampleSize = " @@ -65,8 +88,9 @@ void Vst3Logger::log_request(bool is_host_vst, }); } -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetLatencySamples& request) { +void Vst3Logger::log_request( + bool is_host_vst, + const YaAudioProcessor::GetLatencySamples& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::getLatencySamples()"; @@ -74,7 +98,7 @@ void Vst3Logger::log_request(bool is_host_vst, } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::SetupProcessing& request) { + const YaAudioProcessor::SetupProcessing& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::setupProcessing(setup = ::setProcessing(state = " @@ -95,7 +119,7 @@ void Vst3Logger::log_request(bool is_host_vst, } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::Process& request) { + const YaAudioProcessor::Process& request) { // TODO: Only log this on log level 2 log_request_base(is_host_vst, [&](auto& message) { // TODO: Log about the process data @@ -105,31 +129,13 @@ void Vst3Logger::log_request(bool is_host_vst, } void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetTailSamples& request) { + const YaAudioProcessor::GetTailSamples& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::getTailSamples()"; }); } -void Vst3Logger::log_request(bool is_host_vst, const YaComponent::Construct&) { - log_request_base(is_host_vst, [&](auto& message) { - // TODO: Log the CID on verbosity level 2, and then also report all CIDs - // in the plugin factory - message << "IPluginFactory::createComponent(cid = ..., _iid = " - "IComponent::iid, " - "&obj)"; - }); -} - -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::Destruct& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::~IComponent()"; - }); -} - void Vst3Logger::log_request(bool is_host_vst, const YaComponent::SetIoMode& request) { log_request_base(is_host_vst, [&](auto& message) { @@ -246,6 +252,26 @@ void Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { }); } +void Vst3Logger::log_response(bool is_host_vst, const Ack&) { + log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); +} + +void Vst3Logger::log_response( + bool is_host_vst, + const std::variant& + result) { + log_response_base(is_host_vst, [&](auto& message) { + std::visit(overload{[&](const YaPluginMonolith::ConstructArgs& args) { + message << ""; + }, + [&](const UniversalTResult& code) { + message << code.string(); + }}, + result); + }); +} + void Vst3Logger::log_response( bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse& response) { @@ -268,25 +294,6 @@ void Vst3Logger::log_response( }); } -void Vst3Logger::log_response(bool is_host_vst, const Ack&) { - log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); -} - -void Vst3Logger::log_response( - bool is_host_vst, - const std::variant& result) { - log_response_base(is_host_vst, [&](auto& message) { - std::visit(overload{[&](const YaComponent::ConstructArgs& args) { - message << ""; - }, - [&](const UniversalTResult& code) { - message << code.string(); - }}, - result); - }); -} - void Vst3Logger::log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse& response) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 53e92a65..a45b2e41 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -56,6 +56,8 @@ class Vst3Logger { // flag here indicates whether the request was initiated on the host side // (what we'll call a control message). + void log_request(bool is_host_vst, const YaPluginMonolith::Construct&); + void log_request(bool is_host_vst, const YaPluginMonolith::Destruct&); void log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); void log_request(bool is_host_vst, @@ -69,8 +71,6 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaAudioProcessor::SetProcessing&); void log_request(bool is_host_vst, const YaAudioProcessor::Process&); void log_request(bool is_host_vst, const YaAudioProcessor::GetTailSamples&); - void log_request(bool is_host_vst, const YaComponent::Construct&); - void log_request(bool is_host_vst, const YaComponent::Destruct&); void log_request(bool is_host_vst, const YaComponent::SetIoMode&); void log_request(bool is_host_vst, const YaComponent::GetBusCount&); void log_request(bool is_host_vst, const YaComponent::GetBusInfo&); @@ -86,13 +86,13 @@ class Vst3Logger { void log_request(bool is_host_vst, const WantsConfiguration&); void log_response(bool is_host_vst, const Ack&); + void log_response( + bool is_host_vst, + const std::variant&); void log_response(bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse&); void log_response(bool is_host_vst, const YaAudioProcessor::ProcessResponse&); - void log_response( - bool is_host_vst, - const std::variant&); void log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse&); void log_response(bool is_host_vst, const YaComponent::GetRoutingInfoResponse&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 304899fd..d8aa9a54 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -23,8 +23,8 @@ #include "../configuration.h" #include "../utils.h" #include "common.h" -#include "vst3/component.h" #include "vst3/plugin-factory.h" +#include "vst3/plugin-monolith.h" // Event handling for our VST3 plugins works slightly different from how we // handle VST2 plugins. VST3 does not have a centralized event dispatching @@ -57,8 +57,16 @@ struct WantsConfiguration { * encodes the information we request or the operation we want to perform. A * request of type `ControlRequest(T)` should send back a `T::Response`. */ -using ControlRequest = std::variant component) + Steinberg::IPtr object) : supported( - Steinberg::FUnknownPtr(component)) {} + Steinberg::FUnknownPtr(object)) {} YaAudioProcessor::YaAudioProcessor(const ConstructArgs&& args) : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/audio-processor.h b/src/common/serialization/vst3/audio-processor.h index 140c014a..f3874fd7 100644 --- a/src/common/serialization/vst3/audio-processor.h +++ b/src/common/serialization/vst3/audio-processor.h @@ -29,7 +29,7 @@ /** * Wraps around `IAudioProcessor` for serialization purposes. This is - * instantiated as part of `YaComponent`. + * instantiated as part of `YaPluginMonolith`. */ class YaAudioProcessor : public Steinberg::Vst::IAudioProcessor { public: @@ -40,8 +40,8 @@ class YaAudioProcessor : public Steinberg::Vst::IAudioProcessor { ConstructArgs(); /** - * Check whether an existing implementation implements `IPluginBase` and - * read arguments from it. + * Check whether an existing implementation implements `IAudioProcessor` + * and read arguments from it. */ ConstructArgs(Steinberg::IPtr object); diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/component.cpp index 1ae2c548..5d121d66 100644 --- a/src/common/serialization/vst3/component.cpp +++ b/src/common/serialization/vst3/component.cpp @@ -19,57 +19,22 @@ YaComponent::ConstructArgs::ConstructArgs() {} YaComponent::ConstructArgs::ConstructArgs( - Steinberg::IPtr component, - size_t instance_id) - : instance_id(instance_id), - audio_processor_args(component), - plugin_base_args(component) { - // `IComponent::getControllerClassId` - Steinberg::TUID cid; - if (component->getControllerClassId(cid) == Steinberg::kResultOk) { - edit_controller_cid = std::to_array(cid); + Steinberg::IPtr object) { + auto component = Steinberg::FUnknownPtr(object); + + if (component) { + supported = true; + + // `IComponent::getControllerClassId` + Steinberg::TUID cid; + if (component->getControllerClassId(cid) == Steinberg::kResultOk) { + edit_controller_cid = std::to_array(cid); + } } } YaComponent::YaComponent(const ConstructArgs&& args) - : YaAudioProcessor(std::move(args.audio_processor_args)), - YaPluginBase(std::move(args.plugin_base_args)), - arguments(std::move(args)){FUNKNOWN_CTOR} - - YaComponent::~YaComponent() { - FUNKNOWN_DTOR -} - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" -IMPLEMENT_REFCOUNT(YaComponent) -#pragma GCC diagnostic pop - -tresult PLUGIN_API YaComponent::queryInterface(Steinberg::FIDString _iid, - void** obj) { - QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, - Steinberg::Vst::IComponent) - if (YaPluginBase::supported()) { - // We had to expand the macro here because we need to cast through - // `YaPluginBase`, since `IpluginBase` is also a base of `IComponent` - if (Steinberg::FUnknownPrivate ::iidEqual( - _iid, Steinberg::IPluginBase::iid)) { - addRef(); - *obj = static_cast( - static_cast(this)); - return ::Steinberg ::kResultOk; - } - } - QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, - Steinberg::Vst::IComponent) - if (YaAudioProcessor::supported()) { - QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IAudioProcessor::iid, - Steinberg::Vst::IAudioProcessor) - } - - *obj = nullptr; - return Steinberg::kNoInterface; -} + : arguments(std::move(args)) {} tresult PLUGIN_API YaComponent::getControllerClassId(Steinberg::TUID classId) { if (arguments.edit_controller_cid) { diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/component.h index 09c4f714..f0579990 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/component.h @@ -16,68 +16,41 @@ #pragma once -#include -#include -#include - -#include #include -#include -#include #include #include #include "../../bitsery/ext/vst3.h" #include "../common.h" -#include "audio-processor.h" #include "base.h" -#include "host-application.h" -#include "plugin-base.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" /** - * Wraps around `IComponent` for serialization purposes. See `README.md` for - * more information on how this works. On the Wine plugin host side this is only - * used for serialization, and on the plugin side have an implementation that - * can send control messages. - * - * This implements all interfaces that an `IComponent` might also implement. - * - * We might be able to do some caching here with the buss infos, but since that - * sounds like a huge potential source of errors we'll just do pure callbacks - * for everything other than the edit controller's class ID. - * - * TODO: Rework this into `YaPluginMonolith` - * TODO: Eventually this should (optionally) implement everything supported by - * the SDK's `AudioEffect` component. + * Wraps around `IComponent` for serialization purposes. This is instantiated as + * part of `YaPluginMonolith`. Event though `IComponent` inherits from + * `IPlguinBase`, we'll implement that separately in `YaPluginBase` because + * `IEditController` also inherits from `IPluginBase`. */ -class YaComponent : public Steinberg::Vst::IComponent, - public YaAudioProcessor, - public YaPluginBase { +class YaComponent : public Steinberg::Vst::IComponent { public: /** - * These are the arguments for creating a `YaComponentPluginImpl`. + * These are the arguments for creating a `YaComponent`. */ struct ConstructArgs { ConstructArgs(); /** - * Read arguments from an existing implementation. Depending on the - * supported interface function more or less of this struct will be left - * empty, and `known_iids` will be set accordingly. + * Check whether an existing implementation implements `IComponent` and + * read arguments from it. */ - ConstructArgs(Steinberg::IPtr component, - size_t instance_id); + ConstructArgs(Steinberg::IPtr object); /** - * The unique identifier for this specific instance. + * Whether the object supported this interface. */ - native_size_t instance_id; - - YaAudioProcessor::ConstructArgs audio_processor_args; - YaPluginBase::ConstructArgs plugin_base_args; + bool supported; /** * The class ID of this component's corresponding editor controller. You @@ -87,59 +60,19 @@ class YaComponent : public Steinberg::Vst::IComponent, template void serialize(S& s) { - s.value8b(instance_id); - s.object(audio_processor_args); - s.object(plugin_base_args); + s.value1b(supported); s.ext(edit_controller_cid, bitsery::ext::StdOptional{}, [](S& s, auto& cid) { s.container1b(cid); }); } }; - /** - * Message to request the Wine plugin host to instantiate a new IComponent - * to pass through a call to `IComponent::createInstance(cid, - * IComponent::iid, ...)`. - */ - struct Construct { - using Response = std::variant; - - ArrayUID cid; - - template - void serialize(S& s) { - s.container1b(cid); - } - }; - /** * Instantiate this instance with arguments read from another interface * implementation. */ YaComponent(const ConstructArgs&& args); - /** - * Message to request the Wine plugin host to destroy the IComponent - * instance with the given instance ID. Sent from the destructor of - * `YaComponentPluginImpl`. - */ - struct Destruct { - using Response = Ack; - - native_size_t instance_id; - - template - void serialize(S& s) { - s.value8b(instance_id); - } - }; - - /** - * @remark The plugin side implementation should send a control message to - * clean up the instance on the Wine side in its destructor. - */ - virtual ~YaComponent() = 0; - - DECLARE_FUNKNOWN_METHODS + inline bool supported() { return arguments.supported; } tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override; @@ -381,10 +314,3 @@ class YaComponent : public Steinberg::Vst::IComponent, }; #pragma GCC diagnostic pop - -template -void serialize( - S& s, - std::variant& result) { - s.ext(result, bitsery::ext::StdVariant{}); -} diff --git a/src/common/serialization/vst3/host-application.h b/src/common/serialization/vst3/host-application.h index 80845748..f2a9056b 100644 --- a/src/common/serialization/vst3/host-application.h +++ b/src/common/serialization/vst3/host-application.h @@ -34,6 +34,9 @@ * application context passed during `IPluginBase::intialize()` as well as for * `IPluginFactory3::setHostContext()`. This interface is thus implemented on * both the native plugin side as well as the Wine plugin host side. + * + * TODO: When implementing more host interfaces, also rework this into a + * monolith class like with the plugin. */ class YaHostApplication : public Steinberg::Vst::IHostApplication { public: diff --git a/src/common/serialization/vst3/plugin-base.cpp b/src/common/serialization/vst3/plugin-base.cpp index f595b6ab..e2abb0fd 100644 --- a/src/common/serialization/vst3/plugin-base.cpp +++ b/src/common/serialization/vst3/plugin-base.cpp @@ -19,8 +19,8 @@ YaPluginBase::ConstructArgs::ConstructArgs() {} YaPluginBase::ConstructArgs::ConstructArgs( - Steinberg::IPtr component) - : supported(Steinberg::FUnknownPtr(component)) {} + Steinberg::IPtr object) + : supported(Steinberg::FUnknownPtr(object)) {} YaPluginBase::YaPluginBase(const ConstructArgs&& args) : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin-base.h b/src/common/serialization/vst3/plugin-base.h index 503fe2f4..a4d7e556 100644 --- a/src/common/serialization/vst3/plugin-base.h +++ b/src/common/serialization/vst3/plugin-base.h @@ -29,7 +29,7 @@ /** * Wraps around `IPluginBase` for serialization purposes. Both components and * edit controllers inherit from this. This is instantiated as part of - * `YaComponent` or `YaEditController`. + * `YaPluginMonolith`. */ class YaPluginBase : public Steinberg::IPluginBase { public: diff --git a/src/common/serialization/vst3/plugin-monolith.cpp b/src/common/serialization/vst3/plugin-monolith.cpp new file mode 100644 index 00000000..814185a3 --- /dev/null +++ b/src/common/serialization/vst3/plugin-monolith.cpp @@ -0,0 +1,75 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "plugin-monolith.h" + +YaPluginMonolith::ConstructArgs::ConstructArgs() {} + +YaPluginMonolith::ConstructArgs::ConstructArgs( + Steinberg::IPtr object, + size_t instance_id) + : instance_id(instance_id), + audio_processor_args(object), + component_args(object), + plugin_base_args(object) {} + +YaPluginMonolith::YaPluginMonolith(const ConstructArgs&& args) + : YaAudioProcessor(std::move(args.audio_processor_args)), + YaComponent(std::move(args.component_args)), + YaPluginBase(std::move(args.plugin_base_args)), + arguments(std::move(args)){FUNKNOWN_CTOR} + + YaPluginMonolith::~YaPluginMonolith() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(YaPluginMonolith) +#pragma GCC diagnostic pop + +tresult PLUGIN_API YaPluginMonolith::queryInterface(Steinberg::FIDString _iid, + void** obj) { + if (YaPluginBase::supported()) { + // We had to expand the macro here because we need to cast through + // `YaPluginBase`, since `IpluginBase` is also a base of `IComponent` + if (Steinberg::FUnknownPrivate ::iidEqual(_iid, + Steinberg::FUnknown::iid)) { + addRef(); + *obj = static_cast( + static_cast(this)); + return ::Steinberg ::kResultOk; + } + if (Steinberg::FUnknownPrivate ::iidEqual( + _iid, Steinberg::IPluginBase::iid)) { + addRef(); + *obj = static_cast( + static_cast(this)); + return ::Steinberg ::kResultOk; + } + } + if (YaComponent::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, + Steinberg::Vst::IComponent) + } + if (YaAudioProcessor::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IAudioProcessor::iid, + Steinberg::Vst::IAudioProcessor) + } + + *obj = nullptr; + return Steinberg::kNoInterface; +} diff --git a/src/common/serialization/vst3/plugin-monolith.h b/src/common/serialization/vst3/plugin-monolith.h new file mode 100644 index 00000000..dc94f142 --- /dev/null +++ b/src/common/serialization/vst3/plugin-monolith.h @@ -0,0 +1,151 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include + +#include "../common.h" +#include "audio-processor.h" +#include "base.h" +#include "component.h" +#include "host-application.h" +#include "plugin-base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * An abstract class that optionally implements all VST3 interfaces a plugin + * object could implement. A more in depth explanation can be found in + * `docs/vst3.md`, but the way this works is that we begin with an `FUnknown` + * pointer from the Windows VST3 plugin obtained by a call to + * `IPluginFactory::createInstance()` (with an interface decided by the host). + * We then go through all the plugin interfaces and check whether that object + * supports them one by one. For each supported interface we remember that the + * plugin supports it, and we'll optionally write down some static data (such as + * the edit controller cid) that can't change over the lifetime of the + * application. On the plugin side we then return a `YaPluginMonolith` + * implementation that contains all of this information about interfaces the + * object we're proxying might support. This way we can allow casts to all of + * those object types in `queryInterface()`, essentially perfectly mimicing the + * original object. + * + * This monolith approach is also important when it comes to `IConnectionPoint`. + * The host should be able to connect arbitrary objects together, and the plugin + * can then use the query interface smart pointer casting system to cast those + * objects to the types they want. By having a huge monolithic class that + * implements any interface such an object might also implement, we can allow + * perfect proxying behaviour for connecting components. + */ +class YaPluginMonolith : public YaAudioProcessor, + public YaComponent, + public YaPluginBase { + public: + /** + * These are the arguments for creating a `YaPluginMonolithImpl`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Read from an existing object. We will try to mimic this object, so + * we'll support any interfaces this object also supports. + */ + ConstructArgs(Steinberg::IPtr object, size_t instance_id); + + /** + * The unique identifier for this specific object instance. + */ + native_size_t instance_id; + + YaAudioProcessor::ConstructArgs audio_processor_args; + YaComponent::ConstructArgs component_args; + YaPluginBase::ConstructArgs plugin_base_args; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.object(audio_processor_args); + s.object(component_args); + s.object(plugin_base_args); + } + }; + + /** + * Message to request the Wine plugin host to instantiate a new IComponent + * to pass through a call to `IComponent::createInstance(cid, + * IComponent::iid, ...)`. + */ + struct Construct { + using Response = std::variant; + + ArrayUID cid; + + // TODO: Add an enum class to reify the type of object we want to + // instantiate so we can initialize things other than + // `IComponent`, like `IEditController.` + + template + void serialize(S& s) { + s.container1b(cid); + } + }; + + /** + * Instantiate this object instance with arguments read from another + * interface implementation. + */ + YaPluginMonolith(const ConstructArgs&& args); + + /** + * Message to request the Wine plugin host to destroy this object instance + * with the given instance ID. Sent from the destructor of + * `YaPluginMonolithImpl`. This will cause all smart pointers to the actual + * object in the Wine plugin host to be dropped. + */ + struct Destruct { + using Response = Ack; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + + /** + * @remark The plugin side implementation should send a control message to + * clean up the instance on the Wine side in its destructor. + */ + virtual ~YaPluginMonolith() = 0; + + DECLARE_FUNKNOWN_METHODS + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop + +template +void serialize( + S& s, + std::variant& result) { + s.ext(result, bitsery::ext::StdVariant{}); +} diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/component.cpp index 92329b35..8f802e5a 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/component.cpp @@ -16,22 +16,23 @@ #include "component.h" -YaComponentPluginImpl::YaComponentPluginImpl(Vst3PluginBridge& bridge, - YaComponent::ConstructArgs&& args) - : YaComponent(std::move(args)), bridge(bridge) { +YaPluginMonolithImpl::YaPluginMonolithImpl( + Vst3PluginBridge& bridge, + YaPluginMonolith::ConstructArgs&& args) + : YaPluginMonolith(std::move(args)), bridge(bridge) { bridge.register_component(arguments.instance_id, *this); } -YaComponentPluginImpl::~YaComponentPluginImpl() { +YaPluginMonolithImpl::~YaPluginMonolithImpl() { bridge.send_message( - YaComponent::Destruct{.instance_id = arguments.instance_id}); + YaPluginMonolith::Destruct{.instance_id = arguments.instance_id}); bridge.unregister_component(arguments.instance_id); } tresult PLUGIN_API -YaComponentPluginImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { +YaPluginMonolithImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { // TODO: Successful queries should also be logged - const tresult result = YaComponent::queryInterface(_iid, obj); + const tresult result = YaPluginMonolith::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { bridge.logger.log_unknown_interface("In IComponent::queryInterface()", Steinberg::FUID::fromTUID(_iid)); @@ -40,7 +41,7 @@ YaComponentPluginImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { return result; } -tresult PLUGIN_API YaComponentPluginImpl::setBusArrangements( +tresult PLUGIN_API YaPluginMonolithImpl::setBusArrangements( Steinberg::Vst::SpeakerArrangement* inputs, int32 numIns, Steinberg::Vst::SpeakerArrangement* outputs, @@ -57,7 +58,7 @@ tresult PLUGIN_API YaComponentPluginImpl::setBusArrangements( }); } -tresult PLUGIN_API YaComponentPluginImpl::getBusArrangement( +tresult PLUGIN_API YaPluginMonolithImpl::getBusArrangement( Steinberg::Vst::BusDirection dir, int32 index, Steinberg::Vst::SpeakerArrangement& arr) { @@ -74,30 +75,30 @@ tresult PLUGIN_API YaComponentPluginImpl::getBusArrangement( } tresult PLUGIN_API -YaComponentPluginImpl::canProcessSampleSize(int32 symbolicSampleSize) { +YaPluginMonolithImpl::canProcessSampleSize(int32 symbolicSampleSize) { return bridge.send_message(YaAudioProcessor::CanProcessSampleSize{ .instance_id = arguments.instance_id, .symbolic_sample_size = symbolicSampleSize}); } -uint32 PLUGIN_API YaComponentPluginImpl::getLatencySamples() { +uint32 PLUGIN_API YaPluginMonolithImpl::getLatencySamples() { return bridge.send_message(YaAudioProcessor::GetLatencySamples{ .instance_id = arguments.instance_id}); } tresult PLUGIN_API -YaComponentPluginImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { +YaPluginMonolithImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { return bridge.send_message(YaAudioProcessor::SetupProcessing{ .instance_id = arguments.instance_id, .setup = setup}); } -tresult PLUGIN_API YaComponentPluginImpl::setProcessing(TBool state) { +tresult PLUGIN_API YaPluginMonolithImpl::setProcessing(TBool state) { return bridge.send_message(YaAudioProcessor::SetProcessing{ .instance_id = arguments.instance_id, .state = state}); } tresult PLUGIN_API -YaComponentPluginImpl::process(Steinberg::Vst::ProcessData& data) { +YaPluginMonolithImpl::process(Steinberg::Vst::ProcessData& data) { ProcessResponse response = bridge.send_message(YaAudioProcessor::Process{ .instance_id = arguments.instance_id, .data = data}); @@ -106,29 +107,29 @@ YaComponentPluginImpl::process(Steinberg::Vst::ProcessData& data) { return response.result; } -uint32 PLUGIN_API YaComponentPluginImpl::getTailSamples() { +uint32 PLUGIN_API YaPluginMonolithImpl::getTailSamples() { return bridge.send_message( YaAudioProcessor::GetTailSamples{.instance_id = arguments.instance_id}); } tresult PLUGIN_API -YaComponentPluginImpl::setIoMode(Steinberg::Vst::IoMode mode) { +YaPluginMonolithImpl::setIoMode(Steinberg::Vst::IoMode mode) { return bridge.send_message(YaComponent::SetIoMode{ .instance_id = arguments.instance_id, .mode = mode}); } int32 PLUGIN_API -YaComponentPluginImpl::getBusCount(Steinberg::Vst::MediaType type, - Steinberg::Vst::BusDirection dir) { +YaPluginMonolithImpl::getBusCount(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir) { return bridge.send_message(YaComponent::GetBusCount{ .instance_id = arguments.instance_id, .type = type, .dir = dir}); } tresult PLUGIN_API -YaComponentPluginImpl::getBusInfo(Steinberg::Vst::MediaType type, - Steinberg::Vst::BusDirection dir, - int32 index, - Steinberg::Vst::BusInfo& bus /*out*/) { +YaPluginMonolithImpl::getBusInfo(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::BusInfo& bus /*out*/) { const GetBusInfoResponse response = bridge.send_message( YaComponent::GetBusInfo{.instance_id = arguments.instance_id, .type = type, @@ -140,7 +141,7 @@ YaComponentPluginImpl::getBusInfo(Steinberg::Vst::MediaType type, return response.result; } -tresult PLUGIN_API YaComponentPluginImpl::getRoutingInfo( +tresult PLUGIN_API YaPluginMonolithImpl::getRoutingInfo( Steinberg::Vst::RoutingInfo& inInfo, Steinberg::Vst::RoutingInfo& outInfo /*out*/) { const GetRoutingInfoResponse response = bridge.send_message( @@ -154,10 +155,10 @@ tresult PLUGIN_API YaComponentPluginImpl::getRoutingInfo( } tresult PLUGIN_API -YaComponentPluginImpl::activateBus(Steinberg::Vst::MediaType type, - Steinberg::Vst::BusDirection dir, - int32 index, - TBool state) { +YaPluginMonolithImpl::activateBus(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + TBool state) { return bridge.send_message( YaComponent::ActivateBus{.instance_id = arguments.instance_id, .type = type, @@ -166,17 +167,17 @@ YaComponentPluginImpl::activateBus(Steinberg::Vst::MediaType type, .state = state}); } -tresult PLUGIN_API YaComponentPluginImpl::setActive(TBool state) { +tresult PLUGIN_API YaPluginMonolithImpl::setActive(TBool state) { return bridge.send_message(YaComponent::SetActive{ .instance_id = arguments.instance_id, .state = state}); } -tresult PLUGIN_API YaComponentPluginImpl::setState(Steinberg::IBStream* state) { +tresult PLUGIN_API YaPluginMonolithImpl::setState(Steinberg::IBStream* state) { return bridge.send_message(YaComponent::SetState{ .instance_id = arguments.instance_id, .state = state}); } -tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { +tresult PLUGIN_API YaPluginMonolithImpl::getState(Steinberg::IBStream* state) { const GetStateResponse response = bridge.send_message( YaComponent::GetState{.instance_id = arguments.instance_id}); @@ -185,7 +186,7 @@ tresult PLUGIN_API YaComponentPluginImpl::getState(Steinberg::IBStream* state) { return response.result; } -tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { +tresult PLUGIN_API YaPluginMonolithImpl::initialize(FUnknown* context) { // This `context` will likely be an `IHostApplication`. If it is, we will // store it here, and we'll proxy through all calls to it made from the Wine // side. Otherwise we'll still call `IPluginBase::initialize()` but with a @@ -209,7 +210,7 @@ tresult PLUGIN_API YaComponentPluginImpl::initialize(FUnknown* context) { std::move(host_application_context_args)}); } -tresult PLUGIN_API YaComponentPluginImpl::terminate() { +tresult PLUGIN_API YaPluginMonolithImpl::terminate() { return bridge.send_message( YaPluginBase::Terminate{.instance_id = arguments.instance_id}); } diff --git a/src/plugin/bridges/vst3-impls/component.h b/src/plugin/bridges/vst3-impls/component.h index 8d2a293c..03ecdbd7 100644 --- a/src/plugin/bridges/vst3-impls/component.h +++ b/src/plugin/bridges/vst3-impls/component.h @@ -20,17 +20,17 @@ #include "../vst3.h" -class YaComponentPluginImpl : public YaComponent { +class YaPluginMonolithImpl : public YaPluginMonolith { public: - YaComponentPluginImpl(Vst3PluginBridge& bridge, - YaComponent::ConstructArgs&& args); + YaPluginMonolithImpl(Vst3PluginBridge& bridge, + YaPluginMonolith::ConstructArgs&& args); /** * When the reference count reaches zero and this destructor is called, * we'll send a request to the Wine plugin host to destroy the corresponding * object. */ - ~YaComponentPluginImpl(); + ~YaPluginMonolithImpl(); /** * We'll override the query interface to log queries for interfaces we do diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index a39ac230..6b65b2e1 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -38,13 +38,13 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, ArrayUID cid_array; std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { - std::variant result = - bridge.send_message(YaComponent::Construct{.cid = cid_array}); + std::variant result = + bridge.send_message(YaPluginMonolith::Construct{.cid = cid_array}); return std::visit( overload{ - [&](YaComponent::ConstructArgs&& args) -> tresult { + [&](YaPluginMonolith::ConstructArgs&& args) -> tresult { *obj = static_cast( - new YaComponentPluginImpl(bridge, std::move(args))); + new YaPluginMonolithImpl(bridge, std::move(args))); return Steinberg::kResultOk; }, [&](const UniversalTResult& code) -> tresult { return code; }}, diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.h b/src/plugin/bridges/vst3-impls/plugin-factory.h index 963ff26e..30a2f093 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.h +++ b/src/plugin/bridges/vst3-impls/plugin-factory.h @@ -18,6 +18,7 @@ #include "../vst3.h" +// TODO Rename to YaPluginFactoryImpl class YaPluginFactoryPluginImpl : public YaPluginFactory { public: YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge, diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index c8e45e95..61ae85a1 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -115,10 +115,10 @@ Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { } void Vst3PluginBridge::register_component(size_t instance_id, - YaComponentPluginImpl& component) { + YaPluginMonolithImpl& component) { std::lock_guard lock(component_instances_mutex); component_instances.emplace(instance_id, - std::ref(component)); + std::ref(component)); } void Vst3PluginBridge::unregister_component(size_t instance_id) { diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 402ad38b..341b402c 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -24,7 +24,7 @@ #include "common.h" // Forward declaration -class YaComponentPluginImpl; +class YaPluginMonolithImpl; /** * This handles the communication between the native host and a VST3 plugin @@ -83,9 +83,11 @@ class Vst3PluginBridge : PluginBridge> { * context. * * @see component_instances + * + * TODO: REname to `register_instance` or `register_object` */ void register_component(size_t instance_id, - YaComponentPluginImpl& component); + YaPluginMonolithImpl& component); /** * Remove a previously registered `YaComponentPluginImpl` from the list of @@ -149,7 +151,7 @@ class Vst3PluginBridge : PluginBridge> { * `register_component()` in the constractor, and an instance is then * removed through a call to `unregister_component()` in the destructor. */ - std::map> + std::map> component_instances; std::mutex component_instances_mutex; }; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 1c0e9c36..57dfa2b6 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -60,6 +60,34 @@ void Vst3Bridge::run() { sockets.host_vst_control.receive_messages( std::nullopt, overload{ + [&](const YaPluginMonolith::Construct& args) + -> YaPluginMonolith::Construct::Response { + Steinberg::TUID cid; + std::copy(args.cid.begin(), args.cid.end(), cid); + Steinberg::IPtr component = + module->getFactory() + .createInstance(cid); + if (component) { + std::lock_guard lock(component_instances_mutex); + + const size_t instance_id = generate_instance_id(); + component_instances[instance_id] = std::move(component); + + return YaPluginMonolith::ConstructArgs( + component_instances[instance_id].component, + instance_id); + } else { + // The actual result is lost here + return UniversalTResult(Steinberg::kNotImplemented); + } + }, + [&](const YaPluginMonolith::Destruct& request) + -> YaPluginMonolith::Destruct::Response { + std::lock_guard lock(component_instances_mutex); + component_instances.erase(request.instance_id); + + return Ack{}; + }, [&](YaAudioProcessor::SetBusArrangements& request) -> YaAudioProcessor::SetBusArrangements::Response { return component_instances[request.instance_id] @@ -113,34 +141,6 @@ void Vst3Bridge::run() { return component_instances[request.instance_id] .audio_processor->getTailSamples(); }, - [&](const YaComponent::Construct& args) - -> YaComponent::Construct::Response { - Steinberg::TUID cid; - std::copy(args.cid.begin(), args.cid.end(), cid); - Steinberg::IPtr component = - module->getFactory() - .createInstance(cid); - if (component) { - std::lock_guard lock(component_instances_mutex); - - const size_t instance_id = generate_instance_id(); - component_instances[instance_id] = std::move(component); - - return YaComponent::ConstructArgs( - component_instances[instance_id].component, - instance_id); - } else { - // The actual result is lost here - return UniversalTResult(Steinberg::kNotImplemented); - } - }, - [&](const YaComponent::Destruct& request) - -> YaComponent::Destruct::Response { - std::lock_guard lock(component_instances_mutex); - component_instances.erase(request.instance_id); - - return Ack{}; - }, [&](const YaComponent::SetIoMode& request) -> YaComponent::SetIoMode::Response { return component_instances[request.instance_id] @@ -202,7 +202,8 @@ void Vst3Bridge::run() { -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object // and pass that to the initialize function. This object should - // be cleaned up again during `YaComponent::Destruct`. + // be cleaned up again during `YaPluginMonolith::Destruct`. + // TOOD: This needs changing when we get to `YaHostMonolith` Steinberg::FUnknown* context = nullptr; if (request.host_application_context_args) { component_instances[request.instance_id] From 11bf7532fad1209a0f33e52ab8998a17b8c5d60b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 13:07:42 +0100 Subject: [PATCH 227/456] Rename the monolitic class to Vst3PluginProxy Now it's starting to look promising. --- docs/vst3.md | 6 +- meson.build | 6 +- src/common/logging/vst3.cpp | 23 +-- src/common/logging/vst3.h | 6 +- src/common/serialization/vst3.h | 6 +- src/common/serialization/vst3/README.md | 16 +- .../serialization/vst3/audio-processor.h | 2 +- src/common/serialization/vst3/component.h | 2 +- .../serialization/vst3/host-application.h | 2 +- src/common/serialization/vst3/plugin-base.h | 2 +- .../serialization/vst3/plugin-monolith.h | 152 +----------------- .../{plugin-monolith.cpp => plugin-proxy.cpp} | 16 +- src/common/serialization/vst3/plugin-proxy.h | 151 +++++++++++++++++ src/plugin/bridges/vst3-impls/component.h | 96 +---------- .../bridges/vst3-impls/plugin-factory.cpp | 23 ++- .../bridges/vst3-impls/plugin-factory.h | 7 +- .../{component.cpp => plugin-proxy.cpp} | 68 ++++---- src/plugin/bridges/vst3-impls/plugin-proxy.h | 95 +++++++++++ src/plugin/bridges/vst3.cpp | 6 +- src/plugin/bridges/vst3.h | 7 +- src/wine-host/bridges/vst3.cpp | 14 +- 21 files changed, 352 insertions(+), 354 deletions(-) rename src/common/serialization/vst3/{plugin-monolith.cpp => plugin-proxy.cpp} (85%) create mode 100644 src/common/serialization/vst3/plugin-proxy.h rename src/plugin/bridges/vst3-impls/{component.cpp => plugin-proxy.cpp} (75%) create mode 100644 src/plugin/bridges/vst3-impls/plugin-proxy.h diff --git a/docs/vst3.md b/docs/vst3.md index df08998b..aa0930f0 100644 --- a/docs/vst3.md +++ b/docs/vst3.md @@ -4,10 +4,10 @@ TODO: Flesh this out further TODO: Link to `src/common/serialization/vst3/README.md` -TODO: Mention the new `Ya::supports()` mechanism for monolithic interfaces -through multiple inheritance +TODO: Mention the new `Ya::supports()` mechanism for the monolithic proxy +objects through multiple inheritance -TODO: Explain the monolith. +TODO: Explain the monolith The VST3 SDK uses an architecture where every concrete object inherits from an interface, and every interface inherits from `FUnknown`. `FUnkonwn` offers a diff --git a/meson.build b/meson.build index 99c9644e..baead088 100644 --- a/meson.build +++ b/meson.build @@ -85,15 +85,15 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plugin-base.cpp', - 'src/common/serialization/vst3/plugin-monolith.cpp', + 'src/common/serialization/vst3/plugin-proxy.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', 'src/common/configuration.cpp', 'src/common/plugins.cpp', 'src/common/utils.cpp', 'src/plugin/bridges/vst3.cpp', - 'src/plugin/bridges/vst3-impls/component.cpp', 'src/plugin/bridges/vst3-impls/plugin-factory.cpp', + 'src/plugin/bridges/vst3-impls/plugin-proxy.cpp', 'src/plugin/host-process.cpp', 'src/plugin/utils.cpp', 'src/plugin/vst3-plugin.cpp', @@ -127,7 +127,7 @@ if with_vst3 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plugin-base.cpp', - 'src/common/serialization/vst3/plugin-monolith.cpp', + 'src/common/serialization/vst3/plugin-proxy.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', 'src/wine-host/bridges/vst3-impls/host-application.cpp', diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 51140247..6d285400 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -37,7 +37,7 @@ void Vst3Logger::log_unknown_interface( } void Vst3Logger::log_request(bool is_host_vst, - const YaPluginMonolith::Construct&) { + const Vst3PluginProxy::Construct&) { log_request_base(is_host_vst, [&](auto& message) { // TODO: Log the CID on verbosity level 2, and then also report all CIDs // in the plugin factory @@ -51,7 +51,7 @@ void Vst3Logger::log_request(bool is_host_vst, } void Vst3Logger::log_request(bool is_host_vst, - const YaPluginMonolith::Destruct& request) { + const Vst3PluginProxy::Destruct& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::~IComponent()"; @@ -70,8 +70,9 @@ void Vst3Logger::log_request( }); } -void Vst3Logger::log_request(bool is_host_vst, - const YaAudioProcessor::GetBusArrangement& request) { +void Vst3Logger::log_request( + bool is_host_vst, + const YaAudioProcessor::GetBusArrangement& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::getBusArrangement(dir = " << request.dir @@ -79,8 +80,9 @@ void Vst3Logger::log_request(bool is_host_vst, }); } -void Vst3Logger::log_request(bool is_host_vst, - const YaAudioProcessor::CanProcessSampleSize& request) { +void Vst3Logger::log_request( + bool is_host_vst, + const YaAudioProcessor::CanProcessSampleSize& request) { log_request_base(is_host_vst, [&](auto& message) { message << "::canProcessSampleSize(symbolicSampleSize = " @@ -256,12 +258,11 @@ void Vst3Logger::log_response(bool is_host_vst, const Ack&) { log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); } -void Vst3Logger::log_response( - bool is_host_vst, - const std::variant& - result) { +void Vst3Logger::log_response(bool is_host_vst, + const std::variant& result) { log_response_base(is_host_vst, [&](auto& message) { - std::visit(overload{[&](const YaPluginMonolith::ConstructArgs& args) { + std::visit(overload{[&](const Vst3PluginProxy::ConstructArgs& args) { message << ""; }, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index a45b2e41..36d04315 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -56,8 +56,8 @@ class Vst3Logger { // flag here indicates whether the request was initiated on the host side // (what we'll call a control message). - void log_request(bool is_host_vst, const YaPluginMonolith::Construct&); - void log_request(bool is_host_vst, const YaPluginMonolith::Destruct&); + void log_request(bool is_host_vst, const Vst3PluginProxy::Construct&); + void log_request(bool is_host_vst, const Vst3PluginProxy::Destruct&); void log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); void log_request(bool is_host_vst, @@ -88,7 +88,7 @@ class Vst3Logger { void log_response(bool is_host_vst, const Ack&); void log_response( bool is_host_vst, - const std::variant&); + const std::variant&); void log_response(bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse&); void log_response(bool is_host_vst, diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index d8aa9a54..df052f14 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -24,7 +24,7 @@ #include "../utils.h" #include "common.h" #include "vst3/plugin-factory.h" -#include "vst3/plugin-monolith.h" +#include "vst3/plugin-proxy.h" // Event handling for our VST3 plugins works slightly different from how we // handle VST2 plugins. VST3 does not have a centralized event dispatching @@ -57,8 +57,8 @@ struct WantsConfiguration { * encodes the information we request or the operation we want to perform. A * request of type `ControlRequest(T)` should send back a `T::Response`. */ -using ControlRequest = std::variant. - -#pragma once - -#include -#include - -#include "../common.h" -#include "audio-processor.h" -#include "base.h" -#include "component.h" -#include "host-application.h" -#include "plugin-base.h" - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" - -/** - * An abstract class that optionally implements all VST3 interfaces a plugin - * object could implement. A more in depth explanation can be found in - * `docs/vst3.md`, but the way this works is that we begin with an `FUnknown` - * pointer from the Windows VST3 plugin obtained by a call to - * `IPluginFactory::createInstance()` (with an interface decided by the host). - * We then go through all the plugin interfaces and check whether that object - * supports them one by one. For each supported interface we remember that the - * plugin supports it, and we'll optionally write down some static data (such as - * the edit controller cid) that can't change over the lifetime of the - * application. On the plugin side we then return a `YaPluginMonolith` - * implementation that contains all of this information about interfaces the - * object we're proxying might support. This way we can allow casts to all of - * those object types in `queryInterface()`, essentially perfectly mimicing the - * original object. - * - * This monolith approach is also important when it comes to `IConnectionPoint`. - * The host should be able to connect arbitrary objects together, and the plugin - * can then use the query interface smart pointer casting system to cast those - * objects to the types they want. By having a huge monolithic class that - * implements any interface such an object might also implement, we can allow - * perfect proxying behaviour for connecting components. - */ -class YaPluginMonolith : public YaAudioProcessor, - public YaComponent, - public YaPluginBase { - public: - /** - * These are the arguments for creating a `YaPluginMonolithImpl`. - */ - struct ConstructArgs { - ConstructArgs(); - - /** - * Read from an existing object. We will try to mimic this object, so - * we'll support any interfaces this object also supports. - */ - ConstructArgs(Steinberg::IPtr object, size_t instance_id); - - /** - * The unique identifier for this specific object instance. - */ - native_size_t instance_id; - - YaAudioProcessor::ConstructArgs audio_processor_args; - YaComponent::ConstructArgs component_args; - YaPluginBase::ConstructArgs plugin_base_args; - - template - void serialize(S& s) { - s.value8b(instance_id); - s.object(audio_processor_args); - s.object(component_args); - s.object(plugin_base_args); - } - }; - - /** - * Message to request the Wine plugin host to instantiate a new IComponent - * to pass through a call to `IComponent::createInstance(cid, - * IComponent::iid, ...)`. - */ - struct Construct { - using Response = std::variant; - - ArrayUID cid; - - // TODO: Add an enum class to reify the type of object we want to - // instantiate so we can initialize things other than - // `IComponent`, like `IEditController.` - - template - void serialize(S& s) { - s.container1b(cid); - } - }; - - /** - * Instantiate this object instance with arguments read from another - * interface implementation. - */ - YaPluginMonolith(const ConstructArgs&& args); - - /** - * Message to request the Wine plugin host to destroy this object instance - * with the given instance ID. Sent from the destructor of - * `YaPluginMonolithImpl`. This will cause all smart pointers to the actual - * object in the Wine plugin host to be dropped. - */ - struct Destruct { - using Response = Ack; - - native_size_t instance_id; - - template - void serialize(S& s) { - s.value8b(instance_id); - } - }; - - /** - * @remark The plugin side implementation should send a control message to - * clean up the instance on the Wine side in its destructor. - */ - virtual ~YaPluginMonolith() = 0; - - DECLARE_FUNKNOWN_METHODS - - protected: - ConstructArgs arguments; -}; - -#pragma GCC diagnostic pop - -template -void serialize( - S& s, - std::variant& result) { - s.ext(result, bitsery::ext::StdVariant{}); -} +Vst3PluginProxyVst3PluginProxyVst3PluginProxyVst3PluginProxy diff --git a/src/common/serialization/vst3/plugin-monolith.cpp b/src/common/serialization/vst3/plugin-proxy.cpp similarity index 85% rename from src/common/serialization/vst3/plugin-monolith.cpp rename to src/common/serialization/vst3/plugin-proxy.cpp index 814185a3..a286abc5 100644 --- a/src/common/serialization/vst3/plugin-monolith.cpp +++ b/src/common/serialization/vst3/plugin-proxy.cpp @@ -14,11 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "plugin-monolith.h" +#include "plugin-proxy.h" -YaPluginMonolith::ConstructArgs::ConstructArgs() {} +Vst3PluginProxy::ConstructArgs::ConstructArgs() {} -YaPluginMonolith::ConstructArgs::ConstructArgs( +Vst3PluginProxy::ConstructArgs::ConstructArgs( Steinberg::IPtr object, size_t instance_id) : instance_id(instance_id), @@ -26,23 +26,23 @@ YaPluginMonolith::ConstructArgs::ConstructArgs( component_args(object), plugin_base_args(object) {} -YaPluginMonolith::YaPluginMonolith(const ConstructArgs&& args) +Vst3PluginProxy::Vst3PluginProxy(const ConstructArgs&& args) : YaAudioProcessor(std::move(args.audio_processor_args)), YaComponent(std::move(args.component_args)), YaPluginBase(std::move(args.plugin_base_args)), arguments(std::move(args)){FUNKNOWN_CTOR} - YaPluginMonolith::~YaPluginMonolith() { + Vst3PluginProxy::~Vst3PluginProxy() { FUNKNOWN_DTOR } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" -IMPLEMENT_REFCOUNT(YaPluginMonolith) +IMPLEMENT_REFCOUNT(Vst3PluginProxy) #pragma GCC diagnostic pop -tresult PLUGIN_API YaPluginMonolith::queryInterface(Steinberg::FIDString _iid, - void** obj) { +tresult PLUGIN_API Vst3PluginProxy::queryInterface(Steinberg::FIDString _iid, + void** obj) { if (YaPluginBase::supported()) { // We had to expand the macro here because we need to cast through // `YaPluginBase`, since `IpluginBase` is also a base of `IComponent` diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h new file mode 100644 index 00000000..c6bf9fc2 --- /dev/null +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -0,0 +1,151 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include + +#include "../common.h" +#include "audio-processor.h" +#include "base.h" +#include "component.h" +#include "host-application.h" +#include "plugin-base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * An abstract class that optionally implements all VST3 interfaces a plugin + * object could implement. A more in depth explanation can be found in + * `docs/vst3.md`, but the way this works is that we begin with an `FUnknown` + * pointer from the Windows VST3 plugin obtained by a call to + * `IPluginFactory::createInstance()` (with an interface decided by the host). + * We then go through all the plugin interfaces and check whether that object + * supports them one by one. For each supported interface we remember that the + * plugin supports it, and we'll optionally write down some static data (such as + * the edit controller cid) that can't change over the lifetime of the + * application. On the plugin side we then return a `Vst3PluginProxyImpl` object + * that contains all of this information about interfaces the object we're + * proxying might support. This way we can allow casts to all of those object + * types in `queryInterface()`, essentially perfectly mimicing the original + * object. + * + * This monolith approach is also important when it comes to `IConnectionPoint`. + * The host should be able to connect arbitrary objects together, and the plugin + * can then use the query interface smart pointer casting system to cast those + * objects to the types they want. By having a huge monolithic class that + * implements any interface such an object might also implement, we can allow + * perfect proxying behaviour for connecting components. + */ +class Vst3PluginProxy : public YaAudioProcessor, + public YaComponent, + public YaPluginBase { + public: + /** + * These are the arguments for creating a `Vst3PluginProxyImpl`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Read from an existing object. We will try to mimic this object, so + * we'll support any interfaces this object also supports. + */ + ConstructArgs(Steinberg::IPtr object, size_t instance_id); + + /** + * The unique identifier for this specific object instance. + */ + native_size_t instance_id; + + YaAudioProcessor::ConstructArgs audio_processor_args; + YaComponent::ConstructArgs component_args; + YaPluginBase::ConstructArgs plugin_base_args; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.object(audio_processor_args); + s.object(component_args); + s.object(plugin_base_args); + } + }; + + /** + * Message to request the Wine plugin host to instantiate a new IComponent + * to pass through a call to `IComponent::createInstance(cid, + * IComponent::iid, ...)`. + */ + struct Construct { + using Response = std::variant; + + ArrayUID cid; + + // TODO: Add an enum class to reify the type of object we want to + // instantiate so we can initialize things other than + // `IComponent`, like `IEditController.` + + template + void serialize(S& s) { + s.container1b(cid); + } + }; + + /** + * Instantiate this object instance with arguments read from another + * interface implementation. + */ + Vst3PluginProxy(const ConstructArgs&& args); + + /** + * Message to request the Wine plugin host to destroy this object instance + * with the given instance ID. Sent from the destructor of + * `Vst3PluginProxyImpl`. This will cause all smart pointers to the actual + * object in the Wine plugin host to be dropped. + */ + struct Destruct { + using Response = Ack; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + + /** + * @remark The plugin side implementation should send a control message to + * clean up the instance on the Wine side in its destructor. + */ + virtual ~Vst3PluginProxy() = 0; + + DECLARE_FUNKNOWN_METHODS + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop + +template +void serialize( + S& s, + std::variant& result) { + s.ext(result, bitsery::ext::StdVariant{}); +} diff --git a/src/plugin/bridges/vst3-impls/component.h b/src/plugin/bridges/vst3-impls/component.h index 03ecdbd7..bb620bb3 100644 --- a/src/plugin/bridges/vst3-impls/component.h +++ b/src/plugin/bridges/vst3-impls/component.h @@ -1,95 +1 @@ -// yabridge: a Wine VST bridge -// Copyright (C) 2020 Robbert van der Helm -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#pragma once - -#include - -#include "../vst3.h" - -class YaPluginMonolithImpl : public YaPluginMonolith { - public: - YaPluginMonolithImpl(Vst3PluginBridge& bridge, - YaPluginMonolith::ConstructArgs&& args); - - /** - * When the reference count reaches zero and this destructor is called, - * we'll send a request to the Wine plugin host to destroy the corresponding - * object. - */ - ~YaPluginMonolithImpl(); - - /** - * We'll override the query interface to log queries for interfaces we do - * not (yet) support. - */ - tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, - void** obj) override; - - // From `IAudioProcessor` - tresult PLUGIN_API - setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, - int32 numIns, - Steinberg::Vst::SpeakerArrangement* outputs, - int32 numOuts) override; - tresult PLUGIN_API - getBusArrangement(Steinberg::Vst::BusDirection dir, - int32 index, - Steinberg::Vst::SpeakerArrangement& arr) override; - tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override; - uint32 PLUGIN_API getLatencySamples() override; - tresult PLUGIN_API - setupProcessing(Steinberg::Vst::ProcessSetup& setup) override; - tresult PLUGIN_API setProcessing(TBool state) override; - tresult PLUGIN_API process(Steinberg::Vst::ProcessData& data) override; - uint32 PLUGIN_API getTailSamples() override; - - // From `IComponent` - tresult PLUGIN_API setIoMode(Steinberg::Vst::IoMode mode) override; - int32 PLUGIN_API getBusCount(Steinberg::Vst::MediaType type, - Steinberg::Vst::BusDirection dir) override; - tresult PLUGIN_API - getBusInfo(Steinberg::Vst::MediaType type, - Steinberg::Vst::BusDirection dir, - int32 index, - Steinberg::Vst::BusInfo& bus /*out*/) override; - tresult PLUGIN_API - getRoutingInfo(Steinberg::Vst::RoutingInfo& inInfo, - Steinberg::Vst::RoutingInfo& outInfo /*out*/) override; - tresult PLUGIN_API activateBus(Steinberg::Vst::MediaType type, - Steinberg::Vst::BusDirection dir, - int32 index, - TBool state) override; - tresult PLUGIN_API setActive(TBool state) override; - tresult PLUGIN_API setState(Steinberg::IBStream* state) override; - tresult PLUGIN_API getState(Steinberg::IBStream* state) override; - - // From `IPluginBase` - tresult PLUGIN_API initialize(FUnknown* context) override; - tresult PLUGIN_API terminate() override; - - private: - Vst3PluginBridge& bridge; - - /** - * An `IHostApplication` instance if we get one through - * `IPluginBase::initialize()`. This should be the same for all plugin - * instances so we should not have to store it here separately, but for the - * sake of correctness we will. - */ - Steinberg::FUnknownPtr - host_application_context; -}; +YaPluginProxyImplYaPluginProxyImplYaPluginProxyImpl diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 6b65b2e1..06a8319f 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -18,17 +18,16 @@ #include -#include "component.h" +#include "plugin-proxy.h" -YaPluginFactoryPluginImpl::YaPluginFactoryPluginImpl( - Vst3PluginBridge& bridge, - YaPluginFactory::ConstructArgs&& args) +YaPluginFactoryImpl::YaPluginFactoryImpl(Vst3PluginBridge& bridge, + YaPluginFactory::ConstructArgs&& args) : YaPluginFactory(std::move(args)), bridge(bridge) {} tresult PLUGIN_API -YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, - Steinberg::FIDString _iid, - void** obj) { +YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, + Steinberg::FIDString _iid, + void** obj) { // TODO: Do the same thing for other types // These arw pointers are scary. The idea here is that we return a newly @@ -38,13 +37,13 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, ArrayUID cid_array; std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { - std::variant result = - bridge.send_message(YaPluginMonolith::Construct{.cid = cid_array}); + std::variant result = + bridge.send_message(Vst3PluginProxy::Construct{.cid = cid_array}); return std::visit( overload{ - [&](YaPluginMonolith::ConstructArgs&& args) -> tresult { + [&](Vst3PluginProxy::ConstructArgs&& args) -> tresult { *obj = static_cast( - new YaPluginMonolithImpl(bridge, std::move(args))); + new Vst3PluginProxyImpl(bridge, std::move(args))); return Steinberg::kResultOk; }, [&](const UniversalTResult& code) -> tresult { return code; }}, @@ -69,7 +68,7 @@ YaPluginFactoryPluginImpl::createInstance(Steinberg::FIDString cid, } tresult PLUGIN_API -YaPluginFactoryPluginImpl::setHostContext(Steinberg::FUnknown* context) { +YaPluginFactoryImpl::setHostContext(Steinberg::FUnknown* context) { // This `context` will likely be an `IHostApplication`. If it is, we will // store it for future calls, create a proxy object on the Wine side, and // then pass it to the Windows VST3 plugin's plugin factory using the same diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.h b/src/plugin/bridges/vst3-impls/plugin-factory.h index 30a2f093..7743f2d2 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.h +++ b/src/plugin/bridges/vst3-impls/plugin-factory.h @@ -18,11 +18,10 @@ #include "../vst3.h" -// TODO Rename to YaPluginFactoryImpl -class YaPluginFactoryPluginImpl : public YaPluginFactory { +class YaPluginFactoryImpl : public YaPluginFactory { public: - YaPluginFactoryPluginImpl(Vst3PluginBridge& bridge, - YaPluginFactory::ConstructArgs&& args); + YaPluginFactoryImpl(Vst3PluginBridge& bridge, + YaPluginFactory::ConstructArgs&& args); tresult PLUGIN_API createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, diff --git a/src/plugin/bridges/vst3-impls/component.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp similarity index 75% rename from src/plugin/bridges/vst3-impls/component.cpp rename to src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 8f802e5a..e79bb986 100644 --- a/src/plugin/bridges/vst3-impls/component.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -14,25 +14,24 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "component.h" +#include "plugin-proxy.h" -YaPluginMonolithImpl::YaPluginMonolithImpl( - Vst3PluginBridge& bridge, - YaPluginMonolith::ConstructArgs&& args) - : YaPluginMonolith(std::move(args)), bridge(bridge) { +Vst3PluginProxyImpl::Vst3PluginProxyImpl(Vst3PluginBridge& bridge, + Vst3PluginProxy::ConstructArgs&& args) + : Vst3PluginProxy(std::move(args)), bridge(bridge) { bridge.register_component(arguments.instance_id, *this); } -YaPluginMonolithImpl::~YaPluginMonolithImpl() { +Vst3PluginProxyImpl::~Vst3PluginProxyImpl() { bridge.send_message( - YaPluginMonolith::Destruct{.instance_id = arguments.instance_id}); + Vst3PluginProxy::Destruct{.instance_id = arguments.instance_id}); bridge.unregister_component(arguments.instance_id); } tresult PLUGIN_API -YaPluginMonolithImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { +Vst3PluginProxyImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { // TODO: Successful queries should also be logged - const tresult result = YaPluginMonolith::queryInterface(_iid, obj); + const tresult result = Vst3PluginProxy::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { bridge.logger.log_unknown_interface("In IComponent::queryInterface()", Steinberg::FUID::fromTUID(_iid)); @@ -41,7 +40,7 @@ YaPluginMonolithImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { return result; } -tresult PLUGIN_API YaPluginMonolithImpl::setBusArrangements( +tresult PLUGIN_API Vst3PluginProxyImpl::setBusArrangements( Steinberg::Vst::SpeakerArrangement* inputs, int32 numIns, Steinberg::Vst::SpeakerArrangement* outputs, @@ -58,7 +57,7 @@ tresult PLUGIN_API YaPluginMonolithImpl::setBusArrangements( }); } -tresult PLUGIN_API YaPluginMonolithImpl::getBusArrangement( +tresult PLUGIN_API Vst3PluginProxyImpl::getBusArrangement( Steinberg::Vst::BusDirection dir, int32 index, Steinberg::Vst::SpeakerArrangement& arr) { @@ -75,30 +74,30 @@ tresult PLUGIN_API YaPluginMonolithImpl::getBusArrangement( } tresult PLUGIN_API -YaPluginMonolithImpl::canProcessSampleSize(int32 symbolicSampleSize) { +Vst3PluginProxyImpl::canProcessSampleSize(int32 symbolicSampleSize) { return bridge.send_message(YaAudioProcessor::CanProcessSampleSize{ .instance_id = arguments.instance_id, .symbolic_sample_size = symbolicSampleSize}); } -uint32 PLUGIN_API YaPluginMonolithImpl::getLatencySamples() { +uint32 PLUGIN_API Vst3PluginProxyImpl::getLatencySamples() { return bridge.send_message(YaAudioProcessor::GetLatencySamples{ .instance_id = arguments.instance_id}); } tresult PLUGIN_API -YaPluginMonolithImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { +Vst3PluginProxyImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { return bridge.send_message(YaAudioProcessor::SetupProcessing{ .instance_id = arguments.instance_id, .setup = setup}); } -tresult PLUGIN_API YaPluginMonolithImpl::setProcessing(TBool state) { +tresult PLUGIN_API Vst3PluginProxyImpl::setProcessing(TBool state) { return bridge.send_message(YaAudioProcessor::SetProcessing{ .instance_id = arguments.instance_id, .state = state}); } tresult PLUGIN_API -YaPluginMonolithImpl::process(Steinberg::Vst::ProcessData& data) { +Vst3PluginProxyImpl::process(Steinberg::Vst::ProcessData& data) { ProcessResponse response = bridge.send_message(YaAudioProcessor::Process{ .instance_id = arguments.instance_id, .data = data}); @@ -107,29 +106,28 @@ YaPluginMonolithImpl::process(Steinberg::Vst::ProcessData& data) { return response.result; } -uint32 PLUGIN_API YaPluginMonolithImpl::getTailSamples() { +uint32 PLUGIN_API Vst3PluginProxyImpl::getTailSamples() { return bridge.send_message( YaAudioProcessor::GetTailSamples{.instance_id = arguments.instance_id}); } -tresult PLUGIN_API -YaPluginMonolithImpl::setIoMode(Steinberg::Vst::IoMode mode) { +tresult PLUGIN_API Vst3PluginProxyImpl::setIoMode(Steinberg::Vst::IoMode mode) { return bridge.send_message(YaComponent::SetIoMode{ .instance_id = arguments.instance_id, .mode = mode}); } int32 PLUGIN_API -YaPluginMonolithImpl::getBusCount(Steinberg::Vst::MediaType type, - Steinberg::Vst::BusDirection dir) { +Vst3PluginProxyImpl::getBusCount(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir) { return bridge.send_message(YaComponent::GetBusCount{ .instance_id = arguments.instance_id, .type = type, .dir = dir}); } tresult PLUGIN_API -YaPluginMonolithImpl::getBusInfo(Steinberg::Vst::MediaType type, - Steinberg::Vst::BusDirection dir, - int32 index, - Steinberg::Vst::BusInfo& bus /*out*/) { +Vst3PluginProxyImpl::getBusInfo(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::BusInfo& bus /*out*/) { const GetBusInfoResponse response = bridge.send_message( YaComponent::GetBusInfo{.instance_id = arguments.instance_id, .type = type, @@ -141,7 +139,7 @@ YaPluginMonolithImpl::getBusInfo(Steinberg::Vst::MediaType type, return response.result; } -tresult PLUGIN_API YaPluginMonolithImpl::getRoutingInfo( +tresult PLUGIN_API Vst3PluginProxyImpl::getRoutingInfo( Steinberg::Vst::RoutingInfo& inInfo, Steinberg::Vst::RoutingInfo& outInfo /*out*/) { const GetRoutingInfoResponse response = bridge.send_message( @@ -155,10 +153,10 @@ tresult PLUGIN_API YaPluginMonolithImpl::getRoutingInfo( } tresult PLUGIN_API -YaPluginMonolithImpl::activateBus(Steinberg::Vst::MediaType type, - Steinberg::Vst::BusDirection dir, - int32 index, - TBool state) { +Vst3PluginProxyImpl::activateBus(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + TBool state) { return bridge.send_message( YaComponent::ActivateBus{.instance_id = arguments.instance_id, .type = type, @@ -167,17 +165,17 @@ YaPluginMonolithImpl::activateBus(Steinberg::Vst::MediaType type, .state = state}); } -tresult PLUGIN_API YaPluginMonolithImpl::setActive(TBool state) { +tresult PLUGIN_API Vst3PluginProxyImpl::setActive(TBool state) { return bridge.send_message(YaComponent::SetActive{ .instance_id = arguments.instance_id, .state = state}); } -tresult PLUGIN_API YaPluginMonolithImpl::setState(Steinberg::IBStream* state) { +tresult PLUGIN_API Vst3PluginProxyImpl::setState(Steinberg::IBStream* state) { return bridge.send_message(YaComponent::SetState{ .instance_id = arguments.instance_id, .state = state}); } -tresult PLUGIN_API YaPluginMonolithImpl::getState(Steinberg::IBStream* state) { +tresult PLUGIN_API Vst3PluginProxyImpl::getState(Steinberg::IBStream* state) { const GetStateResponse response = bridge.send_message( YaComponent::GetState{.instance_id = arguments.instance_id}); @@ -186,7 +184,7 @@ tresult PLUGIN_API YaPluginMonolithImpl::getState(Steinberg::IBStream* state) { return response.result; } -tresult PLUGIN_API YaPluginMonolithImpl::initialize(FUnknown* context) { +tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* context) { // This `context` will likely be an `IHostApplication`. If it is, we will // store it here, and we'll proxy through all calls to it made from the Wine // side. Otherwise we'll still call `IPluginBase::initialize()` but with a @@ -210,7 +208,7 @@ tresult PLUGIN_API YaPluginMonolithImpl::initialize(FUnknown* context) { std::move(host_application_context_args)}); } -tresult PLUGIN_API YaPluginMonolithImpl::terminate() { +tresult PLUGIN_API Vst3PluginProxyImpl::terminate() { return bridge.send_message( YaPluginBase::Terminate{.instance_id = arguments.instance_id}); } diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h new file mode 100644 index 00000000..f7ec18c7 --- /dev/null +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -0,0 +1,95 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "../vst3.h" + +class Vst3PluginProxyImpl : public Vst3PluginProxy { + public: + Vst3PluginProxyImpl(Vst3PluginBridge& bridge, + Vst3PluginProxy::ConstructArgs&& args); + + /** + * When the reference count reaches zero and this destructor is called, + * we'll send a request to the Wine plugin host to destroy the corresponding + * object. + */ + ~Vst3PluginProxyImpl(); + + /** + * We'll override the query interface to log queries for interfaces we do + * not (yet) support. + */ + tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, + void** obj) override; + + // From `IAudioProcessor` + tresult PLUGIN_API + setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, + int32 numIns, + Steinberg::Vst::SpeakerArrangement* outputs, + int32 numOuts) override; + tresult PLUGIN_API + getBusArrangement(Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::SpeakerArrangement& arr) override; + tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override; + uint32 PLUGIN_API getLatencySamples() override; + tresult PLUGIN_API + setupProcessing(Steinberg::Vst::ProcessSetup& setup) override; + tresult PLUGIN_API setProcessing(TBool state) override; + tresult PLUGIN_API process(Steinberg::Vst::ProcessData& data) override; + uint32 PLUGIN_API getTailSamples() override; + + // From `IComponent` + tresult PLUGIN_API setIoMode(Steinberg::Vst::IoMode mode) override; + int32 PLUGIN_API getBusCount(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir) override; + tresult PLUGIN_API + getBusInfo(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + Steinberg::Vst::BusInfo& bus /*out*/) override; + tresult PLUGIN_API + getRoutingInfo(Steinberg::Vst::RoutingInfo& inInfo, + Steinberg::Vst::RoutingInfo& outInfo /*out*/) override; + tresult PLUGIN_API activateBus(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 index, + TBool state) override; + tresult PLUGIN_API setActive(TBool state) override; + tresult PLUGIN_API setState(Steinberg::IBStream* state) override; + tresult PLUGIN_API getState(Steinberg::IBStream* state) override; + + // From `IPluginBase` + tresult PLUGIN_API initialize(FUnknown* context) override; + tresult PLUGIN_API terminate() override; + + private: + Vst3PluginBridge& bridge; + + /** + * An `IHostApplication` instance if we get one through + * `IPluginBase::initialize()`. This should be the same for all plugin + * instances so we should not have to store it here separately, but for the + * sake of correctness we will. + */ + Steinberg::FUnknownPtr + host_application_context; +}; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 61ae85a1..b2c16499 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -108,17 +108,17 @@ Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { YaPluginFactory::Construct{}, std::pair(logger, true)); plugin_factory = - new YaPluginFactoryPluginImpl(*this, std::move(factory_args)); + new YaPluginFactoryImpl(*this, std::move(factory_args)); } return plugin_factory; } void Vst3PluginBridge::register_component(size_t instance_id, - YaPluginMonolithImpl& component) { + Vst3PluginProxyImpl& component) { std::lock_guard lock(component_instances_mutex); component_instances.emplace(instance_id, - std::ref(component)); + std::ref(component)); } void Vst3PluginBridge::unregister_component(size_t instance_id) { diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 341b402c..ad6fb009 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -24,7 +24,7 @@ #include "common.h" // Forward declaration -class YaPluginMonolithImpl; +class Vst3PluginProxyImpl; /** * This handles the communication between the native host and a VST3 plugin @@ -86,8 +86,7 @@ class Vst3PluginBridge : PluginBridge> { * * TODO: REname to `register_instance` or `register_object` */ - void register_component(size_t instance_id, - YaPluginMonolithImpl& component); + void register_component(size_t instance_id, Vst3PluginProxyImpl& component); /** * Remove a previously registered `YaComponentPluginImpl` from the list of @@ -151,7 +150,7 @@ class Vst3PluginBridge : PluginBridge> { * `register_component()` in the constractor, and an instance is then * removed through a call to `unregister_component()` in the destructor. */ - std::map> + std::map> component_instances; std::mutex component_instances_mutex; }; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 57dfa2b6..816746b9 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -60,8 +60,8 @@ void Vst3Bridge::run() { sockets.host_vst_control.receive_messages( std::nullopt, overload{ - [&](const YaPluginMonolith::Construct& args) - -> YaPluginMonolith::Construct::Response { + [&](const Vst3PluginProxy::Construct& args) + -> Vst3PluginProxy::Construct::Response { Steinberg::TUID cid; std::copy(args.cid.begin(), args.cid.end(), cid); Steinberg::IPtr component = @@ -73,7 +73,7 @@ void Vst3Bridge::run() { const size_t instance_id = generate_instance_id(); component_instances[instance_id] = std::move(component); - return YaPluginMonolith::ConstructArgs( + return Vst3PluginProxy::ConstructArgs( component_instances[instance_id].component, instance_id); } else { @@ -81,8 +81,8 @@ void Vst3Bridge::run() { return UniversalTResult(Steinberg::kNotImplemented); } }, - [&](const YaPluginMonolith::Destruct& request) - -> YaPluginMonolith::Destruct::Response { + [&](const Vst3PluginProxy::Destruct& request) + -> Vst3PluginProxy::Destruct::Response { std::lock_guard lock(component_instances_mutex); component_instances.erase(request.instance_id); @@ -202,8 +202,8 @@ void Vst3Bridge::run() { -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object // and pass that to the initialize function. This object should - // be cleaned up again during `YaPluginMonolith::Destruct`. - // TOOD: This needs changing when we get to `YaHostMonolith` + // be cleaned up again during `Vst3PluginProxy::Destruct`. + // TODO: This needs changing when we get to `Vst3HostProxy` Steinberg::FUnknown* context = nullptr; if (request.host_application_context_args) { component_instances[request.instance_id] From 655195a4153fea000f5e8f62d0a417a8cb7a6129 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 13:12:54 +0100 Subject: [PATCH 228/456] Move the plugin interfaces to a subdirectory --- meson.build | 12 ++++++------ src/common/serialization/vst3/plugin-proxy.h | 6 +++--- .../vst3/{ => plugin}/audio-processor.cpp | 0 .../vst3/{ => plugin}/audio-processor.h | 8 ++++---- .../serialization/vst3/{ => plugin}/component.cpp | 0 .../serialization/vst3/{ => plugin}/component.h | 6 +++--- .../serialization/vst3/{ => plugin}/plugin-base.cpp | 0 .../serialization/vst3/{ => plugin}/plugin-base.h | 6 +++--- 8 files changed, 19 insertions(+), 19 deletions(-) rename src/common/serialization/vst3/{ => plugin}/audio-processor.cpp (100%) rename src/common/serialization/vst3/{ => plugin}/audio-processor.h (98%) rename src/common/serialization/vst3/{ => plugin}/component.cpp (100%) rename src/common/serialization/vst3/{ => plugin}/component.h (99%) rename src/common/serialization/vst3/{ => plugin}/plugin-base.cpp (100%) rename src/common/serialization/vst3/{ => plugin}/plugin-base.h (97%) diff --git a/meson.build b/meson.build index baead088..2a1b1b09 100644 --- a/meson.build +++ b/meson.build @@ -77,14 +77,14 @@ vst3_plugin_sources = [ 'src/common/communication/common.cpp', 'src/common/logging/common.cpp', 'src/common/logging/vst3.cpp', - 'src/common/serialization/vst3/audio-processor.cpp', + 'src/common/serialization/vst3/plugin/audio-processor.cpp', + 'src/common/serialization/vst3/plugin/component.cpp', + 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/base.cpp', - 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/event-list.cpp', 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', - 'src/common/serialization/vst3/plugin-base.cpp', 'src/common/serialization/vst3/plugin-proxy.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', @@ -119,14 +119,14 @@ host_sources = [ if with_vst3 host_sources += [ 'src/common/logging/vst3.cpp', - 'src/common/serialization/vst3/audio-processor.cpp', + 'src/common/serialization/vst3/plugin/audio-processor.cpp', + 'src/common/serialization/vst3/plugin/component.cpp', + 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/base.cpp', - 'src/common/serialization/vst3/component.cpp', 'src/common/serialization/vst3/event-list.cpp', 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', - 'src/common/serialization/vst3/plugin-base.cpp', 'src/common/serialization/vst3/plugin-proxy.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index c6bf9fc2..2b0730f8 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -20,11 +20,11 @@ #include #include "../common.h" -#include "audio-processor.h" #include "base.h" -#include "component.h" #include "host-application.h" -#include "plugin-base.h" +#include "plugin/audio-processor.h" +#include "plugin/component.h" +#include "plugin/plugin-base.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" diff --git a/src/common/serialization/vst3/audio-processor.cpp b/src/common/serialization/vst3/plugin/audio-processor.cpp similarity index 100% rename from src/common/serialization/vst3/audio-processor.cpp rename to src/common/serialization/vst3/plugin/audio-processor.cpp diff --git a/src/common/serialization/vst3/audio-processor.h b/src/common/serialization/vst3/plugin/audio-processor.h similarity index 98% rename from src/common/serialization/vst3/audio-processor.h rename to src/common/serialization/vst3/plugin/audio-processor.h index eaffb9ad..88deb6d7 100644 --- a/src/common/serialization/vst3/audio-processor.h +++ b/src/common/serialization/vst3/plugin/audio-processor.h @@ -19,10 +19,10 @@ #include #include -#include "../common.h" -#include "base.h" -#include "host-application.h" -#include "process-data.h" +#include "../../common.h" +#include "../base.h" +#include "../host-application.h" +#include "../process-data.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" diff --git a/src/common/serialization/vst3/component.cpp b/src/common/serialization/vst3/plugin/component.cpp similarity index 100% rename from src/common/serialization/vst3/component.cpp rename to src/common/serialization/vst3/plugin/component.cpp diff --git a/src/common/serialization/vst3/component.h b/src/common/serialization/vst3/plugin/component.h similarity index 99% rename from src/common/serialization/vst3/component.h rename to src/common/serialization/vst3/plugin/component.h index 85dfaa75..8700e81f 100644 --- a/src/common/serialization/vst3/component.h +++ b/src/common/serialization/vst3/plugin/component.h @@ -20,9 +20,9 @@ #include #include -#include "../../bitsery/ext/vst3.h" -#include "../common.h" -#include "base.h" +#include "../../../bitsery/ext/vst3.h" +#include "../../common.h" +#include "../base.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" diff --git a/src/common/serialization/vst3/plugin-base.cpp b/src/common/serialization/vst3/plugin/plugin-base.cpp similarity index 100% rename from src/common/serialization/vst3/plugin-base.cpp rename to src/common/serialization/vst3/plugin/plugin-base.cpp diff --git a/src/common/serialization/vst3/plugin-base.h b/src/common/serialization/vst3/plugin/plugin-base.h similarity index 97% rename from src/common/serialization/vst3/plugin-base.h rename to src/common/serialization/vst3/plugin/plugin-base.h index fcc2389c..3ad5f118 100644 --- a/src/common/serialization/vst3/plugin-base.h +++ b/src/common/serialization/vst3/plugin/plugin-base.h @@ -19,9 +19,9 @@ #include #include -#include "../common.h" -#include "base.h" -#include "host-application.h" +#include "../../common.h" +#include "../base.h" +#include "../host-application.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" From 481975860c53a53c9ecee002594efae8151c91c3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 13:20:16 +0100 Subject: [PATCH 229/456] Use the new simple supports flags for the factory This is both more type safe and as it turns out much more manageable. --- src/common/communication/vst3.h | 2 -- .../serialization/vst3/plugin-factory.cpp | 15 +++++------ .../serialization/vst3/plugin-factory.h | 26 +++++++------------ 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 3f73f95a..714a738a 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -77,8 +77,6 @@ class Vst3MessageHandler : public AdHocSocketHandler { * -> host callbacks isntead. Optional since it only has to be set on the * plugin's side. * - * TODO: Is it feasible to move `logging` to the constructor instead? - * * @relates Vst3MessageHandler::receive_messages */ template diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index 831c1c36..7a2a33a0 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -25,7 +25,6 @@ YaPluginFactory::ConstructArgs::ConstructArgs() {} YaPluginFactory::ConstructArgs::ConstructArgs( Steinberg::IPtr factory) { - known_iids.insert(factory->iid); // `IPluginFactory::getFactoryInfo` if (Steinberg::PFactoryInfo info; factory->getFactoryInfo(&info) == Steinberg::kResultOk) { @@ -47,7 +46,7 @@ YaPluginFactory::ConstructArgs::ConstructArgs( return; } - known_iids.insert(factory2->iid); + supports_plugin_factory_2 = true; // `IpluginFactory2::getClassInfo2` class_infos_2.resize(num_classes); for (int i = 0; i < num_classes; i++) { @@ -62,7 +61,7 @@ YaPluginFactory::ConstructArgs::ConstructArgs( return; } - known_iids.insert(factory3->iid); + supports_plugin_factory_3 = true; // `IpluginFactory3::getClassInfoUnicode` class_infos_unicode.resize(num_classes); for (int i = 0; i < num_classes; i++) { @@ -90,15 +89,13 @@ tresult PLUGIN_API YaPluginFactory::queryInterface(Steinberg::FIDString _iid, void** obj) { QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, Steinberg::IPluginFactory) - if (arguments.known_iids.contains(Steinberg::IPluginFactory::iid)) { - QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory::iid, - Steinberg::IPluginFactory) - } - if (arguments.known_iids.contains(Steinberg::IPluginFactory2::iid)) { + QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory::iid, + Steinberg::IPluginFactory) + if (arguments.supports_plugin_factory_2) { QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory2::iid, Steinberg::IPluginFactory2) } - if (arguments.known_iids.contains(Steinberg::IPluginFactory3::iid)) { + if (arguments.supports_plugin_factory_3) { QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory3::iid, Steinberg::IPluginFactory3) } diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 933f0bf8..259ff23f 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -16,10 +16,7 @@ #pragma once -#include - #include -#include #include #include @@ -27,15 +24,12 @@ #include "base.h" #include "host-application.h" -// TODO: After implementing one or two more of these, abstract away some of the -// nasty bits - #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" /** * Wraps around `IPluginFactory{1,2,3}` for serialization purposes. See - * `README.md` for more information on how this works. + * `docs/vst3.md` for more information on how this works. */ class YaPluginFactory : public Steinberg::IPluginFactory3 { public: @@ -53,12 +47,14 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { ConstructArgs(Steinberg::IPtr factory); /** - * The IIDs that the interface we serialized supports. - * - * TODO: Replace this with a set of boolean flags, just like we're doing - * with the other interfaces. + * Whether `factory` supported `IPluginFactory2`. */ - std::set known_iids; + bool supports_plugin_factory_2; + + /** + * Whether `factory` supported `IPluginFactory3`. + */ + bool supports_plugin_factory_3; /** * For `IPluginFactory::getFactoryInfo`. @@ -92,10 +88,8 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { template void serialize(S& s) { - s.ext(known_iids, bitsery::ext::StdSet{32}, - [](S& s, Steinberg::FUID& iid) { - s.ext(iid, bitsery::ext::FUID{}); - }); + s.value1b(supports_plugin_factory_2); + s.value1b(supports_plugin_factory_3); s.ext(factory_info, bitsery::ext::StdOptional{}); s.value4b(num_classes); s.container(class_infos_1, 2048, From d0e96da21a5426e56fc246b51340645b3e3722bf Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 13:33:34 +0100 Subject: [PATCH 230/456] Rename register_component to register_plugin_proxy --- .../serialization/vst3/plugin-factory.h | 11 +++-- src/plugin/bridges/vst3-impls/component.h | 1 - .../bridges/vst3-impls/plugin-proxy.cpp | 4 +- src/plugin/bridges/vst3-impls/plugin-proxy.h | 2 + src/plugin/bridges/vst3.cpp | 16 +++---- src/plugin/bridges/vst3.h | 44 +++++++++---------- 6 files changed, 37 insertions(+), 41 deletions(-) delete mode 100644 src/plugin/bridges/vst3-impls/component.h diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index 259ff23f..a37a39a0 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -34,7 +34,7 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { public: /** - * These are the arguments for creating a `YaPluginFactoryPluginImpl`. + * These are the arguments for creating a `YaPluginFactoryImpl`. */ struct ConstructArgs { ConstructArgs(); @@ -125,9 +125,9 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { YaPluginFactory(const ConstructArgs&& args); /** - * We do not need to implement the destructor in - * `YaPluginFactoryPluginImpl`, since when the sockets are closed, RAII will - * clean up the Windows VST3 module we loaded along with its factory for us. + * We do not need to implement the destructor in `YaPluginFactoryImpl`, + * since when the sockets are closed, RAII will clean up the Windows VST3 + * module we loaded along with its factory for us. */ virtual ~YaPluginFactory(); @@ -139,8 +139,7 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { tresult PLUGIN_API getClassInfo(Steinberg::int32 index, Steinberg::PClassInfo* info) override; /** - * See the implementation in `YaPluginFactoryPluginImpl` for how this is - * handled. + * See the implementation in `YaPluginFactoryImpl` for how this is handled. */ virtual tresult PLUGIN_API createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, diff --git a/src/plugin/bridges/vst3-impls/component.h b/src/plugin/bridges/vst3-impls/component.h deleted file mode 100644 index bb620bb3..00000000 --- a/src/plugin/bridges/vst3-impls/component.h +++ /dev/null @@ -1 +0,0 @@ -YaPluginProxyImplYaPluginProxyImplYaPluginProxyImpl diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index e79bb986..5744e35a 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -19,13 +19,13 @@ Vst3PluginProxyImpl::Vst3PluginProxyImpl(Vst3PluginBridge& bridge, Vst3PluginProxy::ConstructArgs&& args) : Vst3PluginProxy(std::move(args)), bridge(bridge) { - bridge.register_component(arguments.instance_id, *this); + bridge.register_plugin_proxy(*this); } Vst3PluginProxyImpl::~Vst3PluginProxyImpl() { bridge.send_message( Vst3PluginProxy::Destruct{.instance_id = arguments.instance_id}); - bridge.unregister_component(arguments.instance_id); + bridge.unregister_plugin_proxy(arguments.instance_id); } tresult PLUGIN_API diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index f7ec18c7..a6123023 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -39,6 +39,8 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, void** obj) override; + inline size_t instance_id() { return arguments.instance_id; } + // From `IAudioProcessor` tresult PLUGIN_API setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index b2c16499..5df60b9a 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -18,6 +18,7 @@ #include "src/common/serialization/vst3.h" #include "vst3-impls/plugin-factory.h" +#include "vst3-impls/plugin-proxy.h" // There are still some design decisions that need some more thought // TODO: Check whether `IPlugView::isPlatformTypeSupported` needs special @@ -114,14 +115,13 @@ Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { return plugin_factory; } -void Vst3PluginBridge::register_component(size_t instance_id, - Vst3PluginProxyImpl& component) { - std::lock_guard lock(component_instances_mutex); - component_instances.emplace(instance_id, - std::ref(component)); +void Vst3PluginBridge::register_plugin_proxy(Vst3PluginProxyImpl& component) { + std::lock_guard lock(plugin_proxies_mutex); + plugin_proxies.emplace(component.instance_id(), + std::ref(component)); } -void Vst3PluginBridge::unregister_component(size_t instance_id) { - std::lock_guard lock(component_instances_mutex); - component_instances.erase(instance_id); +void Vst3PluginBridge::unregister_plugin_proxy(size_t instance_id) { + std::lock_guard lock(plugin_proxies_mutex); + plugin_proxies.erase(instance_id); } diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index ad6fb009..c0ad49e7 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -73,34 +73,32 @@ class Vst3PluginBridge : PluginBridge> { Steinberg::IPluginFactory* get_plugin_factory(); /** - * Add a `YaComponentPluginImpl` to the list of registered components so we - * can handle host callbacks, called during its constructor. + * Add a `Vst3PluginProxyImpl` to the list of registered proxy objects so we + * can handle host callbacks. This function is called in + * `Vst3PluginProxyImpl`'s constructor. * * @param instance_id The instance ID generated by the plugin host when * instantiating the `IComponent`. Used as a stable name to refer to * these. - * @param component The actual component instance so we can use its host + * @param plugin_proxy The actual proxy object we can access its host * context. * - * @see component_instances - * - * TODO: REname to `register_instance` or `register_object` + * @see plugin_proxies */ - void register_component(size_t instance_id, Vst3PluginProxyImpl& component); + void register_plugin_proxy(Vst3PluginProxyImpl& proxy_object); /** - * Remove a previously registered `YaComponentPluginImpl` from the list of - * registered components so we can handle host callbacks, called during its - * destructor after asking the Wine plugin host to destroy the component on - * its side.. + * Remove a previously registered `Vst3PluginProxyImpl` from the list of + * registered proxy objects. Called during the object's destructor after + * asking the Wine plugin host to destroy the component on its side. * * @param instance_id The instance ID generated by the plugin host when * instantiating the `IComponent`. Used as a stable name to refer to * these. * - * @see component_instances + * @see plugin_proxies */ - void unregister_component(size_t instance_id); + void unregister_plugin_proxy(size_t instance_id); /** * Send a control message to the Wine plugin host return the response. This @@ -140,17 +138,15 @@ class Vst3PluginBridge : PluginBridge> { private: /** - * All `IComponent` instances we created from this plugin. We only need to - * keep track of them in case the 'real' `IComponent` instance tries to do a - * callback through the host context. We store the copy of the host context - * passed during `initialize()` in the `YaComponentPluginImpl`. The IDs here - * are the same IDs as generated by the Wine plugin host. We store raw - * pointers instead of smart pointers because this shouldn't affect the - * reference counting. An instance is added here through a call by - * `register_component()` in the constractor, and an instance is then - * removed through a call to `unregister_component()` in the destructor. + * All VST3 plugin objects we created from this plugin. We keep track of + * these in case the plugin does a host callback, so we can associate that + * call with the exact host context object passed to it during a call to + * `initialize()`. The IDs here are the same IDs as generated by the Wine + * plugin host. An instance is added here through a call by + * `register_plugin_proxy()` in the constractor, and an instance is then + * removed through a call to `unregister_plugin_proxy()` in the destructor. */ std::map> - component_instances; - std::mutex component_instances_mutex; + plugin_proxies; + std::mutex plugin_proxies_mutex; }; From a5834cd438fb1aeec1bbba56c3ea781608198446 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 13:47:43 +0100 Subject: [PATCH 231/456] Replace ComponentInstance with a generic variant This allows casting to supported interface from an FUnknown. --- src/wine-host/bridges/vst3.cpp | 81 ++++++++++++++++++---------------- src/wine-host/bridges/vst3.h | 44 +++++++++--------- 2 files changed, 66 insertions(+), 59 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 816746b9..147d8b05 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -22,13 +22,13 @@ #include "vst3-impls/host-application.h" -ComponentInstance::ComponentInstance() {} +PluginObject::PluginObject() {} -ComponentInstance::ComponentInstance( - Steinberg::IPtr component) - : component(component), - plugin_base(component), - audio_processor(component) {} +PluginObject::PluginObject(Steinberg::IPtr object) + : object(object), + audio_processor(object), + component(object), + plugin_base(object) {} Vst3Bridge::Vst3Bridge(MainContext& main_context, std::string plugin_dll_path, @@ -64,33 +64,36 @@ void Vst3Bridge::run() { -> Vst3PluginProxy::Construct::Response { Steinberg::TUID cid; std::copy(args.cid.begin(), args.cid.end(), cid); - Steinberg::IPtr component = + // TODO: Change this to allow creating different tyeps of + // objects + Steinberg::IPtr object = module->getFactory() .createInstance(cid); - if (component) { - std::lock_guard lock(component_instances_mutex); + if (object) { + std::lock_guard lock(object_instances_mutex); const size_t instance_id = generate_instance_id(); - component_instances[instance_id] = std::move(component); + object_instances[instance_id] = std::move(object); + // This is where the magic happens. Here we deduce which + // interfaces are supported by this object so we can create + // a one-to-one proxy of it. return Vst3PluginProxy::ConstructArgs( - component_instances[instance_id].component, - instance_id); + object_instances[instance_id].object, instance_id); } else { - // The actual result is lost here - return UniversalTResult(Steinberg::kNotImplemented); + return UniversalTResult(Steinberg::kResultFalse); } }, [&](const Vst3PluginProxy::Destruct& request) -> Vst3PluginProxy::Destruct::Response { - std::lock_guard lock(component_instances_mutex); - component_instances.erase(request.instance_id); + std::lock_guard lock(object_instances_mutex); + object_instances.erase(request.instance_id); return Ack{}; }, [&](YaAudioProcessor::SetBusArrangements& request) -> YaAudioProcessor::SetBusArrangements::Response { - return component_instances[request.instance_id] + return object_instances[request.instance_id] .audio_processor->setBusArrangements( request.inputs.data(), request.num_ins, request.outputs.data(), request.num_outs); @@ -98,7 +101,7 @@ void Vst3Bridge::run() { [&](YaAudioProcessor::GetBusArrangement& request) -> YaAudioProcessor::GetBusArrangement::Response { const tresult result = - component_instances[request.instance_id] + object_instances[request.instance_id] .audio_processor->getBusArrangement( request.dir, request.index, request.arr); @@ -107,29 +110,29 @@ void Vst3Bridge::run() { }, [&](const YaAudioProcessor::CanProcessSampleSize& request) -> YaAudioProcessor::CanProcessSampleSize::Response { - return component_instances[request.instance_id] + return object_instances[request.instance_id] .audio_processor->canProcessSampleSize( request.symbolic_sample_size); }, [&](const YaAudioProcessor::GetLatencySamples& request) -> YaAudioProcessor::GetLatencySamples::Response { - return component_instances[request.instance_id] + return object_instances[request.instance_id] .audio_processor->getLatencySamples(); }, [&](YaAudioProcessor::SetupProcessing& request) -> YaAudioProcessor::SetupProcessing::Response { - return component_instances[request.instance_id] + return object_instances[request.instance_id] .audio_processor->setupProcessing(request.setup); }, [&](const YaAudioProcessor::SetProcessing& request) -> YaAudioProcessor::SetProcessing::Response { - return component_instances[request.instance_id] + return object_instances[request.instance_id] .audio_processor->setProcessing(request.state); }, [&](YaAudioProcessor::Process& request) -> YaAudioProcessor::Process::Response { const tresult result = - component_instances[request.instance_id] + object_instances[request.instance_id] .audio_processor->process(request.data.get()); return YaAudioProcessor::ProcessResponse{ @@ -138,25 +141,24 @@ void Vst3Bridge::run() { }, [&](const YaAudioProcessor::GetTailSamples& request) -> YaAudioProcessor::GetTailSamples::Response { - return component_instances[request.instance_id] + return object_instances[request.instance_id] .audio_processor->getTailSamples(); }, [&](const YaComponent::SetIoMode& request) -> YaComponent::SetIoMode::Response { - return component_instances[request.instance_id] + return object_instances[request.instance_id] .component->setIoMode(request.mode); }, [&](const YaComponent::GetBusCount& request) -> YaComponent::GetBusCount::Response { - return component_instances[request.instance_id] + return object_instances[request.instance_id] .component->getBusCount(request.type, request.dir); }, [&](YaComponent::GetBusInfo& request) -> YaComponent::GetBusInfo::Response { const tresult result = - component_instances[request.instance_id] - .component->getBusInfo(request.type, request.dir, - request.index, request.bus); + object_instances[request.instance_id].component->getBusInfo( + request.type, request.dir, request.index, request.bus); return YaComponent::GetBusInfoResponse{ .result = result, .updated_bus = request.bus}; @@ -164,7 +166,7 @@ void Vst3Bridge::run() { [&](YaComponent::GetRoutingInfo& request) -> YaComponent::GetRoutingInfo::Response { const tresult result = - component_instances[request.instance_id] + object_instances[request.instance_id] .component->getRoutingInfo(request.in_info, request.out_info); @@ -175,25 +177,26 @@ void Vst3Bridge::run() { }, [&](const YaComponent::ActivateBus& request) -> YaComponent::ActivateBus::Response { - return component_instances[request.instance_id] + return object_instances[request.instance_id] .component->activateBus(request.type, request.dir, request.index, request.state); }, [&](const YaComponent::SetActive& request) -> YaComponent::SetActive::Response { - return component_instances[request.instance_id] + return object_instances[request.instance_id] .component->setActive(request.state); }, [&](YaComponent::SetState& request) -> YaComponent::SetState::Response { - return component_instances[request.instance_id] + return object_instances[request.instance_id] .component->setState(&request.state); }, [&](YaComponent::GetState& request) -> YaComponent::GetState::Response { VectorStream stream; - const tresult result = component_instances[request.instance_id] - .component->getState(&stream); + const tresult result = + object_instances[request.instance_id].component->getState( + &stream); return YaComponent::GetStateResponse{ .result = result, .updated_state = std::move(stream)}; @@ -206,21 +209,21 @@ void Vst3Bridge::run() { // TODO: This needs changing when we get to `Vst3HostProxy` Steinberg::FUnknown* context = nullptr; if (request.host_application_context_args) { - component_instances[request.instance_id] + object_instances[request.instance_id] .hsot_application_context = Steinberg::owned(new YaHostApplicationHostImpl( *this, std::move(*request.host_application_context_args))); - context = component_instances[request.instance_id] + context = object_instances[request.instance_id] .hsot_application_context; } - return component_instances[request.instance_id] + return object_instances[request.instance_id] .plugin_base->initialize(context); }, [&](const YaPluginBase::Terminate& request) -> YaPluginBase::Terminate::Response { - return component_instances[request.instance_id] + return object_instances[request.instance_id] .plugin_base->terminate(); }, [&](const YaPluginFactory::Construct&) diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 2dfa2c9a..0d4c4032 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -25,17 +25,17 @@ #include "common.h" /** - * A holder for an `IComponent` instance created from the factory along with any - * host context proxy objects belonging to it, and several predefined - * `FUnknownPtrs` so we don't have to do these dynamic casts all the times.. - * - * TODO: When implementing `IEditController`, change this to use `IPluginBase` - * as the base interface. + * A holder for plugin object instance created from the factory. This stores all + * relevant interface smart pointers to that object so we can handle control + * messages sent by the plugin without having to do these expensive casts all + * the time. This also stores any additional context data, such as the + * `IHostApplication` instance passed to the plugin during + * `IPluginBase::initialize()`. */ -struct ComponentInstance { - ComponentInstance(); +struct PluginObject { + PluginObject(); - ComponentInstance(Steinberg::IPtr component); + PluginObject(Steinberg::IPtr object); /** * If the host passes an `IHostApplication` during @@ -45,15 +45,16 @@ struct ComponentInstance { Steinberg::IPtr hsot_application_context; /** - * The `IComponent` instance we created. + * The base object we cast from. */ - Steinberg::IPtr component; + Steinberg::IPtr object; // All smart pointers below are created from `component`. They will be null // pointers if `component` did not implement the interface. - Steinberg::FUnknownPtr plugin_base; Steinberg::FUnknownPtr audio_processor; + Steinberg::FUnknownPtr component; + Steinberg::FUnknownPtr plugin_base; }; /** @@ -138,12 +139,15 @@ class Vst3Bridge : public HostBridge { */ Steinberg::IPtr plugin_factory_host_application_context; - // Below are managed instances we created for - // `IPluginFactory::createInstance()`. The keys in all of these maps are the - // unique identifiers we generated for them so we can identify specific - // instances. The mutexes are used for operations that insert or remove - // items, and not for regular access. - - std::map component_instances; - std::mutex component_instances_mutex; + /** + * These are all the objects we have created through the Windows VST3 + * plugins' plugin factory. The keys in all of these maps are the unique + * identifiers we generated for them so we can identify specific instances. + * During the proxy object's destructor (on the plugin side), we'll get a + * request to remove the corresponding plugin object from this map. This + * will cause all pointers to it to get dropped and the object to be cleaned + * up. + */ + std::map object_instances; + std::mutex object_instances_mutex; }; From f71c7a3895f1f490a4a0487c131af6fa45cb2d3e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 13:51:39 +0100 Subject: [PATCH 232/456] Move work dropping to Vst2PluginBridge destructor For consistency with `Vst3PluginBridge`. --- src/plugin/bridges/vst2.cpp | 22 +++++++++------------- src/plugin/bridges/vst2.h | 6 ++++++ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/plugin/bridges/vst2.cpp b/src/plugin/bridges/vst2.cpp index 54f394f0..842f3b2f 100644 --- a/src/plugin/bridges/vst2.cpp +++ b/src/plugin/bridges/vst2.cpp @@ -19,10 +19,6 @@ #include "../../common/communication/vst2.h" #include "../utils.h" -// I'd rather use std::filesystem instead, but Boost.Process depends on -// boost::filesystem -namespace fs = boost::filesystem; - intptr_t dispatch_proxy(AEffect*, int, int, intptr_t, void*, float); void process_proxy(AEffect*, float**, float**, int); void process_replacing_proxy(AEffect*, float**, float**, int); @@ -119,6 +115,15 @@ Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback) update_aeffect(plugin, initialized_plugin); } +Vst2PluginBridge::~Vst2PluginBridge() { + // Drop all work make sure all sockets are closed + plugin_host->terminate(); + + // The `stop()` method will cause the IO context to just drop all of its + // outstanding work immediately + io_context.stop(); +} + class DispatchDataConverter : DefaultDataConverter { public: DispatchDataConverter(std::vector& chunk_data, @@ -389,15 +394,6 @@ intptr_t Vst2PluginBridge::dispatch(AEffect* /*plugin*/, logger.log("The plugin crashed during shutdown, ignoring"); } - plugin_host->terminate(); - - // The `stop()` method will cause the IO context to just drop all of - // its work immediately and not throw any exceptions that would have - // been caused by pipes and sockets being closed. - io_context.stop(); - - // TODO: For consistency with the VST3 version, move the above to - // the destructor delete this; return return_value; diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index 0b7cb3c9..46415d8e 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -49,6 +49,12 @@ class Vst2PluginBridge : PluginBridge> { */ Vst2PluginBridge(audioMasterCallback host_callback); + /** + * Terminate the Wine plugin host process and drop all work when the module + * gets unloaded. + */ + ~Vst2PluginBridge(); + // The four below functions are the handlers from the VST2 API. They are // called through proxy functions in `plugin.cpp`. From 78f9203378fc2c7beb3e600064f99a8cd7bd6f2a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 14:20:49 +0100 Subject: [PATCH 233/456] Implement a UID formatting function --- src/common/logging/vst3.cpp | 5 +---- src/common/serialization/vst3/base.cpp | 17 +++++++++++++++++ src/common/serialization/vst3/base.h | 5 +++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 6d285400..7e02385a 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -24,10 +24,7 @@ void Vst3Logger::log_unknown_interface( const std::string& where, const std::optional& uid) { if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) { - char uid_string[128] = ""; - if (uid) { - uid->print(uid_string, Steinberg::FUID::UIDPrintStyle::kCLASS_UID); - } + std::string uid_string = uid ? format_uid(*uid) : ""; std::ostringstream message; message << "[unknown interface] " << where << ": " << uid_string; diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index d8f21041..a44c1719 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -15,10 +15,27 @@ // along with this program. If not, see . #include +#include +#include #include #include "base.h" +std::string format_uid(const Steinberg::FUID& uid) { + // This is the same as `FUID::print`, but without any macro prefixes + uint32 l1, l2, l3, l4; + uid.to4Int(l1, l2, l3, l4); + + std::ostringstream formatted_uid; + formatted_uid << std::hex << std::uppercase << "(0x" << std::setfill('0') + << std::setw(8) << l1 << ", 0x" << std::setfill('0') + << std::setw(8) << l2 << ", 0x" << std::setfill('0') + << std::setw(8) << l3 << ", 0x" << std::setfill('0') + << std::setw(8) << l4 << ")"; + + return formatted_uid.str(); +} + std::u16string tchar_pointer_to_u16string(const Steinberg::Vst::TChar* string) { #ifdef __WINE__ // This is great, thanks Steinberg diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 97504639..8ee5c8b5 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -53,6 +53,11 @@ constexpr size_t max_num_speakers = 16384; */ constexpr size_t max_vector_stream_size = 50 << 20; +/** + * Format a FUID as a simple hexadecimal four-tuple. + */ +std::string format_uid(const Steinberg::FUID& uid); + /** * Convert a UTF-16 C-style string to an `std::u16string`. Who event invented * UTF-16? From 1f5bd43fe803e6482d2200745811b2efa79319f1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 14:28:02 +0100 Subject: [PATCH 234/456] Print CIDs in IPluginBase::initialize() --- src/common/logging/vst3.cpp | 8 ++++---- src/common/serialization/vst3/base.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 7e02385a..3f511c1b 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -34,14 +34,14 @@ void Vst3Logger::log_unknown_interface( } void Vst3Logger::log_request(bool is_host_vst, - const Vst3PluginProxy::Construct&) { + const Vst3PluginProxy::Construct& args) { log_request_base(is_host_vst, [&](auto& message) { - // TODO: Log the CID on verbosity level 2, and then also report all CIDs - // in the plugin factory // TODO: When adding the enum class for instantiating different types, // make sure to reflect those in the constructor and destructor // logging - message << "IPluginFactory::createComponent(cid = ..., _iid = " + message << "IPluginFactory::createComponent(cid = " + << format_uid(Steinberg::FUID::fromTUID(args.cid.data())) + << ", _iid = " "IComponent::iid, " "&obj)"; }); diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index a44c1719..ef810b0a 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -27,11 +27,11 @@ std::string format_uid(const Steinberg::FUID& uid) { uid.to4Int(l1, l2, l3, l4); std::ostringstream formatted_uid; - formatted_uid << std::hex << std::uppercase << "(0x" << std::setfill('0') + formatted_uid << std::hex << std::uppercase << "{0x" << std::setfill('0') << std::setw(8) << l1 << ", 0x" << std::setfill('0') << std::setw(8) << l2 << ", 0x" << std::setfill('0') << std::setw(8) << l3 << ", 0x" << std::setfill('0') - << std::setw(8) << l4 << ")"; + << std::setw(8) << l4 << "}"; return formatted_uid.str(); } From 231a0293cb19e2b0b09885b5bb6bd9e75a5cd899 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 14:40:49 +0100 Subject: [PATCH 235/456] Change VST3 log message direction prefix It looks much mess noisy this way at the cost of maybe being slightly less clear at a glance. --- src/common/logging/vst3.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 36d04315..8360b828 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -122,7 +122,7 @@ class Vst3Logger { if (is_host_vst) { message << "[host -> vst] >> "; } else { - message << "[vst -> host] >> "; + message << "[host <- vst] >> "; } callback(message); @@ -142,7 +142,7 @@ class Vst3Logger { if (is_host_vst) { message << "[host -> vst] "; } else { - message << "[vst -> host] "; + message << "[host <- vst] "; } callback(message); From 1ce12227fbe1c380b692d3fb3b0456035903403c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 15:12:09 +0100 Subject: [PATCH 236/456] Add logging for IAudioProcessor::process() This is super verbose, but I'm sure it's going to be useful at some point. --- src/common/logging/vst3.cpp | 102 ++++++++++++++++-- src/common/logging/vst3.h | 25 +++-- src/common/serialization/vst3/event-list.cpp | 6 +- src/common/serialization/vst3/event-list.h | 5 + .../serialization/vst3/parameter-changes.cpp | 4 + .../serialization/vst3/parameter-changes.h | 6 ++ .../serialization/vst3/process-data.cpp | 5 + src/common/serialization/vst3/process-data.h | 7 +- 8 files changed, 140 insertions(+), 20 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 3f511c1b..55f9c115 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -119,12 +119,60 @@ void Vst3Logger::log_request(bool is_host_vst, void Vst3Logger::log_request(bool is_host_vst, const YaAudioProcessor::Process& request) { - // TODO: Only log this on log level 2 - log_request_base(is_host_vst, [&](auto& message) { - // TODO: Log about the process data - message << "::process(TODO)"; - }); + log_request_base( + is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { + // This is incredibly verbose, but if you're really a plugin that + // handles processing in a weird way you're going to need all of + // this + + std::ostringstream num_input_channels; + num_input_channels << "["; + for (bool is_first = true; + const auto& buffers : request.data.inputs) { + num_input_channels << (is_first ? "" : ", ") + << buffers.num_channels(); + is_first = false; + } + num_input_channels << "]"; + + std::ostringstream num_output_channels; + num_output_channels << "["; + for (bool is_first = true; + const auto& num_channels : request.data.outputs_num_channels) { + num_output_channels << (is_first ? "" : ", ") << num_channels; + is_first = false; + } + num_output_channels << "]"; + + message << "::process(data = , output_parameter_changes = " + << (request.data.output_parameter_changes_supported + ? "" + : "nullptr") + << ", input_events = "; + if (request.data.input_events) { + message << "num_events() + << " events>"; + } else { + message << "nullptr"; + } + message << ", output_events = " + << (request.data.output_events_supported ? "" + : "nullptr") + << ", process_context = " + << (request.data.process_context ? "" + : "nullptr") + << ", process_mode = " << request.data.process_mode + << ", symbolic_sample_size = " + << request.data.symbolic_sample_size << ">)"; + }); } void Vst3Logger::log_request(bool is_host_vst, @@ -284,12 +332,44 @@ void Vst3Logger::log_response( void Vst3Logger::log_response( bool is_host_vst, const YaAudioProcessor::ProcessResponse& response) { - // TODO: Only log this on verbosity level 2 - log_response_base(is_host_vst, [&](auto& message) { - message << response.result.string(); + log_response_base( + is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { + message << response.result.string(); - // TODO: Log response - }); + // This is incredibly verbose, but if you're really a plugin that + // handles processing in a weird way you're going to need all of + // this + + std::ostringstream num_output_channels; + num_output_channels << "["; + for (bool is_first = true; + const auto& buffers : response.output_data.outputs) { + num_output_channels << (is_first ? "" : ", ") + << buffers.num_channels(); + is_first = false; + } + num_output_channels << "]"; + + message << ""; + + if (response.output_data.output_parameter_changes) { + message << ", num_parameters() + << " parameters>"; + } else { + message << ", host does not support parameter outputs"; + } + + if (response.output_data.output_events) { + message << ", num_events() + << " events>"; + } else { + message << ", host does not support event outputs"; + } + }); } void Vst3Logger::log_response(bool is_host_vst, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 8360b828..2e23c85b 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -115,9 +115,10 @@ class Vst3Logger { * every logging function so we don't have to repeat it everywhere. */ template F> - void log_request_base(bool is_host_vst, F callback) { - if (BOOST_UNLIKELY(logger.verbosity >= - Logger::Verbosity::most_events)) { + void log_request_base(bool is_host_vst, + Logger::Verbosity min_verbosity, + F callback) { + if (BOOST_UNLIKELY(logger.verbosity >= min_verbosity)) { std::ostringstream message; if (is_host_vst) { message << "[host -> vst] >> "; @@ -130,14 +131,20 @@ class Vst3Logger { } } + template F> + void log_request_base(bool is_host_vst, F callback) { + log_request_base(is_host_vst, Logger::Verbosity::most_events, callback); + } + /** * Log a response with a standard prefix based on the boolean flag we pass * to every logging function so we don't have to repeat it everywhere. */ template F> - void log_response_base(bool is_host_vst, F callback) { - if (BOOST_UNLIKELY(logger.verbosity >= - Logger::Verbosity::most_events)) { + void log_response_base(bool is_host_vst, + Logger::Verbosity min_verbosity, + F callback) { + if (BOOST_UNLIKELY(logger.verbosity >= min_verbosity)) { std::ostringstream message; if (is_host_vst) { message << "[host -> vst] "; @@ -149,4 +156,10 @@ class Vst3Logger { log(message.str()); } } + + template F> + void log_response_base(bool is_host_vst, F callback) { + log_response_base(is_host_vst, Logger::Verbosity::most_events, + callback); + } }; diff --git a/src/common/serialization/vst3/event-list.cpp b/src/common/serialization/vst3/event-list.cpp index a60f7023..da882533 100644 --- a/src/common/serialization/vst3/event-list.cpp +++ b/src/common/serialization/vst3/event-list.cpp @@ -190,8 +190,10 @@ YaEventList::YaEventList(Steinberg::Vst::IEventList& event_list) { } } -YaEventList::~YaEventList() { - FUNKNOWN_DTOR +YaEventList::~YaEventList(){FUNKNOWN_DTOR} + +size_t YaEventList::num_events() const { + return events.size(); } void YaEventList::write_back_outputs( diff --git a/src/common/serialization/vst3/event-list.h b/src/common/serialization/vst3/event-list.h index 84ae6059..ce052bea 100644 --- a/src/common/serialization/vst3/event-list.h +++ b/src/common/serialization/vst3/event-list.h @@ -227,6 +227,11 @@ class YaEventList : public Steinberg::Vst::IEventList { DECLARE_FUNKNOWN_METHODS + /** + * Return the number of events we store. Used in debug logs. + */ + size_t num_events() const; + /** * Write these events an output events queue on the `ProcessData` object * provided by the host. diff --git a/src/common/serialization/vst3/parameter-changes.cpp b/src/common/serialization/vst3/parameter-changes.cpp index 44c4bb62..83b8ed50 100644 --- a/src/common/serialization/vst3/parameter-changes.cpp +++ b/src/common/serialization/vst3/parameter-changes.cpp @@ -41,6 +41,10 @@ IMPLEMENT_FUNKNOWN_METHODS(YaParameterChanges, Steinberg::Vst::IParameterChanges::iid) #pragma GCC diagnostic pop +size_t YaParameterChanges::num_parameters() const { + return queues.size(); +} + void YaParameterChanges::write_back_outputs( Steinberg::Vst::IParameterChanges& output_queues) const { for (auto& queue : queues) { diff --git a/src/common/serialization/vst3/parameter-changes.h b/src/common/serialization/vst3/parameter-changes.h index 600dd08b..83fc3a3e 100644 --- a/src/common/serialization/vst3/parameter-changes.h +++ b/src/common/serialization/vst3/parameter-changes.h @@ -45,6 +45,12 @@ class YaParameterChanges : public Steinberg::Vst::IParameterChanges { DECLARE_FUNKNOWN_METHODS + /** + * Return the number of parameter we have parameter change queues for. Used + * in debug logs. + */ + size_t num_parameters() const; + /** * Write these changes back to an output parameter changes queue on the * `ProcessData` object provided by the host. diff --git a/src/common/serialization/vst3/process-data.cpp b/src/common/serialization/vst3/process-data.cpp index b733ee4c..1e805495 100644 --- a/src/common/serialization/vst3/process-data.cpp +++ b/src/common/serialization/vst3/process-data.cpp @@ -92,6 +92,11 @@ Steinberg::Vst::AudioBusBuffers YaAudioBusBuffers::get() { return reconstructed_buffers; } +size_t YaAudioBusBuffers::num_channels() const { + return std::visit([&](const auto& buffers) { return buffers.size(); }, + buffers); +} + void YaAudioBusBuffers::write_back_outputs( Steinberg::Vst::AudioBusBuffers& output_buffers) const { output_buffers.silenceFlags = silence_flags; diff --git a/src/common/serialization/vst3/process-data.h b/src/common/serialization/vst3/process-data.h index 2a38052b..846244ea 100644 --- a/src/common/serialization/vst3/process-data.h +++ b/src/common/serialization/vst3/process-data.h @@ -72,6 +72,11 @@ class YaAudioBusBuffers { */ Steinberg::Vst::AudioBusBuffers get(); + /** + * Return the number of channels in `buffers`. Only used for debug logs. + */ + size_t num_channels() const; + /** * Write these buffers and the silence flag back to an `AudioBusBuffers * object provided by the host. @@ -203,7 +208,6 @@ class YaProcessData { // of the `output*` fields defined below it } - private: // These fields are input and context data read from the original // `ProcessData` object @@ -266,6 +270,7 @@ class YaProcessData { */ std::optional process_context; + private: // These are the same fields as in `YaProcessDataResponse`. We'll generate // these as part of creating `reconstructed_process_data`, and they will be // moved into a response object during `move_outputs_to_response()`. From 7a5de5d35ebe312c27c7eb11751b707785e937cc Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 15:43:13 +0100 Subject: [PATCH 237/456] Update the VST3 implementation documentation --- docs/vst3.md | 84 ++++++++++++++++++++++------------------------------ 1 file changed, 36 insertions(+), 48 deletions(-) diff --git a/docs/vst3.md b/docs/vst3.md index aa0930f0..6bd1fc5a 100644 --- a/docs/vst3.md +++ b/docs/vst3.md @@ -1,14 +1,9 @@ # VST3 serialization -TODO: Flesh this out further +TODO: Flesh this out further, update the instantiation part, make the proxying part clearer TODO: Link to `src/common/serialization/vst3/README.md` -TODO: Mention the new `Ya::supports()` mechanism for the monolithic proxy -objects through multiple inheritance - -TODO: Explain the monolith - The VST3 SDK uses an architecture where every concrete object inherits from an interface, and every interface inherits from `FUnknown`. `FUnkonwn` offers a dynamic casting interface through `queryInterface()` and a reference counting @@ -34,52 +29,45 @@ Yabridge's serialization and communication model for VST3 is thus a lot more complicated than for VST2 since all of these objects are loosely coupled and are instantiated and managed by the host. The basic model works as follows: -1. For an interface `IFoo`, we provide a possibly abstract implementation called - `YaFoo`. -2. When we want to _proxy_ an interface from one side to the other (let's assume - we want to allow the native VST3 host to call functions on the `IFoo` - provided by the Windows VST3 plugin), we need to provide a `YaFoo` - implementation on the native plugin side that can do callbacks to the - corresponding `IFoo` object in the Wine plugin host. For most objects, this - works by first generating a unique identifier to be able to refer to this - specific `IFoo` instance, and then serializing that identifier together with - any static payload data into a `YaFoo::ConstructArgs` object. This - `YaFoo::ConstructArgs` copies this data through a `IPtr` smart pointer - to the original object we're proxying. This object can be serialized and - transmitted to the other side using bitsery. -3. The original `IFoo` we are proxying gets added to an - `std::map>` (in our assumed scenario, this happens on the - Wine plugin host's side) with the key being that unique instance identifier - we generated so we can refer to it later on. -4. `YaFoo` implements all the boilerplate required for `FUnknown`. This includes - the constructor, destructor and methods required for reference counting, as - well as the query interface. It also implements any static lookup functions - that can be performed using the data contained in a `YaFoo::ConstructArgs` - object. Any functions that perform side effects or return dynamic data and - thus require a callback or control message are marked as pure virtual. These - callbacks can be performed through yabridge's `Vst3MessageHandler` message - handling interface. For the sake of clarity, we use the term _callback_ for - `plugin -> host` function calls and _control message_ for `host -> plugin` - function calls. -5. The side that requested the object (which we assume to be the native plugin - here), creates a _proxy object_ called `YaFoo{Plugin,Host}Impl`, so - `YaFooPluginImpl` in this case. This is an instance of `YaFoo` and thus - `IFoo`, so we can pass it as an `IFoo` pointer to the host. This object takes - those `YaFoo::ConstructArgs` and a reference to the bridge instance so it can - do callbacks or send control messages. -6. If `IFoo` is a versioned interface such as `IPluginFactory{,2,3}`, the +1. The main idea behind yabridge's VST3 implementation is that we define + monolithic proxy objects that can proxy any object created by the Windows + VST3 plugin. These proxy objects indirectly inherit from all applicable + interfaces defiend in the VST3 SDK. `Vst3PluginProxy` implements all + interfaces that can be implemented by plugins, and `Vst3HostProxy` implements + all interfaces that are to be implemented by the host. +2. For every interface `IFoo`, we provide an abstract implementation called + `YaFoo`. This implementation mostly contain message object we use to make + specific function calls on the actual objects we are proxying. The + implementation also comes with a function that takes an `FUnknown` pointer, + checks whether the object behind that pointer supports `IFoo`, and then + stores the result along with any potential static payload data as a + `YaFoo::ConstrctArgs` object. +3. Proxy object are instantiated while handling + `IPluginFactory::createInstance()` for `Vst3PluginProxy`, and during + `IPluginBase::initialize()` and `IPluginFactory::setHostContext()` for + `Vst3HostProxy`. On the receiving side of those functions (where we call the + actual function implemented by the plugin or the host), we receive an + `IPtr` smart pointer to an object provided by the host or the plugin. We + use this object to iterate over every applicable `YaFoo` as mentioend above. + All of these `YaFoo::ConstructArgs` objects along with a unique identifier + for this specific object are then serialized and transmitted to the other + side. With this information we can create a proxy object that supports all + the same interfaces (and thus allows calls to the functions in those + interfaces) as the original object we are proxying. +4. As mentioend, every object we instantiate gets assigned a unique identifier. + When dealign with objects created by the Windows VST3 plugin, the object's + `FUnknown` pointer will be stored in an `std::map` map. + This way we can refer to it later on when we receive a request to call a + specific function on the plugin. +5. If `IFoo` is a versioned interface such as `IPluginFactory{,2,3}`, the creation of `YaFoo::ConstrctArgs` and the definition of `YaFoo`'s query interface work slightly differently. When copying the data for a plugin factory, we'll start copying from `IPluginFactory`, and we'll copy data from each newer version of the interface that the `IPtr` supports. During this process we keep track of which interfaces were supported by the - native plugin in a `known_iids` set. In our query interface method we then - only report support for the same interfaces that were supported by the - original `IPtr` we are proxying. ## Interface Instantiation @@ -87,7 +75,7 @@ Creating a new instance of an interface using the plugin factory wroks as follows. This describes the object lifecycle. The actual serialization and proxying is described in the section above. -1. The host calls `createInterface(cid, _iid, obj)` on an IPluginFactory +1. The host calls `createInterface(cid, _iid, obj)` on an `IPluginFactory` implementation exposed to the host as described above. 2. We check which interface we support matches the `_iid`. If we don't support the interface, we'll log a message about it and return that we do not support From 2155240cca4cdcb3c5e3344a445b912ee0daffcd Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 16:39:50 +0100 Subject: [PATCH 238/456] Add stubs for IEditController{,2} --- meson.build | 2 + src/common/serialization/vst3/README.md | 1 + .../serialization/vst3/plugin-monolith.h | 1 - .../serialization/vst3/plugin-proxy.cpp | 16 ++- src/common/serialization/vst3/plugin-proxy.h | 4 + .../vst3/plugin/audio-processor.h | 2 +- .../serialization/vst3/plugin/component.h | 2 +- .../vst3/plugin/edit-controller.cpp | 29 ++++ .../vst3/plugin/edit-controller.h | 126 ++++++++++++++++++ .../serialization/vst3/plugin/plugin-base.h | 2 +- .../bridges/vst3-impls/plugin-proxy.cpp | 108 +++++++++++++++ src/plugin/bridges/vst3-impls/plugin-proxy.h | 41 ++++++ 12 files changed, 327 insertions(+), 7 deletions(-) delete mode 100644 src/common/serialization/vst3/plugin-monolith.h create mode 100644 src/common/serialization/vst3/plugin/edit-controller.cpp create mode 100644 src/common/serialization/vst3/plugin/edit-controller.h diff --git a/meson.build b/meson.build index 2a1b1b09..649acc47 100644 --- a/meson.build +++ b/meson.build @@ -79,6 +79,7 @@ vst3_plugin_sources = [ 'src/common/logging/vst3.cpp', 'src/common/serialization/vst3/plugin/audio-processor.cpp', 'src/common/serialization/vst3/plugin/component.cpp', + 'src/common/serialization/vst3/plugin/edit-controller.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/event-list.cpp', @@ -121,6 +122,7 @@ if with_vst3 'src/common/logging/vst3.cpp', 'src/common/serialization/vst3/plugin/audio-processor.cpp', 'src/common/serialization/vst3/plugin/component.cpp', + 'src/common/serialization/vst3/plugin/edit-controller.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/event-list.cpp', diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index add0c5b3..800bc2af 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -12,6 +12,7 @@ VST3 interfaces are implemented as follows: | `Vst3PluginProxy` | | All of the below: | | `YaAudioProcessor` | `Vst3PluginProxy` | `IAudioProcessor` | | `YaComponent` | `Vst3PluginProxy` | `IComponent` | +| `YaEditController` | `Vst3PluginProxy` | `IEditController`, `IEditController2` | | `YaPluginBase` | `Vst3PluginProxy` | `IPluginBase` | | `YaHostApplication` | | `iHostAPplication` | | `YaPluginFactory` | | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | diff --git a/src/common/serialization/vst3/plugin-monolith.h b/src/common/serialization/vst3/plugin-monolith.h deleted file mode 100644 index 49da2d38..00000000 --- a/src/common/serialization/vst3/plugin-monolith.h +++ /dev/null @@ -1 +0,0 @@ -Vst3PluginProxyVst3PluginProxyVst3PluginProxyVst3PluginProxy diff --git a/src/common/serialization/vst3/plugin-proxy.cpp b/src/common/serialization/vst3/plugin-proxy.cpp index a286abc5..db6b5746 100644 --- a/src/common/serialization/vst3/plugin-proxy.cpp +++ b/src/common/serialization/vst3/plugin-proxy.cpp @@ -24,11 +24,13 @@ Vst3PluginProxy::ConstructArgs::ConstructArgs( : instance_id(instance_id), audio_processor_args(object), component_args(object), + edit_controller_2_args(object), plugin_base_args(object) {} Vst3PluginProxy::Vst3PluginProxy(const ConstructArgs&& args) : YaAudioProcessor(std::move(args.audio_processor_args)), YaComponent(std::move(args.component_args)), + YaEditController2(std::move(args.edit_controller_2_args)), YaPluginBase(std::move(args.plugin_base_args)), arguments(std::move(args)){FUNKNOWN_CTOR} @@ -61,13 +63,21 @@ tresult PLUGIN_API Vst3PluginProxy::queryInterface(Steinberg::FIDString _iid, return ::Steinberg ::kResultOk; } } + if (YaAudioProcessor::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IAudioProcessor::iid, + Steinberg::Vst::IAudioProcessor) + } if (YaComponent::supported()) { QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, Steinberg::Vst::IComponent) } - if (YaAudioProcessor::supported()) { - QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IAudioProcessor::iid, - Steinberg::Vst::IAudioProcessor) + if (YaEditController2::supported_version_1()) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IEditController::iid, + Steinberg::Vst::IEditController) + } + if (YaEditController2::supported_version_2()) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IEditController2::iid, + Steinberg::Vst::IEditController2) } *obj = nullptr; diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index 2b0730f8..feae9d1c 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -24,6 +24,7 @@ #include "host-application.h" #include "plugin/audio-processor.h" #include "plugin/component.h" +#include "plugin/edit-controller.h" #include "plugin/plugin-base.h" #pragma GCC diagnostic push @@ -54,6 +55,7 @@ */ class Vst3PluginProxy : public YaAudioProcessor, public YaComponent, + public YaEditController2, public YaPluginBase { public: /** @@ -75,12 +77,14 @@ class Vst3PluginProxy : public YaAudioProcessor, YaAudioProcessor::ConstructArgs audio_processor_args; YaComponent::ConstructArgs component_args; + YaEditController2::ConstructArgs edit_controller_2_args; YaPluginBase::ConstructArgs plugin_base_args; template void serialize(S& s) { s.value8b(instance_id); s.object(audio_processor_args); + s.object(edit_controller_2_args); s.object(component_args); s.object(plugin_base_args); } diff --git a/src/common/serialization/vst3/plugin/audio-processor.h b/src/common/serialization/vst3/plugin/audio-processor.h index 88deb6d7..7f15cbbf 100644 --- a/src/common/serialization/vst3/plugin/audio-processor.h +++ b/src/common/serialization/vst3/plugin/audio-processor.h @@ -62,7 +62,7 @@ class YaAudioProcessor : public Steinberg::Vst::IAudioProcessor { */ YaAudioProcessor(const ConstructArgs&& args); - inline bool supported() { return arguments.supported; } + inline bool supported() const { return arguments.supported; } /** * Message to pass through a call to diff --git a/src/common/serialization/vst3/plugin/component.h b/src/common/serialization/vst3/plugin/component.h index 8700e81f..6fb190bd 100644 --- a/src/common/serialization/vst3/plugin/component.h +++ b/src/common/serialization/vst3/plugin/component.h @@ -72,7 +72,7 @@ class YaComponent : public Steinberg::Vst::IComponent { */ YaComponent(const ConstructArgs&& args); - inline bool supported() { return arguments.supported; } + inline bool supported() const { return arguments.supported; } tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override; diff --git a/src/common/serialization/vst3/plugin/edit-controller.cpp b/src/common/serialization/vst3/plugin/edit-controller.cpp new file mode 100644 index 00000000..720cabac --- /dev/null +++ b/src/common/serialization/vst3/plugin/edit-controller.cpp @@ -0,0 +1,29 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "edit-controller.h" + +YaEditController2::ConstructArgs::ConstructArgs() {} + +YaEditController2::ConstructArgs::ConstructArgs( + Steinberg::IPtr object) + : supported_version_1( + Steinberg::FUnknownPtr(object)), + supported_version_2( + Steinberg::FUnknownPtr(object)) {} + +YaEditController2::YaEditController2(const ConstructArgs&& args) + : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin/edit-controller.h b/src/common/serialization/vst3/plugin/edit-controller.h new file mode 100644 index 00000000..2a9e42d9 --- /dev/null +++ b/src/common/serialization/vst3/plugin/edit-controller.h @@ -0,0 +1,126 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "../../common.h" +#include "../base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IEditController{,2}` for serialization purposes. This is + * instantiated as part of `Vst3PluginProxy`. + * + * Steinberg forgot to inherit `IEditController2` from `IEditController` event + * if it says it does in the docs, so we'll pretend they just that. + */ +class YaEditController2 : public Steinberg::Vst::IEditController, + public Steinberg::Vst::IEditController2 { + public: + /** + * These are the arguments for creating a `YaEditController2`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Check whether an existing implementation implements `IEditController` + * and `IEditController2` and read arguments from it. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported `IEditController`. + */ + bool supported_version_1; + + /** + * Whether the object supported `IEditController2`. + */ + bool supported_version_2; + + template + void serialize(S& s) { + s.value1b(supported_version_1); + s.value1b(supported_version_2); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaEditController2(const ConstructArgs&& args); + + inline bool supported_version_1() const { + return arguments.supported_version_1; + } + inline bool supported_version_2() const { + return arguments.supported_version_2; + } + + // From `IEditController` + + virtual tresult PLUGIN_API + setComponentState(Steinberg::IBStream* state) override = 0; + virtual tresult PLUGIN_API + setState(Steinberg::IBStream* state) override = 0; + virtual tresult PLUGIN_API + getState(Steinberg::IBStream* state) override = 0; + virtual int32 PLUGIN_API getParameterCount() override = 0; + virtual tresult PLUGIN_API + getParameterInfo(int32 paramIndex, + Steinberg::Vst::ParameterInfo& info /*out*/) override = 0; + virtual tresult PLUGIN_API getParamStringByValue( + Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue valueNormalized /*in*/, + Steinberg::Vst::String128 string /*out*/) override = 0; + virtual tresult PLUGIN_API getParamValueByString( + Steinberg::Vst::ParamID id, + Steinberg::Vst::TChar* string /*in*/, + Steinberg::Vst::ParamValue& valueNormalized /*out*/) override = 0; + virtual Steinberg::Vst::ParamValue PLUGIN_API normalizedParamToPlain( + Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue valueNormalized) override = 0; + virtual Steinberg::Vst::ParamValue PLUGIN_API + plainParamToNormalized(Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue plainValue) override = 0; + virtual Steinberg::Vst::ParamValue PLUGIN_API + getParamNormalized(Steinberg::Vst::ParamID id) override = 0; + virtual tresult PLUGIN_API + setParamNormalized(Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue value) override = 0; + virtual tresult PLUGIN_API setComponentHandler( + Steinberg::Vst::IComponentHandler* handler) override = 0; + virtual Steinberg::IPlugView* PLUGIN_API + createView(Steinberg::FIDString name) override = 0; + + // From `IEditController2` + + virtual tresult PLUGIN_API + setKnobMode(Steinberg::Vst::KnobMode mode) override = 0; + virtual tresult PLUGIN_API openHelp(TBool onlyCheck) override = 0; + virtual tresult PLUGIN_API openAboutBox(TBool onlyCheck) override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop diff --git a/src/common/serialization/vst3/plugin/plugin-base.h b/src/common/serialization/vst3/plugin/plugin-base.h index 3ad5f118..4e44161b 100644 --- a/src/common/serialization/vst3/plugin/plugin-base.h +++ b/src/common/serialization/vst3/plugin/plugin-base.h @@ -62,7 +62,7 @@ class YaPluginBase : public Steinberg::IPluginBase { */ YaPluginBase(const ConstructArgs&& args); - inline bool supported() { return arguments.supported; } + inline bool supported() const { return arguments.supported; } /** * Message to pass through a call to `IPluginBase::initialize()` to the Wine diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 5744e35a..7a36cee6 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -184,6 +184,114 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getState(Steinberg::IBStream* state) { return response.result; } +// FIXME: Fix `{set,get}State()` for `IEditController` as mentioned in the +// header + +tresult PLUGIN_API +Vst3PluginProxyImpl::setComponentState(Steinberg::IBStream* state) { + // TODO: Implement + bridge.logger.log("TODO IEditController::setComponentState()"); + return Steinberg::kNotImplemented; +} + +int32 PLUGIN_API Vst3PluginProxyImpl::getParameterCount() { + // TODO: Implement + bridge.logger.log("TODO IEditController::getParameterCount()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PluginProxyImpl::getParameterInfo( + int32 paramIndex, + Steinberg::Vst::ParameterInfo& info /*out*/) { + // TODO: Implement + bridge.logger.log("TODO IEditController::getParameterInfo()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PluginProxyImpl::getParamStringByValue( + Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue valueNormalized /*in*/, + Steinberg::Vst::String128 string /*out*/) { + // TODO: Implement + bridge.logger.log("TODO IEditController::getParamStringByValue()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PluginProxyImpl::getParamValueByString( + Steinberg::Vst::ParamID id, + Steinberg::Vst::TChar* string /*in*/, + Steinberg::Vst::ParamValue& valueNormalized /*out*/) { + // TODO: Implement + bridge.logger.log("TODO IEditController::getParamValueByString()"); + return Steinberg::kNotImplemented; +} + +Steinberg::Vst::ParamValue PLUGIN_API +Vst3PluginProxyImpl::normalizedParamToPlain( + Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue valueNormalized) { + // TODO: Implement + bridge.logger.log("TODO IEditController::normalizedParamToPlain()"); + return Steinberg::kNotImplemented; +} + +Steinberg::Vst::ParamValue PLUGIN_API +Vst3PluginProxyImpl::plainParamToNormalized( + Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue plainValue) { + // TODO: Implement + bridge.logger.log("TODO IEditController::plainParamToNormalized()"); + return Steinberg::kNotImplemented; +} + +Steinberg::Vst::ParamValue PLUGIN_API +Vst3PluginProxyImpl::getParamNormalized(Steinberg::Vst::ParamID id) { + // TODO: Implement + bridge.logger.log("TODO IEditController::getParamNormalized()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::setParamNormalized(Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue value) { + // TODO: Implement + bridge.logger.log("TODO IEditController::setParamNormalized()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PluginProxyImpl::setComponentHandler( + Steinberg::Vst::IComponentHandler* handler) { + // TODO: Implement + bridge.logger.log("TODO IEditController::setComponentHandler()"); + return Steinberg::kNotImplemented; +} + +Steinberg::IPlugView* PLUGIN_API +Vst3PluginProxyImpl::createView(Steinberg::FIDString name) { + // TODO: Implement + bridge.logger.log("TODO IEditController::createView()"); + return nullptr; +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::setKnobMode(Steinberg::Vst::KnobMode mode) { + // TODO: Implement + bridge.logger.log("TODO IEditController2::setKnobMode()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PluginProxyImpl::openHelp(TBool onlyCheck) { + // TODO: Implement + bridge.logger.log("TODO IEditController2::openHelp()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PluginProxyImpl::openAboutBox(TBool onlyCheck) { + // TODO: Implement + bridge.logger.log("TODO IEditController2::openAboutBox()"); + return Steinberg::kNotImplemented; +} + tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* context) { // This `context` will likely be an `IHostApplication`. If it is, we will // store it here, and we'll proxy through all calls to it made from the Wine diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index a6123023..a8df71c6 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -79,6 +79,47 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { tresult PLUGIN_API setState(Steinberg::IBStream* state) override; tresult PLUGIN_API getState(Steinberg::IBStream* state) override; + // From `IEditController` + tresult PLUGIN_API setComponentState(Steinberg::IBStream* state) override; + // FIXME: These are duplicate, we need to change the implementation to call + // this on either `object_instances[instance_id].component` or + // `object_instances[instance_id].edit_controller` depending on which + // one exists. + // tresult PLUGIN_API setState(Steinberg::IBStream* state) override; + // tresult PLUGIN_API getState(Steinberg::IBStream* state) override; + int32 PLUGIN_API getParameterCount() override; + tresult PLUGIN_API + getParameterInfo(int32 paramIndex, + Steinberg::Vst::ParameterInfo& info /*out*/) override; + tresult PLUGIN_API + getParamStringByValue(Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue valueNormalized /*in*/, + Steinberg::Vst::String128 string /*out*/) override; + tresult PLUGIN_API getParamValueByString( + Steinberg::Vst::ParamID id, + Steinberg::Vst::TChar* string /*in*/, + Steinberg::Vst::ParamValue& valueNormalized /*out*/) override; + Steinberg::Vst::ParamValue PLUGIN_API + normalizedParamToPlain(Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue valueNormalized) override; + Steinberg::Vst::ParamValue PLUGIN_API + plainParamToNormalized(Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue plainValue) override; + Steinberg::Vst::ParamValue PLUGIN_API + getParamNormalized(Steinberg::Vst::ParamID id) override; + tresult PLUGIN_API + setParamNormalized(Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue value) override; + tresult PLUGIN_API + setComponentHandler(Steinberg::Vst::IComponentHandler* handler) override; + Steinberg::IPlugView* PLUGIN_API + createView(Steinberg::FIDString name) override; + + // From `IEditController2` + tresult PLUGIN_API setKnobMode(Steinberg::Vst::KnobMode mode) override; + tresult PLUGIN_API openHelp(TBool onlyCheck) override; + tresult PLUGIN_API openAboutBox(TBool onlyCheck) override; + // From `IPluginBase` tresult PLUGIN_API initialize(FUnknown* context) override; tresult PLUGIN_API terminate() override; From 286023bc224d95f00cb77629ced68c444f0db487 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 17:01:26 +0100 Subject: [PATCH 239/456] Allow creating IEditController instances Now `IPluginFactory::createInstance()` can create multiple (and in our case, all relevant) different types of objects. --- src/common/logging/vst3.cpp | 25 +++++---- src/common/serialization/vst3/plugin-proxy.h | 17 ++++-- .../bridges/vst3-impls/plugin-factory.cpp | 56 +++++++++++++------ src/wine-host/bridges/vst3.cpp | 28 +++++++--- 4 files changed, 88 insertions(+), 38 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 55f9c115..a84fbd4f 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -34,24 +34,29 @@ void Vst3Logger::log_unknown_interface( } void Vst3Logger::log_request(bool is_host_vst, - const Vst3PluginProxy::Construct& args) { + const Vst3PluginProxy::Construct& request) { log_request_base(is_host_vst, [&](auto& message) { - // TODO: When adding the enum class for instantiating different types, - // make sure to reflect those in the constructor and destructor - // logging message << "IPluginFactory::createComponent(cid = " - << format_uid(Steinberg::FUID::fromTUID(args.cid.data())) - << ", _iid = " - "IComponent::iid, " - "&obj)"; + << format_uid(Steinberg::FUID::fromTUID(request.cid.data())) + << ", _iid = "; + switch (request.requested_interface) { + case Vst3PluginProxy::Construct::Interface::IComponent: + message << "IComponent::iid"; + break; + case Vst3PluginProxy::Construct::Interface::IEditController: + message << "IEditController::iid"; + break; + } + message << ", &obj)"; }); } void Vst3Logger::log_request(bool is_host_vst, const Vst3PluginProxy::Destruct& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::~IComponent()"; + // We don't know what class this instance was originally instantiated + // as, but it also doesn't really matter + message << "::~FUnknown()"; }); } diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index feae9d1c..4bd14240 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -93,20 +93,29 @@ class Vst3PluginProxy : public YaAudioProcessor, /** * Message to request the Wine plugin host to instantiate a new IComponent * to pass through a call to `IComponent::createInstance(cid, - * IComponent::iid, ...)`. + * ::iid, ...)`. */ struct Construct { using Response = std::variant; ArrayUID cid; - // TODO: Add an enum class to reify the type of object we want to - // instantiate so we can initialize things other than - // `IComponent`, like `IEditController.` + /** + * The interface the host was trying to instantiate an object for. + * Technically the host can create any kind of object, but these are the + * objects that are actually used. + */ + enum class Interface { + IComponent, + IEditController, + }; + + Interface requested_interface; template void serialize(S& s) { s.container1b(cid); + s.value4b(requested_interface); } }; diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 06a8319f..359fb740 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -28,26 +28,16 @@ tresult PLUGIN_API YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, void** obj) { - // TODO: Do the same thing for other types - - // These arw pointers are scary. The idea here is that we return a newly - // initialized object (that initializes itself with a reference count of 1), - // and then the receiving side will use `Steinberg::owned()` to adopt it to - // an `IPtr`. ArrayUID cid_array; std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); + + Vst3PluginProxy::Construct::Interface requested_interface; if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { - std::variant result = - bridge.send_message(Vst3PluginProxy::Construct{.cid = cid_array}); - return std::visit( - overload{ - [&](Vst3PluginProxy::ConstructArgs&& args) -> tresult { - *obj = static_cast( - new Vst3PluginProxyImpl(bridge, std::move(args))); - return Steinberg::kResultOk; - }, - [&](const UniversalTResult& code) -> tresult { return code; }}, - std::move(result)); + requested_interface = Vst3PluginProxy::Construct::Interface::IComponent; + } else if (Steinberg::FIDStringsEqual( + _iid, Steinberg::Vst::IEditController::iid)) { + requested_interface = + Vst3PluginProxy::Construct::Interface::IEditController; } else { // When the host requests an interface we do not (yet) implement, we'll // print a recognizable log message. I don't think they include a safe @@ -65,6 +55,38 @@ YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, return Steinberg::kNotImplemented; } + + std::variant result = + bridge.send_message(Vst3PluginProxy::Construct{ + .cid = cid_array, .requested_interface = requested_interface}); + + return std::visit( + overload{ + [&](Vst3PluginProxy::ConstructArgs&& args) -> tresult { + // These pointers are scary. The idea here is that we return a + // newly initialized object (that initializes itself with a + // reference count of 1), and then the receiving side will use + // `Steinberg::owned()` to adopt it to an `IPtr`. + Vst3PluginProxyImpl* proxy_object = + new Vst3PluginProxyImpl(bridge, std::move(args)); + + // We return a properly downcasted version of the proxy object + // we just created + switch (requested_interface) { + case Vst3PluginProxy::Construct::Interface::IComponent: + *obj = static_cast( + proxy_object); + break; + case Vst3PluginProxy::Construct::Interface::IEditController: + *obj = static_cast( + proxy_object); + break; + } + + return Steinberg::kResultOk; + }, + [&](const UniversalTResult& code) -> tresult { return code; }}, + std::move(result)); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 147d8b05..9a2062ee 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -60,15 +60,29 @@ void Vst3Bridge::run() { sockets.host_vst_control.receive_messages( std::nullopt, overload{ - [&](const Vst3PluginProxy::Construct& args) + [&](const Vst3PluginProxy::Construct& request) -> Vst3PluginProxy::Construct::Response { Steinberg::TUID cid; - std::copy(args.cid.begin(), args.cid.end(), cid); - // TODO: Change this to allow creating different tyeps of - // objects - Steinberg::IPtr object = - module->getFactory() - .createInstance(cid); + std::copy(request.cid.begin(), request.cid.end(), cid); + + // Even though we're requesting a specific interface (to mimic + // what the host is doing), we're immediately upcasting it to an + // `FUnknown` so we can create a perfect proxy object. + Steinberg::IPtr object; + switch (request.requested_interface) { + case Vst3PluginProxy::Construct::Interface::IComponent: + object = + module->getFactory() + .createInstance( + cid); + break; + case Vst3PluginProxy::Construct::Interface::IEditController: + object = module->getFactory() + .createInstance< + Steinberg::Vst::IEditController>(cid); + break; + } + if (object) { std::lock_guard lock(object_instances_mutex); From d8694b062b263578331e850cfb83659a178c1661 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 17:51:47 +0100 Subject: [PATCH 240/456] Add IEditController to the PluginObject interfaces --- src/wine-host/bridges/vst3.cpp | 1 + src/wine-host/bridges/vst3.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 9a2062ee..ec3967c2 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -28,6 +28,7 @@ PluginObject::PluginObject(Steinberg::IPtr object) : object(object), audio_processor(object), component(object), + edit_controller(object), plugin_base(object) {} Vst3Bridge::Vst3Bridge(MainContext& main_context, diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 0d4c4032..508e2f01 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -54,6 +54,7 @@ struct PluginObject { Steinberg::FUnknownPtr audio_processor; Steinberg::FUnknownPtr component; + Steinberg::FUnknownPtr edit_controller; Steinberg::FUnknownPtr plugin_base; }; From fe2de8de8de71440a08b96152b9c28555c06ad87 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 17:52:51 +0100 Subject: [PATCH 241/456] Unify handling for *::{get,set}State Since these functions are exactly the same, and for whatever reason they didn't just add them to the `IPluginBase` both `IComponent` and `IEditController`. inherit from --- src/common/logging/vst3.cpp | 57 ++++++++++--------- src/common/logging/vst3.h | 7 ++- src/common/serialization/vst3.h | 4 +- src/common/serialization/vst3/plugin-proxy.h | 52 +++++++++++++++++ .../serialization/vst3/plugin/component.h | 53 ++--------------- .../vst3/plugin/edit-controller.h | 6 ++ .../bridges/vst3-impls/plugin-proxy.cpp | 11 ++-- src/plugin/bridges/vst3-impls/plugin-proxy.h | 9 +-- src/wine-host/bridges/vst3.cpp | 45 ++++++++++----- 9 files changed, 136 insertions(+), 108 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index a84fbd4f..3abd4cec 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -60,6 +60,23 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const Vst3PluginProxy::SetState& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "<{IComponent,IEditController}* #" << request.instance_id + << ">::setState(state = )"; + }); +} + +void Vst3Logger::log_request(bool is_host_vst, + const Vst3PluginProxy::GetState& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "<{IComponent,IEditController}* #" << request.instance_id + << ">::getState(state = )"; + }); +} + void Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { @@ -247,23 +264,6 @@ void Vst3Logger::log_request(bool is_host_vst, }); } -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::SetState& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::setState(state = )"; - }); -} - -void Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetState& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << "::getState(state = )"; - }); -} - void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { @@ -323,6 +323,18 @@ void Vst3Logger::log_response(bool is_host_vst, }); } +void Vst3Logger::log_response( + bool is_host_vst, + const Vst3PluginProxy::GetStateResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", "; + } + }); +} + void Vst3Logger::log_response( bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse& response) { @@ -403,17 +415,6 @@ void Vst3Logger::log_response( }); } -void Vst3Logger::log_response(bool is_host_vst, - const YaComponent::GetStateResponse& response) { - log_response_base(is_host_vst, [&](auto& message) { - message << response.result.string(); - if (response.result == Steinberg::kResultOk) { - message << ", "; - } - }); -} - void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs& args) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 2e23c85b..65c3ff4a 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -58,6 +58,8 @@ class Vst3Logger { void log_request(bool is_host_vst, const Vst3PluginProxy::Construct&); void log_request(bool is_host_vst, const Vst3PluginProxy::Destruct&); + void log_request(bool is_host_vst, const Vst3PluginProxy::SetState&); + void log_request(bool is_host_vst, const Vst3PluginProxy::GetState&); void log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); void log_request(bool is_host_vst, @@ -77,8 +79,6 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::GetRoutingInfo&); void log_request(bool is_host_vst, const YaComponent::ActivateBus&); void log_request(bool is_host_vst, const YaComponent::SetActive&); - void log_request(bool is_host_vst, const YaComponent::SetState&); - void log_request(bool is_host_vst, const YaComponent::GetState&); void log_request(bool is_host_vst, const YaPluginBase::Initialize&); void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); @@ -89,6 +89,8 @@ class Vst3Logger { void log_response( bool is_host_vst, const std::variant&); + void log_response(bool is_host_vst, + const Vst3PluginProxy::GetStateResponse&); void log_response(bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse&); void log_response(bool is_host_vst, @@ -96,7 +98,6 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse&); void log_response(bool is_host_vst, const YaComponent::GetRoutingInfoResponse&); - void log_response(bool is_host_vst, const YaComponent::GetStateResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index df052f14..5cb7549f 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -59,6 +59,8 @@ struct WantsConfiguration { */ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(instance_id); + s.object(state); + } + }; + + /** + * The response code and written state for a call to + * `{IComponent,IEditController}::getState(state)`. + */ + struct GetStateResponse { + UniversalTResult result; + VectorStream updated_state; + + template + void serialize(S& s) { + s.object(result); + s.object(updated_state); + } + }; + + /** + * Message to pass through a call to + * `{IComponent,IEditController}::getState(state)` to the Wine plugin host. + */ + struct GetState { + using Response = GetStateResponse; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + protected: ConstructArgs arguments; }; diff --git a/src/common/serialization/vst3/plugin/component.h b/src/common/serialization/vst3/plugin/component.h index 6fb190bd..60b73b7a 100644 --- a/src/common/serialization/vst3/plugin/component.h +++ b/src/common/serialization/vst3/plugin/component.h @@ -255,57 +255,12 @@ class YaComponent : public Steinberg::Vst::IComponent { virtual tresult PLUGIN_API setActive(TBool state) override = 0; - /** - * Message to pass through a call to `IComponent::setState(state)` to the - * Wine plugin host. - */ - struct SetState { - using Response = UniversalTResult; - - native_size_t instance_id; - - VectorStream state; - - template - void serialize(S& s) { - s.value8b(instance_id); - s.object(state); - } - }; - + // `setState()` and `getState()` are defiend in both `IComponent` and + // `IEditController`. Since an object can only ever implement one or the + // other, the messages for calling either are defined directly on + // `Vst3PluginProxy`. virtual tresult PLUGIN_API setState(Steinberg::IBStream* state) override = 0; - - /** - * The response code and written state for a call to - * `IComponent::getState(state)`. - */ - struct GetStateResponse { - UniversalTResult result; - VectorStream updated_state; - - template - void serialize(S& s) { - s.object(result); - s.object(updated_state); - } - }; - - /** - * Message to pass through a call to `IComponent::getState(state)` to the - * Wine plugin host. - */ - struct GetState { - using Response = GetStateResponse; - - native_size_t instance_id; - - template - void serialize(S& s) { - s.value8b(instance_id); - } - }; - virtual tresult PLUGIN_API getState(Steinberg::IBStream* state) override = 0; diff --git a/src/common/serialization/vst3/plugin/edit-controller.h b/src/common/serialization/vst3/plugin/edit-controller.h index 2a9e42d9..df549cf7 100644 --- a/src/common/serialization/vst3/plugin/edit-controller.h +++ b/src/common/serialization/vst3/plugin/edit-controller.h @@ -80,10 +80,16 @@ class YaEditController2 : public Steinberg::Vst::IEditController, virtual tresult PLUGIN_API setComponentState(Steinberg::IBStream* state) override = 0; + + // `setState()` and `getState()` are defiend in both `IComponent` and + // `IEditController`. Since an object can only ever implement one or the + // other, the messages for calling either are defined directly on + // `Vst3PluginProxy`. virtual tresult PLUGIN_API setState(Steinberg::IBStream* state) override = 0; virtual tresult PLUGIN_API getState(Steinberg::IBStream* state) override = 0; + virtual int32 PLUGIN_API getParameterCount() override = 0; virtual tresult PLUGIN_API getParameterInfo(int32 paramIndex, diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 7a36cee6..00c7944d 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -171,22 +171,23 @@ tresult PLUGIN_API Vst3PluginProxyImpl::setActive(TBool state) { } tresult PLUGIN_API Vst3PluginProxyImpl::setState(Steinberg::IBStream* state) { - return bridge.send_message(YaComponent::SetState{ + // Since both interfaces contain this function, this is used for both + // `IComponent::setState()` as well as `IEditController::setState()` + return bridge.send_message(Vst3PluginProxy::SetState{ .instance_id = arguments.instance_id, .state = state}); } tresult PLUGIN_API Vst3PluginProxyImpl::getState(Steinberg::IBStream* state) { + // Since both interfaces contain this function, this is used for both + // `IComponent::getState()` as well as `IEditController::getState()` const GetStateResponse response = bridge.send_message( - YaComponent::GetState{.instance_id = arguments.instance_id}); + Vst3PluginProxy::GetState{.instance_id = arguments.instance_id}); assert(response.updated_state.write_back(state) == Steinberg::kResultOk); return response.result; } -// FIXME: Fix `{set,get}State()` for `IEditController` as mentioned in the -// header - tresult PLUGIN_API Vst3PluginProxyImpl::setComponentState(Steinberg::IBStream* state) { // TODO: Implement diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index a8df71c6..964460c8 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -81,12 +81,9 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { // From `IEditController` tresult PLUGIN_API setComponentState(Steinberg::IBStream* state) override; - // FIXME: These are duplicate, we need to change the implementation to call - // this on either `object_instances[instance_id].component` or - // `object_instances[instance_id].edit_controller` depending on which - // one exists. - // tresult PLUGIN_API setState(Steinberg::IBStream* state) override; - // tresult PLUGIN_API getState(Steinberg::IBStream* state) override; + // `IEditController` also contains `getState()` and `setState()` functions. + // These are identical to those defiend in `IComponent` and they're thus + // handled in in the same function. int32 PLUGIN_API getParameterCount() override; tresult PLUGIN_API getParameterInfo(int32 paramIndex, diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index ec3967c2..a65784f7 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -106,6 +106,36 @@ void Vst3Bridge::run() { return Ack{}; }, + [&](Vst3PluginProxy::SetState& request) + -> Vst3PluginProxy::SetState::Response { + // This same function is defined in both `IComponent` and + // `IEditController`, so the host is calling one or the other + if (object_instances[request.instance_id].component) { + return object_instances[request.instance_id] + .component->setState(&request.state); + } else { + return object_instances[request.instance_id] + .edit_controller->setState(&request.state); + } + }, + [&](Vst3PluginProxy::GetState& request) + -> Vst3PluginProxy::GetState::Response { + VectorStream stream; + tresult result; + + // This same function is defined in both `IComponent` and + // `IEditController`, so the host is calling one or the other + if (object_instances[request.instance_id].component) { + result = object_instances[request.instance_id] + .component->getState(&stream); + } else { + result = object_instances[request.instance_id] + .edit_controller->getState(&stream); + } + + return Vst3PluginProxy::GetStateResponse{ + .result = result, .updated_state = std::move(stream)}; + }, [&](YaAudioProcessor::SetBusArrangements& request) -> YaAudioProcessor::SetBusArrangements::Response { return object_instances[request.instance_id] @@ -201,21 +231,6 @@ void Vst3Bridge::run() { return object_instances[request.instance_id] .component->setActive(request.state); }, - [&](YaComponent::SetState& request) - -> YaComponent::SetState::Response { - return object_instances[request.instance_id] - .component->setState(&request.state); - }, - [&](YaComponent::GetState& request) - -> YaComponent::GetState::Response { - VectorStream stream; - const tresult result = - object_instances[request.instance_id].component->getState( - &stream); - - return YaComponent::GetStateResponse{ - .result = result, .updated_state = std::move(stream)}; - }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From b2cee1e750993ac89c1df81d62b819c1d28b6dbe Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 17:54:57 +0100 Subject: [PATCH 242/456] Rename PluginObject to InstanceInterfaces To make it a bit clearer that this is a holder of interface smart pointers. --- src/wine-host/bridges/vst3.cpp | 5 +++-- src/wine-host/bridges/vst3.h | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index a65784f7..f6dfd607 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -22,9 +22,10 @@ #include "vst3-impls/host-application.h" -PluginObject::PluginObject() {} +InstanceInterfaces::InstanceInterfaces() {} -PluginObject::PluginObject(Steinberg::IPtr object) +InstanceInterfaces::InstanceInterfaces( + Steinberg::IPtr object) : object(object), audio_processor(object), component(object), diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 508e2f01..b31ef7da 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -32,10 +32,10 @@ * `IHostApplication` instance passed to the plugin during * `IPluginBase::initialize()`. */ -struct PluginObject { - PluginObject(); +struct InstanceInterfaces { + InstanceInterfaces(); - PluginObject(Steinberg::IPtr object); + InstanceInterfaces(Steinberg::IPtr object); /** * If the host passes an `IHostApplication` during @@ -149,6 +149,6 @@ class Vst3Bridge : public HostBridge { * will cause all pointers to it to get dropped and the object to be cleaned * up. */ - std::map object_instances; + std::map object_instances; std::mutex object_instances_mutex; }; From b3e39daed09c10bc2bc91e91728e256d68f37bb9 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 17:58:32 +0100 Subject: [PATCH 243/456] Fix typo in log message --- src/common/logging/vst3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 3abd4cec..4e656701 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -425,5 +425,5 @@ void Vst3Logger::log_response(bool is_host_vst, void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { log_response_base(is_host_vst, - [&](auto& message) { message << ""; }); } From 69ec7b3726ef73314e4ae02ee444f178102339b1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 18:15:32 +0100 Subject: [PATCH 244/456] Fix FIDString to FUID conversion --- src/plugin/bridges/vst3-impls/plugin-factory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 359fb740..18793d27 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -45,9 +45,9 @@ YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, // to do. std::optional uid; constexpr size_t uid_size = sizeof(Steinberg::TUID); - if (_iid && strnlen(_iid, uid_size + 1) == uid_size) { + if (_iid && strnlen(_iid, uid_size) >= uid_size) { uid = Steinberg::FUID::fromTUID( - *reinterpret_cast(&_iid)); + *reinterpret_cast(&*_iid)); } bridge.logger.log_unknown_interface( From 9bad0eb50fa61a86637737e18a598bb06513d248 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 18:27:35 +0100 Subject: [PATCH 245/456] Work around bug in Bitwig Studio These FIDStrings are not terminated by null bytes, and thus `FIDStringsEqual` things they don't match any knows IIDs since they appear to have different lengths. --- .../bridges/vst3-impls/plugin-factory.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 18793d27..969b0805 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -28,14 +28,26 @@ tresult PLUGIN_API YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, void** obj) { + if (!_iid || !cid) { + return Steinberg::kInvalidArgument; + } + ArrayUID cid_array; std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); + // FIXME: `_iid` in Bitwig Studio 3.3.1 is not null terminated, and the + // comparison below will thus fail since the strings have different + // lengths. We'll temporarily work around this, and if this end sup + // not being us doing something strange then we should report it. + constexpr size_t uid_size = sizeof(Steinberg::TUID); + std::string iid_string(_iid, uid_size); + Vst3PluginProxy::Construct::Interface requested_interface; - if (Steinberg::FIDStringsEqual(_iid, Steinberg::Vst::IComponent::iid)) { + if (Steinberg::FIDStringsEqual(iid_string.c_str(), + Steinberg::Vst::IComponent::iid)) { requested_interface = Vst3PluginProxy::Construct::Interface::IComponent; } else if (Steinberg::FIDStringsEqual( - _iid, Steinberg::Vst::IEditController::iid)) { + iid_string.c_str(), Steinberg::Vst::IEditController::iid)) { requested_interface = Vst3PluginProxy::Construct::Interface::IEditController; } else { @@ -44,7 +56,6 @@ YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, // way to convert a `FIDString/char*` into a `FUID`, so this will have // to do. std::optional uid; - constexpr size_t uid_size = sizeof(Steinberg::TUID); if (_iid && strnlen(_iid, uid_size) >= uid_size) { uid = Steinberg::FUID::fromTUID( *reinterpret_cast(&*_iid)); From 96c630e608e195aca793829e36ced395dd6d8ff8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 18:51:32 +0100 Subject: [PATCH 246/456] Fix typo in log message --- src/common/logging/vst3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 4e656701..8678c48c 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -367,7 +367,7 @@ void Vst3Logger::log_response( } num_output_channels << "]"; - message << ""; if (response.output_data.output_parameter_changes) { From 7eb7e87953e3d1ef86f1f7457da789edfdeacd96 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 21:50:04 +0100 Subject: [PATCH 247/456] Implement IEditController::setComponentState() --- src/common/logging/vst3.cpp | 10 ++++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 1 + .../vst3/plugin/edit-controller.h | 18 ++++++++++++++++++ src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 8678c48c..7ac9fc7e 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -264,6 +264,16 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request( + bool is_host_vst, + const YaEditController2::SetComponentState& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::setComponentState(state = )"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 65c3ff4a..6c7d668b 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -79,6 +79,8 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::GetRoutingInfo&); void log_request(bool is_host_vst, const YaComponent::ActivateBus&); void log_request(bool is_host_vst, const YaComponent::SetActive&); + void log_request(bool is_host_vst, + const YaEditController2::SetComponentState&); void log_request(bool is_host_vst, const YaPluginBase::Initialize&); void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 5cb7549f..a59f9831 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -75,6 +75,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(instance_id); + s.object(state); + } + }; + virtual tresult PLUGIN_API setComponentState(Steinberg::IBStream* state) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 00c7944d..1e4cea8f 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -190,9 +190,8 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getState(Steinberg::IBStream* state) { tresult PLUGIN_API Vst3PluginProxyImpl::setComponentState(Steinberg::IBStream* state) { - // TODO: Implement - bridge.logger.log("TODO IEditController::setComponentState()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaEditController2::SetComponentState{ + .instance_id = arguments.instance_id, .state = state}); } int32 PLUGIN_API Vst3PluginProxyImpl::getParameterCount() { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index f6dfd607..3eb353cd 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -232,6 +232,11 @@ void Vst3Bridge::run() { return object_instances[request.instance_id] .component->setActive(request.state); }, + [&](YaEditController2::SetComponentState& request) + -> YaEditController2::SetComponentState::Response { + return object_instances[request.instance_id] + .edit_controller->setComponentState(&request.state); + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From 4cc24f74d192d4f6b05f60e633e8217492249c1d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 21:54:27 +0100 Subject: [PATCH 248/456] Implement IEditController::getParameterCount() --- src/common/logging/vst3.cpp | 9 +++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 1 + .../serialization/vst3/plugin/edit-controller.h | 15 +++++++++++++++ src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 7ac9fc7e..76503b80 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -274,6 +274,15 @@ void Vst3Logger::log_request( }); } +void Vst3Logger::log_request( + bool is_host_vst, + const YaEditController2::GetParameterCount& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getParameterCount()"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 6c7d668b..88b0ad94 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -81,6 +81,8 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::SetActive&); void log_request(bool is_host_vst, const YaEditController2::SetComponentState&); + void log_request(bool is_host_vst, + const YaEditController2::GetParameterCount&); void log_request(bool is_host_vst, const YaPluginBase::Initialize&); void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index a59f9831..3bfe17af 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -76,6 +76,7 @@ using ControlRequest = std::variant; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + virtual int32 PLUGIN_API getParameterCount() override = 0; virtual tresult PLUGIN_API getParameterInfo(int32 paramIndex, diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 1e4cea8f..97d1f022 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -195,9 +195,8 @@ Vst3PluginProxyImpl::setComponentState(Steinberg::IBStream* state) { } int32 PLUGIN_API Vst3PluginProxyImpl::getParameterCount() { - // TODO: Implement - bridge.logger.log("TODO IEditController::getParameterCount()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaEditController2::GetParameterCount{ + .instance_id = arguments.instance_id}); } tresult PLUGIN_API Vst3PluginProxyImpl::getParameterInfo( diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 3eb353cd..70b2f8da 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -237,6 +237,11 @@ void Vst3Bridge::run() { return object_instances[request.instance_id] .edit_controller->setComponentState(&request.state); }, + [&](YaEditController2::GetParameterCount& request) + -> YaEditController2::GetParameterCount::Response { + return object_instances[request.instance_id] + .edit_controller->getParameterCount(); + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From ccc5688f0c4b63397ee2e0250d717326ed556f37 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 22:16:20 +0100 Subject: [PATCH 249/456] Implement IEditController::getParameterInfo() --- src/common/logging/vst3.cpp | 25 +++++++++ src/common/logging/vst3.h | 4 ++ src/common/serialization/vst3.h | 1 + .../vst3/plugin/edit-controller.h | 53 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 12 +++-- src/wine-host/bridges/vst3.cpp | 12 ++++- 6 files changed, 103 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 76503b80..73eb1dca 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -16,6 +16,8 @@ #include "vst3.h" +#include + #include "src/common/serialization/vst3.h" Vst3Logger::Vst3Logger(Logger& generic_logger) : logger(generic_logger) {} @@ -283,6 +285,16 @@ void Vst3Logger::log_request( }); } +void Vst3Logger::log_request( + bool is_host_vst, + const YaEditController2::GetParameterInfo& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getParameterInfo(paramIndex = " << request.param_index + << ", &info)"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { @@ -434,6 +446,19 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaEditController2::GetParameterInfoResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + std::string title = + VST3::StringConvert::convert(response.updated_info.title); + message << ", "; + } + }); +} + void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs& args) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 88b0ad94..9bdd923b 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -83,6 +83,8 @@ class Vst3Logger { const YaEditController2::SetComponentState&); void log_request(bool is_host_vst, const YaEditController2::GetParameterCount&); + void log_request(bool is_host_vst, + const YaEditController2::GetParameterInfo&); void log_request(bool is_host_vst, const YaPluginBase::Initialize&); void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); @@ -102,6 +104,8 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse&); void log_response(bool is_host_vst, const YaComponent::GetRoutingInfoResponse&); + void log_response(bool is_host_vst, + const YaEditController2::GetParameterInfoResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 3bfe17af..5e43b064 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -77,6 +77,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.object(result); + s.object(updated_info); + } + }; + + /** + * Message to pass through a call to + * `IEditController::getParameterINfo(param_index, &info)` to the Wine + * plugin host. + */ + struct GetParameterInfo { + using Response = GetParameterInfoResponse; + + native_size_t instance_id; + + int32 param_index; + Steinberg::Vst::ParameterInfo info; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(param_index); + s.object(info); + } + }; + virtual tresult PLUGIN_API getParameterInfo(int32 paramIndex, Steinberg::Vst::ParameterInfo& info /*out*/) override = 0; @@ -163,3 +200,19 @@ class YaEditController2 : public Steinberg::Vst::IEditController, }; #pragma GCC diagnostic pop + +namespace Steinberg { +namespace Vst { +template +void serialize(S& s, ParameterInfo& info) { + s.value4b(info.id); + s.container2b(info.title); + s.container2b(info.shortTitle); + s.container2b(info.units); + s.value4b(info.stepCount); + s.value8b(info.defaultNormalizedValue); + s.value4b(info.unitId); + s.value4b(info.flags); +} +} // namespace Vst +} // namespace Steinberg diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 97d1f022..c1290b9d 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -202,9 +202,15 @@ int32 PLUGIN_API Vst3PluginProxyImpl::getParameterCount() { tresult PLUGIN_API Vst3PluginProxyImpl::getParameterInfo( int32 paramIndex, Steinberg::Vst::ParameterInfo& info /*out*/) { - // TODO: Implement - bridge.logger.log("TODO IEditController::getParameterInfo()"); - return Steinberg::kNotImplemented; + const GetParameterInfoResponse response = + bridge.send_message(YaEditController2::GetParameterInfo{ + .instance_id = arguments.instance_id, + .param_index = paramIndex, + .info = info}); + + info = response.updated_info; + + return response.result; } tresult PLUGIN_API Vst3PluginProxyImpl::getParamStringByValue( diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 70b2f8da..31766450 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -237,11 +237,21 @@ void Vst3Bridge::run() { return object_instances[request.instance_id] .edit_controller->setComponentState(&request.state); }, - [&](YaEditController2::GetParameterCount& request) + [&](const YaEditController2::GetParameterCount& request) -> YaEditController2::GetParameterCount::Response { return object_instances[request.instance_id] .edit_controller->getParameterCount(); }, + [&](YaEditController2::GetParameterInfo& request) + -> YaEditController2::GetParameterInfo::Response { + const tresult result = + object_instances[request.instance_id] + .edit_controller->getParameterInfo(request.param_index, + request.info); + + return YaEditController2::GetParameterInfoResponse{ + .result = result, .updated_info = request.info}; + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From 585d1e736cc260de066bd97a207811c400fa1cf2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 22:42:23 +0100 Subject: [PATCH 250/456] Implement IEditController::getParamStringByValue() --- src/common/logging/vst3.cpp | 23 ++++++++++ src/common/logging/vst3.h | 4 ++ src/common/serialization/vst3.h | 1 + .../vst3/plugin/edit-controller.h | 43 ++++++++++++++++++- .../bridges/vst3-impls/plugin-proxy.cpp | 13 ++++-- src/wine-host/bridges/vst3.cpp | 12 ++++++ 6 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 73eb1dca..044462b5 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -295,6 +295,17 @@ void Vst3Logger::log_request( }); } +void Vst3Logger::log_request( + bool is_host_vst, + const YaEditController2::GetParamStringByValue& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << "::getParamStringByValue(id = " << request.id + << ", valueNormalized = " << request.value_normalized + << ", &string)"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { @@ -459,6 +470,18 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaEditController2::GetParamStringByValueResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + std::string title = VST3::StringConvert::convert(response.string); + message << ", \"" << title << "\""; + } + }); +} + void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs& args) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 9bdd923b..36b0cb65 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -85,6 +85,8 @@ class Vst3Logger { const YaEditController2::GetParameterCount&); void log_request(bool is_host_vst, const YaEditController2::GetParameterInfo&); + void log_request(bool is_host_vst, + const YaEditController2::GetParamStringByValue&); void log_request(bool is_host_vst, const YaPluginBase::Initialize&); void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); @@ -106,6 +108,8 @@ class Vst3Logger { const YaComponent::GetRoutingInfoResponse&); void log_response(bool is_host_vst, const YaEditController2::GetParameterInfoResponse&); + void log_response(bool is_host_vst, + const YaEditController2::GetParamStringByValueResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 5e43b064..83835b92 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -78,6 +78,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.object(result); + s.container2b(string, std::extent_v); + } + }; + + /** + * Message to pass through a call to + * `IEditController::getParamStringByValue(id, value_normalized, &string)` + * to the Wine plugin host. + */ + struct GetParamStringByValue { + using Response = GetParamStringByValueResponse; + + native_size_t instance_id; + + Steinberg::Vst::ParamID id; + Steinberg::Vst::ParamValue value_normalized; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(id); + s.value8b(value_normalized); + } + }; + virtual tresult PLUGIN_API getParamStringByValue( Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized /*in*/, diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index c1290b9d..1006be26 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -217,9 +217,16 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getParamStringByValue( Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized /*in*/, Steinberg::Vst::String128 string /*out*/) { - // TODO: Implement - bridge.logger.log("TODO IEditController::getParamStringByValue()"); - return Steinberg::kNotImplemented; + const GetParamStringByValueResponse response = + bridge.send_message(YaEditController2::GetParamStringByValue{ + .instance_id = arguments.instance_id, + .id = id, + .value_normalized = valueNormalized}); + + std::copy(response.string.begin(), response.string.end(), string); + string[response.string.size()] = 0; + + return response.result; } tresult PLUGIN_API Vst3PluginProxyImpl::getParamValueByString( diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 31766450..62267f3c 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -252,6 +252,18 @@ void Vst3Bridge::run() { return YaEditController2::GetParameterInfoResponse{ .result = result, .updated_info = request.info}; }, + [&](YaEditController2::GetParamStringByValue& request) + -> YaEditController2::GetParamStringByValue::Response { + Steinberg::Vst::String128 string{0}; + const tresult result = + object_instances[request.instance_id] + .edit_controller->getParamStringByValue( + request.id, request.value_normalized, string); + + return YaEditController2::GetParamStringByValueResponse{ + .result = result, + .string = tchar_pointer_to_u16string(string)}; + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From 7fd488beef11f81d5206c8e676dc816fd9a1a556 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 22:55:02 +0100 Subject: [PATCH 251/456] Fix plugin proxy cosntruction response log message --- src/common/logging/vst3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 044462b5..d75086d2 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -355,7 +355,7 @@ void Vst3Logger::log_response(bool is_host_vst, UniversalTResult>& result) { log_response_base(is_host_vst, [&](auto& message) { std::visit(overload{[&](const Vst3PluginProxy::ConstructArgs& args) { - message << ""; }, [&](const UniversalTResult& code) { From de9250076b001584a67cc99ee26075831e079dd4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 23:03:26 +0100 Subject: [PATCH 252/456] Fix very important typo in IBStream reading No wonder it didn't work. --- src/common/serialization/vst3/base.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index ef810b0a..1756b8ac 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -195,7 +195,7 @@ VectorStream::VectorStream(Steinberg::IBStream* stream) { int32 num_bytes_read; buffer.resize(size); - assert(stream->seek(0, Steinberg::IBStream::IStreamSeekMode::kIBSeekSet) != + assert(stream->seek(0, Steinberg::IBStream::IStreamSeekMode::kIBSeekSet) == Steinberg::kResultOk); assert(stream->read(buffer.data(), size, &num_bytes_read) == Steinberg::kResultOk); From 7809c9094c280a43959b28c3f1581a4385bedc79 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 23:06:30 +0100 Subject: [PATCH 253/456] Fix query interface log message --- src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 1006be26..f0cd6c6d 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -33,7 +33,7 @@ Vst3PluginProxyImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { // TODO: Successful queries should also be logged const tresult result = Vst3PluginProxy::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { - bridge.logger.log_unknown_interface("In IComponent::queryInterface()", + bridge.logger.log_unknown_interface("In FUnknown::queryInterface()", Steinberg::FUID::fromTUID(_iid)); } From 2c01bd8bf136e6a87b319e984372bda2c59cc1b3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 23:12:01 +0100 Subject: [PATCH 254/456] Fix typo in IEditController::setComponentState log --- src/common/logging/vst3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index d75086d2..0b2e717f 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -272,7 +272,7 @@ void Vst3Logger::log_request( log_request_base(is_host_vst, [&](auto& message) { message << "::setComponentState(state = )"; + << request.state.size() << " bytes>)"; }); } From a953eb3bfcb3d31860eefea58e86d761e5722808 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 23:12:29 +0100 Subject: [PATCH 255/456] Take PrimitiveWrapper values by value Should not make any difference here anyway since we're dealing with primitives. --- src/common/serialization/vst3/base.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 8ee5c8b5..414c8105 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -96,7 +96,7 @@ template class PrimitiveWrapper { public: PrimitiveWrapper() {} - PrimitiveWrapper(T&& value) : value(value) {} + PrimitiveWrapper(T value) : value(value) {} operator T() const { return value; } From 68c7b9b081343ace27238ea3c23d6513c2e436ac Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 23:32:56 +0100 Subject: [PATCH 256/456] Destroy VST3 objects within the main IO context --- src/wine-host/bridges/vst3.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 62267f3c..f857521e 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -16,8 +16,11 @@ #include "vst3.h" +#include + #include "../boost-fix.h" +#include #include #include "vst3-impls/host-application.h" @@ -102,9 +105,24 @@ void Vst3Bridge::run() { }, [&](const Vst3PluginProxy::Destruct& request) -> Vst3PluginProxy::Destruct::Response { - std::lock_guard lock(object_instances_mutex); - object_instances.erase(request.instance_id); + std::promise latch; + boost::asio::dispatch(main_context.context, [&]() { + // Remove the instance from within the main IO context so + // removing it doesn't interfere with the Win32 message loop + std::lock_guard lock(object_instances_mutex); + object_instances.erase(request.instance_id); + + latch.set_value(); + }); + + // XXX: I don't think we have to wait for the object to be + // deleted most of the time, but I can imagine a situation + // where the plugin does a host callback triggered by a + // Win32 timer in between where the above closure is being + // executed and when the actual host application context on + // the plugin side gets deallocated. + latch.get_future().wait(); return Ack{}; }, [&](Vst3PluginProxy::SetState& request) From 8066e1d2ee7a5be804d4a4d42d56acea89f4e6e3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 17 Dec 2020 23:43:52 +0100 Subject: [PATCH 257/456] Fix writing back vector streams --- src/common/serialization/vst3/base.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index 1756b8ac..0ba0ad31 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -229,11 +229,12 @@ tresult VectorStream::write_back(Steinberg::IBStream* stream) const { int32 num_bytes_written; assert(stream->seek(0, kIBSeekSet) == Steinberg::kResultOk); - assert(stream->write(const_cast(buffer.data()), buffer.size(), - &num_bytes_written) == Steinberg::kResultOk); - - assert(num_bytes_written == 0 || - static_cast(num_bytes_written) == buffer.size()); + // When writing zero bytes, some hosts will return `kResultFalse` + if (stream->write(const_cast(buffer.data()), buffer.size(), + &num_bytes_written) == Steinberg::kResultOk) { + assert(num_bytes_written == 0 || + static_cast(num_bytes_written) == buffer.size()); + } return Steinberg::kResultOk; } From 381ca253c159ecf031e92c160accd76c916904ad Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 12:52:05 +0100 Subject: [PATCH 258/456] Fix uninitialized seek position in VectorStream --- src/common/serialization/vst3/base.cpp | 16 +++++++--------- src/common/serialization/vst3/base.h | 2 +- src/wine-host/bridges/vst3.cpp | 4 +++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index 0ba0ad31..0ef96324 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -193,7 +193,7 @@ VectorStream::VectorStream(Steinberg::IBStream* stream) { int64 size; assert(stream->tell(&size) == Steinberg::kResultOk); - int32 num_bytes_read; + int32 num_bytes_read = 0; buffer.resize(size); assert(stream->seek(0, Steinberg::IBStream::IStreamSeekMode::kIBSeekSet) == Steinberg::kResultOk); @@ -227,14 +227,12 @@ tresult VectorStream::write_back(Steinberg::IBStream* stream) const { return Steinberg::kInvalidArgument; } - int32 num_bytes_written; + int32 num_bytes_written = 0; assert(stream->seek(0, kIBSeekSet) == Steinberg::kResultOk); - // When writing zero bytes, some hosts will return `kResultFalse` - if (stream->write(const_cast(buffer.data()), buffer.size(), - &num_bytes_written) == Steinberg::kResultOk) { - assert(num_bytes_written == 0 || - static_cast(num_bytes_written) == buffer.size()); - } + assert(stream->write(const_cast(buffer.data()), buffer.size(), + &num_bytes_written) == Steinberg::kResultOk); + assert(num_bytes_written == 0 || + static_cast(num_bytes_written) == buffer.size()); return Steinberg::kResultOk; } @@ -276,7 +274,7 @@ tresult PLUGIN_API VectorStream::write(void* buffer, } std::copy_n(reinterpret_cast(buffer), numBytes, - &this->buffer[seek_position]); + this->buffer.begin() + seek_position); seek_position += numBytes; if (numBytesWritten) { diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 414c8105..7c8d8319 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -225,7 +225,7 @@ class VectorStream : public Steinberg::IBStream, private: std::vector buffer; - size_t seek_position; + size_t seek_position = 0; }; #pragma GCC diagnostic pop diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index f857521e..c5decbce 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -139,7 +139,7 @@ void Vst3Bridge::run() { }, [&](Vst3PluginProxy::GetState& request) -> Vst3PluginProxy::GetState::Response { - VectorStream stream; + VectorStream stream{}; tresult result; // This same function is defined in both `IComponent` and @@ -288,6 +288,8 @@ void Vst3Bridge::run() { // and pass that to the initialize function. This object should // be cleaned up again during `Vst3PluginProxy::Destruct`. // TODO: This needs changing when we get to `Vst3HostProxy` + // TODO: Does this have to be run from the UI thread? Figure out + // if it does Steinberg::FUnknown* context = nullptr; if (request.host_application_context_args) { object_instances[request.instance_id] From e36f53b103dff4dcc631601fb5a014e65db96ad2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 13:09:58 +0100 Subject: [PATCH 259/456] Change VST3 log format to be more readable --- src/common/logging/vst3.cpp | 110 +++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 52 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 0b2e717f..9b3f6dce 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -58,15 +58,16 @@ void Vst3Logger::log_request(bool is_host_vst, log_request_base(is_host_vst, [&](auto& message) { // We don't know what class this instance was originally instantiated // as, but it also doesn't really matter - message << "::~FUnknown()"; + message << request.instance_id << ": FUnknown::~FUnknown()"; }); } void Vst3Logger::log_request(bool is_host_vst, const Vst3PluginProxy::SetState& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "<{IComponent,IEditController}* #" << request.instance_id - << ">::setState(state = )"; }); } @@ -74,8 +75,9 @@ void Vst3Logger::log_request(bool is_host_vst, void Vst3Logger::log_request(bool is_host_vst, const Vst3PluginProxy::GetState& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "<{IComponent,IEditController}* #" << request.instance_id - << ">::getState(state = )"; + message + << request.instance_id + << ": {IComponent,IEditController}::getState(state = )"; }); } @@ -83,8 +85,9 @@ void Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::setBusArrangements(inputs = [SpeakerArrangement; " + message << request.instance_id + << ": IAudioProcessor::setBusArrangements(inputs = " + "[SpeakerArrangement; " << request.inputs.size() << "], numIns = " << request.num_ins << ", outputs = [SpeakerArrangement; " << request.outputs.size() << "], numOuts = " << request.num_outs << ")"; @@ -95,8 +98,8 @@ void Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::GetBusArrangement& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::getBusArrangement(dir = " << request.dir + message << request.instance_id + << ": IAudioProcessor::getBusArrangement(dir = " << request.dir << ", index = " << request.index << ", &arr)"; }); } @@ -105,9 +108,10 @@ void Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::CanProcessSampleSize& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::canProcessSampleSize(symbolicSampleSize = " - << request.symbolic_sample_size << ")"; + message + << request.instance_id + << ": IAudioProcessor::canProcessSampleSize(symbolicSampleSize = " + << request.symbolic_sample_size << ")"; }); } @@ -115,16 +119,17 @@ void Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::GetLatencySamples& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::getLatencySamples()"; + message << request.instance_id + << ": IAudioProcessor::getLatencySamples()"; }); } void Vst3Logger::log_request(bool is_host_vst, const YaAudioProcessor::SetupProcessing& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::setupProcessing(setup = ::setProcessing(state = " + message << request.instance_id + << ": IAudioProcessor::setProcessing(state = " << (request.state ? "true" : "false") << ")"; }); } @@ -168,8 +173,9 @@ void Vst3Logger::log_request(bool is_host_vst, } num_output_channels << "]"; - message << "::process(data = ::getTailSamples()"; + message << request.instance_id << ": IAudioProcessor::getTailSamples()"; }); } void Vst3Logger::log_request(bool is_host_vst, const YaComponent::SetIoMode& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::setIoMode(mode = " << request.mode << ")"; + message << request.instance_id + << ": IComponent::setIoMode(mode = " << request.mode << ")"; }); } void Vst3Logger::log_request(bool is_host_vst, const YaComponent::GetBusCount& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::getBusCount(type = " << request.type + message << request.instance_id + << ": IComponent::getBusCount(type = " << request.type << ", dir = " << request.dir << ")"; }); } @@ -227,8 +232,8 @@ void Vst3Logger::log_request(bool is_host_vst, void Vst3Logger::log_request(bool is_host_vst, const YaComponent::GetBusInfo& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::getBusInfo(type = " << request.type + message << request.instance_id + << ": IComponent::getBusInfo(type = " << request.type << ", dir = " << request.dir << ", index = " << request.index << ", &bus)"; }); @@ -237,21 +242,21 @@ void Vst3Logger::log_request(bool is_host_vst, void Vst3Logger::log_request(bool is_host_vst, const YaComponent::GetRoutingInfo& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::getRoutingInfo(inInfo = , outInfo = )"; + message + << request.instance_id + << ": IComponent::getRoutingInfo(inInfo = , outInfo = )"; }); } void Vst3Logger::log_request(bool is_host_vst, const YaComponent::ActivateBus& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::activateBus(type = " << request.type + message << request.instance_id + << ": IComponent::activateBus(type = " << request.type << ", dir = " << request.dir << ", index = " << request.index << ", state = " << (request.state ? "true" : "false") << ")"; }); @@ -260,9 +265,8 @@ void Vst3Logger::log_request(bool is_host_vst, void Vst3Logger::log_request(bool is_host_vst, const YaComponent::SetActive& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::setActive(state = " << (request.state ? "true" : "false") - << ")"; + message << request.instance_id << ": IComponent::setActive(state = " + << (request.state ? "true" : "false") << ")"; }); } @@ -270,8 +274,9 @@ void Vst3Logger::log_request( bool is_host_vst, const YaEditController2::SetComponentState& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::setComponentState(state = )"; }); } @@ -280,8 +285,8 @@ void Vst3Logger::log_request( bool is_host_vst, const YaEditController2::GetParameterCount& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::getParameterCount()"; + message << request.instance_id + << ": IEditController::getParameterCount()"; }); } @@ -289,9 +294,9 @@ void Vst3Logger::log_request( bool is_host_vst, const YaEditController2::GetParameterInfo& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::getParameterInfo(paramIndex = " << request.param_index - << ", &info)"; + message << request.instance_id + << ": IEditController::getParameterInfo(paramIndex = " + << request.param_index << ", &info)"; }); } @@ -299,8 +304,9 @@ void Vst3Logger::log_request( bool is_host_vst, const YaEditController2::GetParamStringByValue& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::getParamStringByValue(id = " << request.id + message << request.instance_id + << ": IEditController::getParamStringByValue(id = " + << request.id << ", valueNormalized = " << request.value_normalized << ", &string)"; }); @@ -309,8 +315,8 @@ void Vst3Logger::log_request( void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::initialize(context = "; + message << request.instance_id + << ": IPluginBase::initialize(context = "; if (request.host_application_context_args) { message << ""; } else { @@ -323,7 +329,7 @@ void Vst3Logger::log_request(bool is_host_vst, void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Terminate& request) { log_request_base(is_host_vst, [&](auto& message) { - message << "::terminate()"; + message << request.instance_id << ": IPluginBase::terminate()"; }); } From f83e526fc6796bf62f73992fbd55bcbbd6b5dc4d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 13:36:48 +0100 Subject: [PATCH 260/456] Add stubs for IConnectionPoint --- README.md | 2 +- meson.build | 2 + .../serialization/vst3/plugin-proxy.cpp | 6 ++ src/common/serialization/vst3/plugin-proxy.h | 6 +- .../vst3/plugin/connection-point.cpp | 27 +++++++ .../vst3/plugin/connection-point.h | 79 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 19 +++++ src/plugin/bridges/vst3-impls/plugin-proxy.h | 5 ++ 8 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 src/common/serialization/vst3/plugin/connection-point.cpp create mode 100644 src/common/serialization/vst3/plugin/connection-point.h diff --git a/README.md b/README.md index 1c3e5d9f..cb80e01b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ incomplete list of things that still have to be done before this can be used: - Interfaces left to implement: - `YaHostApplicationHostImpl::createComponent()` - `IConnectionPoint` to supplement `IComponent` - - `IEditController{,2}` + - Finish implementing `IEditController{,2}` - All other mandatory interfaces - All other optional interfaces - Fully implemented: see [this diff --git a/meson.build b/meson.build index 649acc47..3a5a944a 100644 --- a/meson.build +++ b/meson.build @@ -79,6 +79,7 @@ vst3_plugin_sources = [ 'src/common/logging/vst3.cpp', 'src/common/serialization/vst3/plugin/audio-processor.cpp', 'src/common/serialization/vst3/plugin/component.cpp', + 'src/common/serialization/vst3/plugin/connection-point.cpp', 'src/common/serialization/vst3/plugin/edit-controller.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/base.cpp', @@ -122,6 +123,7 @@ if with_vst3 'src/common/logging/vst3.cpp', 'src/common/serialization/vst3/plugin/audio-processor.cpp', 'src/common/serialization/vst3/plugin/component.cpp', + 'src/common/serialization/vst3/plugin/connection-point.cpp', 'src/common/serialization/vst3/plugin/edit-controller.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/base.cpp', diff --git a/src/common/serialization/vst3/plugin-proxy.cpp b/src/common/serialization/vst3/plugin-proxy.cpp index db6b5746..12637ad6 100644 --- a/src/common/serialization/vst3/plugin-proxy.cpp +++ b/src/common/serialization/vst3/plugin-proxy.cpp @@ -24,12 +24,14 @@ Vst3PluginProxy::ConstructArgs::ConstructArgs( : instance_id(instance_id), audio_processor_args(object), component_args(object), + connection_point_args(object), edit_controller_2_args(object), plugin_base_args(object) {} Vst3PluginProxy::Vst3PluginProxy(const ConstructArgs&& args) : YaAudioProcessor(std::move(args.audio_processor_args)), YaComponent(std::move(args.component_args)), + YaConnectionPoint(std::move(args.connection_point_args)), YaEditController2(std::move(args.edit_controller_2_args)), YaPluginBase(std::move(args.plugin_base_args)), arguments(std::move(args)){FUNKNOWN_CTOR} @@ -71,6 +73,10 @@ tresult PLUGIN_API Vst3PluginProxy::queryInterface(Steinberg::FIDString _iid, QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponent::iid, Steinberg::Vst::IComponent) } + if (YaConnectionPoint::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IConnectionPoint::iid, + Steinberg::Vst::IConnectionPoint) + } if (YaEditController2::supported_version_1()) { QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IEditController::iid, Steinberg::Vst::IEditController) diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index 8bb54ec1..dc7a67ca 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -24,6 +24,7 @@ #include "host-application.h" #include "plugin/audio-processor.h" #include "plugin/component.h" +#include "plugin/connection-point.h" #include "plugin/edit-controller.h" #include "plugin/plugin-base.h" @@ -55,6 +56,7 @@ */ class Vst3PluginProxy : public YaAudioProcessor, public YaComponent, + public YaConnectionPoint, public YaEditController2, public YaPluginBase { public: @@ -77,6 +79,7 @@ class Vst3PluginProxy : public YaAudioProcessor, YaAudioProcessor::ConstructArgs audio_processor_args; YaComponent::ConstructArgs component_args; + YaConnectionPoint::ConstructArgs connection_point_args; YaEditController2::ConstructArgs edit_controller_2_args; YaPluginBase::ConstructArgs plugin_base_args; @@ -84,8 +87,9 @@ class Vst3PluginProxy : public YaAudioProcessor, void serialize(S& s) { s.value8b(instance_id); s.object(audio_processor_args); - s.object(edit_controller_2_args); s.object(component_args); + s.object(connection_point_args); + s.object(edit_controller_2_args); s.object(plugin_base_args); } }; diff --git a/src/common/serialization/vst3/plugin/connection-point.cpp b/src/common/serialization/vst3/plugin/connection-point.cpp new file mode 100644 index 00000000..5114521d --- /dev/null +++ b/src/common/serialization/vst3/plugin/connection-point.cpp @@ -0,0 +1,27 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "connection-point.h" + +YaConnectionPoint::ConstructArgs::ConstructArgs() {} + +YaConnectionPoint::ConstructArgs::ConstructArgs( + Steinberg::IPtr object) + : supported( + Steinberg::FUnknownPtr(object)) {} + +YaConnectionPoint::YaConnectionPoint(const ConstructArgs&& args) + : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin/connection-point.h b/src/common/serialization/vst3/plugin/connection-point.h new file mode 100644 index 00000000..0829c280 --- /dev/null +++ b/src/common/serialization/vst3/plugin/connection-point.h @@ -0,0 +1,79 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include + +#include "../../common.h" +#include "../base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IConnectionPoint` for serialization purposes. This is + * instantiated as part of `Vst3PluginProxy`. Because we use this giant + * monolithic proxy class we can easily directly connect different objects by + * checking if they're a `Vst3PluginProxy` and then fetching that object's + * instance ID (if the host doesn't place a proxy object here). + * + * TODO: Make sure we somehow handle proxies created by the host here. + */ +class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint { + public: + /** + * These are the arguments for creating a `YaConnectionPoint`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Check whether an existing implementation implements + * `IConnectionPoint` and read arguments from it. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + template + void serialize(S& s) { + s.value1b(supported); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaConnectionPoint(const ConstructArgs&& args); + + inline bool supported() const { return arguments.supported; } + + virtual tresult PLUGIN_API connect(IConnectionPoint* other) override = 0; + virtual tresult PLUGIN_API disconnect(IConnectionPoint* other) override = 0; + virtual tresult PLUGIN_API + notify(Steinberg::Vst::IMessage* message) override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index f0cd6c6d..e809b94f 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -188,6 +188,25 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getState(Steinberg::IBStream* state) { return response.result; } +tresult PLUGIN_API Vst3PluginProxyImpl::connect(IConnectionPoint* other) { + // TODO: Implement + bridge.logger.log("TODO IConnectionPoint::connect()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PluginProxyImpl::disconnect(IConnectionPoint* other) { + // TODO: Implement + bridge.logger.log("TODO IConnectionPoint::disconnect()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::notify(Steinberg::Vst::IMessage* message) { + // TODO: Implement + bridge.logger.log("TODO IConnectionPoint::notify()"); + return Steinberg::kNotImplemented; +} + tresult PLUGIN_API Vst3PluginProxyImpl::setComponentState(Steinberg::IBStream* state) { return bridge.send_message(YaEditController2::SetComponentState{ diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index 964460c8..7d6bbafa 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -79,6 +79,11 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { tresult PLUGIN_API setState(Steinberg::IBStream* state) override; tresult PLUGIN_API getState(Steinberg::IBStream* state) override; + // From `IConnectionPoint` + tresult PLUGIN_API connect(IConnectionPoint* other) override; + tresult PLUGIN_API disconnect(IConnectionPoint* other) override; + tresult PLUGIN_API notify(Steinberg::Vst::IMessage* message) override; + // From `IEditController` tresult PLUGIN_API setComponentState(Steinberg::IBStream* state) override; // `IEditController` also contains `getState()` and `setState()` functions. From 41a9ca5a361f12e0b0d84efc6fad5fa49e0dddee Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 13:44:55 +0100 Subject: [PATCH 261/456] Add boilerplate for connecting Vst3PluginProxies This way we can identify the actual objects and directly connect them on the Wine side. --- src/common/serialization/vst3/plugin-proxy.h | 6 ++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 21 ++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index dc7a67ca..48cd5ccd 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -154,6 +154,12 @@ class Vst3PluginProxy : public YaAudioProcessor, DECLARE_FUNKNOWN_METHODS + /** + * Get this object's instance ID. Used in `IConnectionPoint` to identify and + * connect specific objects. + */ + inline size_t instance_id() const { return arguments.instance_id; } + // We'll define messages for functions that have identical definitions in // multiple interfaces below. When the Wine plugin host process handles // these it should check which of the interfaces is supported on the host. diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index e809b94f..4e68416a 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -189,9 +189,24 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getState(Steinberg::IBStream* state) { } tresult PLUGIN_API Vst3PluginProxyImpl::connect(IConnectionPoint* other) { - // TODO: Implement - bridge.logger.log("TODO IConnectionPoint::connect()"); - return Steinberg::kNotImplemented; + // When the host is trying to connect two plugin proxy objects, we can just + // identify the other object by its instance IDs and then connect the + // objects in the Wine plugin host directly + if (auto other_proxy = dynamic_cast(other)) { + // TODO: Remove debug + bridge.logger.log("Host is trying to connect us with instance " + + std::to_string(other_proxy->instance_id())); + + // TODO: Implement + bridge.logger.log("TODO IConnectionPoint::connect()"); + return Steinberg::kNotImplemented; + } else { + // TODO: Add support for `ConnectionProxy` and similar objects + bridge.logger.log( + "WARNING: The host passed a proxy proxy object to " + "'IConnectionPoint::connect()'. This is currently not supported."); + return Steinberg::kNotImplemented; + } } tresult PLUGIN_API Vst3PluginProxyImpl::disconnect(IConnectionPoint* other) { From cfa484946756b8a189663179c94cbb26c3f6b6e3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 14:09:20 +0100 Subject: [PATCH 262/456] Partially implement IConnectionPoint::connect() This now works for direct connections, which is probably how most hosts will use this. --- README.md | 2 +- src/common/logging/vst3.cpp | 9 ++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + .../vst3/plugin/connection-point.h | 23 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 10 +++----- src/wine-host/bridges/vst3.cpp | 11 +++++++++ src/wine-host/bridges/vst3.h | 1 + 8 files changed, 50 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cb80e01b..96507bd6 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ incomplete list of things that still have to be done before this can be used: - Interfaces left to implement: - `YaHostApplicationHostImpl::createComponent()` - - `IConnectionPoint` to supplement `IComponent` + - The other parts of `IConnectionPoint`, including support for host provided proxies - Finish implementing `IEditController{,2}` - All other mandatory interfaces - All other optional interfaces diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 9b3f6dce..2d7a0d7e 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -270,6 +270,15 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaConnectionPoint::Connect& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IConnectionPoint::connect(other = )"; + }); +} + void Vst3Logger::log_request( bool is_host_vst, const YaEditController2::SetComponentState& request) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 36b0cb65..c259da72 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -79,6 +79,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::GetRoutingInfo&); void log_request(bool is_host_vst, const YaComponent::ActivateBus&); void log_request(bool is_host_vst, const YaComponent::SetActive&); + void log_request(bool is_host_vst, const YaConnectionPoint::Connect&); void log_request(bool is_host_vst, const YaEditController2::SetComponentState&); void log_request(bool is_host_vst, diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 83835b92..d634d647 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -75,6 +75,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(instance_id); + s.value8b(other_instance_id); + } + }; + virtual tresult PLUGIN_API connect(IConnectionPoint* other) override = 0; virtual tresult PLUGIN_API disconnect(IConnectionPoint* other) override = 0; virtual tresult PLUGIN_API diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 4e68416a..4fa96ff5 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -193,13 +193,9 @@ tresult PLUGIN_API Vst3PluginProxyImpl::connect(IConnectionPoint* other) { // identify the other object by its instance IDs and then connect the // objects in the Wine plugin host directly if (auto other_proxy = dynamic_cast(other)) { - // TODO: Remove debug - bridge.logger.log("Host is trying to connect us with instance " + - std::to_string(other_proxy->instance_id())); - - // TODO: Implement - bridge.logger.log("TODO IConnectionPoint::connect()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaConnectionPoint::Connect{ + .instance_id = arguments.instance_id, + .other_instance_id = other_proxy->instance_id()}); } else { // TODO: Add support for `ConnectionProxy` and similar objects bridge.logger.log( diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index c5decbce..2163e59d 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -32,6 +32,7 @@ InstanceInterfaces::InstanceInterfaces( : object(object), audio_processor(object), component(object), + connection_point(object), edit_controller(object), plugin_base(object) {} @@ -250,6 +251,16 @@ void Vst3Bridge::run() { return object_instances[request.instance_id] .component->setActive(request.state); }, + [&](const YaConnectionPoint::Connect& request) + -> YaConnectionPoint::Connect::Response { + // We can directly connect the underlying objects + // TODO: Add support for connecting objects through a proxy + // object provided by the host + return object_instances[request.instance_id] + .connection_point->connect( + object_instances[request.other_instance_id] + .connection_point); + }, [&](YaEditController2::SetComponentState& request) -> YaEditController2::SetComponentState::Response { return object_instances[request.instance_id] diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index b31ef7da..0253d6b3 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -54,6 +54,7 @@ struct InstanceInterfaces { Steinberg::FUnknownPtr audio_processor; Steinberg::FUnknownPtr component; + Steinberg::FUnknownPtr connection_point; Steinberg::FUnknownPtr edit_controller; Steinberg::FUnknownPtr plugin_base; }; From 74f21f12a56e489e4d2ffc33191890fd0e4650db Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 14:20:28 +0100 Subject: [PATCH 263/456] Use a getter for a proxy object's instance ID We also need this to get some random other object's instance ID, so might as well use it everywhere. --- src/common/serialization/vst3/plugin-proxy.h | 2 +- .../bridges/vst3-impls/plugin-proxy.cpp | 78 +++++++++---------- src/plugin/bridges/vst3-impls/plugin-proxy.h | 2 - 3 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index 48cd5ccd..cdd1f79b 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -212,7 +212,7 @@ class Vst3PluginProxy : public YaAudioProcessor, } }; - protected: + private: ConstructArgs arguments; }; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 4fa96ff5..1d2a7ea3 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -24,8 +24,8 @@ Vst3PluginProxyImpl::Vst3PluginProxyImpl(Vst3PluginBridge& bridge, Vst3PluginProxyImpl::~Vst3PluginProxyImpl() { bridge.send_message( - Vst3PluginProxy::Destruct{.instance_id = arguments.instance_id}); - bridge.unregister_plugin_proxy(arguments.instance_id); + Vst3PluginProxy::Destruct{.instance_id = instance_id()}); + bridge.unregister_plugin_proxy(instance_id()); } tresult PLUGIN_API @@ -47,7 +47,7 @@ tresult PLUGIN_API Vst3PluginProxyImpl::setBusArrangements( int32 numOuts) { assert(inputs && outputs); return bridge.send_message(YaAudioProcessor::SetBusArrangements{ - .instance_id = arguments.instance_id, + .instance_id = instance_id(), .inputs = std::vector( inputs, &inputs[numIns]), .num_ins = numIns, @@ -61,12 +61,11 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getBusArrangement( Steinberg::Vst::BusDirection dir, int32 index, Steinberg::Vst::SpeakerArrangement& arr) { - const GetBusArrangementResponse response = - bridge.send_message(YaAudioProcessor::GetBusArrangement{ - .instance_id = arguments.instance_id, - .dir = dir, - .index = index, - .arr = arr}); + const GetBusArrangementResponse response = bridge.send_message( + YaAudioProcessor::GetBusArrangement{.instance_id = instance_id(), + .dir = dir, + .index = index, + .arr = arr}); arr = response.updated_arr; @@ -76,30 +75,30 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getBusArrangement( tresult PLUGIN_API Vst3PluginProxyImpl::canProcessSampleSize(int32 symbolicSampleSize) { return bridge.send_message(YaAudioProcessor::CanProcessSampleSize{ - .instance_id = arguments.instance_id, + .instance_id = instance_id(), .symbolic_sample_size = symbolicSampleSize}); } uint32 PLUGIN_API Vst3PluginProxyImpl::getLatencySamples() { - return bridge.send_message(YaAudioProcessor::GetLatencySamples{ - .instance_id = arguments.instance_id}); + return bridge.send_message( + YaAudioProcessor::GetLatencySamples{.instance_id = instance_id()}); } tresult PLUGIN_API Vst3PluginProxyImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { return bridge.send_message(YaAudioProcessor::SetupProcessing{ - .instance_id = arguments.instance_id, .setup = setup}); + .instance_id = instance_id(), .setup = setup}); } tresult PLUGIN_API Vst3PluginProxyImpl::setProcessing(TBool state) { return bridge.send_message(YaAudioProcessor::SetProcessing{ - .instance_id = arguments.instance_id, .state = state}); + .instance_id = instance_id(), .state = state}); } tresult PLUGIN_API Vst3PluginProxyImpl::process(Steinberg::Vst::ProcessData& data) { - ProcessResponse response = bridge.send_message(YaAudioProcessor::Process{ - .instance_id = arguments.instance_id, .data = data}); + ProcessResponse response = bridge.send_message( + YaAudioProcessor::Process{.instance_id = instance_id(), .data = data}); response.output_data.write_back_outputs(data); @@ -108,19 +107,19 @@ Vst3PluginProxyImpl::process(Steinberg::Vst::ProcessData& data) { uint32 PLUGIN_API Vst3PluginProxyImpl::getTailSamples() { return bridge.send_message( - YaAudioProcessor::GetTailSamples{.instance_id = arguments.instance_id}); + YaAudioProcessor::GetTailSamples{.instance_id = instance_id()}); } tresult PLUGIN_API Vst3PluginProxyImpl::setIoMode(Steinberg::Vst::IoMode mode) { - return bridge.send_message(YaComponent::SetIoMode{ - .instance_id = arguments.instance_id, .mode = mode}); + return bridge.send_message( + YaComponent::SetIoMode{.instance_id = instance_id(), .mode = mode}); } int32 PLUGIN_API Vst3PluginProxyImpl::getBusCount(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir) { return bridge.send_message(YaComponent::GetBusCount{ - .instance_id = arguments.instance_id, .type = type, .dir = dir}); + .instance_id = instance_id(), .type = type, .dir = dir}); } tresult PLUGIN_API @@ -129,7 +128,7 @@ Vst3PluginProxyImpl::getBusInfo(Steinberg::Vst::MediaType type, int32 index, Steinberg::Vst::BusInfo& bus /*out*/) { const GetBusInfoResponse response = bridge.send_message( - YaComponent::GetBusInfo{.instance_id = arguments.instance_id, + YaComponent::GetBusInfo{.instance_id = instance_id(), .type = type, .dir = dir, .index = index, @@ -143,7 +142,7 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getRoutingInfo( Steinberg::Vst::RoutingInfo& inInfo, Steinberg::Vst::RoutingInfo& outInfo /*out*/) { const GetRoutingInfoResponse response = bridge.send_message( - YaComponent::GetRoutingInfo{.instance_id = arguments.instance_id, + YaComponent::GetRoutingInfo{.instance_id = instance_id(), .in_info = inInfo, .out_info = outInfo}); @@ -158,7 +157,7 @@ Vst3PluginProxyImpl::activateBus(Steinberg::Vst::MediaType type, int32 index, TBool state) { return bridge.send_message( - YaComponent::ActivateBus{.instance_id = arguments.instance_id, + YaComponent::ActivateBus{.instance_id = instance_id(), .type = type, .dir = dir, .index = index, @@ -166,22 +165,22 @@ Vst3PluginProxyImpl::activateBus(Steinberg::Vst::MediaType type, } tresult PLUGIN_API Vst3PluginProxyImpl::setActive(TBool state) { - return bridge.send_message(YaComponent::SetActive{ - .instance_id = arguments.instance_id, .state = state}); + return bridge.send_message( + YaComponent::SetActive{.instance_id = instance_id(), .state = state}); } tresult PLUGIN_API Vst3PluginProxyImpl::setState(Steinberg::IBStream* state) { // Since both interfaces contain this function, this is used for both // `IComponent::setState()` as well as `IEditController::setState()` return bridge.send_message(Vst3PluginProxy::SetState{ - .instance_id = arguments.instance_id, .state = state}); + .instance_id = instance_id(), .state = state}); } tresult PLUGIN_API Vst3PluginProxyImpl::getState(Steinberg::IBStream* state) { // Since both interfaces contain this function, this is used for both // `IComponent::getState()` as well as `IEditController::getState()` const GetStateResponse response = bridge.send_message( - Vst3PluginProxy::GetState{.instance_id = arguments.instance_id}); + Vst3PluginProxy::GetState{.instance_id = instance_id()}); assert(response.updated_state.write_back(state) == Steinberg::kResultOk); @@ -194,7 +193,7 @@ tresult PLUGIN_API Vst3PluginProxyImpl::connect(IConnectionPoint* other) { // objects in the Wine plugin host directly if (auto other_proxy = dynamic_cast(other)) { return bridge.send_message(YaConnectionPoint::Connect{ - .instance_id = arguments.instance_id, + .instance_id = instance_id(), .other_instance_id = other_proxy->instance_id()}); } else { // TODO: Add support for `ConnectionProxy` and similar objects @@ -221,22 +220,21 @@ Vst3PluginProxyImpl::notify(Steinberg::Vst::IMessage* message) { tresult PLUGIN_API Vst3PluginProxyImpl::setComponentState(Steinberg::IBStream* state) { return bridge.send_message(YaEditController2::SetComponentState{ - .instance_id = arguments.instance_id, .state = state}); + .instance_id = instance_id(), .state = state}); } int32 PLUGIN_API Vst3PluginProxyImpl::getParameterCount() { - return bridge.send_message(YaEditController2::GetParameterCount{ - .instance_id = arguments.instance_id}); + return bridge.send_message( + YaEditController2::GetParameterCount{.instance_id = instance_id()}); } tresult PLUGIN_API Vst3PluginProxyImpl::getParameterInfo( int32 paramIndex, Steinberg::Vst::ParameterInfo& info /*out*/) { - const GetParameterInfoResponse response = - bridge.send_message(YaEditController2::GetParameterInfo{ - .instance_id = arguments.instance_id, - .param_index = paramIndex, - .info = info}); + const GetParameterInfoResponse response = bridge.send_message( + YaEditController2::GetParameterInfo{.instance_id = instance_id(), + .param_index = paramIndex, + .info = info}); info = response.updated_info; @@ -249,7 +247,7 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getParamStringByValue( Steinberg::Vst::String128 string /*out*/) { const GetParamStringByValueResponse response = bridge.send_message(YaEditController2::GetParamStringByValue{ - .instance_id = arguments.instance_id, + .instance_id = instance_id(), .id = id, .value_normalized = valueNormalized}); @@ -345,7 +343,7 @@ tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* context) { host_application_context_args = std::nullopt; if (host_application_context) { host_application_context_args = YaHostApplication::ConstructArgs( - host_application_context, arguments.instance_id); + host_application_context, instance_id()); } else { bridge.logger.log_unknown_interface( "In IPluginBase::initialize()", @@ -353,12 +351,12 @@ tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* context) { } return bridge.send_message( - YaPluginBase::Initialize{.instance_id = arguments.instance_id, + YaPluginBase::Initialize{.instance_id = instance_id(), .host_application_context_args = std::move(host_application_context_args)}); } tresult PLUGIN_API Vst3PluginProxyImpl::terminate() { return bridge.send_message( - YaPluginBase::Terminate{.instance_id = arguments.instance_id}); + YaPluginBase::Terminate{.instance_id = instance_id()}); } diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index 7d6bbafa..eb47f462 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -39,8 +39,6 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, void** obj) override; - inline size_t instance_id() { return arguments.instance_id; } - // From `IAudioProcessor` tresult PLUGIN_API setBusArrangements(Steinberg::Vst::SpeakerArrangement* inputs, From af1d57371257b2baf05ed31ebdcdf88eddbe20a5 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 14:31:26 +0100 Subject: [PATCH 264/456] Add IConnectionPoint to the implemented interfaces --- src/common/serialization/vst3/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 800bc2af..da2774ae 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -12,6 +12,7 @@ VST3 interfaces are implemented as follows: | `Vst3PluginProxy` | | All of the below: | | `YaAudioProcessor` | `Vst3PluginProxy` | `IAudioProcessor` | | `YaComponent` | `Vst3PluginProxy` | `IComponent` | +| `YaConnectionPoint` | `Vst3PluginProxy` | `IConnectionPoint` | | `YaEditController` | `Vst3PluginProxy` | `IEditController`, `IEditController2` | | `YaPluginBase` | `Vst3PluginProxy` | `IPluginBase` | | `YaHostApplication` | | `iHostAPplication` | From d2585a35501f9dc9caac374f94c711daba06a5f2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 20:22:34 +0100 Subject: [PATCH 265/456] Add a todo about reusing YaProcessData objects --- src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 1d2a7ea3..2acaa1ff 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -97,6 +97,8 @@ tresult PLUGIN_API Vst3PluginProxyImpl::setProcessing(TBool state) { tresult PLUGIN_API Vst3PluginProxyImpl::process(Steinberg::Vst::ProcessData& data) { + // TODO: Check whether reusing a `YaProcessData` object make a difference in + // terms of performance ProcessResponse response = bridge.send_message( YaAudioProcessor::Process{.instance_id = instance_id(), .data = data}); From d99f880277c1d14f196f4a70f818bb4b281b7eeb Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 20:43:43 +0100 Subject: [PATCH 266/456] Rename YaHostApplication implementation The context should make it obvious where it's implemented, and with the current design we only an implementation on one of the two sides. --- README.md | 2 +- docs/vst3.md | 22 +++++++++++-------- src/common/serialization/vst3/README.md | 17 +++++++++----- .../serialization/vst3/host-application.h | 6 ++--- .../bridges/vst3-impls/host-application.cpp | 14 ++++++------ .../bridges/vst3-impls/host-application.h | 4 ++-- src/wine-host/bridges/vst3.cpp | 7 +++--- 7 files changed, 41 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 96507bd6..e422afc6 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This branch is still very far removed from being in a usable state. Below is an incomplete list of things that still have to be done before this can be used: - Interfaces left to implement: - - `YaHostApplicationHostImpl::createComponent()` + - `IHostApplication::createComponent()` - The other parts of `IConnectionPoint`, including support for host provided proxies - Finish implementing `IEditController{,2}` - All other mandatory interfaces diff --git a/docs/vst3.md b/docs/vst3.md index 6bd1fc5a..2398bd61 100644 --- a/docs/vst3.md +++ b/docs/vst3.md @@ -35,6 +35,10 @@ instantiated and managed by the host. The basic model works as follows: interfaces defiend in the VST3 SDK. `Vst3PluginProxy` implements all interfaces that can be implemented by plugins, and `Vst3HostProxy` implements all interfaces that are to be implemented by the host. + + TODO: Find out if `Vst3HostProxy` is needed, or if objects provided by the + host never implement multiple interfaces (which I think might be the case) + 2. For every interface `IFoo`, we provide an abstract implementation called `YaFoo`. This implementation mostly contain message object we use to make specific function calls on the actual objects we are proxying. The @@ -45,15 +49,15 @@ instantiated and managed by the host. The basic model works as follows: 3. Proxy object are instantiated while handling `IPluginFactory::createInstance()` for `Vst3PluginProxy`, and during `IPluginBase::initialize()` and `IPluginFactory::setHostContext()` for - `Vst3HostProxy`. On the receiving side of those functions (where we call the - actual function implemented by the plugin or the host), we receive an - `IPtr` smart pointer to an object provided by the host or the plugin. We - use this object to iterate over every applicable `YaFoo` as mentioend above. - All of these `YaFoo::ConstructArgs` objects along with a unique identifier - for this specific object are then serialized and transmitted to the other - side. With this information we can create a proxy object that supports all - the same interfaces (and thus allows calls to the functions in those - interfaces) as the original object we are proxying. + `Vst3HostProxy` (TODO: Same here). On the receiving side of those functions + (where we call the actual function implemented by the plugin or the host), we + receive an `IPtr` smart pointer to an object provided by the host or the + plugin. We use this object to iterate over every applicable `YaFoo` as + mentioend above. All of these `YaFoo::ConstructArgs` objects along with a + unique identifier for this specific object are then serialized and + transmitted to the other side. With this information we can create a proxy + object that supports all the same interfaces (and thus allows calls to the + functions in those interfaces) as the original object we are proxying. 4. As mentioend, every object we instantiate gets assigned a unique identifier. When dealign with objects created by the Windows VST3 plugin, the object's `FUnknown` pointer will be stored in an `std::map` map. diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index da2774ae..b0c39685 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -5,22 +5,27 @@ TODO: After merging into master, update this link to just point to GitHub See [docs/vst3.md](../../../../docs/vst3.md) for more information on how the serialization works. -VST3 interfaces are implemented as follows: +VST3 plugin interfaces are implemented as follows: -| Yabridge class | Included in | Interfaces | +| yabridge class | Included in | Interfaces | | ------------------- | ----------------- | ------------------------------------------------------ | +| `YaPluginFactory` | | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | | `Vst3PluginProxy` | | All of the below: | | `YaAudioProcessor` | `Vst3PluginProxy` | `IAudioProcessor` | | `YaComponent` | `Vst3PluginProxy` | `IComponent` | | `YaConnectionPoint` | `Vst3PluginProxy` | `IConnectionPoint` | | `YaEditController` | `Vst3PluginProxy` | `IEditController`, `IEditController2` | | `YaPluginBase` | `Vst3PluginProxy` | `IPluginBase` | -| `YaHostApplication` | | `iHostAPplication` | -| `YaPluginFactory` | | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | -The following interfaces are implemented purely fur serialization purposes: +VST3 host interfaces are implemented as follows: -| Yabridge class | Interfaces | Notes | +| yabridge class | Interfaces | +| ------------------- | ------------------ | +| `YaHostApplication` | `IHostApplication` | + +The following (host) interfaces are also implemented fur serialization purposes: + +| yabridge class | Interfaces | Notes | | -------------------- | ------------------- | ---------------------------------------------------------------------- | | `YaEventList` | `IEventList` | Comes with a lot of serialization wrappers around the related structs. | | `YaParameterChanges` | `IParameterChanges` | | diff --git a/src/common/serialization/vst3/host-application.h b/src/common/serialization/vst3/host-application.h index dc289e7b..3b871bae 100644 --- a/src/common/serialization/vst3/host-application.h +++ b/src/common/serialization/vst3/host-application.h @@ -95,9 +95,9 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { /** * The lifetime of this object should be bound to the object we created it - * for. When for instance the `IComponent` instance with id `x` gets dropped - * and we also track a `YaHostApplicationHostImpl` for the component with - * instance id `x`, then that should also be dropped. + * for. When for instance the `Vst3PluginProxy` instance with id `n` gets + * dropped and we also track a `YaHostApplicationImpl` for the component + * with instance id `n`, then that should also be dropped. */ virtual ~YaHostApplication(); diff --git a/src/wine-host/bridges/vst3-impls/host-application.cpp b/src/wine-host/bridges/vst3-impls/host-application.cpp index 7d77d917..211e6242 100644 --- a/src/wine-host/bridges/vst3-impls/host-application.cpp +++ b/src/wine-host/bridges/vst3-impls/host-application.cpp @@ -18,7 +18,7 @@ #include -YaHostApplicationHostImpl::YaHostApplicationHostImpl( +YaHostApplicationImpl::YaHostApplicationImpl( Vst3Bridge& bridge, YaHostApplication::ConstructArgs&& args) : YaHostApplication(std::move(args)), bridge(bridge) { @@ -27,8 +27,9 @@ YaHostApplicationHostImpl::YaHostApplicationHostImpl( } tresult PLUGIN_API -YaHostApplicationHostImpl::queryInterface(const Steinberg::TUID _iid, - void** obj) { +YaHostApplicationImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { + // I don't think it's expected of a host to implement multiple interfaces on + // this object, so if we do get a call here it's important that it's logged // TODO: Successful queries should also be logged const tresult result = YaHostApplication::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { @@ -39,10 +40,9 @@ YaHostApplicationHostImpl::queryInterface(const Steinberg::TUID _iid, return result; } -tresult PLUGIN_API -YaHostApplicationHostImpl::createInstance(Steinberg::TUID cid, - Steinberg::TUID _iid, - void** obj) { +tresult PLUGIN_API YaHostApplicationImpl::createInstance(Steinberg::TUID cid, + Steinberg::TUID _iid, + void** obj) { // TODO: Implement std::cerr << "TODO: IHostApplication::createInstance()" << std::endl; return Steinberg::kNotImplemented; diff --git a/src/wine-host/bridges/vst3-impls/host-application.h b/src/wine-host/bridges/vst3-impls/host-application.h index c9c302f9..29170dc6 100644 --- a/src/wine-host/bridges/vst3-impls/host-application.h +++ b/src/wine-host/bridges/vst3-impls/host-application.h @@ -20,9 +20,9 @@ #include "../vst3.h" -class YaHostApplicationHostImpl : public YaHostApplication { +class YaHostApplicationImpl : public YaHostApplication { public: - YaHostApplicationHostImpl(Vst3Bridge& bridge, + YaHostApplicationImpl(Vst3Bridge& bridge, YaHostApplication::ConstructArgs&& args); /** diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 2163e59d..b1255d22 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -298,14 +298,15 @@ void Vst3Bridge::run() { // If we got passed a host context, we'll create a proxy object // and pass that to the initialize function. This object should // be cleaned up again during `Vst3PluginProxy::Destruct`. - // TODO: This needs changing when we get to `Vst3HostProxy` + // TODO: This needs changing if it turns out we need a + // `Vst3HostProxy` // TODO: Does this have to be run from the UI thread? Figure out // if it does Steinberg::FUnknown* context = nullptr; if (request.host_application_context_args) { object_instances[request.instance_id] .hsot_application_context = - Steinberg::owned(new YaHostApplicationHostImpl( + Steinberg::owned(new YaHostApplicationImpl( *this, std::move(*request.host_application_context_args))); context = object_instances[request.instance_id] @@ -328,7 +329,7 @@ void Vst3Bridge::run() { [&](YaPluginFactory::SetHostContext& request) -> YaPluginFactory::SetHostContext::Response { plugin_factory_host_application_context = - Steinberg::owned(new YaHostApplicationHostImpl( + Steinberg::owned(new YaHostApplicationImpl( *this, std::move(request.host_application_context_args))); From a4e2a18480efa41d86bbf51e5a7383a220893d2d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 21:21:36 +0100 Subject: [PATCH 267/456] Implement IEditController::getParamValueByString() --- src/common/logging/vst3.cpp | 31 +++++++++++++-- src/common/logging/vst3.h | 4 ++ src/common/serialization/vst3.h | 1 + .../vst3/plugin/edit-controller.h | 39 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 10 +++-- src/wine-host/bridges/vst3.cpp | 15 +++++++ 6 files changed, 93 insertions(+), 7 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 2d7a0d7e..b7bb7d46 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -321,6 +321,18 @@ void Vst3Logger::log_request( }); } +void Vst3Logger::log_request( + bool is_host_vst, + const YaEditController2::GetParamValueByString& request) { + log_request_base(is_host_vst, [&](auto& message) { + std::string param_title = VST3::StringConvert::convert(request.string); + message << request.instance_id + << ": IEditController::getParamValueByString(id = " + << request.id << ", string = " << param_title + << ", &valueNormalized)"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { @@ -478,9 +490,9 @@ void Vst3Logger::log_response( log_response_base(is_host_vst, [&](auto& message) { message << response.result.string(); if (response.result == Steinberg::kResultOk) { - std::string title = + std::string param_title = VST3::StringConvert::convert(response.updated_info.title); - message << ", "; + message << ", "; } }); } @@ -491,8 +503,19 @@ void Vst3Logger::log_response( log_response_base(is_host_vst, [&](auto& message) { message << response.result.string(); if (response.result == Steinberg::kResultOk) { - std::string title = VST3::StringConvert::convert(response.string); - message << ", \"" << title << "\""; + std::string value = VST3::StringConvert::convert(response.string); + message << ", \"" << value << "\""; + } + }); +} + +void Vst3Logger::log_response( + bool is_host_vst, + const YaEditController2::GetParamValueByStringResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", " << response.value_normalized << std::endl; } }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index c259da72..37d82312 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -88,6 +88,8 @@ class Vst3Logger { const YaEditController2::GetParameterInfo&); void log_request(bool is_host_vst, const YaEditController2::GetParamStringByValue&); + void log_request(bool is_host_vst, + const YaEditController2::GetParamValueByString&); void log_request(bool is_host_vst, const YaPluginBase::Initialize&); void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); @@ -111,6 +113,8 @@ class Vst3Logger { const YaEditController2::GetParameterInfoResponse&); void log_response(bool is_host_vst, const YaEditController2::GetParamStringByValueResponse&); + void log_response(bool is_host_vst, + const YaEditController2::GetParamValueByStringResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index d634d647..5960daab 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -80,6 +80,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.object(result); + s.value8b(value_normalized); + } + }; + + /** + * Message to pass through a call to + * `IEditController::getParamValueByString(id, string, &value_normalized)` + * to the Wine plugin host. + */ + struct GetParamValueByString { + using Response = GetParamValueByStringResponse; + + native_size_t instance_id; + + Steinberg::Vst::ParamID id; + std::u16string string; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(id); + s.container2b(string, std::extent_v); + } + }; + virtual tresult PLUGIN_API getParamValueByString( Steinberg::Vst::ParamID id, Steinberg::Vst::TChar* string /*in*/, diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 2acaa1ff..609d505c 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -263,9 +263,13 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getParamValueByString( Steinberg::Vst::ParamID id, Steinberg::Vst::TChar* string /*in*/, Steinberg::Vst::ParamValue& valueNormalized /*out*/) { - // TODO: Implement - bridge.logger.log("TODO IEditController::getParamValueByString()"); - return Steinberg::kNotImplemented; + const GetParamValueByStringResponse response = + bridge.send_message(YaEditController2::GetParamValueByString{ + .instance_id = instance_id(), .id = id, .string = string}); + + valueNormalized = response.value_normalized; + + return response.result; } Steinberg::Vst::ParamValue PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index b1255d22..46a878a7 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -293,6 +293,21 @@ void Vst3Bridge::run() { .result = result, .string = tchar_pointer_to_u16string(string)}; }, + [&](YaEditController2::GetParamValueByString& request) + -> YaEditController2::GetParamValueByString::Response { + Steinberg::Vst::ParamValue value_normalized; + const tresult result = + object_instances[request.instance_id] + .edit_controller->getParamValueByString( + request.id, + const_cast( + u16string_to_tchar_pointer( + request.string.c_str())), + value_normalized); + + return YaEditController2::GetParamValueByStringResponse{ + .result = result, .value_normalized = value_normalized}; + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From 78d5e3bbfb05b5d95cd1c21d7270cc9caffe3079 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 21:29:02 +0100 Subject: [PATCH 268/456] Implement IEditController::normalizedValueToPlain --- src/common/logging/vst3.cpp | 11 ++++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 1 + .../vst3/plugin/edit-controller.h | 22 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 15 +++++++------ src/wine-host/bridges/vst3.cpp | 9 ++++++-- 6 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index b7bb7d46..c8d57369 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -333,6 +333,17 @@ void Vst3Logger::log_request( }); } +void Vst3Logger::log_request( + bool is_host_vst, + const YaEditController2::NormalizedParamToPlain& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IEditController::normalizedParamToPlain(id = " + << request.id + << ", valueNormalized = " << request.value_normalized << ")"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 37d82312..c5a84b53 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -90,6 +90,8 @@ class Vst3Logger { const YaEditController2::GetParamStringByValue&); void log_request(bool is_host_vst, const YaEditController2::GetParamValueByString&); + void log_request(bool is_host_vst, + const YaEditController2::NormalizedParamToPlain&); void log_request(bool is_host_vst, const YaPluginBase::Initialize&); void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 5960daab..97bf7619 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -81,6 +81,7 @@ using ControlRequest = std::variant; + + native_size_t instance_id; + + Steinberg::Vst::ParamID id; + Steinberg::Vst::ParamValue value_normalized; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(id); + s.value8b(value_normalized); + } + }; + virtual Steinberg::Vst::ParamValue PLUGIN_API normalizedParamToPlain( Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 609d505c..fe1ed8d1 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -276,9 +276,10 @@ Steinberg::Vst::ParamValue PLUGIN_API Vst3PluginProxyImpl::normalizedParamToPlain( Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized) { - // TODO: Implement - bridge.logger.log("TODO IEditController::normalizedParamToPlain()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaEditController2::NormalizedParamToPlain{ + .instance_id = instance_id(), + .id = id, + .value_normalized = valueNormalized}); } Steinberg::Vst::ParamValue PLUGIN_API @@ -339,10 +340,10 @@ tresult PLUGIN_API Vst3PluginProxyImpl::openAboutBox(TBool onlyCheck) { } tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* context) { - // This `context` will likely be an `IHostApplication`. If it is, we will - // store it here, and we'll proxy through all calls to it made from the Wine - // side. Otherwise we'll still call `IPluginBase::initialize()` but with a - // null pointer instead. + // This `context` will likely be an `IHostApplication`. If it is, we + // will store it here, and we'll proxy through all calls to it made from + // the Wine side. Otherwise we'll still call `IPluginBase::initialize()` + // but with a null pointer instead. host_application_context = context; std::optional diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 46a878a7..2c27817d 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -281,7 +281,7 @@ void Vst3Bridge::run() { return YaEditController2::GetParameterInfoResponse{ .result = result, .updated_info = request.info}; }, - [&](YaEditController2::GetParamStringByValue& request) + [&](const YaEditController2::GetParamStringByValue& request) -> YaEditController2::GetParamStringByValue::Response { Steinberg::Vst::String128 string{0}; const tresult result = @@ -293,7 +293,7 @@ void Vst3Bridge::run() { .result = result, .string = tchar_pointer_to_u16string(string)}; }, - [&](YaEditController2::GetParamValueByString& request) + [&](const YaEditController2::GetParamValueByString& request) -> YaEditController2::GetParamValueByString::Response { Steinberg::Vst::ParamValue value_normalized; const tresult result = @@ -308,6 +308,11 @@ void Vst3Bridge::run() { return YaEditController2::GetParamValueByStringResponse{ .result = result, .value_normalized = value_normalized}; }, + [&](const YaEditController2::NormalizedParamToPlain& request) { + return object_instances[request.instance_id] + .edit_controller->normalizedParamToPlain( + request.id, request.value_normalized); + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From fcbf198fee5425340e9416e26d3faeb8a0aa9958 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 21:35:07 +0100 Subject: [PATCH 269/456] Implement IEditController::plainParamToNormalized --- src/common/logging/vst3.cpp | 11 ++++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 1 + .../vst3/plugin/edit-controller.h | 22 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index c8d57369..f5bdc1b8 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -344,6 +344,17 @@ void Vst3Logger::log_request( }); } +void Vst3Logger::log_request( + bool is_host_vst, + const YaEditController2::PlainParamToNormalized& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IEditController::plainParamToNormalized(id = " + << request.id << ", plainValue = " << request.plain_value + << ")"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index c5a84b53..f098ad92 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -92,6 +92,8 @@ class Vst3Logger { const YaEditController2::GetParamValueByString&); void log_request(bool is_host_vst, const YaEditController2::NormalizedParamToPlain&); + void log_request(bool is_host_vst, + const YaEditController2::PlainParamToNormalized&); void log_request(bool is_host_vst, const YaPluginBase::Initialize&); void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 97bf7619..f0e3b2c1 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -82,6 +82,7 @@ using ControlRequest = std::variant; + + native_size_t instance_id; + + Steinberg::Vst::ParamID id; + Steinberg::Vst::ParamValue plain_value; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(id); + s.value8b(plain_value); + } + }; + virtual Steinberg::Vst::ParamValue PLUGIN_API plainParamToNormalized(Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue plainValue) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index fe1ed8d1..c04f4e9f 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -286,9 +286,8 @@ Steinberg::Vst::ParamValue PLUGIN_API Vst3PluginProxyImpl::plainParamToNormalized( Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue plainValue) { - // TODO: Implement - bridge.logger.log("TODO IEditController::plainParamToNormalized()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaEditController2::PlainParamToNormalized{ + .instance_id = instance_id(), .id = id, .plain_value = plainValue}); } Steinberg::Vst::ParamValue PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 2c27817d..5c1e73c2 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -313,6 +313,11 @@ void Vst3Bridge::run() { .edit_controller->normalizedParamToPlain( request.id, request.value_normalized); }, + [&](const YaEditController2::PlainParamToNormalized& request) { + return object_instances[request.instance_id] + .edit_controller->plainParamToNormalized( + request.id, request.plain_value); + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From bc0c13778f886655dca730ae7b13bc8605b75923 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 21:53:04 +0100 Subject: [PATCH 270/456] Implement IEditController::getParamNormalized() --- src/common/logging/vst3.cpp | 10 +++++ src/common/logging/vst3.h | 2 + src/common/serialization/vst3.h | 1 + .../vst3/plugin/edit-controller.h | 40 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 5 +-- src/wine-host/bridges/vst3.cpp | 4 ++ 6 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index f5bdc1b8..620b65a6 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -355,6 +355,16 @@ void Vst3Logger::log_request( }); } +void Vst3Logger::log_request( + bool is_host_vst, + const YaEditController2::GetParamNormalized& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IEditController::getParamNormalized(id = " << request.id + << ")"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index f098ad92..2c0600e0 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -94,6 +94,8 @@ class Vst3Logger { const YaEditController2::NormalizedParamToPlain&); void log_request(bool is_host_vst, const YaEditController2::PlainParamToNormalized&); + void log_request(bool is_host_vst, + const YaEditController2::GetParamNormalized&); void log_request(bool is_host_vst, const YaPluginBase::Initialize&); void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index f0e3b2c1..e36e4e8b 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -83,6 +83,7 @@ using ControlRequest = std::variant; + + native_size_t instance_id; + + Steinberg::Vst::ParamID id; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(id); + } + }; + virtual Steinberg::Vst::ParamValue PLUGIN_API getParamNormalized(Steinberg::Vst::ParamID id) override = 0; + + /** + * Message to pass through a call to + * `IEditController::setParamNormalized(id, value)` to the Wine plugin host. + */ + struct SetParamNormalized { + using Response = UniversalTResult; + + native_size_t instance_id; + + Steinberg::Vst::ParamID id; + Steinberg::Vst::ParamValue value; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(id); + s.value8b(value); + } + }; + virtual tresult PLUGIN_API setParamNormalized(Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue value) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index c04f4e9f..0eed3a5d 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -292,9 +292,8 @@ Vst3PluginProxyImpl::plainParamToNormalized( Steinberg::Vst::ParamValue PLUGIN_API Vst3PluginProxyImpl::getParamNormalized(Steinberg::Vst::ParamID id) { - // TODO: Implement - bridge.logger.log("TODO IEditController::getParamNormalized()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaEditController2::GetParamNormalized{ + .instance_id = instance_id(), .id = id}); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 5c1e73c2..d5ca8487 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -318,6 +318,10 @@ void Vst3Bridge::run() { .edit_controller->plainParamToNormalized( request.id, request.plain_value); }, + [&](const YaEditController2::GetParamNormalized& request) { + return object_instances[request.instance_id] + .edit_controller->getParamNormalized(request.id); + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From 43f5e65b453e8def7950f5a244bc3b5d928b8a6a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 21:53:13 +0100 Subject: [PATCH 271/456] Implement IEditController::setParamNormalized() --- src/common/logging/vst3.cpp | 10 ++++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 1 + src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 620b65a6..dd351d66 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -365,6 +365,16 @@ void Vst3Logger::log_request( }); } +void Vst3Logger::log_request( + bool is_host_vst, + const YaEditController2::SetParamNormalized& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IEditController::setParamNormalized(id = " << request.id + << ", value = " << request.value << ")"; + }); +} + void Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 2c0600e0..d978b464 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -96,6 +96,8 @@ class Vst3Logger { const YaEditController2::PlainParamToNormalized&); void log_request(bool is_host_vst, const YaEditController2::GetParamNormalized&); + void log_request(bool is_host_vst, + const YaEditController2::SetParamNormalized&); void log_request(bool is_host_vst, const YaPluginBase::Initialize&); void log_request(bool is_host_vst, const YaPluginBase::Terminate&); void log_request(bool is_host_vst, const YaPluginFactory::Construct&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index e36e4e8b..ee85fd7b 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -84,6 +84,7 @@ using ControlRequest = std::variantgetParamNormalized(request.id); }, + [&](const YaEditController2::SetParamNormalized& request) { + return object_instances[request.instance_id] + .edit_controller->setParamNormalized(request.id, + request.value); + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From bbcdf9c685b46d13899926206e21137b6bf84799 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 22:14:10 +0100 Subject: [PATCH 272/456] Loosen assertions in VectorStream::write_back --- src/common/serialization/vst3/base.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index 0ef96324..8be06e16 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -229,10 +229,12 @@ tresult VectorStream::write_back(Steinberg::IBStream* stream) const { int32 num_bytes_written = 0; assert(stream->seek(0, kIBSeekSet) == Steinberg::kResultOk); - assert(stream->write(const_cast(buffer.data()), buffer.size(), - &num_bytes_written) == Steinberg::kResultOk); - assert(num_bytes_written == 0 || - static_cast(num_bytes_written) == buffer.size()); + if (stream->write(const_cast(buffer.data()), buffer.size(), + &num_bytes_written) == Steinberg::kResultOk) { + // Some implementations will return `kResultFalse` when writing 0 bytes + assert(num_bytes_written == 0 || + static_cast(num_bytes_written) == buffer.size()); + } return Steinberg::kResultOk; } From 151ca17ed1bd9d3d979b85966c2dec8801d7d6f1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 22:31:06 +0100 Subject: [PATCH 273/456] Don't seek to the beginning when writing stream --- src/common/serialization/vst3/base.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/serialization/vst3/base.cpp b/src/common/serialization/vst3/base.cpp index 8be06e16..dabaf68e 100644 --- a/src/common/serialization/vst3/base.cpp +++ b/src/common/serialization/vst3/base.cpp @@ -227,8 +227,9 @@ tresult VectorStream::write_back(Steinberg::IBStream* stream) const { return Steinberg::kInvalidArgument; } + // A `stream->seek(0, kIBSeekSet)` breaks restoring states in Bitwig. Not + // sure if Bitwig is prepending a header or if this is expected behaviour. int32 num_bytes_written = 0; - assert(stream->seek(0, kIBSeekSet) == Steinberg::kResultOk); if (stream->write(const_cast(buffer.data()), buffer.size(), &num_bytes_written) == Steinberg::kResultOk) { // Some implementations will return `kResultFalse` when writing 0 bytes From 70e01e17c1c90b85033251026776ec1e89ae4034 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 22:54:53 +0100 Subject: [PATCH 274/456] Add note wrong parameter value strings in Bitwig --- src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 50f99432..6c556a63 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -247,6 +247,9 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getParamStringByValue( Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized /*in*/, Steinberg::Vst::String128 string /*out*/) { + // FIXME: In Bitwig we sometimes write old values when moving a knob very + // quickly. The issue is not that responses are received out of + // order. const GetParamStringByValueResponse response = bridge.send_message(YaEditController2::GetParamStringByValue{ .instance_id = instance_id(), From 24fc584fca86c825412f0a83521eea2bd932e07c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 23:05:03 +0100 Subject: [PATCH 275/456] Hide a few more messages on verbosity level 1 --- src/common/logging/vst3.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index dd351d66..82e98863 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -107,12 +107,14 @@ void Vst3Logger::log_request( void Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::CanProcessSampleSize& request) { - log_request_base(is_host_vst, [&](auto& message) { - message - << request.instance_id - << ": IAudioProcessor::canProcessSampleSize(symbolicSampleSize = " - << request.symbolic_sample_size << ")"; - }); + log_request_base( + is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { + message + << request.instance_id + << ": IAudioProcessor::canProcessSampleSize(symbolicSampleSize " + "= " + << request.symbolic_sample_size << ")"; + }); } void Vst3Logger::log_request( @@ -207,9 +209,11 @@ void Vst3Logger::log_request(bool is_host_vst, void Vst3Logger::log_request(bool is_host_vst, const YaAudioProcessor::GetTailSamples& request) { - log_request_base(is_host_vst, [&](auto& message) { - message << request.instance_id << ": IAudioProcessor::getTailSamples()"; - }); + log_request_base(is_host_vst, Logger::Verbosity::all_events, + [&](auto& message) { + message << request.instance_id + << ": IAudioProcessor::getTailSamples()"; + }); } void Vst3Logger::log_request(bool is_host_vst, From 6734962886c87c8a2a9f7dabef5a771cbf9545af Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 23:08:18 +0100 Subject: [PATCH 276/456] Add todo about filtering response log messages --- src/common/logging/vst3.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index d978b464..c321d61a 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -139,6 +139,10 @@ class Vst3Logger { /** * Log a request with a standard prefix based on the boolean flag we pass to * every logging function so we don't have to repeat it everywhere. + * + * TODO: Make these logging messages return a boolean based on whether or + * not it was filtered out. Don't show the responses for filtered out + * messages. */ template F> void log_request_base(bool is_host_vst, From 82512499591495a0caf14bb9c5110f4815fbfddd Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 18 Dec 2020 23:10:07 +0100 Subject: [PATCH 277/456] Remove fixme about out of order messages This appears to 100% be a bug in ValhallaSuperMassive. --- src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 6c556a63..50f99432 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -247,9 +247,6 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getParamStringByValue( Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized /*in*/, Steinberg::Vst::String128 string /*out*/) { - // FIXME: In Bitwig we sometimes write old values when moving a knob very - // quickly. The issue is not that responses are received out of - // order. const GetParamStringByValueResponse response = bridge.send_message(YaEditController2::GetParamStringByValue{ .instance_id = instance_id(), From 71493299ec6b31c1c7424c0d5f8c30dc001933f1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 12:39:08 +0100 Subject: [PATCH 278/456] Implement IConnectionPoint::disconnect --- README.md | 3 ++- src/common/logging/vst3.cpp | 9 +++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/README.md | 2 +- .../vst3/plugin/connection-point.h | 27 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 16 ++++++++--- src/wine-host/bridges/vst3.cpp | 9 +++++++ 8 files changed, 63 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e422afc6..82e9a8a1 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ incomplete list of things that still have to be done before this can be used: - Interfaces left to implement: - `IHostApplication::createComponent()` - - The other parts of `IConnectionPoint`, including support for host provided proxies + - `IConnectionPoint::notify()`, and support for indirectly connecting + components through message passing proxies - Finish implementing `IEditController{,2}` - All other mandatory interfaces - All other optional interfaces diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 82e98863..50321f3f 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -283,6 +283,15 @@ void Vst3Logger::log_request(bool is_host_vst, }); } +void Vst3Logger::log_request(bool is_host_vst, + const YaConnectionPoint::Disconnect& request) { + log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IConnectionPoint::disconnect(other = )"; + }); +} + void Vst3Logger::log_request( bool is_host_vst, const YaEditController2::SetComponentState& request) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index c321d61a..b4e07bad 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -80,6 +80,7 @@ class Vst3Logger { void log_request(bool is_host_vst, const YaComponent::ActivateBus&); void log_request(bool is_host_vst, const YaComponent::SetActive&); void log_request(bool is_host_vst, const YaConnectionPoint::Connect&); + void log_request(bool is_host_vst, const YaConnectionPoint::Disconnect&); void log_request(bool is_host_vst, const YaEditController2::SetComponentState&); void log_request(bool is_host_vst, diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index ee85fd7b..1f2d6ba9 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -76,6 +76,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(instance_id); + s.value8b(other_instance_id); + } + }; + virtual tresult PLUGIN_API disconnect(IConnectionPoint* other) override = 0; virtual tresult PLUGIN_API notify(Steinberg::Vst::IMessage* message) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 50f99432..d9dc4a2d 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -207,9 +207,19 @@ tresult PLUGIN_API Vst3PluginProxyImpl::connect(IConnectionPoint* other) { } tresult PLUGIN_API Vst3PluginProxyImpl::disconnect(IConnectionPoint* other) { - // TODO: Implement - bridge.logger.log("TODO IConnectionPoint::disconnect()"); - return Steinberg::kNotImplemented; + // See `Vst3PluginProxyImpl::connect()` + if (auto other_proxy = dynamic_cast(other)) { + return bridge.send_message(YaConnectionPoint::Disconnect{ + .instance_id = instance_id(), + .other_instance_id = other_proxy->instance_id()}); + } else { + // TODO: Add support for `ConnectionProxy` and similar objects + bridge.logger.log( + "WARNING: The host passed a proxy proxy object to " + "'IConnectionPoint::disconnect()'. This is currently not " + "supported."); + return Steinberg::kNotImplemented; + } } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index fa45e490..f7f16e46 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -261,6 +261,15 @@ void Vst3Bridge::run() { object_instances[request.other_instance_id] .connection_point); }, + [&](const YaConnectionPoint::Disconnect& request) + -> YaConnectionPoint::Disconnect::Response { + // TODO: Add support for connecting objects through a proxy + // object provided by the host + return object_instances[request.instance_id] + .connection_point->disconnect( + object_instances[request.other_instance_id] + .connection_point); + }, [&](YaEditController2::SetComponentState& request) -> YaEditController2::SetComponentState::Response { return object_instances[request.instance_id] From 38c37f2721c3fe954a2fec67eda80bb4e8a3c5f5 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 12:48:18 +0100 Subject: [PATCH 279/456] Don't log responses for filtered out requests --- src/common/communication/vst3.h | 17 ++- src/common/logging/vst3.cpp | 210 ++++++++++++++++---------------- src/common/logging/vst3.h | 118 +++++++++--------- 3 files changed, 175 insertions(+), 170 deletions(-) diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 714a738a..1d9a72f9 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -106,9 +106,13 @@ class Vst3MessageHandler : public AdHocSocketHandler { std::optional> logging) { using TResponse = typename T::Response; + // Since a lot of messages just return a `tresult`, we can't filter out + // responses based on the response message type. Instead, we'll just + // only print the responses when the request was not filtered out. + bool should_log_response = false; if (logging) { auto [logger, is_host_vst] = *logging; - logger.log_request(is_host_vst, object); + should_log_response = logger.log_request(is_host_vst, object); } // A socket only handles a single request at a time as to prevent @@ -125,7 +129,7 @@ class Vst3MessageHandler : public AdHocSocketHandler { return std::monostate{}; }); - if (logging) { + if (should_log_response) { auto [logger, is_host_vst] = *logging; logger.log_response(!is_host_vst, response_object); } @@ -165,11 +169,14 @@ class Vst3MessageHandler : public AdHocSocketHandler { const auto process_message = [&](boost::asio::local::stream_protocol::socket& socket) { auto request = read_object(socket); + + // See the comment in `receive_into()` for more information + bool should_log_response = false; if (logging) { - std::visit( + should_log_response = std::visit( [&](const auto& object) { auto [logger, is_host_vst] = *logging; - logger.log_request(is_host_vst, object); + return logger.log_request(is_host_vst, object); }, request); } @@ -181,7 +188,7 @@ class Vst3MessageHandler : public AdHocSocketHandler { [&](T object) { typename T::Response response = callback(object); - if (logging) { + if (should_log_response) { auto [logger, is_host_vst] = *logging; logger.log_response(!is_host_vst, response); } diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 50321f3f..0a3b6610 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -35,9 +35,9 @@ void Vst3Logger::log_unknown_interface( } } -void Vst3Logger::log_request(bool is_host_vst, +bool Vst3Logger::log_request(bool is_host_vst, const Vst3PluginProxy::Construct& request) { - log_request_base(is_host_vst, [&](auto& message) { + return log_request_base(is_host_vst, [&](auto& message) { message << "IPluginFactory::createComponent(cid = " << format_uid(Steinberg::FUID::fromTUID(request.cid.data())) << ", _iid = "; @@ -53,18 +53,18 @@ void Vst3Logger::log_request(bool is_host_vst, }); } -void Vst3Logger::log_request(bool is_host_vst, +bool Vst3Logger::log_request(bool is_host_vst, const Vst3PluginProxy::Destruct& request) { - log_request_base(is_host_vst, [&](auto& message) { + return log_request_base(is_host_vst, [&](auto& message) { // We don't know what class this instance was originally instantiated // as, but it also doesn't really matter message << request.instance_id << ": FUnknown::~FUnknown()"; }); } -void Vst3Logger::log_request(bool is_host_vst, +bool Vst3Logger::log_request(bool is_host_vst, const Vst3PluginProxy::SetState& request) { - log_request_base(is_host_vst, [&](auto& message) { + return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id << ": {IComponent,IEditController}::setState(state = " ")"; }); } -void Vst3Logger::log_request( +bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { - log_request_base(is_host_vst, [&](auto& message) { + return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id << ": IAudioProcessor::setBusArrangements(inputs = " "[SpeakerArrangement; " @@ -94,20 +94,20 @@ void Vst3Logger::log_request( }); } -void Vst3Logger::log_request( +bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::GetBusArrangement& request) { - log_request_base(is_host_vst, [&](auto& message) { + return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id << ": IAudioProcessor::getBusArrangement(dir = " << request.dir << ", index = " << request.index << ", &arr)"; }); } -void Vst3Logger::log_request( +bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::CanProcessSampleSize& request) { - log_request_base( + return log_request_base( is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { message << request.instance_id @@ -117,18 +117,18 @@ void Vst3Logger::log_request( }); } -void Vst3Logger::log_request( +bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::GetLatencySamples& request) { - log_request_base(is_host_vst, [&](auto& message) { + return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id << ": IAudioProcessor::getLatencySamples()"; }); } -void Vst3Logger::log_request(bool is_host_vst, +bool Vst3Logger::log_request(bool is_host_vst, const YaAudioProcessor::SetupProcessing& request) { - log_request_base(is_host_vst, [&](auto& message) { + return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id << ": IAudioProcessor::setupProcessing(setup = " ")"; }); } -void Vst3Logger::log_request(bool is_host_vst, +bool Vst3Logger::log_request(bool is_host_vst, const YaConnectionPoint::Disconnect& request) { - log_request_base(is_host_vst, [&](auto& message) { + return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id << ": IConnectionPoint::disconnect(other = )"; }); } -void Vst3Logger::log_request( +bool Vst3Logger::log_request( bool is_host_vst, const YaEditController2::SetComponentState& request) { - log_request_base(is_host_vst, [&](auto& message) { + return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id << ": IEditController::setComponentState(state = "; }); } @@ -473,44 +473,42 @@ void Vst3Logger::log_response( void Vst3Logger::log_response( bool is_host_vst, const YaAudioProcessor::ProcessResponse& response) { - log_response_base( - is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { - message << response.result.string(); + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); - // This is incredibly verbose, but if you're really a plugin that - // handles processing in a weird way you're going to need all of - // this + // This is incredibly verbose, but if you're really a plugin that + // handles processing in a weird way you're going to need all of this - std::ostringstream num_output_channels; - num_output_channels << "["; - for (bool is_first = true; - const auto& buffers : response.output_data.outputs) { - num_output_channels << (is_first ? "" : ", ") - << buffers.num_channels(); - is_first = false; - } - num_output_channels << "]"; + std::ostringstream num_output_channels; + num_output_channels << "["; + for (bool is_first = true; + const auto& buffers : response.output_data.outputs) { + num_output_channels << (is_first ? "" : ", ") + << buffers.num_channels(); + is_first = false; + } + num_output_channels << "]"; - message << ", "; + message << ", "; - if (response.output_data.output_parameter_changes) { - message << ", num_parameters() - << " parameters>"; - } else { - message << ", host does not support parameter outputs"; - } + if (response.output_data.output_parameter_changes) { + message << ", num_parameters() + << " parameters>"; + } else { + message << ", host does not support parameter outputs"; + } - if (response.output_data.output_events) { - message << ", num_events() - << " events>"; - } else { - message << ", host does not support event outputs"; - } - }); + if (response.output_data.output_events) { + message << ", num_events() + << " events>"; + } else { + message << ", host does not support event outputs"; + } + }); } void Vst3Logger::log_response(bool is_host_vst, diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index b4e07bad..52c476e8 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -55,55 +55,58 @@ class Vst3Logger { // that print information about the request and the response. The boolean // flag here indicates whether the request was initiated on the host side // (what we'll call a control message). + // `log_response()` should only be called if the corresponding + // `log_request()` call returned `true`. This way we can filter out the + // log message for the response together with the request. - void log_request(bool is_host_vst, const Vst3PluginProxy::Construct&); - void log_request(bool is_host_vst, const Vst3PluginProxy::Destruct&); - void log_request(bool is_host_vst, const Vst3PluginProxy::SetState&); - void log_request(bool is_host_vst, const Vst3PluginProxy::GetState&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const Vst3PluginProxy::Construct&); + bool log_request(bool is_host_vst, const Vst3PluginProxy::Destruct&); + bool log_request(bool is_host_vst, const Vst3PluginProxy::SetState&); + bool log_request(bool is_host_vst, const Vst3PluginProxy::GetState&); + bool log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaAudioProcessor::GetBusArrangement&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaAudioProcessor::CanProcessSampleSize&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaAudioProcessor::GetLatencySamples&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaAudioProcessor::SetupProcessing&); - void log_request(bool is_host_vst, const YaAudioProcessor::SetProcessing&); - void log_request(bool is_host_vst, const YaAudioProcessor::Process&); - void log_request(bool is_host_vst, const YaAudioProcessor::GetTailSamples&); - void log_request(bool is_host_vst, const YaComponent::SetIoMode&); - void log_request(bool is_host_vst, const YaComponent::GetBusCount&); - void log_request(bool is_host_vst, const YaComponent::GetBusInfo&); - void log_request(bool is_host_vst, const YaComponent::GetRoutingInfo&); - void log_request(bool is_host_vst, const YaComponent::ActivateBus&); - void log_request(bool is_host_vst, const YaComponent::SetActive&); - void log_request(bool is_host_vst, const YaConnectionPoint::Connect&); - void log_request(bool is_host_vst, const YaConnectionPoint::Disconnect&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaAudioProcessor::SetProcessing&); + bool log_request(bool is_host_vst, const YaAudioProcessor::Process&); + bool log_request(bool is_host_vst, const YaAudioProcessor::GetTailSamples&); + bool log_request(bool is_host_vst, const YaComponent::SetIoMode&); + bool log_request(bool is_host_vst, const YaComponent::GetBusCount&); + bool log_request(bool is_host_vst, const YaComponent::GetBusInfo&); + bool log_request(bool is_host_vst, const YaComponent::GetRoutingInfo&); + bool log_request(bool is_host_vst, const YaComponent::ActivateBus&); + bool log_request(bool is_host_vst, const YaComponent::SetActive&); + bool log_request(bool is_host_vst, const YaConnectionPoint::Connect&); + bool log_request(bool is_host_vst, const YaConnectionPoint::Disconnect&); + bool log_request(bool is_host_vst, const YaEditController2::SetComponentState&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaEditController2::GetParameterCount&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaEditController2::GetParameterInfo&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaEditController2::GetParamStringByValue&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaEditController2::GetParamValueByString&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaEditController2::NormalizedParamToPlain&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaEditController2::PlainParamToNormalized&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaEditController2::GetParamNormalized&); - void log_request(bool is_host_vst, + bool log_request(bool is_host_vst, const YaEditController2::SetParamNormalized&); - void log_request(bool is_host_vst, const YaPluginBase::Initialize&); - void log_request(bool is_host_vst, const YaPluginBase::Terminate&); - void log_request(bool is_host_vst, const YaPluginFactory::Construct&); - void log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); - void log_request(bool is_host_vst, const WantsConfiguration&); + bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); + bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); + bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); + bool log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); + bool log_request(bool is_host_vst, const WantsConfiguration&); void log_response(bool is_host_vst, const Ack&); void log_response( @@ -141,12 +144,11 @@ class Vst3Logger { * Log a request with a standard prefix based on the boolean flag we pass to * every logging function so we don't have to repeat it everywhere. * - * TODO: Make these logging messages return a boolean based on whether or - * not it was filtered out. Don't show the responses for filtered out - * messages. + * Returns `true` if the log message was displayed, and the response should + * thus also be logged. */ template F> - void log_request_base(bool is_host_vst, + bool log_request_base(bool is_host_vst, Logger::Verbosity min_verbosity, F callback) { if (BOOST_UNLIKELY(logger.verbosity >= min_verbosity)) { @@ -159,38 +161,36 @@ class Vst3Logger { callback(message); log(message.str()); + + return true; + } else { + return false; } } template F> - void log_request_base(bool is_host_vst, F callback) { - log_request_base(is_host_vst, Logger::Verbosity::most_events, callback); + bool log_request_base(bool is_host_vst, F callback) { + return log_request_base(is_host_vst, Logger::Verbosity::most_events, + callback); } /** * Log a response with a standard prefix based on the boolean flag we pass * to every logging function so we don't have to repeat it everywhere. + * + * This should only be called when the corresonding `log_request()` returned + * `true`. */ - template F> - void log_response_base(bool is_host_vst, - Logger::Verbosity min_verbosity, - F callback) { - if (BOOST_UNLIKELY(logger.verbosity >= min_verbosity)) { - std::ostringstream message; - if (is_host_vst) { - message << "[host -> vst] "; - } else { - message << "[host <- vst] "; - } - - callback(message); - log(message.str()); - } - } - template F> void log_response_base(bool is_host_vst, F callback) { - log_response_base(is_host_vst, Logger::Verbosity::most_events, - callback); + std::ostringstream message; + if (is_host_vst) { + message << "[host -> vst] "; + } else { + message << "[host <- vst] "; + } + + callback(message); + log(message.str()); } }; From fa256ab9be42dd19f02368cbb908b5ad62eb00c9 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 12:51:00 +0100 Subject: [PATCH 280/456] Only log IComponent::getBusCount() on verbosity 2 --- src/common/logging/vst3.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 0a3b6610..8b47f2a5 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -226,11 +226,14 @@ bool Vst3Logger::log_request(bool is_host_vst, bool Vst3Logger::log_request(bool is_host_vst, const YaComponent::GetBusCount& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message << request.instance_id - << ": IComponent::getBusCount(type = " << request.type - << ", dir = " << request.dir << ")"; - }); + // JUCE-based hosts will call this every processing cycle, for some reason + // (it shouldn't be allwoed to change during processing, right?) + return log_request_base( + is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { + message << request.instance_id + << ": IComponent::getBusCount(type = " << request.type + << ", dir = " << request.dir << ")"; + }); } bool Vst3Logger::log_request(bool is_host_vst, From a197ad897c8297a80390d2a75f1d3fed3e18d76e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 13:01:14 +0100 Subject: [PATCH 281/456] Clean up IPluginFactory::createInstance() We'll just do all the checks up front, that's much cleaner and also safer. --- .../bridges/vst3-impls/plugin-factory.cpp | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 969b0805..26f205c1 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -28,18 +28,20 @@ tresult PLUGIN_API YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, void** obj) { - if (!_iid || !cid) { + constexpr size_t uid_size = sizeof(Steinberg::TUID); + if (!cid || !_iid || strnlen(cid, uid_size) < uid_size || + strnlen(_iid, uid_size) < uid_size) { return Steinberg::kInvalidArgument; } ArrayUID cid_array; - std::copy(cid, cid + sizeof(Steinberg::TUID), cid_array.begin()); + std::copy(cid, cid + std::extent_v, cid_array.begin()); // FIXME: `_iid` in Bitwig Studio 3.3.1 is not null terminated, and the // comparison below will thus fail since the strings have different - // lengths. We'll temporarily work around this, and if this end sup - // not being us doing something strange then we should report it. - constexpr size_t uid_size = sizeof(Steinberg::TUID); + // lengths. Since it looks like the module implementation that comes + // with the SDK has this same issue I think it might just be a case + // of Steinberg not following its own specifications. std::string iid_string(_iid, uid_size); Vst3PluginProxy::Construct::Interface requested_interface; @@ -55,12 +57,8 @@ YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, // print a recognizable log message. I don't think they include a safe // way to convert a `FIDString/char*` into a `FUID`, so this will have // to do. - std::optional uid; - if (_iid && strnlen(_iid, uid_size) >= uid_size) { - uid = Steinberg::FUID::fromTUID( - *reinterpret_cast(&*_iid)); - } - + const Steinberg::FUID uid = Steinberg::FUID::fromTUID( + *reinterpret_cast(&*_iid)); bridge.logger.log_unknown_interface( "In IPluginFactory::createInstance()", uid); From 95b30b069e9997d89000eca01b3725d7aa3ea0fe Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 13:23:53 +0100 Subject: [PATCH 282/456] Include libyabridge-vst3.so in the build artifacts --- .github/workflows/build.yml | 4 ++-- README.md | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e8e3bca1..0e796134 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,7 +53,7 @@ jobs: - name: Create an archive for the binaries run: | mkdir yabridge - cp build/libyabridge-vst2.so build/yabridge-{host,group}{,-32}.exe{,.so} yabridge + cp build/libyabridge-vst{2,3}.so build/yabridge-{host,group}{,-32}.exe{,.so} yabridge cp CHANGELOG.md README.md yabridge tar -caf "$ARCHIVE_NAME" yabridge @@ -92,7 +92,7 @@ jobs: - name: Create an archive for the binaries run: | mkdir yabridge - cp build/libyabridge-vst2.so build/yabridge-{host,group}{,-32}.exe{,.so} yabridge + cp build/libyabridge-vst{2,3}.so build/yabridge-{host,group}{,-32}.exe{,.so} yabridge cp CHANGELOG.md README.md yabridge tar -caf "$ARCHIVE_NAME" yabridge diff --git a/README.md b/README.md index 82e9a8a1..1e180a09 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ incomplete list of things that still have to be done before this can be used: - All other optional interfaces - Fully implemented: see [this document](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/src/common/serialization/vst3/README.md) -- Update the GitHub Actions workflows. - Update yabridgectl to handle buth VST2 and VST3 plugins. - Update all documentation to refer to VST2 and VST3 support separately, and figure out how to do this in the least confusing way possible. @@ -33,7 +32,7 @@ incomplete list of things that still have to be done before this can be used: - Pay close attention when updating the plugin groups section of the readme, since VST3 plugins by design cannot be hosted completely individually (as in, each plugin is basically in its own group). -- Update all the AUR packages. +- Update all the AUR packages for the `libyabridge-vst{2,3}.so` changes. - Test the binaries built on GitHub on plain Ubuntu 18.04, are we missing any static linking? - When this is in a usable enough state to be merged into master, make sure to From be78ff50be34140bf8190befbc2d972020d7a19c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 13:27:00 +0100 Subject: [PATCH 283/456] Update Ya*::ConstructArgs docstrings --- src/common/serialization/vst3/host-application.h | 3 +-- src/common/serialization/vst3/plugin-factory.h | 2 +- src/common/serialization/vst3/plugin-proxy.h | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/common/serialization/vst3/host-application.h b/src/common/serialization/vst3/host-application.h index 3b871bae..be0ec11b 100644 --- a/src/common/serialization/vst3/host-application.h +++ b/src/common/serialization/vst3/host-application.h @@ -41,8 +41,7 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { public: /** - * These are the arguments for creating a - * `YaYaHostApplication{Plugin,Host}Impl`. + * These are the arguments for constructing a `YaHostApplicationImpl`. */ struct ConstructArgs { ConstructArgs(); diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index a37a39a0..bf2a554e 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -34,7 +34,7 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { public: /** - * These are the arguments for creating a `YaPluginFactoryImpl`. + * These are the arguments for constructing a `YaPluginFactoryImpl`. */ struct ConstructArgs { ConstructArgs(); diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index cdd1f79b..5b69e2e3 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -61,7 +61,7 @@ class Vst3PluginProxy : public YaAudioProcessor, public YaPluginBase { public: /** - * These are the arguments for creating a `Vst3PluginProxyImpl`. + * These are the arguments for constructing a `Vst3PluginProxyImpl`. */ struct ConstructArgs { ConstructArgs(); From 1ede385784d0d107919cf7d3e1e069c611a390c3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 13:40:37 +0100 Subject: [PATCH 284/456] Clean up YaHostApplication --- .../serialization/vst3/host-application.cpp | 4 +-- .../serialization/vst3/host-application.h | 29 ++++++++----------- .../bridges/vst3-impls/host-application.h | 2 +- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/common/serialization/vst3/host-application.cpp b/src/common/serialization/vst3/host-application.cpp index 044f4d7a..1b8b4b88 100644 --- a/src/common/serialization/vst3/host-application.cpp +++ b/src/common/serialization/vst3/host-application.cpp @@ -20,8 +20,8 @@ YaHostApplication::ConstructArgs::ConstructArgs() {} YaHostApplication::ConstructArgs::ConstructArgs( Steinberg::IPtr context, - std::optional component_instance_id) - : component_instance_id(component_instance_id) { + std::optional owner_instance_id) + : owner_instance_id(owner_instance_id) { Steinberg::Vst::String128 name_array; if (context->getName(name_array) == Steinberg::kResultOk) { name = tchar_pointer_to_u16string(name_array); diff --git a/src/common/serialization/vst3/host-application.h b/src/common/serialization/vst3/host-application.h index be0ec11b..9ad1a586 100644 --- a/src/common/serialization/vst3/host-application.h +++ b/src/common/serialization/vst3/host-application.h @@ -29,14 +29,12 @@ #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" /** - * Wraps around `IHostApplication` for serialization purposes. See `README.md` - * for more information on how this works. This is used both to proxy the host - * application context passed during `IPluginBase::intialize()` as well as for - * `IPluginFactory3::setHostContext()`. This interface is thus implemented on - * both the native plugin side as well as the Wine plugin host side. - * - * TODO: When implementing more host interfaces, also rework this into a - * monolithic proxy class like with the plugin. + * Wraps around `IHostApplication` for serialization purposes. An instance of + * this proxy object will be initialized on the Wine plugin host side after the + * host passes an actual instance to the plugin, and all function calls made to + * this proxy will be passed through to the actual object. This is used to proxy + * both the host application context passed during `IPluginBase::intialize()` as + * well as for the 'global' context in `IPluginFactory3::setHostContext()`. */ class YaHostApplication : public Steinberg::Vst::IHostApplication { public: @@ -50,15 +48,14 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { * Read arguments from an existing implementation. */ ConstructArgs(Steinberg::IPtr context, - std::optional component_instance_id); + std::optional owner_instance_id); /** - * The unique instance identifier of the component this host context has - * been passed to and thus belongs to, if we are handling - * `IpluginBase::initialize()`. When handling - * `IPluginFactory::setHostContext()` this will be empty. + * The unique instance identifier of the proxy object instance this host + * context has been passed to and thus belongs to. If we are handling + * When handling `IPluginFactory::setHostContext()` this will be empty. */ - std::optional component_instance_id; + std::optional owner_instance_id; /** * For `IHostApplication::getName`. @@ -67,7 +64,7 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { template void serialize(S& s) { - s.ext(component_instance_id, bitsery::ext::StdOptional{}, + s.ext(owner_instance_id, bitsery::ext::StdOptional{}, [](S& s, native_size_t& instance_id) { s.value8b(instance_id); }); @@ -87,8 +84,6 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { * `Destruct` messages. This object's lifetime is bound to that of the * objects they are passed to. If those objects get dropped, then the host * contexts should also be dropped. - * - * TODO: Check if this ends up working out this way */ YaHostApplication(const ConstructArgs&& args); diff --git a/src/wine-host/bridges/vst3-impls/host-application.h b/src/wine-host/bridges/vst3-impls/host-application.h index 29170dc6..9d7f5c29 100644 --- a/src/wine-host/bridges/vst3-impls/host-application.h +++ b/src/wine-host/bridges/vst3-impls/host-application.h @@ -23,7 +23,7 @@ class YaHostApplicationImpl : public YaHostApplication { public: YaHostApplicationImpl(Vst3Bridge& bridge, - YaHostApplication::ConstructArgs&& args); + YaHostApplication::ConstructArgs&& args); /** * We'll override the query interface to log queries for interfaces we do From 7e3568e333ebea521a26b281006ae25614fec773 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 13:48:21 +0100 Subject: [PATCH 285/456] Rename YaEditController2 to YaEditController Adding versions to our implementations doesn't work when the versions and extensions start becoming non-numerical. This is what happened with `IComponentHandler`. --- src/common/logging/vst3.cpp | 24 ++++++------- src/common/logging/vst3.h | 24 ++++++------- src/common/serialization/vst3.h | 18 +++++----- .../serialization/vst3/plugin-proxy.cpp | 6 ++-- src/common/serialization/vst3/plugin-proxy.h | 4 +-- .../vst3/plugin/edit-controller.cpp | 6 ++-- .../vst3/plugin/edit-controller.h | 8 ++--- .../bridges/vst3-impls/plugin-proxy.cpp | 22 ++++++------ src/wine-host/bridges/vst3.cpp | 34 +++++++++---------- 9 files changed, 73 insertions(+), 73 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 8b47f2a5..51e6852d 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -297,7 +297,7 @@ bool Vst3Logger::log_request(bool is_host_vst, bool Vst3Logger::log_request( bool is_host_vst, - const YaEditController2::SetComponentState& request) { + const YaEditController::SetComponentState& request) { return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id << ": IEditController::setComponentState(state = object) : supported_version_1( Steinberg::FUnknownPtr(object)), supported_version_2( Steinberg::FUnknownPtr(object)) {} -YaEditController2::YaEditController2(const ConstructArgs&& args) +YaEditController::YaEditController(const ConstructArgs&& args) : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin/edit-controller.h b/src/common/serialization/vst3/plugin/edit-controller.h index 23458c75..abdf681c 100644 --- a/src/common/serialization/vst3/plugin/edit-controller.h +++ b/src/common/serialization/vst3/plugin/edit-controller.h @@ -31,11 +31,11 @@ * Steinberg forgot to inherit `IEditController2` from `IEditController` event * if it says it does in the docs, so we'll pretend they just that. */ -class YaEditController2 : public Steinberg::Vst::IEditController, - public Steinberg::Vst::IEditController2 { +class YaEditController : public Steinberg::Vst::IEditController, + public Steinberg::Vst::IEditController2 { public: /** - * These are the arguments for creating a `YaEditController2`. + * These are the arguments for creating a `YaEditController`. */ struct ConstructArgs { ConstructArgs(); @@ -67,7 +67,7 @@ class YaEditController2 : public Steinberg::Vst::IEditController, * Instantiate this instance with arguments read from another interface * implementation. */ - YaEditController2(const ConstructArgs&& args); + YaEditController(const ConstructArgs&& args); inline bool supported_version_1() const { return arguments.supported_version_1; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index d9dc4a2d..8405dd35 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -231,22 +231,22 @@ Vst3PluginProxyImpl::notify(Steinberg::Vst::IMessage* message) { tresult PLUGIN_API Vst3PluginProxyImpl::setComponentState(Steinberg::IBStream* state) { - return bridge.send_message(YaEditController2::SetComponentState{ + return bridge.send_message(YaEditController::SetComponentState{ .instance_id = instance_id(), .state = state}); } int32 PLUGIN_API Vst3PluginProxyImpl::getParameterCount() { return bridge.send_message( - YaEditController2::GetParameterCount{.instance_id = instance_id()}); + YaEditController::GetParameterCount{.instance_id = instance_id()}); } tresult PLUGIN_API Vst3PluginProxyImpl::getParameterInfo( int32 paramIndex, Steinberg::Vst::ParameterInfo& info /*out*/) { const GetParameterInfoResponse response = bridge.send_message( - YaEditController2::GetParameterInfo{.instance_id = instance_id(), - .param_index = paramIndex, - .info = info}); + YaEditController::GetParameterInfo{.instance_id = instance_id(), + .param_index = paramIndex, + .info = info}); info = response.updated_info; @@ -258,7 +258,7 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getParamStringByValue( Steinberg::Vst::ParamValue valueNormalized /*in*/, Steinberg::Vst::String128 string /*out*/) { const GetParamStringByValueResponse response = - bridge.send_message(YaEditController2::GetParamStringByValue{ + bridge.send_message(YaEditController::GetParamStringByValue{ .instance_id = instance_id(), .id = id, .value_normalized = valueNormalized}); @@ -274,7 +274,7 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getParamValueByString( Steinberg::Vst::TChar* string /*in*/, Steinberg::Vst::ParamValue& valueNormalized /*out*/) { const GetParamValueByStringResponse response = - bridge.send_message(YaEditController2::GetParamValueByString{ + bridge.send_message(YaEditController::GetParamValueByString{ .instance_id = instance_id(), .id = id, .string = string}); valueNormalized = response.value_normalized; @@ -286,7 +286,7 @@ Steinberg::Vst::ParamValue PLUGIN_API Vst3PluginProxyImpl::normalizedParamToPlain( Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized) { - return bridge.send_message(YaEditController2::NormalizedParamToPlain{ + return bridge.send_message(YaEditController::NormalizedParamToPlain{ .instance_id = instance_id(), .id = id, .value_normalized = valueNormalized}); @@ -296,20 +296,20 @@ Steinberg::Vst::ParamValue PLUGIN_API Vst3PluginProxyImpl::plainParamToNormalized( Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue plainValue) { - return bridge.send_message(YaEditController2::PlainParamToNormalized{ + return bridge.send_message(YaEditController::PlainParamToNormalized{ .instance_id = instance_id(), .id = id, .plain_value = plainValue}); } Steinberg::Vst::ParamValue PLUGIN_API Vst3PluginProxyImpl::getParamNormalized(Steinberg::Vst::ParamID id) { - return bridge.send_message(YaEditController2::GetParamNormalized{ + return bridge.send_message(YaEditController::GetParamNormalized{ .instance_id = instance_id(), .id = id}); } tresult PLUGIN_API Vst3PluginProxyImpl::setParamNormalized(Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue value) { - return bridge.send_message(YaEditController2::SetParamNormalized{ + return bridge.send_message(YaEditController::SetParamNormalized{ .instance_id = instance_id(), .id = id, .value = value}); } diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index f7f16e46..8c8c1dc9 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -270,40 +270,40 @@ void Vst3Bridge::run() { object_instances[request.other_instance_id] .connection_point); }, - [&](YaEditController2::SetComponentState& request) - -> YaEditController2::SetComponentState::Response { + [&](YaEditController::SetComponentState& request) + -> YaEditController::SetComponentState::Response { return object_instances[request.instance_id] .edit_controller->setComponentState(&request.state); }, - [&](const YaEditController2::GetParameterCount& request) - -> YaEditController2::GetParameterCount::Response { + [&](const YaEditController::GetParameterCount& request) + -> YaEditController::GetParameterCount::Response { return object_instances[request.instance_id] .edit_controller->getParameterCount(); }, - [&](YaEditController2::GetParameterInfo& request) - -> YaEditController2::GetParameterInfo::Response { + [&](YaEditController::GetParameterInfo& request) + -> YaEditController::GetParameterInfo::Response { const tresult result = object_instances[request.instance_id] .edit_controller->getParameterInfo(request.param_index, request.info); - return YaEditController2::GetParameterInfoResponse{ + return YaEditController::GetParameterInfoResponse{ .result = result, .updated_info = request.info}; }, - [&](const YaEditController2::GetParamStringByValue& request) - -> YaEditController2::GetParamStringByValue::Response { + [&](const YaEditController::GetParamStringByValue& request) + -> YaEditController::GetParamStringByValue::Response { Steinberg::Vst::String128 string{0}; const tresult result = object_instances[request.instance_id] .edit_controller->getParamStringByValue( request.id, request.value_normalized, string); - return YaEditController2::GetParamStringByValueResponse{ + return YaEditController::GetParamStringByValueResponse{ .result = result, .string = tchar_pointer_to_u16string(string)}; }, - [&](const YaEditController2::GetParamValueByString& request) - -> YaEditController2::GetParamValueByString::Response { + [&](const YaEditController::GetParamValueByString& request) + -> YaEditController::GetParamValueByString::Response { Steinberg::Vst::ParamValue value_normalized; const tresult result = object_instances[request.instance_id] @@ -314,24 +314,24 @@ void Vst3Bridge::run() { request.string.c_str())), value_normalized); - return YaEditController2::GetParamValueByStringResponse{ + return YaEditController::GetParamValueByStringResponse{ .result = result, .value_normalized = value_normalized}; }, - [&](const YaEditController2::NormalizedParamToPlain& request) { + [&](const YaEditController::NormalizedParamToPlain& request) { return object_instances[request.instance_id] .edit_controller->normalizedParamToPlain( request.id, request.value_normalized); }, - [&](const YaEditController2::PlainParamToNormalized& request) { + [&](const YaEditController::PlainParamToNormalized& request) { return object_instances[request.instance_id] .edit_controller->plainParamToNormalized( request.id, request.plain_value); }, - [&](const YaEditController2::GetParamNormalized& request) { + [&](const YaEditController::GetParamNormalized& request) { return object_instances[request.instance_id] .edit_controller->getParamNormalized(request.id); }, - [&](const YaEditController2::SetParamNormalized& request) { + [&](const YaEditController::SetParamNormalized& request) { return object_instances[request.instance_id] .edit_controller->setParamNormalized(request.id, request.value); From 9bca4796a5a807d8a656f265bd9180bd09c4ef81 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 14:08:41 +0100 Subject: [PATCH 286/456] Add boilerplate for Vst3ComponentHandlerProxy --- src/common/serialization/vst3/README.md | 11 +- .../vst3/component-handler-proxy.h | 115 ++++++++++++++++++ .../serialization/vst3/host-application.h | 4 +- src/common/serialization/vst3/plugin-proxy.h | 3 - 4 files changed, 124 insertions(+), 9 deletions(-) create mode 100644 src/common/serialization/vst3/component-handler-proxy.h diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index a6d10363..c626bb36 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -19,11 +19,14 @@ VST3 plugin interfaces are implemented as follows: VST3 host interfaces are implemented as follows: -| yabridge class | Interfaces | -| ------------------- | ------------------ | -| `YaHostApplication` | `IHostApplication` | +| yabridge class | Included in | Interfaces | +| --------------------------- | --------------------------- | ------------------- | +| `YaHostApplication` | | `IHostApplication` | +| `Vst3ComponentHandlerProxy` | | All of the below: | +| `YaComponentHandler` | `Vst3ComponentHandlerProxy` | `IComponentHandler` | -The following (host) interfaces are also implemented for serialization purposes: +The following host interfaces are passed as function arguments and are thus also +implemented for serialization purposes: | yabridge class | Interfaces | Notes | | -------------------- | ------------------- | ---------------------------------------------------------------------- | diff --git a/src/common/serialization/vst3/component-handler-proxy.h b/src/common/serialization/vst3/component-handler-proxy.h new file mode 100644 index 00000000..597d6219 --- /dev/null +++ b/src/common/serialization/vst3/component-handler-proxy.h @@ -0,0 +1,115 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "../common.h" +#include "base.h" +#include "host-application.h" +#include "plugin/audio-processor.h" +#include "plugin/component.h" +#include "plugin/connection-point.h" +#include "plugin/edit-controller.h" +#include "plugin/plugin-base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * An abstract class that implements `IComponentHandler`, and optionally also + * all other VST3 interfaces an object passed to + * `IEditController::setComponentHandler()`. This works exactly the same as + * `Vst3PluginProxy`, but instead of proxying for an object provided by the + * plugin we are proxying for the `IComponentHandler*` argument passed to plugin + * by the host. + */ +class Vst3ComponentHandlerProxy { + public: + /** + * These are the arguments for constructing a + * `Vst3ComponentHandlerProxyImpl`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Read from an existing object. We will try to mimic this object, so + * we'll support any interfaces this object also supports. + */ + ConstructArgs(Steinberg::IPtr object, + size_t owner_instance_id); + + /** + * The unique instance identifier of the proxy object instance this + * component handler has been passed to and thus belongs to. This way we + * can refer to the correct 'actual' `IComponentHandler` instance when + * the plugin does a callback. + */ + native_size_t owner_instance_id; + + // TODO: + // YaAudioProcessor::ConstructArgs audio_processor_args; + // YaComponent::ConstructArgs component_args; + // YaConnectionPoint::ConstructArgs connection_point_args; + // YaEditController::ConstructArgs edit_controller_2_args; + // YaPluginBase::ConstructArgs plugin_base_args; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + // TODO: + // s.object(audio_processor_args); + // s.object(component_args); + // s.object(connection_point_args); + // s.object(edit_controller_2_args); + // s.object(plugin_base_args); + } + }; + + /** + * Instantiate this instance with arguments read from an actual component + * handler. + * + * @note Since this is passed as part of + * `IEditController::setComponentHandler()`, there are no direct + * `Construct` or `Destruct` messages. This object's lifetime is bound to + * that of the objects they are passed to. If those objects get dropped, + * then the host contexts should also be dropped. + */ + Vst3ComponentHandlerProxy(const ConstructArgs&& args); + + /** + * The lifetime of this object should be bound to the object we created it + * for. When for instance the `Vst3PluginProxy` instance with id `n` gets + * dropped a corresponding `Vst3ComponentHandlerProxyImpl` should also be + * dropped. + */ + virtual ~Vst3ComponentHandlerProxy() = 0; + + DECLARE_FUNKNOWN_METHODS + + /** + * Get the instance ID of the owner of this object. + */ + inline size_t owner_instance_id() const { + return arguments.owner_instance_id; + } + + private: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop diff --git a/src/common/serialization/vst3/host-application.h b/src/common/serialization/vst3/host-application.h index 9ad1a586..122c5f94 100644 --- a/src/common/serialization/vst3/host-application.h +++ b/src/common/serialization/vst3/host-application.h @@ -90,8 +90,8 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { /** * The lifetime of this object should be bound to the object we created it * for. When for instance the `Vst3PluginProxy` instance with id `n` gets - * dropped and we also track a `YaHostApplicationImpl` for the component - * with instance id `n`, then that should also be dropped. + * dropped, the corresponding `YaHostApplicationImpl` then that should also + * be dropped. */ virtual ~YaHostApplication(); diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index fe60da24..0fe9776e 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -17,11 +17,8 @@ #pragma once #include -#include #include "../common.h" -#include "base.h" -#include "host-application.h" #include "plugin/audio-processor.h" #include "plugin/component.h" #include "plugin/connection-point.h" From 54e73d2d191d2fab14180da4b2bba848f314ae3d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 14:18:57 +0100 Subject: [PATCH 287/456] Split YaEditController into YaEditController{,2} Since even though the title would make you assume it's a versioned interface, it's not. --- meson.build | 2 + src/common/serialization/vst3/README.md | 3 +- .../serialization/vst3/plugin-proxy.cpp | 8 +- src/common/serialization/vst3/plugin-proxy.h | 6 +- .../vst3/plugin/edit-controller-2.cpp | 27 +++++++ .../vst3/plugin/edit-controller-2.h | 73 +++++++++++++++++++ .../vst3/plugin/edit-controller.cpp | 6 +- .../vst3/plugin/edit-controller.h | 40 ++-------- 8 files changed, 124 insertions(+), 41 deletions(-) create mode 100644 src/common/serialization/vst3/plugin/edit-controller-2.cpp create mode 100644 src/common/serialization/vst3/plugin/edit-controller-2.h diff --git a/meson.build b/meson.build index 3a5a944a..6ff4cece 100644 --- a/meson.build +++ b/meson.build @@ -81,6 +81,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/plugin/component.cpp', 'src/common/serialization/vst3/plugin/connection-point.cpp', 'src/common/serialization/vst3/plugin/edit-controller.cpp', + 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/event-list.cpp', @@ -125,6 +126,7 @@ if with_vst3 'src/common/serialization/vst3/plugin/component.cpp', 'src/common/serialization/vst3/plugin/connection-point.cpp', 'src/common/serialization/vst3/plugin/edit-controller.cpp', + 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/event-list.cpp', diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index c626bb36..7bbe6f94 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -14,7 +14,8 @@ VST3 plugin interfaces are implemented as follows: | `YaAudioProcessor` | `Vst3PluginProxy` | `IAudioProcessor` | | `YaComponent` | `Vst3PluginProxy` | `IComponent` | | `YaConnectionPoint` | `Vst3PluginProxy` | `IConnectionPoint` | -| `YaEditController` | `Vst3PluginProxy` | `IEditController`, `IEditController2` | +| `YaEditController` | `Vst3PluginProxy` | `IEditController` | +| `YaEditController2` | `Vst3PluginProxy` | `IEditController2` | | `YaPluginBase` | `Vst3PluginProxy` | `IPluginBase` | VST3 host interfaces are implemented as follows: diff --git a/src/common/serialization/vst3/plugin-proxy.cpp b/src/common/serialization/vst3/plugin-proxy.cpp index 02a7ad74..5f4b07a1 100644 --- a/src/common/serialization/vst3/plugin-proxy.cpp +++ b/src/common/serialization/vst3/plugin-proxy.cpp @@ -25,6 +25,7 @@ Vst3PluginProxy::ConstructArgs::ConstructArgs( audio_processor_args(object), component_args(object), connection_point_args(object), + edit_controller_args(object), edit_controller_2_args(object), plugin_base_args(object) {} @@ -32,7 +33,8 @@ Vst3PluginProxy::Vst3PluginProxy(const ConstructArgs&& args) : YaAudioProcessor(std::move(args.audio_processor_args)), YaComponent(std::move(args.component_args)), YaConnectionPoint(std::move(args.connection_point_args)), - YaEditController(std::move(args.edit_controller_2_args)), + YaEditController(std::move(args.edit_controller_args)), + YaEditController2(std::move(args.edit_controller_2_args)), YaPluginBase(std::move(args.plugin_base_args)), arguments(std::move(args)){FUNKNOWN_CTOR} @@ -77,11 +79,11 @@ tresult PLUGIN_API Vst3PluginProxy::queryInterface(Steinberg::FIDString _iid, QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IConnectionPoint::iid, Steinberg::Vst::IConnectionPoint) } - if (YaEditController::supported_version_1()) { + if (YaEditController::supported()) { QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IEditController::iid, Steinberg::Vst::IEditController) } - if (YaEditController::supported_version_2()) { + if (YaEditController2::supported()) { QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IEditController2::iid, Steinberg::Vst::IEditController2) } diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index 0fe9776e..9e6d177e 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -22,6 +22,7 @@ #include "plugin/audio-processor.h" #include "plugin/component.h" #include "plugin/connection-point.h" +#include "plugin/edit-controller-2.h" #include "plugin/edit-controller.h" #include "plugin/plugin-base.h" @@ -55,6 +56,7 @@ class Vst3PluginProxy : public YaAudioProcessor, public YaComponent, public YaConnectionPoint, public YaEditController, + public YaEditController2, public YaPluginBase { public: /** @@ -77,7 +79,8 @@ class Vst3PluginProxy : public YaAudioProcessor, YaAudioProcessor::ConstructArgs audio_processor_args; YaComponent::ConstructArgs component_args; YaConnectionPoint::ConstructArgs connection_point_args; - YaEditController::ConstructArgs edit_controller_2_args; + YaEditController::ConstructArgs edit_controller_args; + YaEditController2::ConstructArgs edit_controller_2_args; YaPluginBase::ConstructArgs plugin_base_args; template @@ -86,6 +89,7 @@ class Vst3PluginProxy : public YaAudioProcessor, s.object(audio_processor_args); s.object(component_args); s.object(connection_point_args); + s.object(edit_controller_args); s.object(edit_controller_2_args); s.object(plugin_base_args); } diff --git a/src/common/serialization/vst3/plugin/edit-controller-2.cpp b/src/common/serialization/vst3/plugin/edit-controller-2.cpp new file mode 100644 index 00000000..aef36d0a --- /dev/null +++ b/src/common/serialization/vst3/plugin/edit-controller-2.cpp @@ -0,0 +1,27 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "edit-controller-2.h" + +YaEditController2::ConstructArgs::ConstructArgs() {} + +YaEditController2::ConstructArgs::ConstructArgs( + Steinberg::IPtr object) + : supported( + Steinberg::FUnknownPtr(object)) {} + +YaEditController2::YaEditController2(const ConstructArgs&& args) + : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin/edit-controller-2.h b/src/common/serialization/vst3/plugin/edit-controller-2.h new file mode 100644 index 00000000..12f93efc --- /dev/null +++ b/src/common/serialization/vst3/plugin/edit-controller-2.h @@ -0,0 +1,73 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "../../common.h" +#include "../base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IEditController2` for serialization purposes. This is + * instantiated as part of `Vst3PluginProxy`. + */ +class YaEditController2 : public Steinberg::Vst::IEditController2 { + public: + /** + * These are the arguments for creating a `YaEditController2`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Check whether an existing implementation implements + * `IEditController2` and read arguments from it. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + template + void serialize(S& s) { + s.value1b(supported); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaEditController2(const ConstructArgs&& args); + + inline bool supported() const { return arguments.supported; } + + virtual tresult PLUGIN_API + setKnobMode(Steinberg::Vst::KnobMode mode) override = 0; + virtual tresult PLUGIN_API openHelp(TBool onlyCheck) override = 0; + virtual tresult PLUGIN_API openAboutBox(TBool onlyCheck) override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop diff --git a/src/common/serialization/vst3/plugin/edit-controller.cpp b/src/common/serialization/vst3/plugin/edit-controller.cpp index b1855879..62fa2d03 100644 --- a/src/common/serialization/vst3/plugin/edit-controller.cpp +++ b/src/common/serialization/vst3/plugin/edit-controller.cpp @@ -20,10 +20,8 @@ YaEditController::ConstructArgs::ConstructArgs() {} YaEditController::ConstructArgs::ConstructArgs( Steinberg::IPtr object) - : supported_version_1( - Steinberg::FUnknownPtr(object)), - supported_version_2( - Steinberg::FUnknownPtr(object)) {} + : supported( + Steinberg::FUnknownPtr(object)) {} YaEditController::YaEditController(const ConstructArgs&& args) : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin/edit-controller.h b/src/common/serialization/vst3/plugin/edit-controller.h index abdf681c..0c5b05de 100644 --- a/src/common/serialization/vst3/plugin/edit-controller.h +++ b/src/common/serialization/vst3/plugin/edit-controller.h @@ -25,14 +25,10 @@ #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" /** - * Wraps around `IEditController{,2}` for serialization purposes. This is + * Wraps around `IEditController` for serialization purposes. This is * instantiated as part of `Vst3PluginProxy`. - * - * Steinberg forgot to inherit `IEditController2` from `IEditController` event - * if it says it does in the docs, so we'll pretend they just that. */ -class YaEditController : public Steinberg::Vst::IEditController, - public Steinberg::Vst::IEditController2 { +class YaEditController : public Steinberg::Vst::IEditController { public: /** * These are the arguments for creating a `YaEditController`. @@ -41,25 +37,19 @@ class YaEditController : public Steinberg::Vst::IEditController, ConstructArgs(); /** - * Check whether an existing implementation implements `IEditController` - * and `IEditController2` and read arguments from it. + * Check whether an existing implementation implements + * `IEditController` and read arguments from it. */ ConstructArgs(Steinberg::IPtr object); /** - * Whether the object supported `IEditController`. + * Whether the object supported this interface. */ - bool supported_version_1; - - /** - * Whether the object supported `IEditController2`. - */ - bool supported_version_2; + bool supported; template void serialize(S& s) { - s.value1b(supported_version_1); - s.value1b(supported_version_2); + s.value1b(supported); } }; @@ -69,14 +59,7 @@ class YaEditController : public Steinberg::Vst::IEditController, */ YaEditController(const ConstructArgs&& args); - inline bool supported_version_1() const { - return arguments.supported_version_1; - } - inline bool supported_version_2() const { - return arguments.supported_version_2; - } - - // From `IEditController` + inline bool supported() const { return arguments.supported; } /** * Message to pass through a call to @@ -350,13 +333,6 @@ class YaEditController : public Steinberg::Vst::IEditController, virtual Steinberg::IPlugView* PLUGIN_API createView(Steinberg::FIDString name) override = 0; - // From `IEditController2` - - virtual tresult PLUGIN_API - setKnobMode(Steinberg::Vst::KnobMode mode) override = 0; - virtual tresult PLUGIN_API openHelp(TBool onlyCheck) override = 0; - virtual tresult PLUGIN_API openAboutBox(TBool onlyCheck) override = 0; - protected: ConstructArgs arguments; }; From f816b5ad5dceccb9d3dc8e05a284c375c89146b0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 14:27:50 +0100 Subject: [PATCH 288/456] Add IComponentHandler to Vst3ComponentHandlerProxy --- meson.build | 4 + .../vst3/component-handler-proxy.cpp | 51 +++++++++++++ .../vst3/component-handler-proxy.h | 24 +----- .../component-handler/component-handler.cpp | 27 +++++++ .../component-handler/component-handler.h | 76 +++++++++++++++++++ 5 files changed, 162 insertions(+), 20 deletions(-) create mode 100644 src/common/serialization/vst3/component-handler-proxy.cpp create mode 100644 src/common/serialization/vst3/component-handler/component-handler.cpp create mode 100644 src/common/serialization/vst3/component-handler/component-handler.h diff --git a/meson.build b/meson.build index 6ff4cece..1c9efea5 100644 --- a/meson.build +++ b/meson.build @@ -83,7 +83,9 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/plugin/edit-controller.cpp', 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', + 'src/common/serialization/vst3/component-handler/component-handler.cpp', 'src/common/serialization/vst3/base.cpp', + 'src/common/serialization/vst3/component-handler-proxy.cpp', 'src/common/serialization/vst3/event-list.cpp', 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', @@ -128,7 +130,9 @@ if with_vst3 'src/common/serialization/vst3/plugin/edit-controller.cpp', 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', + 'src/common/serialization/vst3/component-handler/component-handler.cpp', 'src/common/serialization/vst3/base.cpp', + 'src/common/serialization/vst3/component-handler-proxy.cpp', 'src/common/serialization/vst3/event-list.cpp', 'src/common/serialization/vst3/host-application.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', diff --git a/src/common/serialization/vst3/component-handler-proxy.cpp b/src/common/serialization/vst3/component-handler-proxy.cpp new file mode 100644 index 00000000..3961b91b --- /dev/null +++ b/src/common/serialization/vst3/component-handler-proxy.cpp @@ -0,0 +1,51 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "component-handler-proxy.h" + +Vst3ComponentHandlerProxy::ConstructArgs::ConstructArgs() {} + +Vst3ComponentHandlerProxy::ConstructArgs::ConstructArgs( + Steinberg::IPtr object, + size_t owner_instance_id) + : owner_instance_id(owner_instance_id), component_handler_args(object) {} + +Vst3ComponentHandlerProxy::Vst3ComponentHandlerProxy(const ConstructArgs&& args) + : YaComponentHandler(std::move(args.component_handler_args)), + arguments(std::move(args)){FUNKNOWN_CTOR} + + Vst3ComponentHandlerProxy::~Vst3ComponentHandlerProxy() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(Vst3ComponentHandlerProxy) +#pragma GCC diagnostic pop + +tresult PLUGIN_API +Vst3ComponentHandlerProxy::queryInterface(Steinberg::FIDString _iid, + void** obj) { + if (YaComponentHandler::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, + Steinberg::Vst::IComponentHandler) + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponentHandler::iid, + Steinberg::Vst::IComponentHandler) + } + + *obj = nullptr; + return Steinberg::kNoInterface; +} diff --git a/src/common/serialization/vst3/component-handler-proxy.h b/src/common/serialization/vst3/component-handler-proxy.h index 597d6219..522392c6 100644 --- a/src/common/serialization/vst3/component-handler-proxy.h +++ b/src/common/serialization/vst3/component-handler-proxy.h @@ -17,13 +17,7 @@ #pragma once #include "../common.h" -#include "base.h" -#include "host-application.h" -#include "plugin/audio-processor.h" -#include "plugin/component.h" -#include "plugin/connection-point.h" -#include "plugin/edit-controller.h" -#include "plugin/plugin-base.h" +#include "component-handler/component-handler.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -36,7 +30,7 @@ * plugin we are proxying for the `IComponentHandler*` argument passed to plugin * by the host. */ -class Vst3ComponentHandlerProxy { +class Vst3ComponentHandlerProxy : public YaComponentHandler { public: /** * These are the arguments for constructing a @@ -60,22 +54,12 @@ class Vst3ComponentHandlerProxy { */ native_size_t owner_instance_id; - // TODO: - // YaAudioProcessor::ConstructArgs audio_processor_args; - // YaComponent::ConstructArgs component_args; - // YaConnectionPoint::ConstructArgs connection_point_args; - // YaEditController::ConstructArgs edit_controller_2_args; - // YaPluginBase::ConstructArgs plugin_base_args; + YaComponentHandler::ConstructArgs component_handler_args; template void serialize(S& s) { s.value8b(owner_instance_id); - // TODO: - // s.object(audio_processor_args); - // s.object(component_args); - // s.object(connection_point_args); - // s.object(edit_controller_2_args); - // s.object(plugin_base_args); + s.object(component_handler_args); } }; diff --git a/src/common/serialization/vst3/component-handler/component-handler.cpp b/src/common/serialization/vst3/component-handler/component-handler.cpp new file mode 100644 index 00000000..a9967efd --- /dev/null +++ b/src/common/serialization/vst3/component-handler/component-handler.cpp @@ -0,0 +1,27 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "component-handler.h" + +YaComponentHandler::ConstructArgs::ConstructArgs() {} + +YaComponentHandler::ConstructArgs::ConstructArgs( + Steinberg::IPtr object) + : supported( + Steinberg::FUnknownPtr(object)) {} + +YaComponentHandler::YaComponentHandler(const ConstructArgs&& args) + : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/component-handler/component-handler.h b/src/common/serialization/vst3/component-handler/component-handler.h new file mode 100644 index 00000000..edca75f3 --- /dev/null +++ b/src/common/serialization/vst3/component-handler/component-handler.h @@ -0,0 +1,76 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "../../common.h" +#include "../base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IComponentHandler` for serialization purposes. This is + * instantiated as part of `Vst3ComponentHandlerProxy`. + */ +class YaComponentHandler : public Steinberg::Vst::IComponentHandler { + public: + /** + * These are the arguments for creating a `YaComponentHandler`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Check whether an existing implementation implements + * `IComponentHandler` and read arguments from it. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + template + void serialize(S& s) { + s.value1b(supported); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaComponentHandler(const ConstructArgs&& args); + + inline bool supported() const { return arguments.supported; } + + virtual tresult PLUGIN_API + beginEdit(Steinberg::Vst::ParamID id) override = 0; + virtual tresult PLUGIN_API + performEdit(Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue valueNormalized) override = 0; + virtual tresult PLUGIN_API endEdit(Steinberg::Vst::ParamID id) override = 0; + virtual tresult PLUGIN_API restartComponent(int32 flags) override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop From 132ba0baeb8d36bb20e2313ce661c067edd98f9b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 14:37:50 +0100 Subject: [PATCH 289/456] Add stubs for a component handler proxy impl --- meson.build | 1 + src/common/serialization/vst3.h | 1 + src/plugin/bridges/vst3-impls/plugin-proxy.h | 2 - .../vst3-impls/component-handler-proxy.cpp | 69 +++++++++++++++++++ .../vst3-impls/component-handler-proxy.h | 44 ++++++++++++ .../bridges/vst3-impls/host-application.cpp | 4 +- .../bridges/vst3-impls/host-application.h | 2 - 7 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp create mode 100644 src/wine-host/bridges/vst3-impls/component-handler-proxy.h diff --git a/meson.build b/meson.build index 1c9efea5..5d762f45 100644 --- a/meson.build +++ b/meson.build @@ -140,6 +140,7 @@ if with_vst3 'src/common/serialization/vst3/plugin-proxy.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', + 'src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp', 'src/wine-host/bridges/vst3-impls/host-application.cpp', 'src/wine-host/bridges/vst3.cpp', ] diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 5b6f0c9f..7b7d0a8a 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -23,6 +23,7 @@ #include "../configuration.h" #include "../utils.h" #include "common.h" +#include "vst3/component-handler-proxy.h" #include "vst3/plugin-factory.h" #include "vst3/plugin-proxy.h" diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index eb47f462..d4c0304c 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -16,8 +16,6 @@ #pragma once -#include - #include "../vst3.h" class Vst3PluginProxyImpl : public Vst3PluginProxy { diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp new file mode 100644 index 00000000..273d8fe8 --- /dev/null +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp @@ -0,0 +1,69 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "component-handler-proxy.h" + +#include + +Vst3ComponentHandlerProxyImpl::Vst3ComponentHandlerProxyImpl( + Vst3Bridge& bridge, + Vst3ComponentHandlerProxy::ConstructArgs&& args) + : Vst3ComponentHandlerProxy(std::move(args)), bridge(bridge) { + // The lifecycle is thos object is managed together with that of the plugin + // object instance instance this belongs to +} + +tresult PLUGIN_API +Vst3ComponentHandlerProxyImpl::queryInterface(const Steinberg::TUID _iid, + void** obj) { + // TODO: Successful queries should also be logged + const tresult result = Vst3ComponentHandlerProxy::queryInterface(_iid, obj); + if (result != Steinberg::kResultOk) { + std::cerr << "TODO: Implement unknown interface logging on Wine side" + << std::endl; + } + + return result; +} + +tresult PLUGIN_API +Vst3ComponentHandlerProxyImpl::beginEdit(Steinberg::Vst::ParamID id) { + // TODO: Implement + std::cerr << "TODO: IComponentHandler::beginEdit()" << std::endl; + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::performEdit( + Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue valueNormalized) { + // TODO: Implement + std::cerr << "TODO: IComponentHandler::performEdit()" << std::endl; + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3ComponentHandlerProxyImpl::endEdit(Steinberg::Vst::ParamID id) { + // TODO: Implement + std::cerr << "TODO: IComponentHandler::endEdit()" << std::endl; + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3ComponentHandlerProxyImpl::restartComponent(int32 flags) { + // TODO: Implement + std::cerr << "TODO: IComponentHandler::restartComponent()" << std::endl; + return Steinberg::kNotImplemented; +} diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.h b/src/wine-host/bridges/vst3-impls/component-handler-proxy.h new file mode 100644 index 00000000..a4433891 --- /dev/null +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.h @@ -0,0 +1,44 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "../vst3.h" + +class Vst3ComponentHandlerProxyImpl : public Vst3ComponentHandlerProxy { + public: + Vst3ComponentHandlerProxyImpl( + Vst3Bridge& bridge, + Vst3ComponentHandlerProxy::ConstructArgs&& args); + + /** + * We'll override the query interface to log queries for interfaces we do + * not (yet) support. + */ + tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, + void** obj) override; + + // From `IComponentHandler` + tresult PLUGIN_API beginEdit(Steinberg::Vst::ParamID id) override; + tresult PLUGIN_API + performEdit(Steinberg::Vst::ParamID id, + Steinberg::Vst::ParamValue valueNormalized) override; + tresult PLUGIN_API endEdit(Steinberg::Vst::ParamID id) override; + tresult PLUGIN_API restartComponent(int32 flags) override; + + private: + Vst3Bridge& bridge; +}; diff --git a/src/wine-host/bridges/vst3-impls/host-application.cpp b/src/wine-host/bridges/vst3-impls/host-application.cpp index 211e6242..307cbbde 100644 --- a/src/wine-host/bridges/vst3-impls/host-application.cpp +++ b/src/wine-host/bridges/vst3-impls/host-application.cpp @@ -22,8 +22,8 @@ YaHostApplicationImpl::YaHostApplicationImpl( Vst3Bridge& bridge, YaHostApplication::ConstructArgs&& args) : YaHostApplication(std::move(args)), bridge(bridge) { - // The lifecycle is thos object is managed together with that of the - // `IComponent` instance this belongs to + // The lifecycle is thos object is managed together with that of the plugin + // object instance this belongs to } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3-impls/host-application.h b/src/wine-host/bridges/vst3-impls/host-application.h index 9d7f5c29..ae5df390 100644 --- a/src/wine-host/bridges/vst3-impls/host-application.h +++ b/src/wine-host/bridges/vst3-impls/host-application.h @@ -16,8 +16,6 @@ #pragma once -#include - #include "../vst3.h" class YaHostApplicationImpl : public YaHostApplication { From bf3d802f369d33902a31ba35b9eb4f570bbb3e20 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 15:04:27 +0100 Subject: [PATCH 290/456] Implement IEditController::setComponentHandler() --- src/common/logging/vst3.cpp | 15 +++++++ src/common/logging/vst3.h | 2 + src/common/serialization/vst3.h | 1 + .../vst3/plugin/edit-controller.h | 31 ++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 21 ++++++++-- src/plugin/bridges/vst3-impls/plugin-proxy.h | 8 ++++ src/wine-host/bridges/vst3.cpp | 42 +++++++++++++++---- src/wine-host/bridges/vst3.h | 14 ++++++- 8 files changed, 122 insertions(+), 12 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 51e6852d..233ea3c7 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -391,6 +391,21 @@ bool Vst3Logger::log_request( }); } +bool Vst3Logger::log_request( + bool is_host_vst, + const YaEditController::SetComponentHandler& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IEditController::setComponentHandler(handler = "; + if (request.component_handler_proxy_args) { + message << ""; + } else { + message << ""; + } + message << ")"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { return log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 4f300f10..7174986a 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -102,6 +102,8 @@ class Vst3Logger { const YaEditController::GetParamNormalized&); bool log_request(bool is_host_vst, const YaEditController::SetParamNormalized&); + bool log_request(bool is_host_vst, + const YaEditController::SetComponentHandler&); bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 7b7d0a8a..a8415102 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -87,6 +87,7 @@ using ControlRequest = std::variant #include #include "../../common.h" #include "../base.h" +#include "../component-handler-proxy.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -328,6 +330,35 @@ class YaEditController : public Steinberg::Vst::IEditController { virtual tresult PLUGIN_API setParamNormalized(Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue value) override = 0; + + /** + * Message to pass through a call to + * `IEditController::setComponentHandler(handler)` to the Wine plugin host. + * Like when creating a proxy for a plugin object, we'll read all supported + * interfaces form the component handler instance passed by the host. We'll + * then create a perfect proxy on the plugin side, that can do callbacks to + * the actual component handler passed by the host. + */ + struct SetComponentHandler { + using Response = UniversalTResult; + + native_size_t instance_id; + + /** + * Arguments for instantiating the proxy object. Even though it should + * never happen, if the host passed a null pointer to this function + * we'll mimic that as well. + */ + std::optional + component_handler_proxy_args; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.ext(component_handler_proxy_args, bitsery::ext::StdOptional{}); + } + }; + virtual tresult PLUGIN_API setComponentHandler( Steinberg::Vst::IComponentHandler* handler) override = 0; virtual Steinberg::IPlugView* PLUGIN_API diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 8405dd35..5cd99202 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -315,9 +315,24 @@ Vst3PluginProxyImpl::setParamNormalized(Steinberg::Vst::ParamID id, tresult PLUGIN_API Vst3PluginProxyImpl::setComponentHandler( Steinberg::Vst::IComponentHandler* handler) { - // TODO: Implement - bridge.logger.log("TODO IEditController::setComponentHandler()"); - return Steinberg::kNotImplemented; + std::optional + component_handler_proxy_args = std::nullopt; + if (handler) { + // We'll store the pointer for when the plugin later makes a callback to + // this component handler + component_handler = handler; + + component_handler_proxy_args = Vst3ComponentHandlerProxy::ConstructArgs( + host_application_context, instance_id()); + } else { + bridge.logger.log( + "Null pointer passed to 'IEditController::setComponentHandler'"); + } + + return bridge.send_message(YaEditController::SetComponentHandler{ + .instance_id = instance_id(), + .component_handler_proxy_args = + std::move(component_handler_proxy_args)}); } Steinberg::IPlugView* PLUGIN_API diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index d4c0304c..2b5c2c25 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -133,4 +133,12 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { */ Steinberg::FUnknownPtr host_application_context; + + /** + * The component handler the host passed to us during + * `IEditController::setComponentHandler()`. When the plugin makes a + * callback on a component handler proxy object, we'll pass the call through + * to this object. + */ + Steinberg::IPtr component_handler; }; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 8c8c1dc9..acf09ce3 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -23,6 +23,7 @@ #include #include +#include "vst3-impls/component-handler-proxy.h" #include "vst3-impls/host-application.h" InstanceInterfaces::InstanceInterfaces() {} @@ -336,28 +337,55 @@ void Vst3Bridge::run() { .edit_controller->setParamNormalized(request.id, request.value); }, + [&](YaEditController::SetComponentHandler& request) + -> YaEditController::SetComponentHandler::Response { + // If we got passed a component handler, we'll create a proxy + // object and pass that to the initialize function. The lifetime + // of this object is tied to that of the actual plugin object + // we're proxying for. + // TODO: Does this have to be run from the UI thread? Figure out + // if it does + if (request.component_handler_proxy_args) { + object_instances[request.instance_id] + .component_handler_proxy = + Steinberg::owned(new Vst3ComponentHandlerProxyImpl( + *this, + std::move(*request.component_handler_proxy_args))); + } else { + object_instances[request.instance_id] + .component_handler_proxy = nullptr; + } + + return object_instances[request.instance_id] + .edit_controller->setComponentHandler( + object_instances[request.instance_id] + .component_handler_proxy); + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object - // and pass that to the initialize function. This object should - // be cleaned up again during `Vst3PluginProxy::Destruct`. + // and pass that to the initialize function. The lifetime of + // this object is tied to that of the actual plugin object we're + // proxying for. // TODO: This needs changing if it turns out we need a // `Vst3HostProxy` // TODO: Does this have to be run from the UI thread? Figure out // if it does - Steinberg::FUnknown* context = nullptr; if (request.host_application_context_args) { object_instances[request.instance_id] - .hsot_application_context = + .host_application_context = Steinberg::owned(new YaHostApplicationImpl( *this, std::move(*request.host_application_context_args))); - context = object_instances[request.instance_id] - .hsot_application_context; + } else { + object_instances[request.instance_id] + .host_application_context = nullptr; } return object_instances[request.instance_id] - .plugin_base->initialize(context); + .plugin_base->initialize( + object_instances[request.instance_id] + .host_application_context); }, [&](const YaPluginBase::Terminate& request) -> YaPluginBase::Terminate::Response { diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 0253d6b3..7a9b1259 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -40,9 +40,19 @@ struct InstanceInterfaces { /** * If the host passes an `IHostApplication` during * `IPluginBase::initialize()`, we'll store a proxy object here and then - * pass it to `plugin_base->initialize()`. + * pass it to `plugin_base->initialize()`. Will be initialized with a null + * pointer until used. */ - Steinberg::IPtr hsot_application_context; + Steinberg::IPtr host_application_context; + + /** + * After a call to `IEditController::setComponentHandler()`, we'll create a + * proxy of that component handler just like we did for the plugin object. + * When the plugin calls a function on this object, we make a callback to + * the original object provided by the host. Will be initialized with a null + * pointer until used. + */ + Steinberg::IPtr component_handler_proxy; /** * The base object we cast from. From eacd5f27f5f54d0aac4d81f2d2450d32d334c261 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 15:19:34 +0100 Subject: [PATCH 291/456] Allow class IDs shorter than 16 bytes --- src/plugin/bridges/vst3-impls/plugin-factory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 26f205c1..b5ca25ce 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -28,9 +28,9 @@ tresult PLUGIN_API YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, Steinberg::FIDString _iid, void** obj) { + // Class IDs may be padded with null bytes constexpr size_t uid_size = sizeof(Steinberg::TUID); - if (!cid || !_iid || strnlen(cid, uid_size) < uid_size || - strnlen(_iid, uid_size) < uid_size) { + if (!cid || !_iid || strnlen(_iid, uid_size) < uid_size) { return Steinberg::kInvalidArgument; } From 3b06bca95ef0019280b5bf63b3223d19dfbcba1a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 15:36:50 +0100 Subject: [PATCH 292/456] Implement IComponentHandler::beginEdit() --- src/common/logging/vst3.cpp | 8 ++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 3 ++- .../vst3/component-handler/component-handler.h | 18 ++++++++++++++++++ src/plugin/bridges/vst3-impls/plugin-proxy.h | 6 +++--- src/plugin/bridges/vst3.cpp | 13 +++++++++++-- .../vst3-impls/component-handler-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.h | 10 ++++++++++ 8 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 233ea3c7..f0a3aa6e 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -446,6 +446,14 @@ bool Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaComponentHandler::BeginEdit& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id + << ": IComponentHandler::beginEdit(" << request.id << ")"; + }); +} + void Vst3Logger::log_response(bool is_host_vst, const Ack&) { log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 7174986a..20a1abd0 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -108,7 +108,9 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); bool log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); + bool log_request(bool is_host_vst, const WantsConfiguration&); + bool log_request(bool is_host_vst, const YaComponentHandler::BeginEdit&); void log_response(bool is_host_vst, const Ack&); void log_response( diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index a8415102..0172facf 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -105,7 +105,8 @@ void serialize(S& s, ControlRequest& payload) { * information we want or the operation we want to perform. A request of type * `CallbackRequest(T)` should send back a `T::Response`. */ -using CallbackRequest = std::variant; +using CallbackRequest = + std::variant; template void serialize(S& s, CallbackRequest& payload) { diff --git a/src/common/serialization/vst3/component-handler/component-handler.h b/src/common/serialization/vst3/component-handler/component-handler.h index edca75f3..b907d297 100644 --- a/src/common/serialization/vst3/component-handler/component-handler.h +++ b/src/common/serialization/vst3/component-handler/component-handler.h @@ -61,6 +61,24 @@ class YaComponentHandler : public Steinberg::Vst::IComponentHandler { inline bool supported() const { return arguments.supported; } + /** + * Message to pass through a call to `IComponentHandler::beginEdit(id)` to + * the component handler provided by the host. + */ + struct BeginEdit { + using Response = UniversalTResult; + + native_size_t owner_instance_id; + + Steinberg::Vst::ParamID id; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.value4b(id); + } + }; + virtual tresult PLUGIN_API beginEdit(Steinberg::Vst::ParamID id) override = 0; virtual tresult PLUGIN_API diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index 2b5c2c25..202e1cec 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -122,9 +122,6 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { tresult PLUGIN_API initialize(FUnknown* context) override; tresult PLUGIN_API terminate() override; - private: - Vst3PluginBridge& bridge; - /** * An `IHostApplication` instance if we get one through * `IPluginBase::initialize()`. This should be the same for all plugin @@ -141,4 +138,7 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { * to this object. */ Steinberg::IPtr component_handler; + + private: + Vst3PluginBridge& bridge; }; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 5df60b9a..ab8a44fe 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -79,8 +79,17 @@ Vst3PluginBridge::Vst3PluginBridge() host_callback_handler = std::jthread([&]() { sockets.vst_host_callback.receive_messages( std::pair(logger, false), - overload{[&](const WantsConfiguration&) - -> WantsConfiguration::Response { return config; }}); + overload{ + [&](const WantsConfiguration&) -> WantsConfiguration::Response { + return config; + }, + [&](const YaComponentHandler::BeginEdit& request) + -> YaComponentHandler::BeginEdit::Response { + return plugin_proxies.at(request.owner_instance_id) + .get() + .component_handler->beginEdit(request.id); + }, + }); }); } diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp index 273d8fe8..7f76a54f 100644 --- a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp @@ -41,9 +41,8 @@ Vst3ComponentHandlerProxyImpl::queryInterface(const Steinberg::TUID _iid, tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::beginEdit(Steinberg::Vst::ParamID id) { - // TODO: Implement - std::cerr << "TODO: IComponentHandler::beginEdit()" << std::endl; - return Steinberg::kNotImplemented; + return bridge.send_message(YaComponentHandler::BeginEdit{ + .owner_instance_id = owner_instance_id(), .id = id}); } tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::performEdit( diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 7a9b1259..8e2f2d88 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -103,6 +103,16 @@ class Vst3Bridge : public HostBridge { */ void run() override; + /** + * Send a callback message to the host return the response. This is a + * shorthand for `sockets.vst_host_callback.send_message` for use in VST3 + * interface implementations. + */ + template + typename T::Response send_message(const T& object) { + return sockets.vst_host_callback.send_message(object, std::nullopt); + } + private: /** * Generate a nique instance identifier using an atomic fetch-and-add. This From bb99a539d57fc4468a6380defb2aba6b20edf0b1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 15:45:24 +0100 Subject: [PATCH 293/456] Implement IComponentHandler::performEdit() --- src/common/logging/vst3.cpp | 11 +++++++++- src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 5 +++-- .../component-handler/component-handler.h | 21 +++++++++++++++++++ src/plugin/bridges/vst3.cpp | 7 +++++++ .../vst3-impls/component-handler-proxy.cpp | 7 ++++--- 6 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index f0a3aa6e..3f970d2a 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -450,7 +450,16 @@ bool Vst3Logger::log_request(bool is_host_vst, const YaComponentHandler::BeginEdit& request) { return log_request_base(is_host_vst, [&](auto& message) { message << request.owner_instance_id - << ": IComponentHandler::beginEdit(" << request.id << ")"; + << ": IComponentHandler::beginEdit(id = " << request.id << ")"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaComponentHandler::PerformEdit& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id + << ": IComponentHandler::performEdit(id = " << request.id + << ", valueNormalized = " << request.value_normalized << ")"; }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 20a1abd0..71fbbef6 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -111,6 +111,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const WantsConfiguration&); bool log_request(bool is_host_vst, const YaComponentHandler::BeginEdit&); + bool log_request(bool is_host_vst, const YaComponentHandler::PerformEdit&); void log_response(bool is_host_vst, const Ack&); void log_response( diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 0172facf..c6ab00fb 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -105,8 +105,9 @@ void serialize(S& s, ControlRequest& payload) { * information we want or the operation we want to perform. A request of type * `CallbackRequest(T)` should send back a `T::Response`. */ -using CallbackRequest = - std::variant; +using CallbackRequest = std::variant; template void serialize(S& s, CallbackRequest& payload) { diff --git a/src/common/serialization/vst3/component-handler/component-handler.h b/src/common/serialization/vst3/component-handler/component-handler.h index b907d297..a17d05a2 100644 --- a/src/common/serialization/vst3/component-handler/component-handler.h +++ b/src/common/serialization/vst3/component-handler/component-handler.h @@ -81,6 +81,27 @@ class YaComponentHandler : public Steinberg::Vst::IComponentHandler { virtual tresult PLUGIN_API beginEdit(Steinberg::Vst::ParamID id) override = 0; + + /** + * Message to pass through a call to `IComponentHandler::performEdit(id, + * value_normalized)` to the component handler provided by the host. + */ + struct PerformEdit { + using Response = UniversalTResult; + + native_size_t owner_instance_id; + + Steinberg::Vst::ParamID id; + Steinberg::Vst::ParamValue value_normalized; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.value4b(id); + s.value8b(value_normalized); + } + }; + virtual tresult PLUGIN_API performEdit(Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized) override = 0; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index ab8a44fe..741ba9e8 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -89,6 +89,13 @@ Vst3PluginBridge::Vst3PluginBridge() .get() .component_handler->beginEdit(request.id); }, + [&](const YaComponentHandler::PerformEdit& request) + -> YaComponentHandler::PerformEdit::Response { + return plugin_proxies.at(request.owner_instance_id) + .get() + .component_handler->performEdit( + request.id, request.value_normalized); + }, }); }); } diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp index 7f76a54f..388eaa31 100644 --- a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp @@ -48,9 +48,10 @@ Vst3ComponentHandlerProxyImpl::beginEdit(Steinberg::Vst::ParamID id) { tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::performEdit( Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized) { - // TODO: Implement - std::cerr << "TODO: IComponentHandler::performEdit()" << std::endl; - return Steinberg::kNotImplemented; + return bridge.send_message(YaComponentHandler::PerformEdit{ + .owner_instance_id = owner_instance_id(), + .id = id, + .value_normalized = valueNormalized}); } tresult PLUGIN_API From 25575e2d3a18680c502592d8bd2c64fa95223dbe Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 15:48:06 +0100 Subject: [PATCH 294/456] Implement IComponentHandler::endEdit() --- src/common/logging/vst3.cpp | 8 ++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 3 ++- .../component-handler/component-handler.h | 19 +++++++++++++++++++ src/plugin/bridges/vst3.cpp | 6 ++++++ .../vst3-impls/component-handler-proxy.cpp | 5 ++--- 6 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 3f970d2a..9f0e7cff 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -463,6 +463,14 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaComponentHandler::EndEdit& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id + << ": IComponentHandler::endEdit(id = " << request.id << ")"; + }); +} + void Vst3Logger::log_response(bool is_host_vst, const Ack&) { log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 71fbbef6..3132d8f7 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -112,6 +112,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const WantsConfiguration&); bool log_request(bool is_host_vst, const YaComponentHandler::BeginEdit&); bool log_request(bool is_host_vst, const YaComponentHandler::PerformEdit&); + bool log_request(bool is_host_vst, const YaComponentHandler::EndEdit&); void log_response(bool is_host_vst, const Ack&); void log_response( diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index c6ab00fb..0d0049cc 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -107,7 +107,8 @@ void serialize(S& s, ControlRequest& payload) { */ using CallbackRequest = std::variant; + YaComponentHandler::PerformEdit, + YaComponentHandler::EndEdit>; template void serialize(S& s, CallbackRequest& payload) { diff --git a/src/common/serialization/vst3/component-handler/component-handler.h b/src/common/serialization/vst3/component-handler/component-handler.h index a17d05a2..90e5f217 100644 --- a/src/common/serialization/vst3/component-handler/component-handler.h +++ b/src/common/serialization/vst3/component-handler/component-handler.h @@ -105,6 +105,25 @@ class YaComponentHandler : public Steinberg::Vst::IComponentHandler { virtual tresult PLUGIN_API performEdit(Steinberg::Vst::ParamID id, Steinberg::Vst::ParamValue valueNormalized) override = 0; + + /** + * Message to pass through a call to `IComponentHandler::endEdit(id)` to the + * component handler provided by the host. + */ + struct EndEdit { + using Response = UniversalTResult; + + native_size_t owner_instance_id; + + Steinberg::Vst::ParamID id; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.value4b(id); + } + }; + virtual tresult PLUGIN_API endEdit(Steinberg::Vst::ParamID id) override = 0; virtual tresult PLUGIN_API restartComponent(int32 flags) override = 0; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 741ba9e8..c352b112 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -96,6 +96,12 @@ Vst3PluginBridge::Vst3PluginBridge() .component_handler->performEdit( request.id, request.value_normalized); }, + [&](const YaComponentHandler::EndEdit& request) + -> YaComponentHandler::EndEdit::Response { + return plugin_proxies.at(request.owner_instance_id) + .get() + .component_handler->endEdit(request.id); + }, }); }); } diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp index 388eaa31..730ed3c8 100644 --- a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp @@ -56,9 +56,8 @@ tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::performEdit( tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::endEdit(Steinberg::Vst::ParamID id) { - // TODO: Implement - std::cerr << "TODO: IComponentHandler::endEdit()" << std::endl; - return Steinberg::kNotImplemented; + return bridge.send_message(YaComponentHandler::EndEdit{ + .owner_instance_id = owner_instance_id(), .id = id}); } tresult PLUGIN_API From 1b454371a6189e06be7616f9130ffcd303d6da5c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 15:51:48 +0100 Subject: [PATCH 295/456] Implement IComponentHandler::restartComponent() The base IComponentHandler is now fully implemented. --- src/common/logging/vst3.cpp | 10 ++++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 3 ++- .../component-handler/component-handler.h | 20 +++++++++++++++++++ src/plugin/bridges/vst3.cpp | 6 ++++++ .../vst3-impls/component-handler-proxy.cpp | 5 ++--- 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 9f0e7cff..8cd75273 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -471,6 +471,16 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request( + bool is_host_vst, + const YaComponentHandler::RestartComponent& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id + << ": IComponentHandler::restartComponent(flags = " + << request.flags << ")"; + }); +} + void Vst3Logger::log_response(bool is_host_vst, const Ack&) { log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 3132d8f7..42639df4 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -113,6 +113,8 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaComponentHandler::BeginEdit&); bool log_request(bool is_host_vst, const YaComponentHandler::PerformEdit&); bool log_request(bool is_host_vst, const YaComponentHandler::EndEdit&); + bool log_request(bool is_host_vst, + const YaComponentHandler::RestartComponent&); void log_response(bool is_host_vst, const Ack&); void log_response( diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 0d0049cc..295902b3 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -108,7 +108,8 @@ void serialize(S& s, ControlRequest& payload) { using CallbackRequest = std::variant; + YaComponentHandler::EndEdit, + YaComponentHandler::RestartComponent>; template void serialize(S& s, CallbackRequest& payload) { diff --git a/src/common/serialization/vst3/component-handler/component-handler.h b/src/common/serialization/vst3/component-handler/component-handler.h index 90e5f217..a55f8144 100644 --- a/src/common/serialization/vst3/component-handler/component-handler.h +++ b/src/common/serialization/vst3/component-handler/component-handler.h @@ -125,6 +125,26 @@ class YaComponentHandler : public Steinberg::Vst::IComponentHandler { }; virtual tresult PLUGIN_API endEdit(Steinberg::Vst::ParamID id) override = 0; + + /** + * Message to pass through a call to + * `IComponentHandler::restartComponent(flags)` to the component handler + * provided by the host. + */ + struct RestartComponent { + using Response = UniversalTResult; + + native_size_t owner_instance_id; + + int32 flags; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.value4b(flags); + } + }; + virtual tresult PLUGIN_API restartComponent(int32 flags) override = 0; protected: diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index c352b112..35811bfc 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -102,6 +102,12 @@ Vst3PluginBridge::Vst3PluginBridge() .get() .component_handler->endEdit(request.id); }, + [&](const YaComponentHandler::RestartComponent& request) + -> YaComponentHandler::EndEdit::Response { + return plugin_proxies.at(request.owner_instance_id) + .get() + .component_handler->restartComponent(request.flags); + }, }); }); } diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp index 730ed3c8..615a909f 100644 --- a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp @@ -62,7 +62,6 @@ Vst3ComponentHandlerProxyImpl::endEdit(Steinberg::Vst::ParamID id) { tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::restartComponent(int32 flags) { - // TODO: Implement - std::cerr << "TODO: IComponentHandler::restartComponent()" << std::endl; - return Steinberg::kNotImplemented; + return bridge.send_message(YaComponentHandler::RestartComponent{ + .owner_instance_id = owner_instance_id(), .flags = flags}); } From c970093b5e428707a1bc3aba0138f4340dd57e95 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 16:05:04 +0100 Subject: [PATCH 296/456] Add todo about creating a Vst3HostContextProxy --- src/common/serialization/vst3/host-application.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common/serialization/vst3/host-application.h b/src/common/serialization/vst3/host-application.h index 122c5f94..d8aa46bd 100644 --- a/src/common/serialization/vst3/host-application.h +++ b/src/common/serialization/vst3/host-application.h @@ -35,6 +35,10 @@ * this proxy will be passed through to the actual object. This is used to proxy * both the host application context passed during `IPluginBase::intialize()` as * well as for the 'global' context in `IPluginFactory3::setHostContext()`. + * + * TODO: Create a `Vst3HostContextProxy`, and make this to only interface it + * inherits. For uniformity's sake it's a good idea to have every kind of + * object we directly instantiate be in the same form. */ class YaHostApplication : public Steinberg::Vst::IHostApplication { public: From c94089b83299c987b950849ba9c52f723e3e4447 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 17:00:57 +0100 Subject: [PATCH 297/456] Fix creating component handler proxy --- src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 5cd99202..c2d42901 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -323,7 +323,7 @@ tresult PLUGIN_API Vst3PluginProxyImpl::setComponentHandler( component_handler = handler; component_handler_proxy_args = Vst3ComponentHandlerProxy::ConstructArgs( - host_application_context, instance_id()); + component_handler, instance_id()); } else { bridge.logger.log( "Null pointer passed to 'IEditController::setComponentHandler'"); From 0522f84f4a2d8e6d34e36f0e18dff4e1de00026a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 17:13:17 +0100 Subject: [PATCH 298/456] Create Vst3HostContextProxy from YaHostApplication This is quite a huge refactor, but note everything is consistent (and we're going to need one or two more of these `Vst3*Proxy` objects). Right now nothing extends `IHostApplication`, but this way it will be trivial to add support for more host context interfaces. --- meson.build | 8 +- src/common/logging/vst3.cpp | 14 ++- src/common/serialization/vst3.h | 1 + src/common/serialization/vst3/README.md | 3 +- .../serialization/vst3/host-context-proxy.cpp | 50 +++++++++++ ...ost-application.h => host-context-proxy.h} | 68 +++++++-------- .../{ => host-context}/host-application.cpp | 39 +++------ .../vst3/host-context/host-application.h | 86 +++++++++++++++++++ .../serialization/vst3/plugin-factory.cpp | 4 +- .../serialization/vst3/plugin-factory.h | 18 ++-- .../vst3/plugin/audio-processor.h | 1 - .../serialization/vst3/plugin/component.cpp | 8 +- .../serialization/vst3/plugin/plugin-base.h | 18 ++-- .../bridges/vst3-impls/plugin-factory.cpp | 31 +++---- .../bridges/vst3-impls/plugin-factory.h | 5 +- .../bridges/vst3-impls/plugin-proxy.cpp | 40 ++++----- src/plugin/bridges/vst3-impls/plugin-proxy.h | 18 ++-- ...application.cpp => host-context-proxy.cpp} | 22 ++--- ...ost-application.h => host-context-proxy.h} | 7 +- src/wine-host/bridges/vst3.cpp | 33 +++---- src/wine-host/bridges/vst3.h | 11 ++- 21 files changed, 301 insertions(+), 184 deletions(-) create mode 100644 src/common/serialization/vst3/host-context-proxy.cpp rename src/common/serialization/vst3/{host-application.h => host-context-proxy.h} (54%) rename src/common/serialization/vst3/{ => host-context}/host-application.cpp (57%) create mode 100644 src/common/serialization/vst3/host-context/host-application.h rename src/wine-host/bridges/vst3-impls/{host-application.cpp => host-context-proxy.cpp} (68%) rename src/wine-host/bridges/vst3-impls/{host-application.h => host-context-proxy.h} (85%) diff --git a/meson.build b/meson.build index 5d762f45..f88c0f0f 100644 --- a/meson.build +++ b/meson.build @@ -84,10 +84,11 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/component-handler/component-handler.cpp', + 'src/common/serialization/vst3/host-context/host-application.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component-handler-proxy.cpp', 'src/common/serialization/vst3/event-list.cpp', - 'src/common/serialization/vst3/host-application.cpp', + 'src/common/serialization/vst3/host-context-proxy.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plugin-proxy.cpp', @@ -131,17 +132,18 @@ if with_vst3 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/component-handler/component-handler.cpp', + 'src/common/serialization/vst3/host-context/host-application.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component-handler-proxy.cpp', 'src/common/serialization/vst3/event-list.cpp', - 'src/common/serialization/vst3/host-application.cpp', + 'src/common/serialization/vst3/host-context-proxy.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plugin-proxy.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', 'src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp', - 'src/wine-host/bridges/vst3-impls/host-application.cpp', + 'src/wine-host/bridges/vst3-impls/host-context-proxy.cpp', 'src/wine-host/bridges/vst3.cpp', ] endif diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 8cd75273..9de2ee99 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -411,8 +411,8 @@ bool Vst3Logger::log_request(bool is_host_vst, return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id << ": IPluginBase::initialize(context = "; - if (request.host_application_context_args) { - message << ""; + if (request.host_context_args) { + message << ""; } else { message << ""; } @@ -434,9 +434,15 @@ bool Vst3Logger::log_request(bool is_host_vst, } bool Vst3Logger::log_request(bool is_host_vst, - const YaPluginFactory::SetHostContext&) { + const YaPluginFactory::SetHostContext& request) { return log_request_base(is_host_vst, [&](auto& message) { - message << "IPluginFactory3::setHostContext(IHostApplication*)"; + message << "IPluginFactory3::setHostContext("; + if (request.host_context_args) { + message << ""; + } else { + message << ""; + } + message << ")"; }); } diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 295902b3..f8e33ae6 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -24,6 +24,7 @@ #include "../utils.h" #include "common.h" #include "vst3/component-handler-proxy.h" +#include "vst3/host-context-proxy.h" #include "vst3/plugin-factory.h" #include "vst3/plugin-proxy.h" diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 7bbe6f94..ed41bb22 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -22,7 +22,8 @@ VST3 host interfaces are implemented as follows: | yabridge class | Included in | Interfaces | | --------------------------- | --------------------------- | ------------------- | -| `YaHostApplication` | | `IHostApplication` | +| `Vst3HostContextProxy` | | All of the below: | +| `YaHostApplication` | `Vst3HostContextProxy` | `IHostApplication` | | `Vst3ComponentHandlerProxy` | | All of the below: | | `YaComponentHandler` | `Vst3ComponentHandlerProxy` | `IComponentHandler` | diff --git a/src/common/serialization/vst3/host-context-proxy.cpp b/src/common/serialization/vst3/host-context-proxy.cpp new file mode 100644 index 00000000..1e32b6ab --- /dev/null +++ b/src/common/serialization/vst3/host-context-proxy.cpp @@ -0,0 +1,50 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "host-context-proxy.h" + +Vst3HostContextProxy::ConstructArgs::ConstructArgs() {} + +Vst3HostContextProxy::ConstructArgs::ConstructArgs( + Steinberg::IPtr object, + std::optional owner_instance_id) + : owner_instance_id(owner_instance_id), host_application_args(object) {} + +Vst3HostContextProxy::Vst3HostContextProxy(const ConstructArgs&& args) + : YaHostApplication(std::move(args.host_application_args)), + arguments(std::move(args)){FUNKNOWN_CTOR} + + Vst3HostContextProxy::~Vst3HostContextProxy() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(Vst3HostContextProxy) +#pragma GCC diagnostic pop + +tresult PLUGIN_API +Vst3HostContextProxy::queryInterface(Steinberg::FIDString _iid, void** obj) { + if (YaHostApplication::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, + Steinberg::Vst::IHostApplication) + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IHostApplication::iid, + Steinberg::Vst::IHostApplication) + } + + *obj = nullptr; + return Steinberg::kNoInterface; +} diff --git a/src/common/serialization/vst3/host-application.h b/src/common/serialization/vst3/host-context-proxy.h similarity index 54% rename from src/common/serialization/vst3/host-application.h rename to src/common/serialization/vst3/host-context-proxy.h index d8aa46bd..9f590d4f 100644 --- a/src/common/serialization/vst3/host-application.h +++ b/src/common/serialization/vst3/host-context-proxy.h @@ -16,42 +16,36 @@ #pragma once -#include - -#include -#include -#include - #include "../common.h" -#include "base.h" +#include "host-context/host-application.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" /** - * Wraps around `IHostApplication` for serialization purposes. An instance of - * this proxy object will be initialized on the Wine plugin host side after the - * host passes an actual instance to the plugin, and all function calls made to - * this proxy will be passed through to the actual object. This is used to proxy - * both the host application context passed during `IPluginBase::intialize()` as - * well as for the 'global' context in `IPluginFactory3::setHostContext()`. - * - * TODO: Create a `Vst3HostContextProxy`, and make this to only interface it - * inherits. For uniformity's sake it's a good idea to have every kind of - * object we directly instantiate be in the same form. + * An abstract class that optionally implements all interfaces a `context` + * object passed to `IPluginBase::intialize()` or + * `IPluginFactory3::setHostContext()` might implement. This works exactly the + * same as `Vst3PluginProxy`, but instead of proxying for an object provided by + * the plugin we are proxying for the `FUnknown*` argument passed to plugin by + * the host. When we are proxying for a host context object passed to + * `IPluginBase::initialize()` we'll keep track of the object instance ID the + * actual context object belongs to. */ -class YaHostApplication : public Steinberg::Vst::IHostApplication { +class Vst3HostContextProxy : public YaHostApplication { public: /** - * These are the arguments for constructing a `YaHostApplicationImpl`. + * These are the arguments for constructing a + * `Vst3HostContextProxyImpl`. */ struct ConstructArgs { ConstructArgs(); /** - * Read arguments from an existing implementation. + * Read from an existing object. We will try to mimic this object, so + * we'll support any interfaces this object also supports. */ - ConstructArgs(Steinberg::IPtr context, + ConstructArgs(Steinberg::IPtr object, std::optional owner_instance_id); /** @@ -61,10 +55,7 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { */ std::optional owner_instance_id; - /** - * For `IHostApplication::getName`. - */ - std::optional name; + YaHostApplication::ConstructArgs host_application_args; template void serialize(S& s) { @@ -72,10 +63,7 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { [](S& s, native_size_t& instance_id) { s.value8b(instance_id); }); - s.ext(name, bitsery::ext::StdOptional{}, - [](S& s, std::u16string& name) { - s.text2b(name, std::extent_v); - }); + s.object(host_application_args); } }; @@ -89,25 +77,27 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { * objects they are passed to. If those objects get dropped, then the host * contexts should also be dropped. */ - YaHostApplication(const ConstructArgs&& args); + Vst3HostContextProxy(const ConstructArgs&& args); /** * The lifetime of this object should be bound to the object we created it * for. When for instance the `Vst3PluginProxy` instance with id `n` gets - * dropped, the corresponding `YaHostApplicationImpl` then that should also - * be dropped. + * dropped a corresponding `Vst3HostContextProxyImpl` should also be + * dropped. */ - virtual ~YaHostApplication(); + virtual ~Vst3HostContextProxy() = 0; DECLARE_FUNKNOWN_METHODS - // From `IHostApplication` - tresult PLUGIN_API getName(Steinberg::Vst::String128 name) override; - virtual tresult PLUGIN_API createInstance(Steinberg::TUID cid, - Steinberg::TUID _iid, - void** obj) override = 0; + /** + * Get the instance ID of the owner of this object, if this is not the + * global host context passed to the module's plugin factory. + */ + inline std::optional owner_instance_id() const { + return arguments.owner_instance_id; + } - protected: + private: ConstructArgs arguments; }; diff --git a/src/common/serialization/vst3/host-application.cpp b/src/common/serialization/vst3/host-context/host-application.cpp similarity index 57% rename from src/common/serialization/vst3/host-application.cpp rename to src/common/serialization/vst3/host-context/host-application.cpp index 1b8b4b88..c4a7a3fd 100644 --- a/src/common/serialization/vst3/host-application.cpp +++ b/src/common/serialization/vst3/host-context/host-application.cpp @@ -19,37 +19,22 @@ YaHostApplication::ConstructArgs::ConstructArgs() {} YaHostApplication::ConstructArgs::ConstructArgs( - Steinberg::IPtr context, - std::optional owner_instance_id) - : owner_instance_id(owner_instance_id) { - Steinberg::Vst::String128 name_array; - if (context->getName(name_array) == Steinberg::kResultOk) { - name = tchar_pointer_to_u16string(name_array); + Steinberg::IPtr object) + : supported(false) { + if (auto host_application = + Steinberg::FUnknownPtr(object)) { + supported = true; + + // `IHostApplication::getName` + Steinberg::Vst::String128 name_array; + if (host_application->getName(name_array) == Steinberg::kResultOk) { + name = tchar_pointer_to_u16string(name_array); + } } } YaHostApplication::YaHostApplication(const ConstructArgs&& args) - : arguments(std::move(args)){FUNKNOWN_CTOR} - - YaHostApplication::~YaHostApplication() { - FUNKNOWN_DTOR -} - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" -IMPLEMENT_REFCOUNT(YaHostApplication) -#pragma GCC diagnostic pop - -tresult PLUGIN_API YaHostApplication::queryInterface(Steinberg::FIDString _iid, - void** obj) { - QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, - Steinberg::Vst::IHostApplication); - QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IHostApplication::iid, - Steinberg::Vst::IHostApplication) - - *obj = nullptr; - return Steinberg::kNoInterface; -} + : arguments(std::move(args)) {} tresult PLUGIN_API YaHostApplication::getName(Steinberg::Vst::String128 name) { if (arguments.name) { diff --git a/src/common/serialization/vst3/host-context/host-application.h b/src/common/serialization/vst3/host-context/host-application.h new file mode 100644 index 00000000..7dc449a5 --- /dev/null +++ b/src/common/serialization/vst3/host-context/host-application.h @@ -0,0 +1,86 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include +#include +#include + +#include "../../common.h" +#include "../base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IHostApplication` for serialization purposes. This is + * instantiated as part of `Vst3HostContextProxy`. + */ +class YaHostApplication : public Steinberg::Vst::IHostApplication { + public: + /** + * These are the arguments for creating a `YaHostApplication`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Check whether an existing implementation implements + * `IHostApplication` and read arguments from it. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + /** + * For `IHostApplication::getName`. + */ + std::optional name; + + template + void serialize(S& s) { + s.value1b(supported); + s.ext(name, bitsery::ext::StdOptional{}, + [](S& s, std::u16string& name) { + s.text2b(name, std::extent_v); + }); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaHostApplication(const ConstructArgs&& args); + + inline bool supported() const { return arguments.supported; } + + tresult PLUGIN_API getName(Steinberg::Vst::String128 name) override; + virtual tresult PLUGIN_API createInstance(Steinberg::TUID cid, + Steinberg::TUID _iid, + void** obj) override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index 7a2a33a0..96d91a4e 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -41,7 +41,7 @@ YaPluginFactory::ConstructArgs::ConstructArgs( } } - auto factory2 = Steinberg::FUnknownPtr(factory); + Steinberg::FUnknownPtr factory2(factory); if (!factory2) { return; } @@ -56,7 +56,7 @@ YaPluginFactory::ConstructArgs::ConstructArgs( } } - auto factory3 = Steinberg::FUnknownPtr(factory); + Steinberg::FUnknownPtr factory3(factory); if (!factory3) { return; } diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index bf2a554e..ac78342e 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -22,7 +22,7 @@ #include "../../bitsery/ext/vst3.h" #include "base.h" -#include "host-application.h" +#include "host-context-proxy.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -155,20 +155,22 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { /** * Message to pass through a call to `IPluginFactory3::setHostContext()` to - * the Wine plugin host. A proxy `YaHostApplication` should be created on - * the Wine plugin host and then passed as an argument to - * `IPluginFactory3::setHostContext()`. If the host called - * `IPluginFactory3::setHostContext()` with something other than an - * `IHostApplication*`, we return an error immediately and log the call. + * the Wine plugin host. A `Vst3HostContextProxy` should be created on the + * Wine plugin host and then passed as an argument to + * `IPluginFactory3::setHostContext()`. */ struct SetHostContext { using Response = UniversalTResult; - YaHostApplication::ConstructArgs host_application_context_args; + /** + * Arguments for creating a proxy host context object. If we got passed + * an null pointer we'll reflect that. + */ + std::optional host_context_args; template void serialize(S& s) { - s.object(host_application_context_args); + s.ext(host_context_args, bitsery::ext::StdOptional{}); } }; diff --git a/src/common/serialization/vst3/plugin/audio-processor.h b/src/common/serialization/vst3/plugin/audio-processor.h index 7f15cbbf..3e3c28a7 100644 --- a/src/common/serialization/vst3/plugin/audio-processor.h +++ b/src/common/serialization/vst3/plugin/audio-processor.h @@ -21,7 +21,6 @@ #include "../../common.h" #include "../base.h" -#include "../host-application.h" #include "../process-data.h" #pragma GCC diagnostic push diff --git a/src/common/serialization/vst3/plugin/component.cpp b/src/common/serialization/vst3/plugin/component.cpp index 5d121d66..88debb6e 100644 --- a/src/common/serialization/vst3/plugin/component.cpp +++ b/src/common/serialization/vst3/plugin/component.cpp @@ -19,10 +19,10 @@ YaComponent::ConstructArgs::ConstructArgs() {} YaComponent::ConstructArgs::ConstructArgs( - Steinberg::IPtr object) { - auto component = Steinberg::FUnknownPtr(object); - - if (component) { + Steinberg::IPtr object) + : supported(false) { + if (auto component = + Steinberg::FUnknownPtr(object)) { supported = true; // `IComponent::getControllerClassId` diff --git a/src/common/serialization/vst3/plugin/plugin-base.h b/src/common/serialization/vst3/plugin/plugin-base.h index 4e44161b..ad89d37f 100644 --- a/src/common/serialization/vst3/plugin/plugin-base.h +++ b/src/common/serialization/vst3/plugin/plugin-base.h @@ -21,7 +21,7 @@ #include "../../common.h" #include "../base.h" -#include "../host-application.h" +#include "../host-context-proxy.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -66,23 +66,23 @@ class YaPluginBase : public Steinberg::IPluginBase { /** * Message to pass through a call to `IPluginBase::initialize()` to the Wine - * plugin host. if we pass an `IHostApplication` instance, then a proxy - * `YaHostApplication` should be created and passed as an argument to - * `IPluginBase::initialize()`. If this is absent a null pointer should be - * passed. The lifetime of this `YaHostApplication` object should be bound - * to the `IComponent` we are proxying. + * plugin host. We will read what interfaces the passed context object + * implements so we can then create a proxy object on the Wine side that the + * plugin can use to make callbacks with. The lifetime of this + * `Vst3HostContextProxy` object should be bound to the `IComponent` we are + * proxying. */ struct Initialize { using Response = UniversalTResult; native_size_t instance_id; - std::optional - host_application_context_args; + + std::optional host_context_args; template void serialize(S& s) { s.value8b(instance_id); - s.ext(host_application_context_args, bitsery::ext::StdOptional{}); + s.ext(host_context_args, bitsery::ext::StdOptional{}); } }; diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index b5ca25ce..64fa6c13 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -100,24 +100,21 @@ YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, tresult PLUGIN_API YaPluginFactoryImpl::setHostContext(Steinberg::FUnknown* context) { - // This `context` will likely be an `IHostApplication`. If it is, we will - // store it for future calls, create a proxy object on the Wine side, and - // then pass it to the Windows VST3 plugin's plugin factory using the same - // function. If we get passed anything else we'll just return instead since - // there's nothing we can do with it. - host_application_context = context; + // We will create a proxy object that that supports all the same interfaces + // as `context`, and then we'll store `context` in this object. We can then + // use it to handle callbacks made by the Windows VST3 plugin to this + // context. + host_context = context; - if (host_application_context) { - YaHostApplication::ConstructArgs host_application_context_args( - host_application_context, std::nullopt); - - return bridge.send_message(YaPluginFactory::SetHostContext{ - .host_application_context_args = - std::move(host_application_context_args)}); + std::optional host_context_args{}; + if (host_context) { + host_context_args = + Vst3HostContextProxy::ConstructArgs(host_context, std::nullopt); } else { - bridge.logger.log_unknown_interface( - "In IPluginFactory3::setHostContext(), ignoring", - context ? std::optional(context->iid) : std::nullopt); - return Steinberg::kNotImplemented; + bridge.logger.log( + "Null pointer passed to 'IPluginFactory3::setHostContext()'"); } + + return bridge.send_message(YaPluginFactory::SetHostContext{ + .host_context_args = std::move(host_context_args)}); } diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.h b/src/plugin/bridges/vst3-impls/plugin-factory.h index 7743f2d2..2e6ecc19 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.h +++ b/src/plugin/bridges/vst3-impls/plugin-factory.h @@ -32,9 +32,8 @@ class YaPluginFactoryImpl : public YaPluginFactory { Vst3PluginBridge& bridge; /** - * An `IHostApplication` instance if we get one through + * An host context if we get passed one through * `IPluginFactory3::setHostContext()`. */ - Steinberg::FUnknownPtr - host_application_context; + Steinberg::IPtr host_context; }; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index c2d42901..97b98e9d 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -315,18 +315,18 @@ Vst3PluginProxyImpl::setParamNormalized(Steinberg::Vst::ParamID id, tresult PLUGIN_API Vst3PluginProxyImpl::setComponentHandler( Steinberg::Vst::IComponentHandler* handler) { + // We'll store the pointer for when the plugin later makes a callback to + // this component handler + component_handler = handler; + std::optional component_handler_proxy_args = std::nullopt; if (handler) { - // We'll store the pointer for when the plugin later makes a callback to - // this component handler - component_handler = handler; - component_handler_proxy_args = Vst3ComponentHandlerProxy::ConstructArgs( component_handler, instance_id()); } else { bridge.logger.log( - "Null pointer passed to 'IEditController::setComponentHandler'"); + "Null pointer passed to 'IEditController::setComponentHandler()'"); } return bridge.send_message(YaEditController::SetComponentHandler{ @@ -362,27 +362,23 @@ tresult PLUGIN_API Vst3PluginProxyImpl::openAboutBox(TBool onlyCheck) { } tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* context) { - // This `context` will likely be an `IHostApplication`. If it is, we - // will store it here, and we'll proxy through all calls to it made from - // the Wine side. Otherwise we'll still call `IPluginBase::initialize()` - // but with a null pointer instead. - host_application_context = context; + // We will create a proxy object that that supports all the same interfaces + // as `context`, and then we'll store `context` in this object. We can then + // use it to handle callbacks made by the Windows VST3 plugin to this + // context. + host_context = context; - std::optional - host_application_context_args = std::nullopt; - if (host_application_context) { - host_application_context_args = YaHostApplication::ConstructArgs( - host_application_context, instance_id()); + std::optional host_context_args{}; + if (host_context) { + host_context_args = + Vst3HostContextProxy::ConstructArgs(host_context, instance_id()); } else { - bridge.logger.log_unknown_interface( - "In IPluginBase::initialize()", - context ? std::optional(context->iid) : std::nullopt); + bridge.logger.log("Null pointer passed to 'IPluginBase::initialize()'"); } - return bridge.send_message( - YaPluginBase::Initialize{.instance_id = instance_id(), - .host_application_context_args = - std::move(host_application_context_args)}); + return bridge.send_message(YaPluginBase::Initialize{ + .instance_id = instance_id(), + .host_context_args = std::move(host_context_args)}); } tresult PLUGIN_API Vst3PluginProxyImpl::terminate() { diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index 202e1cec..15e1ee37 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -122,15 +122,6 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { tresult PLUGIN_API initialize(FUnknown* context) override; tresult PLUGIN_API terminate() override; - /** - * An `IHostApplication` instance if we get one through - * `IPluginBase::initialize()`. This should be the same for all plugin - * instances so we should not have to store it here separately, but for the - * sake of correctness we will. - */ - Steinberg::FUnknownPtr - host_application_context; - /** * The component handler the host passed to us during * `IEditController::setComponentHandler()`. When the plugin makes a @@ -141,4 +132,13 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { private: Vst3PluginBridge& bridge; + + /** + * An host context if we get passed one through `IPluginBase::initialize()`. + * We'll read which interfaces it supports and we'll then create a proxy + * object that supports those same interfaces. This should be the same for + * all plugin instances so we should not have to store it here separately, + * but for the sake of correctness we will. + */ + Steinberg::IPtr host_context; }; diff --git a/src/wine-host/bridges/vst3-impls/host-application.cpp b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp similarity index 68% rename from src/wine-host/bridges/vst3-impls/host-application.cpp rename to src/wine-host/bridges/vst3-impls/host-context-proxy.cpp index 307cbbde..b20277b1 100644 --- a/src/wine-host/bridges/vst3-impls/host-application.cpp +++ b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp @@ -14,24 +14,25 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "host-application.h" +#include "host-context-proxy.h" #include -YaHostApplicationImpl::YaHostApplicationImpl( +Vst3HostContextProxyImpl::Vst3HostContextProxyImpl( Vst3Bridge& bridge, - YaHostApplication::ConstructArgs&& args) - : YaHostApplication(std::move(args)), bridge(bridge) { + Vst3HostContextProxy::ConstructArgs&& args) + : Vst3HostContextProxy(std::move(args)), bridge(bridge) { // The lifecycle is thos object is managed together with that of the plugin - // object instance this belongs to + // object instance instance this belongs to } tresult PLUGIN_API -YaHostApplicationImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { +Vst3HostContextProxyImpl::queryInterface(const Steinberg::TUID _iid, + void** obj) { // I don't think it's expected of a host to implement multiple interfaces on // this object, so if we do get a call here it's important that it's logged // TODO: Successful queries should also be logged - const tresult result = YaHostApplication::queryInterface(_iid, obj); + const tresult result = Vst3HostContextProxy::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { std::cerr << "TODO: Implement unknown interface logging on Wine side" << std::endl; @@ -40,9 +41,10 @@ YaHostApplicationImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { return result; } -tresult PLUGIN_API YaHostApplicationImpl::createInstance(Steinberg::TUID cid, - Steinberg::TUID _iid, - void** obj) { +tresult PLUGIN_API +Vst3HostContextProxyImpl::createInstance(Steinberg::TUID cid, + Steinberg::TUID _iid, + void** obj) { // TODO: Implement std::cerr << "TODO: IHostApplication::createInstance()" << std::endl; return Steinberg::kNotImplemented; diff --git a/src/wine-host/bridges/vst3-impls/host-application.h b/src/wine-host/bridges/vst3-impls/host-context-proxy.h similarity index 85% rename from src/wine-host/bridges/vst3-impls/host-application.h rename to src/wine-host/bridges/vst3-impls/host-context-proxy.h index ae5df390..fe6d59a4 100644 --- a/src/wine-host/bridges/vst3-impls/host-application.h +++ b/src/wine-host/bridges/vst3-impls/host-context-proxy.h @@ -18,10 +18,10 @@ #include "../vst3.h" -class YaHostApplicationImpl : public YaHostApplication { +class Vst3HostContextProxyImpl : public Vst3HostContextProxy { public: - YaHostApplicationImpl(Vst3Bridge& bridge, - YaHostApplication::ConstructArgs&& args); + Vst3HostContextProxyImpl(Vst3Bridge& bridge, + Vst3HostContextProxy::ConstructArgs&& args); /** * We'll override the query interface to log queries for interfaces we do @@ -30,6 +30,7 @@ class YaHostApplicationImpl : public YaHostApplication { tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, void** obj) override; + // From `IHostApplication` tresult PLUGIN_API createInstance(Steinberg::TUID cid, Steinberg::TUID _iid, void** obj) override; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index acf09ce3..1d3f0358 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -24,7 +24,7 @@ #include #include "vst3-impls/component-handler-proxy.h" -#include "vst3-impls/host-application.h" +#include "vst3-impls/host-context-proxy.h" InstanceInterfaces::InstanceInterfaces() {} @@ -371,21 +371,19 @@ void Vst3Bridge::run() { // `Vst3HostProxy` // TODO: Does this have to be run from the UI thread? Figure out // if it does - if (request.host_application_context_args) { - object_instances[request.instance_id] - .host_application_context = - Steinberg::owned(new YaHostApplicationImpl( - *this, - std::move(*request.host_application_context_args))); + if (request.host_context_args) { + object_instances[request.instance_id].host_context_proxy = + Steinberg::owned(new Vst3HostContextProxyImpl( + *this, std::move(*request.host_context_args))); } else { - object_instances[request.instance_id] - .host_application_context = nullptr; + object_instances[request.instance_id].host_context_proxy = + nullptr; } return object_instances[request.instance_id] .plugin_base->initialize( object_instances[request.instance_id] - .host_application_context); + .host_context_proxy); }, [&](const YaPluginBase::Terminate& request) -> YaPluginBase::Terminate::Response { @@ -399,16 +397,19 @@ void Vst3Bridge::run() { }, [&](YaPluginFactory::SetHostContext& request) -> YaPluginFactory::SetHostContext::Response { - plugin_factory_host_application_context = - Steinberg::owned(new YaHostApplicationImpl( - *this, - std::move(request.host_application_context_args))); + if (request.host_context_args) { + plugin_factory_host_context = + Steinberg::owned(new Vst3HostContextProxyImpl( + *this, std::move(*request.host_context_args))); + } else { + plugin_factory_host_context = nullptr; + } Steinberg::FUnknownPtr factory_3( module->getFactory().get()); + assert(factory_3); - return factory_3->setHostContext( - plugin_factory_host_application_context); + return factory_3->setHostContext(plugin_factory_host_context); }}); } diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 8e2f2d88..1a955268 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -38,12 +38,12 @@ struct InstanceInterfaces { InstanceInterfaces(Steinberg::IPtr object); /** - * If the host passes an `IHostApplication` during + * If the host passes a host context object during * `IPluginBase::initialize()`, we'll store a proxy object here and then * pass it to `plugin_base->initialize()`. Will be initialized with a null * pointer until used. */ - Steinberg::IPtr host_application_context; + Steinberg::IPtr host_context_proxy; /** * After a call to `IEditController::setComponentHandler()`, we'll create a @@ -155,11 +155,10 @@ class Vst3Bridge : public HostBridge { std::atomic_size_t current_instance_id; /** - * The host application context proxy object if we got passed a host - * application context during a call to `IPluginFactory3::setHostContext()` - * by the host. + * The host context proxy object if we got passed a host context during a + * call to `IPluginFactory3::setHostContext()` by the host. */ - Steinberg::IPtr plugin_factory_host_application_context; + Steinberg::IPtr plugin_factory_host_context; /** * These are all the objects we have created through the Windows VST3 From 85faca736f163c5b72edc18c549687dcff9924c0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 17:19:01 +0100 Subject: [PATCH 299/456] Add a todo for removing cached functions --- .../serialization/vst3/host-context/host-application.cpp | 3 +++ src/common/serialization/vst3/plugin/component.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/common/serialization/vst3/host-context/host-application.cpp b/src/common/serialization/vst3/host-context/host-application.cpp index c4a7a3fd..9072a9ab 100644 --- a/src/common/serialization/vst3/host-context/host-application.cpp +++ b/src/common/serialization/vst3/host-context/host-application.cpp @@ -37,6 +37,9 @@ YaHostApplication::YaHostApplication(const ConstructArgs&& args) : arguments(std::move(args)) {} tresult PLUGIN_API YaHostApplication::getName(Steinberg::Vst::String128 name) { + // TODO: This is now not being logged at all. It's probably better if we + // just drop these two functions that output cached data directly. + // They'll only be used once or twice anyways. if (arguments.name) { // Terminate with a null byte. There are no nice functions for copying // UTF-16 strings (because who would use those?). diff --git a/src/common/serialization/vst3/plugin/component.cpp b/src/common/serialization/vst3/plugin/component.cpp index 88debb6e..5635595d 100644 --- a/src/common/serialization/vst3/plugin/component.cpp +++ b/src/common/serialization/vst3/plugin/component.cpp @@ -37,6 +37,9 @@ YaComponent::YaComponent(const ConstructArgs&& args) : arguments(std::move(args)) {} tresult PLUGIN_API YaComponent::getControllerClassId(Steinberg::TUID classId) { + // TODO: This is now not being logged at all. It's probably better if we + // just drop these two functions that output cached data directly. + // They'll only be used once or twice anyways. if (arguments.edit_controller_cid) { std::copy(arguments.edit_controller_cid->begin(), arguments.edit_controller_cid->end(), classId); From cb815ebb569e15408a24ac45556fbf5db88432a2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 17:40:55 +0100 Subject: [PATCH 300/456] Work around null pointers in Ardour --- src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 97b98e9d..3ab12494 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -45,14 +45,18 @@ tresult PLUGIN_API Vst3PluginProxyImpl::setBusArrangements( int32 numIns, Steinberg::Vst::SpeakerArrangement* outputs, int32 numOuts) { - assert(inputs && outputs); + // NOTE: Ardour passes a null pointer when `numIns` or `numOuts` is 0, so we + // need to work around that return bridge.send_message(YaAudioProcessor::SetBusArrangements{ .instance_id = instance_id(), - .inputs = std::vector( - inputs, &inputs[numIns]), + .inputs = (inputs ? std::vector( + inputs, &inputs[numIns]) + : std::vector()), .num_ins = numIns, - .outputs = std::vector( - outputs, &outputs[numOuts]), + .outputs = + (outputs ? std::vector( + outputs, &outputs[numOuts]) + : std::vector()), .num_outs = numOuts, }); } From 01d84b002976058d3d4123c020c7b484441ebaf5 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 17:49:00 +0100 Subject: [PATCH 301/456] Mention the exact function name in todo message --- src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp | 3 ++- src/wine-host/bridges/vst3-impls/host-context-proxy.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp index 615a909f..8f316833 100644 --- a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp @@ -32,7 +32,8 @@ Vst3ComponentHandlerProxyImpl::queryInterface(const Steinberg::TUID _iid, // TODO: Successful queries should also be logged const tresult result = Vst3ComponentHandlerProxy::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { - std::cerr << "TODO: Implement unknown interface logging on Wine side" + std::cerr << "TODO: Implement unknown interface logging on Wine side " + "for Vst3ComponentHandlerProxyImpl::queryInterface" << std::endl; } diff --git a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp index b20277b1..937b0546 100644 --- a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp @@ -34,7 +34,8 @@ Vst3HostContextProxyImpl::queryInterface(const Steinberg::TUID _iid, // TODO: Successful queries should also be logged const tresult result = Vst3HostContextProxy::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { - std::cerr << "TODO: Implement unknown interface logging on Wine side" + std::cerr << "TODO: Implement unknown interface logging on Wine side " + "for Vst3HostContextProxyImpl::queryInterface" << std::endl; } From 63ae5f330c51f8760ff9bea44a6ebc5fd23d64ee Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 18:28:16 +0100 Subject: [PATCH 302/456] Don't cache IHostApplication::getName() As it turns out there are only two or three functions where we can do this. It also breaks logging, and this function will probably only be called once anyways. More consistency is always better. --- src/common/logging/vst3.cpp | 25 ++++++++++ src/common/logging/vst3.h | 4 ++ src/common/serialization/vst3.h | 3 +- .../vst3/host-context/host-application.cpp | 30 +---------- .../vst3/host-context/host-application.h | 50 +++++++++++++++---- .../bridges/vst3-impls/plugin-factory.cpp | 4 ++ .../bridges/vst3-impls/plugin-factory.h | 5 ++ .../bridges/vst3-impls/plugin-proxy.cpp | 4 ++ src/plugin/bridges/vst3-impls/plugin-proxy.h | 5 ++ src/plugin/bridges/vst3.cpp | 18 +++++++ src/plugin/bridges/vst3.h | 5 +- .../bridges/vst3-impls/host-context-proxy.cpp | 11 ++++ .../bridges/vst3-impls/host-context-proxy.h | 1 + 13 files changed, 124 insertions(+), 41 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 9de2ee99..50f3da77 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -487,6 +487,19 @@ bool Vst3Logger::log_request( }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaHostApplication::GetName& request) { + return log_request_base(is_host_vst, [&](auto& message) { + // This can be called either from a plugin object or from the plugin's + // plugin factory + if (request.owner_instance_id) { + message << *request.owner_instance_id << ": "; + } + + message << "IHostApplication::getName(&name)"; + }); +} + void Vst3Logger::log_response(bool is_host_vst, const Ack&) { log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); } @@ -644,3 +657,15 @@ void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { log_response_base(is_host_vst, [&](auto& message) { message << ""; }); } + +void Vst3Logger::log_response( + bool is_host_vst, + const YaHostApplication::GetNameResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + std::string value = VST3::StringConvert::convert(response.name); + message << ", \"" << value << "\""; + } + }); +} diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 42639df4..ee7e2544 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -115,6 +115,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaComponentHandler::EndEdit&); bool log_request(bool is_host_vst, const YaComponentHandler::RestartComponent&); + bool log_request(bool is_host_vst, const YaHostApplication::GetName&); void log_response(bool is_host_vst, const Ack&); void log_response( @@ -138,6 +139,9 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); + void log_response(bool is_host_vst, + const YaHostApplication::GetNameResponse&); + template void log_response(bool is_host_vst, const PrimitiveWrapper& value) { // For logging all primitive return values other than `tresult` diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index f8e33ae6..955b2c6e 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -110,7 +110,8 @@ using CallbackRequest = std::variant; + YaComponentHandler::RestartComponent, + YaHostApplication::GetName>; template void serialize(S& s, CallbackRequest& payload) { diff --git a/src/common/serialization/vst3/host-context/host-application.cpp b/src/common/serialization/vst3/host-context/host-application.cpp index 9072a9ab..46c12993 100644 --- a/src/common/serialization/vst3/host-context/host-application.cpp +++ b/src/common/serialization/vst3/host-context/host-application.cpp @@ -20,34 +20,8 @@ YaHostApplication::ConstructArgs::ConstructArgs() {} YaHostApplication::ConstructArgs::ConstructArgs( Steinberg::IPtr object) - : supported(false) { - if (auto host_application = - Steinberg::FUnknownPtr(object)) { - supported = true; - - // `IHostApplication::getName` - Steinberg::Vst::String128 name_array; - if (host_application->getName(name_array) == Steinberg::kResultOk) { - name = tchar_pointer_to_u16string(name_array); - } - } -} + : supported( + Steinberg::FUnknownPtr(object)) {} YaHostApplication::YaHostApplication(const ConstructArgs&& args) : arguments(std::move(args)) {} - -tresult PLUGIN_API YaHostApplication::getName(Steinberg::Vst::String128 name) { - // TODO: This is now not being logged at all. It's probably better if we - // just drop these two functions that output cached data directly. - // They'll only be used once or twice anyways. - if (arguments.name) { - // Terminate with a null byte. There are no nice functions for copying - // UTF-16 strings (because who would use those?). - std::copy(arguments.name->begin(), arguments.name->end(), name); - name[arguments.name->size()] = 0; - - return Steinberg::kResultOk; - } else { - return Steinberg::kNotImplemented; - } -} diff --git a/src/common/serialization/vst3/host-context/host-application.h b/src/common/serialization/vst3/host-context/host-application.h index 7dc449a5..5b63dea4 100644 --- a/src/common/serialization/vst3/host-context/host-application.h +++ b/src/common/serialization/vst3/host-context/host-application.h @@ -51,18 +51,9 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { */ bool supported; - /** - * For `IHostApplication::getName`. - */ - std::optional name; - template void serialize(S& s) { s.value1b(supported); - s.ext(name, bitsery::ext::StdOptional{}, - [](S& s, std::u16string& name) { - s.text2b(name, std::extent_v); - }); } }; @@ -74,7 +65,46 @@ class YaHostApplication : public Steinberg::Vst::IHostApplication { inline bool supported() const { return arguments.supported; } - tresult PLUGIN_API getName(Steinberg::Vst::String128 name) override; + /** + * The response code and resulting value for a call to + * `IHostApplication::getName()`. + */ + struct GetNameResponse { + UniversalTResult result; + std::u16string name; + + template + void serialize(S& s) { + s.object(result); + s.text2b(name, std::extent_v); + } + }; + + /** + * Message to pass through a call to `IHostApplication::getName()` to the + * host context provided by the host. + */ + struct GetName { + using Response = GetNameResponse; + + /** + * The object instance whose host context to call this function to. Of + * empty, then the function will be called on the factory's host context + * instead. + */ + std::optional owner_instance_id; + + template + void serialize(S& s) { + s.ext(owner_instance_id, bitsery::ext::StdOptional{}, + [](S& s, native_size_t& instance_id) { + s.value8b(instance_id); + }); + } + }; + + virtual tresult PLUGIN_API + getName(Steinberg::Vst::String128 name) override = 0; virtual tresult PLUGIN_API createInstance(Steinberg::TUID cid, Steinberg::TUID _iid, void** obj) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 64fa6c13..988bf06a 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -106,6 +106,10 @@ YaPluginFactoryImpl::setHostContext(Steinberg::FUnknown* context) { // context. host_context = context; + // Automatically converted smart pointers for when the plugin performs a + // callback later + host_application = host_context; + std::optional host_context_args{}; if (host_context) { host_context_args = diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.h b/src/plugin/bridges/vst3-impls/plugin-factory.h index 2e6ecc19..3e2d93a6 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.h +++ b/src/plugin/bridges/vst3-impls/plugin-factory.h @@ -28,6 +28,11 @@ class YaPluginFactoryImpl : public YaPluginFactory { void** obj) override; tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override; + // The following pointers are cast from `host_context` if `setHostContext()` + // has been called + + Steinberg::FUnknownPtr host_application; + private: Vst3PluginBridge& bridge; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 3ab12494..cabe7b61 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -323,6 +323,10 @@ tresult PLUGIN_API Vst3PluginProxyImpl::setComponentHandler( // this component handler component_handler = handler; + // Automatically converted smart pointers for when the plugin performs a + // callback later + host_application = host_context; + std::optional component_handler_proxy_args = std::nullopt; if (handler) { diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index 15e1ee37..e406f968 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -130,6 +130,11 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { */ Steinberg::IPtr component_handler; + // The following pointers are cast from `host_context` if `setHostContext()` + // has been called + + Steinberg::FUnknownPtr host_application; + private: Vst3PluginBridge& bridge; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 35811bfc..681d6bd3 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -83,6 +83,24 @@ Vst3PluginBridge::Vst3PluginBridge() [&](const WantsConfiguration&) -> WantsConfiguration::Response { return config; }, + [&](const YaHostApplication::GetName& request) + -> YaHostApplication::GetName::Response { + tresult result; + Steinberg::Vst::String128 name{0}; + if (request.owner_instance_id) { + result = plugin_proxies.at(*request.owner_instance_id) + .get() + .host_application->getName(name); + } else { + result = + plugin_factory->host_application->getName(name); + } + + return YaHostApplication::GetNameResponse{ + .result = result, + .name = tchar_pointer_to_u16string(name), + }; + }, [&](const YaComponentHandler::BeginEdit& request) -> YaComponentHandler::BeginEdit::Response { return plugin_proxies.at(request.owner_instance_id) diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index c0ad49e7..613b89f1 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -23,8 +23,9 @@ #include "../../common/logging/vst3.h" #include "common.h" -// Forward declaration +// Forward declarations class Vst3PluginProxyImpl; +class YaPluginFactoryImpl; /** * This handles the communication between the native host and a VST3 plugin @@ -134,7 +135,7 @@ class Vst3PluginBridge : PluginBridge> { * * @related get_plugin_factory */ - YaPluginFactory* plugin_factory = nullptr; + YaPluginFactoryImpl* plugin_factory = nullptr; private: /** diff --git a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp index 937b0546..c3661e16 100644 --- a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp @@ -42,6 +42,17 @@ Vst3HostContextProxyImpl::queryInterface(const Steinberg::TUID _iid, return result; } +tresult PLUGIN_API +Vst3HostContextProxyImpl::getName(Steinberg::Vst::String128 name) { + const GetNameResponse response = bridge.send_message( + YaHostApplication::GetName{.owner_instance_id = owner_instance_id()}); + + std::copy(response.name.begin(), response.name.end(), name); + name[response.name.size()] = 0; + + return response.result; +} + tresult PLUGIN_API Vst3HostContextProxyImpl::createInstance(Steinberg::TUID cid, Steinberg::TUID _iid, diff --git a/src/wine-host/bridges/vst3-impls/host-context-proxy.h b/src/wine-host/bridges/vst3-impls/host-context-proxy.h index fe6d59a4..85ca3476 100644 --- a/src/wine-host/bridges/vst3-impls/host-context-proxy.h +++ b/src/wine-host/bridges/vst3-impls/host-context-proxy.h @@ -31,6 +31,7 @@ class Vst3HostContextProxyImpl : public Vst3HostContextProxy { void** obj) override; // From `IHostApplication` + tresult PLUGIN_API getName(Steinberg::Vst::String128 name) override; tresult PLUGIN_API createInstance(Steinberg::TUID cid, Steinberg::TUID _iid, void** obj) override; From b422f6fd422b21b36f8beca524865fc242cd905d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 18:56:47 +0100 Subject: [PATCH 303/456] Don't cache IComponent::getControllerClassId() For the same reasons as the last commit. Now we don't have any of these cached methods anymore. --- src/common/logging/vst3.cpp | 21 ++++++++++ src/common/logging/vst3.h | 4 ++ src/common/serialization/vst3.h | 1 + .../serialization/vst3/plugin/component.cpp | 26 +----------- .../serialization/vst3/plugin/component.h | 41 +++++++++++++++---- .../bridges/vst3-impls/plugin-proxy.cpp | 10 +++++ src/plugin/bridges/vst3-impls/plugin-proxy.h | 1 + src/wine-host/bridges/vst3.cpp | 10 +++++ 8 files changed, 80 insertions(+), 34 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 50f3da77..dd55ac0e 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -216,6 +216,14 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetControllerClassId& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IComponent::getControllerClassId(&classId)"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaComponent::SetIoMode& request) { return log_request_base(is_host_vst, [&](auto& message) { @@ -583,6 +591,19 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaComponent::GetControllerClassIdResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", " + << format_uid(Steinberg::FUID::fromTUID( + response.editor_cid.data())); + } + }); +} + void Vst3Logger::log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse& response) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index ee7e2544..2ee16a8f 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -76,6 +76,8 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaAudioProcessor::SetProcessing&); bool log_request(bool is_host_vst, const YaAudioProcessor::Process&); bool log_request(bool is_host_vst, const YaAudioProcessor::GetTailSamples&); + bool log_request(bool is_host_vst, + const YaComponent::GetControllerClassId&); bool log_request(bool is_host_vst, const YaComponent::SetIoMode&); bool log_request(bool is_host_vst, const YaComponent::GetBusCount&); bool log_request(bool is_host_vst, const YaComponent::GetBusInfo&); @@ -127,6 +129,8 @@ class Vst3Logger { const YaAudioProcessor::GetBusArrangementResponse&); void log_response(bool is_host_vst, const YaAudioProcessor::ProcessResponse&); + void log_response(bool is_host_vst, + const YaComponent::GetControllerClassIdResponse&); void log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse&); void log_response(bool is_host_vst, const YaComponent::GetRoutingInfoResponse&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 955b2c6e..4908cff3 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -71,6 +71,7 @@ using ControlRequest = std::variant object) - : supported(false) { - if (auto component = - Steinberg::FUnknownPtr(object)) { - supported = true; - - // `IComponent::getControllerClassId` - Steinberg::TUID cid; - if (component->getControllerClassId(cid) == Steinberg::kResultOk) { - edit_controller_cid = std::to_array(cid); - } - } -} + : supported(Steinberg::FUnknownPtr(object)) {} YaComponent::YaComponent(const ConstructArgs&& args) : arguments(std::move(args)) {} - -tresult PLUGIN_API YaComponent::getControllerClassId(Steinberg::TUID classId) { - // TODO: This is now not being logged at all. It's probably better if we - // just drop these two functions that output cached data directly. - // They'll only be used once or twice anyways. - if (arguments.edit_controller_cid) { - std::copy(arguments.edit_controller_cid->begin(), - arguments.edit_controller_cid->end(), classId); - return Steinberg::kResultOk; - } else { - return Steinberg::kNotImplemented; - } -} diff --git a/src/common/serialization/vst3/plugin/component.h b/src/common/serialization/vst3/plugin/component.h index 60b73b7a..9449e6fb 100644 --- a/src/common/serialization/vst3/plugin/component.h +++ b/src/common/serialization/vst3/plugin/component.h @@ -52,17 +52,9 @@ class YaComponent : public Steinberg::Vst::IComponent { */ bool supported; - /** - * The class ID of this component's corresponding editor controller. You - * can't use C-style array in `std::optional`s. - */ - std::optional edit_controller_cid; - template void serialize(S& s) { s.value1b(supported); - s.ext(edit_controller_cid, bitsery::ext::StdOptional{}, - [](S& s, auto& cid) { s.container1b(cid); }); } }; @@ -74,7 +66,38 @@ class YaComponent : public Steinberg::Vst::IComponent { inline bool supported() const { return arguments.supported; } - tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override; + /** + * The response code and returned CID for a call to + * `IComponent::getControllerClassId()`. + */ + struct GetControllerClassIdResponse { + UniversalTResult result; + ArrayUID editor_cid; + + template + void serialize(S& s) { + s.object(result); + s.container1b(editor_cid); + } + }; + + /** + * Message to pass through a call to `IComponent::getControllerClassId()` to + * the Wine plugin host. + */ + struct GetControllerClassId { + using Response = GetControllerClassIdResponse; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + + virtual tresult PLUGIN_API + getControllerClassId(Steinberg::TUID classId) override = 0; /** * Message to pass through a call to `IComponent::setIoMode(mode)` to the diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index cabe7b61..e7050085 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -116,6 +116,16 @@ uint32 PLUGIN_API Vst3PluginProxyImpl::getTailSamples() { YaAudioProcessor::GetTailSamples{.instance_id = instance_id()}); } +tresult PLUGIN_API +Vst3PluginProxyImpl::getControllerClassId(Steinberg::TUID classId) { + const GetControllerClassIdResponse response = bridge.send_message( + YaComponent::GetControllerClassId{.instance_id = instance_id()}); + + std::copy(response.editor_cid.begin(), response.editor_cid.end(), classId); + + return response.result; +} + tresult PLUGIN_API Vst3PluginProxyImpl::setIoMode(Steinberg::Vst::IoMode mode) { return bridge.send_message( YaComponent::SetIoMode{.instance_id = instance_id(), .mode = mode}); diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index e406f968..0d6e856f 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -56,6 +56,7 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { uint32 PLUGIN_API getTailSamples() override; // From `IComponent` + tresult PLUGIN_API getControllerClassId(Steinberg::TUID classId) override; tresult PLUGIN_API setIoMode(Steinberg::Vst::IoMode mode) override; int32 PLUGIN_API getBusCount(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir) override; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 1d3f0358..5a1767d5 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -210,6 +210,16 @@ void Vst3Bridge::run() { return object_instances[request.instance_id] .audio_processor->getTailSamples(); }, + [&](const YaComponent::GetControllerClassId& request) + -> YaComponent::GetControllerClassId::Response { + Steinberg::TUID cid; + const tresult result = + object_instances[request.instance_id] + .component->getControllerClassId(cid); + + return YaComponent::GetControllerClassIdResponse{ + .result = result, .editor_cid = std::to_array(cid)}; + }, [&](const YaComponent::SetIoMode& request) -> YaComponent::SetIoMode::Response { return object_instances[request.instance_id] From a724b378fe8941cef686fa9b35e86ec03c604dd7 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 19:56:49 +0100 Subject: [PATCH 304/456] Move the editor handling back to Vst2Bridge Since we can have multiple editors in Vst3Bridge. --- src/wine-host/bridges/common.cpp | 21 --------------------- src/wine-host/bridges/common.h | 23 ++++++----------------- src/wine-host/bridges/group.h | 1 + src/wine-host/bridges/vst2.cpp | 21 +++++++++++++++++++++ src/wine-host/bridges/vst2.h | 11 +++++++++++ src/wine-host/bridges/vst3.cpp | 17 +++++++++++++++++ src/wine-host/bridges/vst3.h | 4 ++++ src/wine-host/editor.h | 2 ++ 8 files changed, 62 insertions(+), 38 deletions(-) diff --git a/src/wine-host/bridges/common.cpp b/src/wine-host/bridges/common.cpp index 01bd6359..2aeda267 100644 --- a/src/wine-host/bridges/common.cpp +++ b/src/wine-host/bridges/common.cpp @@ -18,24 +18,3 @@ HostBridge::HostBridge(boost::filesystem::path plugin_path) : plugin_path(plugin_path) {} - -void HostBridge::handle_win32_events() { - if (editor) { - editor->handle_win32_events(); - } else { - MSG msg; - - for (int i = 0; i < max_win32_messages && - PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); - i++) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } -} - -void HostBridge::handle_x11_events() { - if (editor) { - editor->handle_x11_events(); - } -} diff --git a/src/wine-host/bridges/common.h b/src/wine-host/bridges/common.h index efefd9ee..d9176725 100644 --- a/src/wine-host/bridges/common.h +++ b/src/wine-host/bridges/common.h @@ -16,7 +16,9 @@ #pragma once -#include "../editor.h" +#include "../boost-fix.h" + +#include /** * The base for the Wine plugin host bridge interface for all plugin types. This @@ -47,7 +49,7 @@ class HostBridge { * Handle X11 events for the editor window if it is open. This can safely be * run from any thread. */ - void handle_x11_events(); + virtual void handle_x11_events() = 0; /** * Run the message loop for this plugin. This is only used for the @@ -63,25 +65,12 @@ class HostBridge { * we have to make sure to always run this loop. The only exception is a in * specific situation that can cause a race condition in some plugins * because of incorrect assumptions made by the plugin. See the dostring for - * `HostBridge::editor` for more information. + * `Vst2Bridge::editor` for more information. */ - void handle_win32_events(); + virtual void handle_win32_events() = 0; /** * The path to the .dll being loaded in the Wine plugin host. */ const boost::filesystem::path plugin_path; - - protected: - /** - * The plugin editor window. Allows embedding the plugin's editor into a - * Wine window, and embedding that Wine window into a window provided by the - * host. Should be empty when the editor is not open. - * - * TODO: This should be moved back to `Vst2Bridge`, `handle_x11_events()`` - * and `handle_win32_events()` should be made pure virtual. A single - * `Vst3Bridge` instance will handle multiple plugin instances because - * of the way VST3 works. - */ - std::optional editor; }; diff --git a/src/wine-host/bridges/group.h b/src/wine-host/bridges/group.h index 47a1c759..f72e4938 100644 --- a/src/wine-host/bridges/group.h +++ b/src/wine-host/bridges/group.h @@ -27,6 +27,7 @@ #include #include "../common/logging/common.h" +#include "../utils.h" #include "common.h" /** diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index 86f58ab9..60b29992 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -350,6 +350,27 @@ void Vst2Bridge::run() { }); } +void Vst2Bridge::handle_x11_events() { + if (editor) { + editor->handle_x11_events(); + } +} + +void Vst2Bridge::handle_win32_events() { + if (editor) { + editor->handle_win32_events(); + } else { + MSG msg; + + for (int i = 0; i < max_win32_messages && + PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); + i++) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +} + intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin, int opcode, int index, diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index c7f68f04..63848d91 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -29,6 +29,7 @@ #include "../../common/communication/vst2.h" #include "../../common/configuration.h" +#include "../editor.h" #include "../utils.h" #include "common.h" @@ -66,6 +67,9 @@ class Vst2Bridge : public HostBridge { */ void run() override; + void handle_x11_events() override; + void handle_win32_events() override; + /** * Forward the host callback made by the plugin to the host and return the * results. @@ -144,6 +148,13 @@ class Vst2Bridge : public HostBridge { */ Vst2Sockets sockets; + /** + * The plugin editor window. Allows embedding the plugin's editor into a + * Wine window, and embedding that Wine window into a window provided by the + * host. Should be empty when the editor is not open. + */ + std::optional editor; + /** * The MIDI events that have been received **and processed** since the last * call to `processReplacing()`. 99% of plugins make a copy of the MIDI diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 5a1767d5..fe9bf891 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -423,6 +423,23 @@ void Vst3Bridge::run() { }}); } +void Vst3Bridge::handle_x11_events() { + // TODO: Implement editors +} + +void Vst3Bridge::handle_win32_events() { + // TODO: Implement editors + + MSG msg; + + for (int i = 0; + i < max_win32_messages && PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); + i++) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + size_t Vst3Bridge::generate_instance_id() { return current_instance_id.fetch_add(1); } diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 1a955268..ff734630 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -22,6 +22,7 @@ #include "../../common/communication/vst3.h" #include "../../common/configuration.h" +#include "../editor.h" #include "common.h" /** @@ -103,6 +104,9 @@ class Vst3Bridge : public HostBridge { */ void run() override; + void handle_x11_events() override; + void handle_win32_events() override; + /** * Send a callback message to the host return the response. This is a * shorthand for `sockets.vst_host_callback.send_message` for use in VST3 diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index 41ff5f38..c75523c1 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#pragma once + // Use the native version of xcb #pragma push_macro("_WIN32") #undef _WIN32 From 76a1ed60829dda6ed77083a36c76cf0e8d34c782 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 20:09:04 +0100 Subject: [PATCH 305/456] Add an IPlugView wrapper --- meson.build | 2 + .../vst3/plug-view/plug-view.cpp | 26 ++++++ .../serialization/vst3/plug-view/plug-view.h | 90 +++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 src/common/serialization/vst3/plug-view/plug-view.cpp create mode 100644 src/common/serialization/vst3/plug-view/plug-view.h diff --git a/meson.build b/meson.build index f88c0f0f..9cb58b34 100644 --- a/meson.build +++ b/meson.build @@ -77,6 +77,7 @@ vst3_plugin_sources = [ 'src/common/communication/common.cpp', 'src/common/logging/common.cpp', 'src/common/logging/vst3.cpp', + 'src/common/serialization/vst3/plug-view/plug-view.cpp', 'src/common/serialization/vst3/plugin/audio-processor.cpp', 'src/common/serialization/vst3/plugin/component.cpp', 'src/common/serialization/vst3/plugin/connection-point.cpp', @@ -125,6 +126,7 @@ host_sources = [ if with_vst3 host_sources += [ 'src/common/logging/vst3.cpp', + 'src/common/serialization/vst3/plug-view/plug-view.cpp', 'src/common/serialization/vst3/plugin/audio-processor.cpp', 'src/common/serialization/vst3/plugin/component.cpp', 'src/common/serialization/vst3/plugin/connection-point.cpp', diff --git a/src/common/serialization/vst3/plug-view/plug-view.cpp b/src/common/serialization/vst3/plug-view/plug-view.cpp new file mode 100644 index 00000000..d5959681 --- /dev/null +++ b/src/common/serialization/vst3/plug-view/plug-view.cpp @@ -0,0 +1,26 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "plug-view.h" + +YaPlugView::ConstructArgs::ConstructArgs() {} + +YaPlugView::ConstructArgs::ConstructArgs( + Steinberg::IPtr object) + : supported(Steinberg::FUnknownPtr(object)) {} + +YaPlugView::YaPlugView(const ConstructArgs&& args) + : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plug-view/plug-view.h b/src/common/serialization/vst3/plug-view/plug-view.h new file mode 100644 index 00000000..f3f400a5 --- /dev/null +++ b/src/common/serialization/vst3/plug-view/plug-view.h @@ -0,0 +1,90 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "../../common.h" +#include "../base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IPlugView` for serialization purposes. This is instantiated as + * part of `Vst3PlugViewProxy`. + */ +class YaPlugView : public Steinberg::IPlugView { + public: + /** + * These are the arguments for creating a `YaPlugView`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Check whether an existing implementation implements `IPlugView` and + * read arguments from it. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + template + void serialize(S& s) { + s.value1b(supported); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaPlugView(const ConstructArgs&& args); + + inline bool supported() const { return arguments.supported; } + + virtual tresult PLUGIN_API + isPlatformTypeSupported(Steinberg::FIDString type) override = 0; + virtual tresult PLUGIN_API attached(void* parent, + Steinberg::FIDString type) override = 0; + virtual tresult PLUGIN_API removed() override = 0; + virtual tresult PLUGIN_API onWheel(float distance) override = 0; + virtual tresult PLUGIN_API onKeyDown(char16 key, + int16 keyCode, + int16 modifiers) override = 0; + virtual tresult PLUGIN_API onKeyUp(char16 key, + int16 keyCode, + int16 modifiers) override = 0; + virtual tresult PLUGIN_API getSize(Steinberg::ViewRect* size) override = 0; + virtual tresult PLUGIN_API + onSize(Steinberg::ViewRect* newSize) override = 0; + virtual tresult PLUGIN_API onFocus(TBool state) override = 0; + virtual tresult PLUGIN_API + setFrame(Steinberg::IPlugFrame* frame) override = 0; + virtual tresult PLUGIN_API canResize() override = 0; + virtual tresult PLUGIN_API + checkSizeConstraint(Steinberg::ViewRect* rect) override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop From e391bbccb75ae2a2dd7df8ee5f07dd45b7643adb Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 20:18:45 +0100 Subject: [PATCH 306/456] Add Vst3PlugViewProxy --- meson.build | 2 + src/common/serialization/vst3/README.md | 22 +++-- src/common/serialization/vst3/base.h | 6 +- .../vst3/component-handler-proxy.h | 2 +- .../serialization/vst3/host-context-proxy.h | 2 +- .../serialization/vst3/plug-view-proxy.cpp | 50 ++++++++++ .../serialization/vst3/plug-view-proxy.h | 94 +++++++++++++++++++ 7 files changed, 163 insertions(+), 15 deletions(-) create mode 100644 src/common/serialization/vst3/plug-view-proxy.cpp create mode 100644 src/common/serialization/vst3/plug-view-proxy.h diff --git a/meson.build b/meson.build index 9cb58b34..88c3280c 100644 --- a/meson.build +++ b/meson.build @@ -92,6 +92,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/host-context-proxy.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', + 'src/common/serialization/vst3/plug-view-proxy.cpp', 'src/common/serialization/vst3/plugin-proxy.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', @@ -141,6 +142,7 @@ if with_vst3 'src/common/serialization/vst3/host-context-proxy.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', + 'src/common/serialization/vst3/plug-view-proxy.cpp', 'src/common/serialization/vst3/plugin-proxy.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index ed41bb22..efe13210 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -7,16 +7,18 @@ serialization works. VST3 plugin interfaces are implemented as follows: -| yabridge class | Included in | Interfaces | -| ------------------- | ----------------- | ------------------------------------------------------ | -| `YaPluginFactory` | | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | -| `Vst3PluginProxy` | | All of the below: | -| `YaAudioProcessor` | `Vst3PluginProxy` | `IAudioProcessor` | -| `YaComponent` | `Vst3PluginProxy` | `IComponent` | -| `YaConnectionPoint` | `Vst3PluginProxy` | `IConnectionPoint` | -| `YaEditController` | `Vst3PluginProxy` | `IEditController` | -| `YaEditController2` | `Vst3PluginProxy` | `IEditController2` | -| `YaPluginBase` | `Vst3PluginProxy` | `IPluginBase` | +| yabridge class | Included in | Interfaces | +| ------------------- | ------------------- | ------------------------------------------------------ | +| `YaPluginFactory` | | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | +| `Vst3PlugViewProxy` | | All of the below: | +| `YaPlugView` | `Vst3PlugViewProxy` | `IPlugView` | +| `Vst3PluginProxy` | | All of the below: | +| `YaAudioProcessor` | `Vst3PluginProxy` | `IAudioProcessor` | +| `YaComponent` | `Vst3PluginProxy` | `IComponent` | +| `YaConnectionPoint` | `Vst3PluginProxy` | `IConnectionPoint` | +| `YaEditController` | `Vst3PluginProxy` | `IEditController` | +| `YaEditController2` | `Vst3PluginProxy` | `IEditController2` | +| `YaPluginBase` | `Vst3PluginProxy` | `IPluginBase` | VST3 host interfaces are implemented as follows: diff --git a/src/common/serialization/vst3/base.h b/src/common/serialization/vst3/base.h index 7c8d8319..8b0bfeb2 100644 --- a/src/common/serialization/vst3/base.h +++ b/src/common/serialization/vst3/base.h @@ -28,9 +28,9 @@ // Yet Another layer of includes, but these are some VST3-specific typedefs that // we'll need for all of our interfaces -using Steinberg::TBool, Steinberg::int8, Steinberg::int16, Steinberg::int32, - Steinberg::int64, Steinberg::uint8, Steinberg::uint16, Steinberg::uint32, - Steinberg::uint64, Steinberg::tresult; +using Steinberg::TBool, Steinberg::char16, Steinberg::int8, Steinberg::int16, + Steinberg::int32, Steinberg::int64, Steinberg::uint8, Steinberg::uint16, + Steinberg::uint32, Steinberg::uint64, Steinberg::tresult; /** * Both `TUID` (`int8_t[16]`) and `FIDString` (`char*`) are hard to work with diff --git a/src/common/serialization/vst3/component-handler-proxy.h b/src/common/serialization/vst3/component-handler-proxy.h index 522392c6..aa0a944d 100644 --- a/src/common/serialization/vst3/component-handler-proxy.h +++ b/src/common/serialization/vst3/component-handler-proxy.h @@ -81,7 +81,7 @@ class Vst3ComponentHandlerProxy : public YaComponentHandler { * dropped a corresponding `Vst3ComponentHandlerProxyImpl` should also be * dropped. */ - virtual ~Vst3ComponentHandlerProxy() = 0; + virtual ~Vst3ComponentHandlerProxy(); DECLARE_FUNKNOWN_METHODS diff --git a/src/common/serialization/vst3/host-context-proxy.h b/src/common/serialization/vst3/host-context-proxy.h index 9f590d4f..e2afe641 100644 --- a/src/common/serialization/vst3/host-context-proxy.h +++ b/src/common/serialization/vst3/host-context-proxy.h @@ -85,7 +85,7 @@ class Vst3HostContextProxy : public YaHostApplication { * dropped a corresponding `Vst3HostContextProxyImpl` should also be * dropped. */ - virtual ~Vst3HostContextProxy() = 0; + virtual ~Vst3HostContextProxy(); DECLARE_FUNKNOWN_METHODS diff --git a/src/common/serialization/vst3/plug-view-proxy.cpp b/src/common/serialization/vst3/plug-view-proxy.cpp new file mode 100644 index 00000000..de2fdf49 --- /dev/null +++ b/src/common/serialization/vst3/plug-view-proxy.cpp @@ -0,0 +1,50 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "plug-view-proxy.h" + +Vst3PlugViewProxy::ConstructArgs::ConstructArgs() {} + +Vst3PlugViewProxy::ConstructArgs::ConstructArgs( + Steinberg::IPtr object, + size_t owner_instance_id) + : owner_instance_id(owner_instance_id), plug_view_args(object) {} + +Vst3PlugViewProxy::Vst3PlugViewProxy(const ConstructArgs&& args) + : YaPlugView(std::move(args.plug_view_args)), + arguments(std::move(args)){FUNKNOWN_CTOR} + + Vst3PlugViewProxy::~Vst3PlugViewProxy() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(Vst3PlugViewProxy) +#pragma GCC diagnostic pop + +tresult PLUGIN_API Vst3PlugViewProxy::queryInterface(Steinberg::FIDString _iid, + void** obj) { + if (YaPlugView::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, + Steinberg::IPlugView) + QUERY_INTERFACE(_iid, obj, Steinberg::IPlugView::iid, + Steinberg::IPlugView) + } + + *obj = nullptr; + return Steinberg::kNoInterface; +} diff --git a/src/common/serialization/vst3/plug-view-proxy.h b/src/common/serialization/vst3/plug-view-proxy.h new file mode 100644 index 00000000..c8d50d4c --- /dev/null +++ b/src/common/serialization/vst3/plug-view-proxy.h @@ -0,0 +1,94 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "../common.h" +#include "plug-view/plug-view.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * An abstract class that implements `IPlugView`, and optionally also all + * extensions to `IPlugView` depending on what the plugin's implementation + * supports. This provides a proxy for the `IPlugView*` returned by a plugin on + * `IEditController::createView()`, and it works exactly the same as + * `Vst3PluginProxy`. + */ +class Vst3PlugViewProxy : public YaPlugView { + public: + /** + * These are the arguments for constructing a + * `Vst3PlugViewProxyImpl`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Read from an existing object. We will try to mimic this object, so + * we'll support any interfaces this object also supports. + */ + ConstructArgs(Steinberg::IPtr object, + size_t owner_instance_id); + + /** + * The unique instance identifier of the proxy object that returned this + * `IPlugView*`. This way we can refer to the correct 'actual' + * `IPlugView*` when the host calls a function on this object. + */ + native_size_t owner_instance_id; + + YaPlugView::ConstructArgs plug_view_args; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.object(plug_view_args); + } + }; + + /** + * Instantiate this instance with arguments read from an actual component + * handler. + * + * @note Since this is passed as part of `IEditController::createView()`, + * there are is no direct `Construct` + * message. The destructor should still send a message to drop the + * original smart pointer. + */ + Vst3PlugViewProxy(const ConstructArgs&& args); + + /** + * @remark The plugin side implementation should send a control message to + * clean up the instance on the Wine side in its destructor. + */ + virtual ~Vst3PlugViewProxy() = 0; + + DECLARE_FUNKNOWN_METHODS + + /** + * Get the instance ID of the owner of this object. + */ + inline size_t owner_instance_id() const { + return arguments.owner_instance_id; + } + + private: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop From f0ece6401873261c3aab2b2a4aa3ae5a4ac5c33c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 20:41:06 +0100 Subject: [PATCH 307/456] Implement the Wine side for createView() --- src/common/logging/vst3.cpp | 23 +++++++++++- src/common/logging/vst3.h | 3 ++ src/common/serialization/vst3.h | 1 + .../vst3/plugin/edit-controller.h | 35 +++++++++++++++++++ src/wine-host/bridges/vst3.cpp | 19 ++++++++++ src/wine-host/bridges/vst3.h | 10 ++++++ 6 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index dd55ac0e..541373a1 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -414,6 +414,15 @@ bool Vst3Logger::log_request( }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaEditController::CreateView& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IEditController::createView(name = " << request.name + << ")"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { return log_request_base(is_host_vst, [&](auto& message) { @@ -661,7 +670,19 @@ void Vst3Logger::log_response( log_response_base(is_host_vst, [&](auto& message) { message << response.result.string(); if (response.result == Steinberg::kResultOk) { - message << ", " << response.value_normalized << std::endl; + message << ", " << response.value_normalized; + } + }); +} + +void Vst3Logger::log_response( + bool is_host_vst, + const YaEditController::CreateViewResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + if (response.plug_view_args) { + message << ""; + } else { + message << ""; } }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 2ee16a8f..de5a73d5 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -106,6 +106,7 @@ class Vst3Logger { const YaEditController::SetParamNormalized&); bool log_request(bool is_host_vst, const YaEditController::SetComponentHandler&); + bool log_request(bool is_host_vst, const YaEditController::CreateView&); bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); @@ -140,6 +141,8 @@ class Vst3Logger { const YaEditController::GetParamStringByValueResponse&); void log_response(bool is_host_vst, const YaEditController::GetParamValueByStringResponse&); + void log_response(bool is_host_vst, + const YaEditController::CreateViewResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 4908cff3..2b1d554d 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -90,6 +90,7 @@ using ControlRequest = std::variant plug_view_args; + + template + void serialize(S& s) { + s.ext(plug_view_args, bitsery::ext::StdOptional{}); + } + }; + + /** + * Message to pass through a call to `IEditController::createView(name)` to + * the Wine plugin host. + */ + struct CreateView { + using Response = CreateViewResponse; + + native_size_t instance_id; + + std::string name; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.text1b(name, 128); + } + }; + virtual Steinberg::IPlugView* PLUGIN_API createView(Steinberg::FIDString name) override = 0; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index fe9bf891..a0ce3426 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -371,6 +371,25 @@ void Vst3Bridge::run() { object_instances[request.instance_id] .component_handler_proxy); }, + [&](const YaEditController::CreateView& request) + -> YaEditController::CreateView::Response { + object_instances[request.instance_id].plug_view = + Steinberg::owned( + object_instances[request.instance_id] + .edit_controller->createView(request.name.c_str())); + + // We'll create a proxy so the host can call functions on this + // `IPlugView` object + return YaEditController::CreateViewResponse{ + .plug_view_args = + (object_instances[request.instance_id].plug_view + ? std::make_optional< + Vst3PlugViewProxy::ConstructArgs>( + object_instances[request.instance_id] + .plug_view, + request.instance_id) + : std::nullopt)}; + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index ff734630..096899c6 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -60,6 +60,16 @@ struct InstanceInterfaces { */ Steinberg::IPtr object; + /** + * The `IPlugView` object the plugin returned from a call to + * `IEditController::createView()`. + * + * XXX: Technically VST3 supports multiple named views, so we could have + * multiple different view for a single plugin. This is not used within + * the SDK, so a single pointer should be fine for now. + */ + Steinberg::IPtr plug_view; + // All smart pointers below are created from `component`. They will be null // pointers if `component` did not implement the interface. From ae057a0acf5c3cfb4d37c1bcd0925a393e06f83a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 20:58:34 +0100 Subject: [PATCH 308/456] Add stubs for a Vst3PlugViewProxy implementation --- meson.build | 1 + .../bridges/vst3-impls/plug-view-proxy.cpp | 122 ++++++++++++++++++ .../bridges/vst3-impls/plug-view-proxy.h | 62 +++++++++ 3 files changed, 185 insertions(+) create mode 100644 src/plugin/bridges/vst3-impls/plug-view-proxy.cpp create mode 100644 src/plugin/bridges/vst3-impls/plug-view-proxy.h diff --git a/meson.build b/meson.build index 88c3280c..416e55f8 100644 --- a/meson.build +++ b/meson.build @@ -101,6 +101,7 @@ vst3_plugin_sources = [ 'src/common/utils.cpp', 'src/plugin/bridges/vst3.cpp', 'src/plugin/bridges/vst3-impls/plugin-factory.cpp', + 'src/plugin/bridges/vst3-impls/plug-view-proxy.cpp', 'src/plugin/bridges/vst3-impls/plugin-proxy.cpp', 'src/plugin/host-process.cpp', 'src/plugin/utils.cpp', diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp new file mode 100644 index 00000000..0ab49fa0 --- /dev/null +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -0,0 +1,122 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "plug-view-proxy.h" + +Vst3PlugViewProxyImpl::Vst3PlugViewProxyImpl( + Vst3PluginBridge& bridge, + Vst3PlugViewProxy::ConstructArgs&& args) + : Vst3PlugViewProxy(std::move(args)), bridge(bridge) {} + +Vst3PlugViewProxyImpl::~Vst3PlugViewProxyImpl() { + // TODO: Implement this: + // // Also drop the plug view smart pointer on the Wine side when this gets + // // dropped + // bridge.send_message( + // Vst3PlugViewProxy::Destruct{.instance_id = instance_id()}); +} + +tresult PLUGIN_API +Vst3PlugViewProxyImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { + // TODO: Successful queries should also be logged + const tresult result = Vst3PlugViewProxy::queryInterface(_iid, obj); + if (result != Steinberg::kResultOk) { + bridge.logger.log_unknown_interface("In IPlugView::queryInterface()", + Steinberg::FUID::fromTUID(_iid)); + } + + return result; +} + +tresult PLUGIN_API +Vst3PlugViewProxyImpl::isPlatformTypeSupported(Steinberg::FIDString type) { + // TODO: Implement + bridge.logger.log("TODO: IPluginView::isPlatformTypeSupported()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PlugViewProxyImpl::attached(void* parent, + Steinberg::FIDString type) { + // TODO: Implement + bridge.logger.log("TODO: IPluginView::attached()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PlugViewProxyImpl::removed() { + // TODO: Implement + bridge.logger.log("TODO: IPluginView::removed()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PlugViewProxyImpl::onWheel(float distance) { + // TODO: Implement + bridge.logger.log("TODO: IPluginView::onWheel()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyDown(char16 key, + int16 keyCode, + int16 modifiers) { + // TODO: Implement + bridge.logger.log("TODO: IPluginView::onKeyDown()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyUp(char16 key, + int16 keyCode, + int16 modifiers) { + // TODO: Implement + bridge.logger.log("TODO: IPluginView::onKeyUp()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PlugViewProxyImpl::getSize(Steinberg::ViewRect* size) { + // TODO: Implement + bridge.logger.log("TODO: IPluginView::getSize()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PlugViewProxyImpl::onSize(Steinberg::ViewRect* newSize) { + // TODO: Implement + bridge.logger.log("TODO: IPluginView::onSize()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PlugViewProxyImpl::onFocus(TBool state) { + // TODO: Implement + bridge.logger.log("TODO: IPluginView::onFocus()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3PlugViewProxyImpl::setFrame(Steinberg::IPlugFrame* frame) { + // TODO: Implement + bridge.logger.log("TODO: IPluginView::setFrame()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PlugViewProxyImpl::canResize() { + // TODO: Implement + bridge.logger.log("TODO: IPluginView::canResize()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3PlugViewProxyImpl::checkSizeConstraint(Steinberg::ViewRect* rect) { + // TODO: Implement + bridge.logger.log("TODO: IPluginView::checkSizeConstraint()"); + return Steinberg::kNotImplemented; +} diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.h b/src/plugin/bridges/vst3-impls/plug-view-proxy.h new file mode 100644 index 00000000..4e497621 --- /dev/null +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.h @@ -0,0 +1,62 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "../vst3.h" + +class Vst3PlugViewProxyImpl : public Vst3PlugViewProxy { + public: + Vst3PlugViewProxyImpl(Vst3PluginBridge& bridge, + Vst3PlugViewProxy::ConstructArgs&& args); + + /** + * When the reference count reaches zero and this destructor is called, + * we'll send a request to the Wine plugin host to destroy the corresponding + * object. + */ + ~Vst3PlugViewProxyImpl(); + + /** + * We'll override the query interface to log queries for interfaces we do + * not (yet) support. + */ + tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, + void** obj) override; + + // From `IPlugView` + tresult PLUGIN_API + isPlatformTypeSupported(Steinberg::FIDString type) override; + tresult PLUGIN_API attached(void* parent, + Steinberg::FIDString type) override; + tresult PLUGIN_API removed() override; + tresult PLUGIN_API onWheel(float distance) override; + tresult PLUGIN_API onKeyDown(char16 key, + int16 keyCode, + int16 modifiers) override; + tresult PLUGIN_API onKeyUp(char16 key, + int16 keyCode, + int16 modifiers) override; + tresult PLUGIN_API getSize(Steinberg::ViewRect* size) override; + tresult PLUGIN_API onSize(Steinberg::ViewRect* newSize) override; + tresult PLUGIN_API onFocus(TBool state) override; + tresult PLUGIN_API setFrame(Steinberg::IPlugFrame* frame) override; + tresult PLUGIN_API canResize() override; + tresult PLUGIN_API checkSizeConstraint(Steinberg::ViewRect* rect) override; + + private: + Vst3PluginBridge& bridge; +}; From b7047a5281230f35beee1868a0dcecc1733adc59 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 21:06:46 +0100 Subject: [PATCH 309/456] Implement IEditController::createView() Even though `Vst3PlugViewProxyImpl` is still only stubs, `IEditController` is now fully implemented. --- README.md | 3 ++- .../bridges/vst3-impls/plugin-proxy.cpp | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1e180a09..06d7930e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ incomplete list of things that still have to be done before this can be used: - `IHostApplication::createComponent()` - `IConnectionPoint::notify()`, and support for indirectly connecting components through message passing proxies - - Finish implementing `IEditController{,2}` + - `IPlugView` + - `IEditController2` - All other mandatory interfaces - All other optional interfaces - Fully implemented: see [this diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index e7050085..6f4830b8 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -16,6 +16,8 @@ #include "plugin-proxy.h" +#include "plug-view-proxy.h" + Vst3PluginProxyImpl::Vst3PluginProxyImpl(Vst3PluginBridge& bridge, Vst3PluginProxy::ConstructArgs&& args) : Vst3PluginProxy(std::move(args)), bridge(bridge) { @@ -355,27 +357,35 @@ tresult PLUGIN_API Vst3PluginProxyImpl::setComponentHandler( Steinberg::IPlugView* PLUGIN_API Vst3PluginProxyImpl::createView(Steinberg::FIDString name) { - // TODO: Implement - bridge.logger.log("TODO IEditController::createView()"); - return nullptr; + CreateViewResponse response = + bridge.send_message(YaEditController::CreateView{ + .instance_id = instance_id(), .name = name}); + + if (response.plug_view_args) { + // The host should manage this. Returning raw pointers feels scary. + return new Vst3PlugViewProxyImpl(bridge, + std::move(*response.plug_view_args)); + } else { + return nullptr; + } } tresult PLUGIN_API Vst3PluginProxyImpl::setKnobMode(Steinberg::Vst::KnobMode mode) { // TODO: Implement - bridge.logger.log("TODO IEditController2::setKnobMode()"); + bridge.logger.log("TODO: IEditController2::setKnobMode()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API Vst3PluginProxyImpl::openHelp(TBool onlyCheck) { // TODO: Implement - bridge.logger.log("TODO IEditController2::openHelp()"); + bridge.logger.log("TODO: IEditController2::openHelp()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API Vst3PluginProxyImpl::openAboutBox(TBool onlyCheck) { // TODO: Implement - bridge.logger.log("TODO IEditController2::openAboutBox()"); + bridge.logger.log("TODO: IEditController2::openAboutBox()"); return Steinberg::kNotImplemented; } From 7306809991fe3949a3845d6a83394824a2e88c49 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 22:17:10 +0100 Subject: [PATCH 310/456] Drop IPlugView pointer when host drops proxy --- src/common/logging/vst3.cpp | 9 +++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 3 ++- src/common/serialization/vst3/plug-view-proxy.h | 16 ++++++++++++++++ .../bridges/vst3-impls/plug-view-proxy.cpp | 9 ++++----- src/wine-host/bridges/vst3.cpp | 8 ++++++++ 6 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 541373a1..6a173766 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -35,6 +35,15 @@ void Vst3Logger::log_unknown_interface( } } +bool Vst3Logger::log_request(bool is_host_vst, + const Vst3PlugViewProxy::Destruct& request) { + return log_request_base(is_host_vst, [&](auto& message) { + // We don't know what class this instance was originally instantiated + // as, but it also doesn't really matter + message << request.owner_instance_id << ": IPlugView::~IPlugView()"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const Vst3PluginProxy::Construct& request) { return log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index de5a73d5..3e02db4f 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -59,6 +59,7 @@ class Vst3Logger { // `log_request()` call returned `true`. This way we can filter out the // log message for the response together with the request. + bool log_request(bool is_host_vst, const Vst3PlugViewProxy::Destruct&); bool log_request(bool is_host_vst, const Vst3PluginProxy::Construct&); bool log_request(bool is_host_vst, const Vst3PluginProxy::Destruct&); bool log_request(bool is_host_vst, const Vst3PluginProxy::SetState&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 2b1d554d..f870e2ed 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -59,7 +59,8 @@ struct WantsConfiguration { * encodes the information we request or the operation we want to perform. A * request of type `ControlRequest(T)` should send back a `T::Response`. */ -using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(owner_instance_id); + } + }; + /** * @remark The plugin side implementation should send a control message to * clean up the instance on the Wine side in its destructor. diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index 0ab49fa0..808f84f2 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -22,11 +22,10 @@ Vst3PlugViewProxyImpl::Vst3PlugViewProxyImpl( : Vst3PlugViewProxy(std::move(args)), bridge(bridge) {} Vst3PlugViewProxyImpl::~Vst3PlugViewProxyImpl() { - // TODO: Implement this: - // // Also drop the plug view smart pointer on the Wine side when this gets - // // dropped - // bridge.send_message( - // Vst3PlugViewProxy::Destruct{.instance_id = instance_id()}); + // Also drop the plug view smart pointer on the Wine side when this gets + // dropped + bridge.send_message( + Vst3PlugViewProxy::Destruct{.owner_instance_id = owner_instance_id()}); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index a0ce3426..f4ca651f 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -67,6 +67,14 @@ void Vst3Bridge::run() { sockets.host_vst_control.receive_messages( std::nullopt, overload{ + [&](const Vst3PlugViewProxy::Destruct& request) + -> Vst3PlugViewProxy::Destruct::Response { + // When the pointer gets dropped by the host, we want to drop it + // here as well + object_instances[request.owner_instance_id].plug_view.reset(); + + return Ack{}; + }, [&](const Vst3PluginProxy::Construct& request) -> Vst3PluginProxy::Construct::Response { Steinberg::TUID cid; From 18a7908bf84e2842224a2ba25d579b3362dcc316 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 22:19:33 +0100 Subject: [PATCH 311/456] Fix typo in stub todo messages --- .../bridges/vst3-impls/plug-view-proxy.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index 808f84f2..28b23c13 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -43,26 +43,26 @@ Vst3PlugViewProxyImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { tresult PLUGIN_API Vst3PlugViewProxyImpl::isPlatformTypeSupported(Steinberg::FIDString type) { // TODO: Implement - bridge.logger.log("TODO: IPluginView::isPlatformTypeSupported()"); + bridge.logger.log("TODO: IPlugView::isPlatformTypeSupported()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API Vst3PlugViewProxyImpl::attached(void* parent, Steinberg::FIDString type) { // TODO: Implement - bridge.logger.log("TODO: IPluginView::attached()"); + bridge.logger.log("TODO: IPlugView::attached()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API Vst3PlugViewProxyImpl::removed() { // TODO: Implement - bridge.logger.log("TODO: IPluginView::removed()"); + bridge.logger.log("TODO: IPlugView::removed()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API Vst3PlugViewProxyImpl::onWheel(float distance) { // TODO: Implement - bridge.logger.log("TODO: IPluginView::onWheel()"); + bridge.logger.log("TODO: IPlugView::onWheel()"); return Steinberg::kNotImplemented; } @@ -70,7 +70,7 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyDown(char16 key, int16 keyCode, int16 modifiers) { // TODO: Implement - bridge.logger.log("TODO: IPluginView::onKeyDown()"); + bridge.logger.log("TODO: IPlugView::onKeyDown()"); return Steinberg::kNotImplemented; } @@ -78,44 +78,44 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyUp(char16 key, int16 keyCode, int16 modifiers) { // TODO: Implement - bridge.logger.log("TODO: IPluginView::onKeyUp()"); + bridge.logger.log("TODO: IPlugView::onKeyUp()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API Vst3PlugViewProxyImpl::getSize(Steinberg::ViewRect* size) { // TODO: Implement - bridge.logger.log("TODO: IPluginView::getSize()"); + bridge.logger.log("TODO: IPlugView::getSize()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API Vst3PlugViewProxyImpl::onSize(Steinberg::ViewRect* newSize) { // TODO: Implement - bridge.logger.log("TODO: IPluginView::onSize()"); + bridge.logger.log("TODO: IPlugView::onSize()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API Vst3PlugViewProxyImpl::onFocus(TBool state) { // TODO: Implement - bridge.logger.log("TODO: IPluginView::onFocus()"); + bridge.logger.log("TODO: IPlugView::onFocus()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API Vst3PlugViewProxyImpl::setFrame(Steinberg::IPlugFrame* frame) { // TODO: Implement - bridge.logger.log("TODO: IPluginView::setFrame()"); + bridge.logger.log("TODO: IPlugView::setFrame()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API Vst3PlugViewProxyImpl::canResize() { // TODO: Implement - bridge.logger.log("TODO: IPluginView::canResize()"); + bridge.logger.log("TODO: IPlugView::canResize()"); return Steinberg::kNotImplemented; } tresult PLUGIN_API Vst3PlugViewProxyImpl::checkSizeConstraint(Steinberg::ViewRect* rect) { // TODO: Implement - bridge.logger.log("TODO: IPluginView::checkSizeConstraint()"); + bridge.logger.log("TODO: IPlugView::checkSizeConstraint()"); return Steinberg::kNotImplemented; } From e72e6d56425a97d7cfe7f2f997836db5ba76de31 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 22:33:58 +0100 Subject: [PATCH 312/456] Implement IPlugView::isPLatformTypeSupported() This of course requires us to substitute the relevant Linux platform type for the Win32 one. --- src/common/logging/vst3.cpp | 15 +++++++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 1 + .../serialization/vst3/plug-view/plug-view.h | 21 +++++++++++++++++++ .../bridges/vst3-impls/plug-view-proxy.cpp | 7 ++++--- src/wine-host/bridges/vst3.cpp | 13 ++++++++++++ 6 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 6a173766..a4048bfc 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -432,6 +432,21 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request( + bool is_host_vst, + const YaPlugView::IsPlatformTypeSupported& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id + << ": IPlugView::isPLatformTypeSupported(type = \"" + << request.type; + if (request.type == Steinberg::kPlatformTypeX11EmbedWindowID) { + message << "\" (will be translated to \"" + << Steinberg::kPlatformTypeHWND << "\")"; + } + message << ")"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { return log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 3e02db4f..1cbff659 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -108,6 +108,8 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaEditController::SetComponentHandler&); bool log_request(bool is_host_vst, const YaEditController::CreateView&); + bool log_request(bool is_host_vst, + const YaPlugView::IsPlatformTypeSupported&); bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index f870e2ed..4a6ee6e0 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -92,6 +92,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(owner_instance_id); + s.text1b(type, 128); + } + }; + virtual tresult PLUGIN_API isPlatformTypeSupported(Steinberg::FIDString type) override = 0; virtual tresult PLUGIN_API attached(void* parent, diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index 28b23c13..ea30b644 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -42,9 +42,10 @@ Vst3PlugViewProxyImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { tresult PLUGIN_API Vst3PlugViewProxyImpl::isPlatformTypeSupported(Steinberg::FIDString type) { - // TODO: Implement - bridge.logger.log("TODO: IPlugView::isPlatformTypeSupported()"); - return Steinberg::kNotImplemented; + // We'll swap the X11 window ID platform type string for the Win32 HWND + // equivalent on the Wine side + return bridge.send_message(YaPlugView::IsPlatformTypeSupported{ + .owner_instance_id = owner_instance_id(), .type = type}); } tresult PLUGIN_API Vst3PlugViewProxyImpl::attached(void* parent, diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index f4ca651f..5498c796 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -398,6 +398,19 @@ void Vst3Bridge::run() { request.instance_id) : std::nullopt)}; }, + [&](const YaPlugView::IsPlatformTypeSupported& request) + -> YaPlugView::IsPlatformTypeSupported::Response { + // The host will of course want to pass an X11 window ID for the + // plugin to embed itself in, so we'll have to translate this to + // a HWND + const std::string type = + request.type == Steinberg::kPlatformTypeX11EmbedWindowID + ? Steinberg::kPlatformTypeHWND + : request.type; + + return object_instances[request.owner_instance_id] + .plug_view->isPlatformTypeSupported(type.c_str()); + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From 49fc896d62107dc28806570507412c0d75f6ec91 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 23:30:47 +0100 Subject: [PATCH 313/456] Remove Win32 effEditIdle() timer Now Editor is completely decoupled from VST2. --- src/wine-host/bridges/vst2.cpp | 8 ++++--- src/wine-host/editor.cpp | 44 ++++------------------------------ src/wine-host/editor.h | 43 +++++++-------------------------- 3 files changed, 19 insertions(+), 76 deletions(-) diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index 60b29992..623f4fa0 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -42,10 +42,12 @@ std::mutex current_bridge_instance_mutex; /** * Opcodes that should always be handled on the main thread because they may * involve GUI operations. + * + * XXX: We removed effEditIdle from here and everything still seems to work + * fine. Verify that this didn't break any plugins. */ const std::set unsafe_opcodes{effOpen, effClose, effEditGetRect, - effEditOpen, effEditClose, effEditIdle, - effEditTop}; + effEditOpen, effEditClose, effEditTop}; intptr_t VST_CALL_CONV host_callback_proxy(AEffect*, int, int, intptr_t, void*, float); @@ -391,7 +393,7 @@ intptr_t Vst2Bridge::dispatch_wrapper(AEffect* plugin, const std::string window_class = "yabridge plugin " + sockets.base_dir.string(); Editor& editor_instance = - editor.emplace(config, window_class, x11_handle, plugin); + editor.emplace(config, window_class, x11_handle); return plugin->dispatcher(plugin, opcode, index, value, editor_instance.get_win32_handle(), diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index da5234fe..1315b945 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -18,9 +18,6 @@ #include -// The Win32 API requires you to hardcode identifiers for tiemrs -constexpr size_t idle_timer_id = 1337; - /** * The most significant bit in an event's response type is used to indicate * whether the event source. @@ -84,8 +81,7 @@ WindowClass::~WindowClass() { Editor::Editor(const Configuration& config, const std::string& window_class_name, - const size_t parent_window_handle, - AEffect* effect) + const size_t parent_window_handle) : x11_connection(xcb_connect(nullptr, nullptr), xcb_disconnect), client_area(get_maximum_screen_dimensions(*x11_connection)), window_class(window_class_name), @@ -110,12 +106,10 @@ Editor::Editor(const Configuration& config, // If `config.editor_double_embed` is set, then we'll also create a child // window in `win32_child_handle`. If we do this before calling // `ShowWindow()` on `win32_handle` we'll run into X11 errors. - idle_timer(win32_handle.get(), idle_timer_id, 100), + win32_child_handle(std::nullopt), parent_window(parent_window_handle), wine_window(get_x11_handle(win32_handle.get())), - topmost_window(find_topmost_window(*x11_connection, parent_window)), - // Needed to send update messages on a timer - plugin(effect) { + topmost_window(find_topmost_window(*x11_connection, parent_window)) { xcb_generic_error_t* error; // Used for input focus grabbing to only grab focus when the window is @@ -225,10 +219,6 @@ HWND Editor::get_win32_handle() const { } } -void Editor::send_idle_event() { - plugin->dispatcher(plugin, effEditIdle, 0, 0, nullptr, 0); -} - void Editor::handle_win32_events() const { MSG msg; @@ -236,21 +226,11 @@ void Editor::handle_win32_events() const { // with child GUI components. So far limiting this to `max_win32_messages` // messages has only been needed for Waves plugins as they otherwise cause // an infinite message loop. + // TODO: If the timer is no longer needed, then we can drop this entire + // function for (int i = 0; i < max_win32_messages && PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); i++) { - // This timer would periodically send `effEditIdle` events so the editor - // remains responsive even during blocking GUI operations such as open - // dropdowns or message boxes. This is only needed when the GUI is - // actually blocked and it will be dispatched by the messaging loop of - // the blocking GUI component. Since we're not touching the - // `effEditIdle` event sent by the host we can always filter this timer - // event out in this event loop. - if (msg.message == WM_TIMER && msg.wParam == idle_timer_id && - msg.hwnd == win32_handle.get()) { - continue; - } - TranslateMessage(&msg); DispatchMessage(&msg); } @@ -470,20 +450,6 @@ LRESULT CALLBACK window_proc(HWND handle, SetWindowLongPtr(handle, GWLP_USERDATA, reinterpret_cast(editor)); } break; - case WM_TIMER: { - auto editor = reinterpret_cast( - GetWindowLongPtr(handle, GWLP_USERDATA)); - if (!editor || wParam != idle_timer_id) { - break; - } - - // We'll send idle messages on a timer. This way the plugin will get - // keep periodically updating its editor either when the host sends - // `effEditIdle` themself, or periodically when the GUI is being - // blocked by a dropdown or a message box. - editor->send_idle_event(); - return 0; - } break; // In case the WM does not support the EWMH active window property, // we'll fall back to grabbing focus when the user clicks on the window // by listening to the generated `WM_PARENTNOTIFY` messages. Otherwise diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index c75523c1..daccf194 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -16,22 +16,21 @@ #pragma once -// Use the native version of xcb -#pragma push_macro("_WIN32") -#undef _WIN32 -#include -#pragma pop_macro("_WIN32") +#include +#include +#include #ifndef NOMINMAX #define NOMINMAX #define WINE_NOWINSOCK #endif -#include #include -#include -#include -#include +// Use the native version of xcb +#pragma push_macro("_WIN32") +#undef _WIN32 +#include +#pragma pop_macro("_WIN32") #include "../common/configuration.h" #include "utils.h" @@ -101,15 +100,12 @@ class Editor { * windows. * @param parent_window_handle The X11 window handle passed by the VST host * for the editor to embed itself into. - * @param effect The plugin this window is being created for. Used to send - * `effEditIdle` messages on a timer. * * @see win32_handle */ Editor(const Configuration& config, const std::string& window_class_name, - const size_t parent_window_handle, - AEffect* effect); + const size_t parent_window_handle); ~Editor(); @@ -131,13 +127,6 @@ class Editor { */ bool supports_ewmh_active_window() const; - /** - * Send a single `effEditIdle` event to the plugin to allow it to update its - * GUI state. This is called periodically from a timer while the GUI is - * being blocked, and also called explicitly by the host on a timer. - */ - void send_idle_event(); - /** * Pump messages from the editor loop loop until all events are process. * Must be run from the same thread the GUI was created in because of Win32 @@ -225,15 +214,6 @@ class Editor { #pragma GCC diagnostic pop - /** - * The Win32 API will block the `DispatchMessage` call when opening e.g. a - * dropdown, but it will still allow timers to be run so the GUI can still - * update in the background. Because of this we send `effEditIdle` to the - * plugin on a timer. The refresh rate is purposely fairly low since the - * host will call `effEditIdle()` explicitely when the plugin is not busy. - */ - Win32Timer idle_timer; - /** * The window handle of the editor window created by the DAW. */ @@ -252,11 +232,6 @@ class Editor { */ const xcb_window_t topmost_window; - /** - *Needed to handle idle updates through a timer - */ - AEffect* plugin; - /** * The atom corresponding to `_NET_ACTIVE_WINDOW`. */ From 09f6d932141862e4228615a8ff8ff944e3f8113f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 23:31:41 +0100 Subject: [PATCH 314/456] Implement IPlugView::attached() --- src/common/logging/vst3.cpp | 14 +++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + .../serialization/vst3/plug-view/plug-view.h | 26 +++++++++++++ .../bridges/vst3-impls/plug-view-proxy.cpp | 9 +++-- src/wine-host/bridges/vst3.cpp | 37 ++++++++++++++++++- src/wine-host/bridges/vst3.h | 6 +++ 7 files changed, 90 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index a4048bfc..cec028f2 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -447,6 +447,20 @@ bool Vst3Logger::log_request( }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaPlugView::Attached& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id + << ": IPlugView::attached(parent = " << request.parent + << ", type = \"" << request.type; + if (request.type == Steinberg::kPlatformTypeX11EmbedWindowID) { + message << "\" (will be translated to \"" + << Steinberg::kPlatformTypeHWND << "\")"; + } + message << ")"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { return log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 1cbff659..68f7c9ed 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -110,6 +110,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaEditController::CreateView&); bool log_request(bool is_host_vst, const YaPlugView::IsPlatformTypeSupported&); + bool log_request(bool is_host_vst, const YaPlugView::Attached&); bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 4a6ee6e0..697c5dba 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -93,6 +93,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(owner_instance_id); + s.value8b(parent); + s.text1b(type, 128); + } + }; + virtual tresult PLUGIN_API attached(void* parent, Steinberg::FIDString type) override = 0; virtual tresult PLUGIN_API removed() override = 0; diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index ea30b644..0976c3a5 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -50,9 +50,12 @@ Vst3PlugViewProxyImpl::isPlatformTypeSupported(Steinberg::FIDString type) { tresult PLUGIN_API Vst3PlugViewProxyImpl::attached(void* parent, Steinberg::FIDString type) { - // TODO: Implement - bridge.logger.log("TODO: IPlugView::attached()"); - return Steinberg::kNotImplemented; + // We will embed the Wine Win32 window into the X11 window provided by the + // host + return bridge.send_message( + YaPlugView::Attached{.owner_instance_id = owner_instance_id(), + .parent = reinterpret_cast(parent), + .type = type}); } tresult PLUGIN_API Vst3PlugViewProxyImpl::removed() { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 5498c796..ecf91f48 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -102,7 +102,7 @@ void Vst3Bridge::run() { std::lock_guard lock(object_instances_mutex); const size_t instance_id = generate_instance_id(); - object_instances[instance_id] = std::move(object); + object_instances.emplace(instance_id, std::move(object)); // This is where the magic happens. Here we deduce which // interfaces are supported by this object so we can create @@ -411,6 +411,41 @@ void Vst3Bridge::run() { return object_instances[request.owner_instance_id] .plug_view->isPlatformTypeSupported(type.c_str()); }, + [&](const YaPlugView::Attached& request) + -> YaPlugView::Attached::Response { + const std::string type = + request.type == Steinberg::kPlatformTypeX11EmbedWindowID + ? Steinberg::kPlatformTypeHWND + : request.type; + + // Just like with VST2 plugins, we'll embed a Wine window into + // the X11 window provided by the host + // TODO: The docs say that we should support XEmbed (and we're + // purposely avoiding that because Wine's implementation + // doesn't work correctly). Check if this causes issues, + // and if it's actually needed (for instance when the host + // resizes the window without informing the plugin) + const auto x11_handle = static_cast(request.parent); + const std::string window_class = + "yabridge plugin " + sockets.base_dir.string() + " " + + std::to_string(request.owner_instance_id); + Editor& editor_instance = + object_instances[request.owner_instance_id].editor.emplace( + config, window_class, x11_handle); + + const tresult result = + object_instances[request.owner_instance_id] + .plug_view->attached(editor_instance.get_win32_handle(), + type.c_str()); + + // Get rid of the editor again if the plugin didn't embed itself + // in it + if (result != Steinberg::kResultOk) { + object_instances[request.owner_instance_id].editor.reset(); + } + + return result; + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 096899c6..026ceb53 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -70,6 +70,12 @@ struct InstanceInterfaces { */ Steinberg::IPtr plug_view; + /** + * This instance's editor, if it has an open editor. Embedding here works + * exactly the same as how it works for VST2 plugins. + */ + std::optional editor; + // All smart pointers below are created from `component`. They will be null // pointers if `component` did not implement the interface. From 960e2d50d1fce8dff98ea6651ee38f7cccf2953f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 23:46:45 +0100 Subject: [PATCH 315/456] Implement IPlugView::getSize() --- src/common/logging/vst3.cpp | 20 +++++++++ src/common/logging/vst3.h | 2 + src/common/serialization/vst3.h | 1 + .../serialization/vst3/plug-view/plug-view.h | 43 +++++++++++++++++++ .../bridges/vst3-impls/plug-view-proxy.cpp | 16 +++++-- src/wine-host/bridges/vst3.cpp | 8 ++++ 6 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index cec028f2..d747b314 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -461,6 +461,13 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaPlugView::GetSize& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id << ": IPlugView::getSize(size*)"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { return log_request_base(is_host_vst, [&](auto& message) { @@ -725,6 +732,19 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response(bool is_host_vst, + const YaPlugView::GetSizeResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", "; + } + }); +} + void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs& args) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 68f7c9ed..689375f3 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -111,6 +111,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPlugView::IsPlatformTypeSupported&); bool log_request(bool is_host_vst, const YaPlugView::Attached&); + bool log_request(bool is_host_vst, const YaPlugView::GetSize&); bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); @@ -147,6 +148,7 @@ class Vst3Logger { const YaEditController::GetParamValueByStringResponse&); void log_response(bool is_host_vst, const YaEditController::CreateViewResponse&); + void log_response(bool is_host_vst, const YaPlugView::GetSizeResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 697c5dba..8b4d4f05 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -94,6 +94,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.object(result); + s.object(updated_size); + } + }; + + /** + * Message to pass through a call to `IPlugView::getSize(&size)`. + */ + struct GetSize { + using Response = GetSizeResponse; + + native_size_t owner_instance_id; + + Steinberg::ViewRect size; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.object(size); + } + }; + virtual tresult PLUGIN_API getSize(Steinberg::ViewRect* size) override = 0; virtual tresult PLUGIN_API onSize(Steinberg::ViewRect* newSize) override = 0; @@ -135,3 +168,13 @@ class YaPlugView : public Steinberg::IPlugView { }; #pragma GCC diagnostic pop + +namespace Steinberg { +template +void serialize(S& s, ViewRect& rect) { + s.value4b(rect.left); + s.value4b(rect.top); + s.value4b(rect.right); + s.value4b(rect.bottom); +} +} // namespace Steinberg diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index 0976c3a5..e71f7d03 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -87,9 +87,19 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyUp(char16 key, } tresult PLUGIN_API Vst3PlugViewProxyImpl::getSize(Steinberg::ViewRect* size) { - // TODO: Implement - bridge.logger.log("TODO: IPlugView::getSize()"); - return Steinberg::kNotImplemented; + if (size) { + const GetSizeResponse response = + bridge.send_message(YaPlugView::GetSize{ + .owner_instance_id = owner_instance_id(), .size = *size}); + + *size = response.updated_size; + + return response.result; + } else { + bridge.logger.log( + "WARNING: Null pointer passed to 'IPlugView::getSize()'"); + return Steinberg::kInvalidArgument; + } } tresult PLUGIN_API Vst3PlugViewProxyImpl::onSize(Steinberg::ViewRect* newSize) { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index ecf91f48..f8e51287 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -446,6 +446,14 @@ void Vst3Bridge::run() { return result; }, + [&](YaPlugView::GetSize& request) -> YaPlugView::GetSize::Response { + const tresult result = + object_instances[request.owner_instance_id] + .plug_view->getSize(&request.size); + + return YaPlugView::GetSizeResponse{ + .result = result, .updated_size = request.size}; + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From 36c2d877c2ae2ddb40d0ca74859bac5ca79aec38 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 19 Dec 2020 23:55:21 +0100 Subject: [PATCH 316/456] Handle X11 events in Vst3Bridge --- src/wine-host/bridges/vst3.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index f8e51287..93477f79 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -507,11 +507,17 @@ void Vst3Bridge::run() { } void Vst3Bridge::handle_x11_events() { - // TODO: Implement editors + std::lock_guard lock(object_instances_mutex); + + for (const auto& [instance_id, object] : object_instances) { + if (object.editor) { + object.editor->handle_x11_events(); + } + } } void Vst3Bridge::handle_win32_events() { - // TODO: Implement editors + // TODO: Remove this function from the `Editor` MSG msg; From 66450407f097e997f58658f56471fc3f96dc7355 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 20 Dec 2020 00:34:51 +0100 Subject: [PATCH 317/456] Instantiate the editor within the main context Otherwise it of course won't work. --- src/wine-host/bridges/vst3.cpp | 36 ++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 93477f79..29f7201f 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -429,22 +429,32 @@ void Vst3Bridge::run() { const std::string window_class = "yabridge plugin " + sockets.base_dir.string() + " " + std::to_string(request.owner_instance_id); - Editor& editor_instance = - object_instances[request.owner_instance_id].editor.emplace( - config, window_class, x11_handle); - const tresult result = - object_instances[request.owner_instance_id] - .plug_view->attached(editor_instance.get_win32_handle(), - type.c_str()); + // Creating the window and having the plugin embed in it should + // be done in the main UI thread + std::promise attach_result{}; + boost::asio::dispatch(main_context.context, [&]() { + Editor& editor_instance = + object_instances[request.owner_instance_id] + .editor.emplace(config, window_class, x11_handle); - // Get rid of the editor again if the plugin didn't embed itself - // in it - if (result != Steinberg::kResultOk) { - object_instances[request.owner_instance_id].editor.reset(); - } + const tresult result = + object_instances[request.owner_instance_id] + .plug_view->attached( + editor_instance.get_win32_handle(), + type.c_str()); - return result; + // Get rid of the editor again if the plugin didn't embed + // itself in it + if (result != Steinberg::kResultOk) { + object_instances[request.owner_instance_id] + .editor.reset(); + } + + attach_result.set_value(result); + }); + + return attach_result.get_future().get(); }, [&](YaPlugView::GetSize& request) -> YaPlugView::GetSize::Response { const tresult result = From 703b6d9285d5d2374d55c8212f119a64ebe62dd1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 20 Dec 2020 11:57:43 +0100 Subject: [PATCH 318/456] Add functions to schedule tasks in main IO context --- src/wine-host/bridges/group.cpp | 2 +- src/wine-host/bridges/vst2.cpp | 18 ++++------ src/wine-host/bridges/vst3.cpp | 63 +++++++++++++++------------------ src/wine-host/utils.h | 52 +++++++++++++++++++++++++-- 4 files changed, 87 insertions(+), 48 deletions(-) diff --git a/src/wine-host/bridges/group.cpp b/src/wine-host/bridges/group.cpp index a544a827..e982f557 100644 --- a/src/wine-host/bridges/group.cpp +++ b/src/wine-host/bridges/group.cpp @@ -122,7 +122,7 @@ void GroupBridge::handle_plugin_run(size_t plugin_id, HostBridge* bridge) { // potentially corrupt our heap. This way we can also properly join the // thread again. If no active plugins remain, then we'll terminate the // process. - boost::asio::post(main_context.context, [this, plugin_id]() { + main_context.schedule_task([this, plugin_id]() { std::lock_guard lock(active_plugins_mutex); // The join is implicit because we're using Win32Thread (which mimics diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index 623f4fa0..891bc9da 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -16,8 +16,6 @@ #include "vst2.h" -#include -#include #include #include @@ -333,15 +331,13 @@ void Vst2Bridge::run() { // instantiated and where the Win32 message loop is // handled. if (unsafe_opcodes.contains(opcode)) { - std::promise dispatch_result; - boost::asio::dispatch(main_context.context, [&]() { - const intptr_t result = dispatch_wrapper( - plugin, opcode, index, value, data, option); - - dispatch_result.set_value(result); - }); - - return dispatch_result.get_future().get(); + return main_context + .run_in_context([&]() { + return dispatch_wrapper(plugin, opcode, + index, value, data, + option); + }) + .get(); } else { return dispatch_wrapper(plugin, opcode, index, value, data, option); diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 29f7201f..5cf3cb9d 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -16,11 +16,8 @@ #include "vst3.h" -#include - #include "../boost-fix.h" -#include #include #include "vst3-impls/component-handler-proxy.h" @@ -115,16 +112,15 @@ void Vst3Bridge::run() { }, [&](const Vst3PluginProxy::Destruct& request) -> Vst3PluginProxy::Destruct::Response { - std::promise latch; - - boost::asio::dispatch(main_context.context, [&]() { - // Remove the instance from within the main IO context so - // removing it doesn't interfere with the Win32 message loop - std::lock_guard lock(object_instances_mutex); - object_instances.erase(request.instance_id); - - latch.set_value(); - }); + main_context + .run_in_context([&]() { + // Remove the instance from within the main IO context + // so removing it doesn't interfere with the Win32 + // message loop + std::lock_guard lock(object_instances_mutex); + object_instances.erase(request.instance_id); + }) + .wait(); // XXX: I don't think we have to wait for the object to be // deleted most of the time, but I can imagine a situation @@ -132,7 +128,6 @@ void Vst3Bridge::run() { // Win32 timer in between where the above closure is being // executed and when the actual host application context on // the plugin side gets deallocated. - latch.get_future().wait(); return Ack{}; }, [&](Vst3PluginProxy::SetState& request) @@ -432,29 +427,29 @@ void Vst3Bridge::run() { // Creating the window and having the plugin embed in it should // be done in the main UI thread - std::promise attach_result{}; - boost::asio::dispatch(main_context.context, [&]() { - Editor& editor_instance = - object_instances[request.owner_instance_id] - .editor.emplace(config, window_class, x11_handle); + return main_context + .run_in_context([&]() { + Editor& editor_instance = + object_instances[request.owner_instance_id] + .editor.emplace(config, window_class, + x11_handle); - const tresult result = - object_instances[request.owner_instance_id] - .plug_view->attached( - editor_instance.get_win32_handle(), - type.c_str()); + const tresult result = + object_instances[request.owner_instance_id] + .plug_view->attached( + editor_instance.get_win32_handle(), + type.c_str()); - // Get rid of the editor again if the plugin didn't embed - // itself in it - if (result != Steinberg::kResultOk) { - object_instances[request.owner_instance_id] - .editor.reset(); - } + // Get rid of the editor again if the plugin didn't + // embed itself in it + if (result != Steinberg::kResultOk) { + object_instances[request.owner_instance_id] + .editor.reset(); + } - attach_result.set_value(result); - }); - - return attach_result.get_future().get(); + return result; + }) + .get(); }, [&](YaPlugView::GetSize& request) -> YaPlugView::GetSize::Response { const tresult result = diff --git a/src/wine-host/utils.h b/src/wine-host/utils.h index 9634673c..c87fc652 100644 --- a/src/wine-host/utils.h +++ b/src/wine-host/utils.h @@ -18,6 +18,7 @@ #include "boost-fix.h" +#include #include #include @@ -27,6 +28,7 @@ #endif #include +#include #include #include @@ -62,6 +64,52 @@ class MainContext { */ void stop(); + /** + * Asynchronously execute a function inside of this main IO context and + * return the results as a future. This is used to make sure that operations + * that may involve the Win32 message loop are all run from the same thread. + */ + template + std::future run_in_context(F fn) { + std::promise result{}; + std::future future = result.get_future(); + boost::asio::dispatch(context, + [result = std::move(result), fn]() mutable { + result.set_value(fn()); + }); + + return future; + } + + /** + * The same as the above, but without returning a value. This allows us to + * wait for the task to have been run. + * + * @overload + */ + template + std::future run_in_context(F fn) { + std::promise result{}; + std::future future = result.get_future(); + boost::asio::dispatch(context, + [result = std::move(result), fn]() mutable { + fn(); + result.set_value(); + }); + + return future; + } + + /** + * Run a task within the IO context. The difference with `run_in_context()` + * is that this version does not guarantee that it's going to be executed as + * soon as possible, and thus we also won't return a future. + */ + template + void schedule_task(F fn) { + boost::asio::post(context, fn); + } + /** * Start a timer to handle events every `event_loop_interval` milliseconds. * @@ -88,8 +136,8 @@ class MainContext { } /** - * The raw IO context. Can and should be used directly for everything that's - * not the event handling loop. + * The raw IO context. Used to bind our sockets onto. Running things within + * this IO context should be done with the functions above. */ boost::asio::io_context context; From f2153148b21c86bf352a51e22d315b089a3ce31e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 20 Dec 2020 12:06:21 +0100 Subject: [PATCH 319/456] Construct and destruct IPlugView from GUI thread --- src/wine-host/bridges/vst3.cpp | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 5cf3cb9d..c6e93056 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -66,9 +66,15 @@ void Vst3Bridge::run() { overload{ [&](const Vst3PlugViewProxy::Destruct& request) -> Vst3PlugViewProxy::Destruct::Response { - // When the pointer gets dropped by the host, we want to drop it - // here as well - object_instances[request.owner_instance_id].plug_view.reset(); + // XXX: Not sure if his has to be run form the UI thread + main_context + .run_in_context([&]() { + // When the pointer gets dropped by the host, we want to + // drop it here as well + object_instances[request.owner_instance_id] + .plug_view.reset(); + }) + .wait(); return Ack{}; }, @@ -376,10 +382,16 @@ void Vst3Bridge::run() { }, [&](const YaEditController::CreateView& request) -> YaEditController::CreateView::Response { - object_instances[request.instance_id].plug_view = - Steinberg::owned( - object_instances[request.instance_id] - .edit_controller->createView(request.name.c_str())); + // Instantiate the object from the GUI thread + main_context + .run_in_context([&]() { + object_instances[request.instance_id].plug_view = + Steinberg::owned( + object_instances[request.instance_id] + .edit_controller->createView( + request.name.c_str())); + }) + .wait(); // We'll create a proxy so the host can call functions on this // `IPlugView` object From b38f2720134e36c56ddefbcdf261f3364dcf226f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 20 Dec 2020 12:29:59 +0100 Subject: [PATCH 320/456] Run all other lifecycle events on main thread This is probably where plugins instantiate timers for their GUI updates. --- src/wine-host/bridges/vst3.cpp | 66 +++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index c6e93056..8f110fc1 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -86,20 +86,35 @@ void Vst3Bridge::run() { // Even though we're requesting a specific interface (to mimic // what the host is doing), we're immediately upcasting it to an // `FUnknown` so we can create a perfect proxy object. - Steinberg::IPtr object; - switch (request.requested_interface) { - case Vst3PluginProxy::Construct::Interface::IComponent: - object = - module->getFactory() - .createInstance( - cid); - break; - case Vst3PluginProxy::Construct::Interface::IEditController: - object = module->getFactory() - .createInstance< - Steinberg::Vst::IEditController>(cid); - break; - } + // We create the object from the GUI thread in case it + // immediatly starts timers or something (even though it + // shouldn't) + Steinberg::IPtr object = + main_context + .run_in_context>( + [&]() -> Steinberg::IPtr { + switch (request.requested_interface) { + case Vst3PluginProxy::Construct::Interface:: + IComponent: + return module->getFactory() + .createInstance< + Steinberg::Vst::IComponent>( + cid); + break; + case Vst3PluginProxy::Construct::Interface:: + IEditController: + return module->getFactory() + .createInstance< + Steinberg::Vst:: + IEditController>(cid); + break; + default: + // Unreachable + return nullptr; + break; + } + }) + .get(); if (object) { std::lock_guard lock(object_instances_mutex); @@ -490,15 +505,26 @@ void Vst3Bridge::run() { nullptr; } - return object_instances[request.instance_id] - .plugin_base->initialize( - object_instances[request.instance_id] - .host_context_proxy); + // XXX: Should `IPlugView::{initialize,terminate}` be run from + // the main UI thread? I can see how plugins would want to + // start timers from here. + return main_context + .run_in_context([&]() { + return object_instances[request.instance_id] + .plugin_base->initialize( + object_instances[request.instance_id] + .host_context_proxy); + }) + .get(); }, [&](const YaPluginBase::Terminate& request) -> YaPluginBase::Terminate::Response { - return object_instances[request.instance_id] - .plugin_base->terminate(); + return main_context + .run_in_context([&]() { + return object_instances[request.instance_id] + .plugin_base->terminate(); + }) + .get(); }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { From e7d2f015da95a04bfe23c8ebe615571b689a67f8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 20 Dec 2020 13:11:25 +0100 Subject: [PATCH 321/456] Move VST3 changelog entry to [Unreleased] It got stuck in an old release after merging. --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fac8c71d..1761788d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - 2020-12-12 +### Added + +- Added the `with-vst3` option to control whether yabridge should be built with + VST3 support. This is enabled by default. + ### Changed - Changed part of the build process considering [this Wine @@ -35,8 +40,6 @@ Versioning](https://semver.org/spec/v2.0.0.html). resutling in very bad performance. See the [compatibility options](https://github.com/robbert-vdh/yabridge#compatibility-options) section of the readme for more information on how to enable this. -- Added the `with-vst3` option to control whether yabridge should be built with - VST3 support. This is enabled by default. ### Changed From af3990e5d467a096c130b3efed2a5599981e4447 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 20 Dec 2020 13:13:23 +0100 Subject: [PATCH 322/456] Mention the libyabridge.so rename --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1761788d..2f873c17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Versioning](https://semver.org/spec/v2.0.0.html). ### Changed +- `libyabridge.so` is now called `libyabridge-vst2.so`. If you're using + yabridgectl then nothing changes here. To avoid any confusion in the future + it's adviced to remove the old `libyabridge.so` file when upgrading. - Changed part of the build process considering [this Wine bug](https://bugs.winehq.org/show_bug.cgi?id=49138). Building with Wine 5.7 and 5.8 required a change, but that change now breaks builds using Wine 6.0 From ecd0de9d7d35ce72a17503b46c35d1d384bc1b95 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 20 Dec 2020 13:16:22 +0100 Subject: [PATCH 323/456] Add todo for adding VST3 entries to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dd1a6f2..4f1e41d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Versioning](https://semver.org/spec/v2.0.0.html). ### Added +TODO: Add the relevant entries here for yabridge's VST3 support + - Added the `with-vst3` option to control whether yabridge should be built with VST3 support. This is enabled by default. From 415c1b56831fd5300fdf532cc2c2a02df56f3e26 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 21 Dec 2020 15:45:47 +0100 Subject: [PATCH 324/456] Allow disabling ad-hoc socket spawning We'll need this for handling `IAudioProcessor` method calls in VST3. We basically want a `Vst3MessageHandler` per `IAudioProcessor` instance, but without the additional socket spawning or extra thread. --- src/common/communication/common.h | 144 ++++++++++++++++++------------ src/common/communication/vst2.h | 4 +- src/common/communication/vst3.h | 17 ++-- 3 files changed, 100 insertions(+), 65 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index e45f06a3..02008a8c 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -447,8 +447,13 @@ class SocketHandler { * * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. + * @tparam ad_hoc_sockets Whether new sockets should be created on demand to be + * able to handle multiple function calls at the same time. If this is set to + * false, then simultaneous `send_message()` calls will have to wait for the + * earlier call to finish. This also means that the listening side does not + * have to spawn a thread to constantly listen for new connections. */ -template +template class AdHocSocketHandler { protected: /** @@ -530,11 +535,15 @@ class AdHocSocketHandler { */ template T send(F callback) { + // When the ad-hoc socket spawning bheaviour is disabled we always want + // to acquire the lock, waiting if necessary. // XXX: Maybe at some point we should benchmark how often this // ad hoc socket spawning mechanism gets used. If some hosts // for instance consistently and repeatedly trigger this then // we might be able to do some optimizations there. - std::unique_lock lock(write_mutex, std::try_to_lock); + std::unique_lock lock = + ad_hoc_sockets ? std::unique_lock(write_mutex, std::try_to_lock) + : std::unique_lock(write_mutex); if (lock.owns_lock()) { // This was used to always block when sending the first message, // because the other side may not be listening for additional @@ -602,71 +611,90 @@ class AdHocSocketHandler { void receive_multi(std::optional> logger, F primary_callback, G secondary_callback) { - // As described above we'll handle incoming requests for `socket` on - // this thread. We'll also listen for incoming connections on `endpoint` - // on another thread. For any incoming connection we'll spawn a new - // thread to handle the request. When `socket` closes and this loop - // breaks, the listener and any still active threads will be cleaned up - // before this function exits. - boost::asio::io_context secondary_context{}; + if constexpr (ad_hoc_sockets) { + // As described above we'll handle incoming requests for `socket` on + // this thread. We'll also listen for incoming connections on + // `endpoint` on another thread. For any incoming connection we'll + // spawn a new thread to handle the request. When `socket` closes + // and this loop breaks, the listener and any still active threads + // will be cleaned up before this function exits. + boost::asio::io_context secondary_context{}; - // The previous acceptor has already been shut down by - // `AdHocSocketHandler::connect()` - acceptor.emplace(secondary_context, endpoint); + // The previous acceptor has already been shut down by + // `AdHocSocketHandler::connect()` + acceptor.emplace(secondary_context, endpoint); - // This works the exact same was as `active_plugins` and - // `next_plugin_id` in `GroupBridge` - std::map active_secondary_requests{}; - std::atomic_size_t next_request_id{}; - std::mutex active_secondary_requests_mutex{}; - accept_requests( - *acceptor, logger, - [&](boost::asio::local::stream_protocol::socket secondary_socket) { - const size_t request_id = next_request_id.fetch_add(1); + // This works the exact same was as `active_plugins` and + // `next_plugin_id` in `GroupBridge` + std::map active_secondary_requests{}; + std::atomic_size_t next_request_id{}; + std::mutex active_secondary_requests_mutex{}; + accept_requests( + *acceptor, logger, + [&](boost::asio::local::stream_protocol::socket + secondary_socket) { + const size_t request_id = next_request_id.fetch_add(1); - // We have to make sure to keep moving these sockets into the - // threads that will handle them - std::lock_guard lock(active_secondary_requests_mutex); - active_secondary_requests[request_id] = Thread( - [&, request_id](boost::asio::local::stream_protocol::socket - secondary_socket) { - secondary_callback(secondary_socket); + // We have to make sure to keep moving these sockets into + // the threads that will handle them + std::lock_guard lock(active_secondary_requests_mutex); + active_secondary_requests[request_id] = Thread( + [&, + request_id](boost::asio::local::stream_protocol::socket + secondary_socket) { + secondary_callback(secondary_socket); - // When we have processed this request, we'll join the - // thread again with the thread that's handling - // `secondary_context` - boost::asio::post(secondary_context, [&, request_id]() { - std::lock_guard lock( - active_secondary_requests_mutex); + // When we have processed this request, we'll join + // the thread again with the thread that's handling + // `secondary_context` + boost::asio::post( + secondary_context, [&, request_id]() { + std::lock_guard lock( + active_secondary_requests_mutex); - // The join is implicit because we're using - // `std::jthread`/`Win32Thread` - active_secondary_requests.erase(request_id); - }); - }, - std::move(secondary_socket)); - }); + // The join is implicit because we're using + // `std::jthread`/`Win32Thread` + active_secondary_requests.erase(request_id); + }); + }, + std::move(secondary_socket)); + }); - Thread secondary_requests_handler([&]() { secondary_context.run(); }); + Thread secondary_requests_handler( + [&]() { secondary_context.run(); }); - // Now we'll handle reads on the primary socket in a loop until the - // socket shuts down - while (true) { - try { - primary_callback(socket); - } catch (const boost::system::system_error&) { - // This happens when the sockets got closed because the plugin - // is being shut down - break; + // Now we'll handle reads on the primary socket in a loop until the + // socket shuts down + while (true) { + try { + primary_callback(socket); + } catch (const boost::system::system_error&) { + // This happens when the sockets got closed because the + // plugin is being shut down + break; + } + } + + // After the primary socket gets terminated (during shutdown) we'll + // make sure all outstanding jobs have been processed and then drop + // all work from the IO context + std::lock_guard lock(active_secondary_requests_mutex); + secondary_context.stop(); + acceptor.reset(); + } else { + // If ad-hoc sockets are disabled, then we only care about the + // primary socket loop (e.g. when handing calls to + // `IAudioProcessor`, where we want the exact same behaviour as when + // handling normal VST3 messages but we don't want to spawn + // additional threads for perofrmance considerations) + while (true) { + try { + primary_callback(socket); + } catch (const boost::system::system_error&) { + break; + } } } - - // After the primary socket gets terminated (during shutdown) we'll make - // sure all outstanding jobs have been processed and then drop all work - // from the IO context - std::lock_guard lock(active_secondary_requests_mutex); - secondary_context.stop(); - acceptor.reset(); } /** diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index 60bb1e95..60f97de9 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -106,7 +106,7 @@ class DefaultDataConverter { * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ template -class EventHandler : public AdHocSocketHandler { +class EventHandler : public AdHocSocketHandler { public: /** * Sets up a single main socket for this type of events. The sockets won't @@ -125,7 +125,7 @@ class EventHandler : public AdHocSocketHandler { EventHandler(boost::asio::io_context& io_context, boost::asio::local::stream_protocol::endpoint endpoint, bool listen) - : AdHocSocketHandler(io_context, endpoint, listen) {} + : AdHocSocketHandler(io_context, endpoint, listen) {} /** * Serialize and send an event over a socket. This is used for both the host diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 1d9a72f9..84833317 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -38,9 +38,14 @@ * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. * @tparam Request Either `ControlRequest` or `CallbackRequest`. + * @tparam ad_hoc_sockets Whether new sockets should be created on demand to be + * able to handle multiple function calls at the same time. If this is set to + * false, then simultaneous `send_message()` calls will have to wait for the + * earlier call to finish. This also means that the listening side does not + * have to spawn a thread to constantly listen for new connections. */ -template -class Vst3MessageHandler : public AdHocSocketHandler { +template +class Vst3MessageHandler : public AdHocSocketHandler { public: /** * Sets up a single main socket for this type of events. The sockets won't @@ -59,7 +64,9 @@ class Vst3MessageHandler : public AdHocSocketHandler { Vst3MessageHandler(boost::asio::io_context& io_context, boost::asio::local::stream_protocol::endpoint endpoint, bool listen) - : AdHocSocketHandler(io_context, endpoint, listen) {} + : AdHocSocketHandler(io_context, + endpoint, + listen) {} /** * Serialize and send an event over a socket and return the appropriate @@ -272,12 +279,12 @@ class Vst3Sockets : public Sockets { * This will be listened on by the Wine plugin host when it calls * `receive_multi()`. */ - Vst3MessageHandler host_vst_control; + Vst3MessageHandler host_vst_control; /** * For sending callbacks from the plugin back to the host. After we have a * better idea of what our communication model looks like we'll probably * want to provide an abstraction similar to `EventHandler`. */ - Vst3MessageHandler vst_host_callback; + Vst3MessageHandler vst_host_callback; }; From 51877796fa3f5f38fdf08f4c6e95c6e2b4f87b12 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 21 Dec 2020 17:25:08 +0100 Subject: [PATCH 325/456] Add dedicated IAudioProcessor/IComponent sockets This way every relevant object instance will get its own thread for handling these calls. The alternative would be creating a full fat Vst3MessageHandler pair for all object instances, but that would be a huge waste. --- src/common/communication/common.h | 14 +- src/common/communication/vst3.h | 138 ++++- src/common/logging/vst3.cpp | 546 +++++++++--------- src/common/logging/vst3.h | 62 +- src/common/serialization/vst3.h | 44 +- .../bridges/vst3-impls/plugin-proxy.cpp | 76 +-- src/plugin/bridges/vst3.cpp | 25 +- src/plugin/bridges/vst3.h | 30 +- src/wine-host/bridges/vst3.cpp | 322 +++++++---- src/wine-host/bridges/vst3.h | 7 + 10 files changed, 764 insertions(+), 500 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 02008a8c..f3f97107 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -491,12 +491,14 @@ class AdHocSocketHandler { if (acceptor) { acceptor->accept(socket); - // As mentioned in `acceptor's` docstring, this acceptor will be - // recreated in `receive_multi()` on another context, and - // potentially on the other side of the connection in the case - // where we're handling `vst_host_callback` VST2 events - acceptor.reset(); - boost::filesystem::remove(endpoint.path()); + if constexpr (ad_hoc_sockets) { + // As mentioned in `acceptor's` docstring, this acceptor will be + // recreated in `receive_multi()` on another context, and + // potentially on the other side of the connection in the case + // where we're handling `vst_host_callback` VST2 events + acceptor.reset(); + boost::filesystem::remove(endpoint.path()); + } } else { socket.connect(endpoint); } diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 84833317..9fb4b546 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include "../logging/vst3.h" @@ -221,6 +222,16 @@ class Vst3MessageHandler : public AdHocSocketHandler { * sockets, and the call to `connect()` will then accept any incoming * connections. * + * We'll have a host -> plugin connection for sending control messages (which is + * just a made up term to more easily differentiate between the two directions), + * and a plugin -> host connection to allow the plugin to make callbacks. Both + * of these connections are capable of spawning additional sockets and threads + * as needed. + * + * For audio processing (or anything that implement `IAudioProcessor` or + * `IComonent`) we'll use dedicated sockets per instance, since we don't want to + * do anything that could increase latency there. + * * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ @@ -250,7 +261,8 @@ class Vst3Sockets : public Sockets { listen), vst_host_callback(io_context, (base_dir / "vst_host_callback.sock").string(), - listen) {} + listen), + io_context(io_context) {} ~Vst3Sockets() { close(); } @@ -264,17 +276,113 @@ class Vst3Sockets : public Sockets { // that may still be active host_vst_control.close(); vst_host_callback.close(); + + // This map should be empty at this point, but who knows + std::lock_guard lock(audio_processor_sockets_mutex); + for (auto& [instance_id, socket] : audio_processor_sockets) { + socket.close(); + } } - // TODO: Since audio processing may be done completely in parallel we might - // want to have a dedicated socket per processor/controller pair. For - // this we would need to figure out how to associate a plugin instance - // with a socket. + /** + * Connect to the dedicated `IAudioProcessor` and `IConnect` handling socket + * for a plugin object instance. This should be called on the plugin side + * after instantiating such an object. + * + * @param instance_id The object instance identifier of the socket. + */ + void add_audio_processor_and_connect(size_t instance_id) { + std::lock_guard lock(audio_processor_sockets_mutex); + audio_processor_sockets.try_emplace( + instance_id, io_context, + (base_dir / ("host_vst_audio_processor_" + + std::to_string(instance_id) + ".sock")) + .string(), + false); + audio_processor_sockets.at(instance_id).connect(); + } + + /** + * Create and listen on a dedicated `IAudioProcessor` and `IConnect` + * handling socket for a plugin object instance. The calling thread will + * block until the socket has been closed. This should be called from the + * Wine plugin host side after instantiating such an object. + * + * @param instance_id The object instance identifier of the socket. + * @param socket_listening_latch A promise we'll set a value for once the + * socket is being listened on so we can wait for it. Otherwise it can be + * that the native plugin already tries to connect to the socket before + * Wine plugin host is even listening on it. + * @param cb An overloaded function that can take every type `T` in the + * `AudioProcessorRequest` variant and then returns `T::Response`. + */ + template + void add_audio_processor_and_listen( + size_t instance_id, + std::promise& socket_listening_latch, + F cb) { + { + std::lock_guard lock(audio_processor_sockets_mutex); + audio_processor_sockets.try_emplace( + instance_id, io_context, + (base_dir / ("host_vst_audio_processor_" + + std::to_string(instance_id) + ".sock")) + .string(), + true); + } + + socket_listening_latch.set_value(); + audio_processor_sockets.at(instance_id).connect(); + audio_processor_sockets.at(instance_id) + .receive_messages(std::nullopt, cb); + } + + /** + * If `instance_id` is in `audio_processor_sockets`, then close its socket + * and remove it from the map. This is called from the destructor of + * `Vst3PluginProxyImpl` on the plugin side and when handling + * `Vst3PluginProxy::Destruct` on the Wine plugin host side. + * + * @param instance_id The object instance identifier of the socket. + * + * @return Whether the socket was closed and removed. Returns false if it + * wasn't in the map. + */ + bool remove_audio_processor(size_t instance_id) { + std::lock_guard lock(audio_processor_sockets_mutex); + if (audio_processor_sockets.contains(instance_id)) { + audio_processor_sockets.at(instance_id).close(); + audio_processor_sockets.erase(instance_id); + + return true; + } else { + return false; + } + } + + /** + * Send a message from the native plugin to the Wine plugin host to handle + * an `IAudioProcessor` or `IComponent` call. Since those functions are + * called from a hot loop we want every instance to have a dedicated socket + * and thread for handling those. + * + * @tparam T Some object in the `AudioProcessorRequest` variant. + */ + template + typename T::Response send_audio_processor_message( + const T& object, + std::optional> logging) { + // TODO: These calls should reuse buffers + return audio_processor_sockets.at(object.instance_id) + .send_message(object, logging); + } /** * For sending messages from the host to the plugin. After we have a better * idea of what our communication model looks like we'll probably want to - * provide an abstraction similar to `EventHandler`. + * provide an abstraction similar to `EventHandler`. For optimization + * reasons calls to `IAudioProcessor` or `IComponent` are handled using the + * dedicated sockets in `audio_processor_sockets`. * * This will be listened on by the Wine plugin host when it calls * `receive_multi()`. @@ -287,4 +395,22 @@ class Vst3Sockets : public Sockets { * want to provide an abstraction similar to `EventHandler`. */ Vst3MessageHandler vst_host_callback; + + private: + boost::asio::io_context& io_context; + + /** + * Every `IAudioProcessor` or `IComponent` instance (which likely implements + * both of those) will get a dedicated socket. These functions are always + * called in a hot loop, so there should not be any waiting or additional + * thread or socket creation happening there. + * + * THe last `false` template arguments means that we'll disable all ad-hoc + * socket and thread spawning behaviour. Otherwise every plugin instance + * would have one dedicated thread for handling function calls to these + * interfaces, and then another dedicated thread just idling around. + */ + std::map> + audio_processor_sockets; + std::mutex audio_processor_sockets_mutex; }; diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index d747b314..f5c6983e 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -90,210 +90,6 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } -bool Vst3Logger::log_request( - bool is_host_vst, - const YaAudioProcessor::SetBusArrangements& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message << request.instance_id - << ": IAudioProcessor::setBusArrangements(inputs = " - "[SpeakerArrangement; " - << request.inputs.size() << "], numIns = " << request.num_ins - << ", outputs = [SpeakerArrangement; " << request.outputs.size() - << "], numOuts = " << request.num_outs << ")"; - }); -} - -bool Vst3Logger::log_request( - bool is_host_vst, - const YaAudioProcessor::GetBusArrangement& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message << request.instance_id - << ": IAudioProcessor::getBusArrangement(dir = " << request.dir - << ", index = " << request.index << ", &arr)"; - }); -} - -bool Vst3Logger::log_request( - bool is_host_vst, - const YaAudioProcessor::CanProcessSampleSize& request) { - return log_request_base( - is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { - message - << request.instance_id - << ": IAudioProcessor::canProcessSampleSize(symbolicSampleSize " - "= " - << request.symbolic_sample_size << ")"; - }); -} - -bool Vst3Logger::log_request( - bool is_host_vst, - const YaAudioProcessor::GetLatencySamples& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message << request.instance_id - << ": IAudioProcessor::getLatencySamples()"; - }); -} - -bool Vst3Logger::log_request(bool is_host_vst, - const YaAudioProcessor::SetupProcessing& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message << request.instance_id - << ": IAudioProcessor::setupProcessing(setup = " - ")"; - }); -} - -bool Vst3Logger::log_request(bool is_host_vst, - const YaAudioProcessor::SetProcessing& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message << request.instance_id - << ": IAudioProcessor::setProcessing(state = " - << (request.state ? "true" : "false") << ")"; - }); -} - -bool Vst3Logger::log_request(bool is_host_vst, - const YaAudioProcessor::Process& request) { - return log_request_base( - is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { - // This is incredibly verbose, but if you're really a plugin that - // handles processing in a weird way you're going to need all of - // this - - std::ostringstream num_input_channels; - num_input_channels << "["; - for (bool is_first = true; - const auto& buffers : request.data.inputs) { - num_input_channels << (is_first ? "" : ", ") - << buffers.num_channels(); - is_first = false; - } - num_input_channels << "]"; - - std::ostringstream num_output_channels; - num_output_channels << "["; - for (bool is_first = true; - const auto& num_channels : request.data.outputs_num_channels) { - num_output_channels << (is_first ? "" : ", ") << num_channels; - is_first = false; - } - num_output_channels << "]"; - - message << request.instance_id - << ": IAudioProcessor::process(data = , output_parameter_changes = " - << (request.data.output_parameter_changes_supported - ? "" - : "nullptr") - << ", input_events = "; - if (request.data.input_events) { - message << "num_events() - << " events>"; - } else { - message << "nullptr"; - } - message << ", output_events = " - << (request.data.output_events_supported ? "" - : "nullptr") - << ", process_context = " - << (request.data.process_context ? "" - : "nullptr") - << ", process_mode = " << request.data.process_mode - << ", symbolic_sample_size = " - << request.data.symbolic_sample_size << ">)"; - }); -} - -bool Vst3Logger::log_request(bool is_host_vst, - const YaAudioProcessor::GetTailSamples& request) { - return log_request_base( - is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { - message << request.instance_id - << ": IAudioProcessor::getTailSamples()"; - }); -} - -bool Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetControllerClassId& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message << request.instance_id - << ": IComponent::getControllerClassId(&classId)"; - }); -} - -bool Vst3Logger::log_request(bool is_host_vst, - const YaComponent::SetIoMode& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message << request.instance_id - << ": IComponent::setIoMode(mode = " << request.mode << ")"; - }); -} - -bool Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetBusCount& request) { - // JUCE-based hosts will call this every processing cycle, for some reason - // (it shouldn't be allwoed to change during processing, right?) - return log_request_base( - is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { - message << request.instance_id - << ": IComponent::getBusCount(type = " << request.type - << ", dir = " << request.dir << ")"; - }); -} - -bool Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetBusInfo& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message << request.instance_id - << ": IComponent::getBusInfo(type = " << request.type - << ", dir = " << request.dir << ", index = " << request.index - << ", &bus)"; - }); -} - -bool Vst3Logger::log_request(bool is_host_vst, - const YaComponent::GetRoutingInfo& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message - << request.instance_id - << ": IComponent::getRoutingInfo(inInfo = , outInfo = )"; - }); -} - -bool Vst3Logger::log_request(bool is_host_vst, - const YaComponent::ActivateBus& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message << request.instance_id - << ": IComponent::activateBus(type = " << request.type - << ", dir = " << request.dir << ", index = " << request.index - << ", state = " << (request.state ? "true" : "false") << ")"; - }); -} - -bool Vst3Logger::log_request(bool is_host_vst, - const YaComponent::SetActive& request) { - return log_request_base(is_host_vst, [&](auto& message) { - message << request.instance_id << ": IComponent::setActive(state = " - << (request.state ? "true" : "false") << ")"; - }); -} - bool Vst3Logger::log_request(bool is_host_vst, const YaConnectionPoint::Connect& request) { return log_request_base(is_host_vst, [&](auto& message) { @@ -508,6 +304,210 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request( + bool is_host_vst, + const YaAudioProcessor::SetBusArrangements& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IAudioProcessor::setBusArrangements(inputs = " + "[SpeakerArrangement; " + << request.inputs.size() << "], numIns = " << request.num_ins + << ", outputs = [SpeakerArrangement; " << request.outputs.size() + << "], numOuts = " << request.num_outs << ")"; + }); +} + +bool Vst3Logger::log_request( + bool is_host_vst, + const YaAudioProcessor::GetBusArrangement& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IAudioProcessor::getBusArrangement(dir = " << request.dir + << ", index = " << request.index << ", &arr)"; + }); +} + +bool Vst3Logger::log_request( + bool is_host_vst, + const YaAudioProcessor::CanProcessSampleSize& request) { + return log_request_base( + is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { + message + << request.instance_id + << ": IAudioProcessor::canProcessSampleSize(symbolicSampleSize " + "= " + << request.symbolic_sample_size << ")"; + }); +} + +bool Vst3Logger::log_request( + bool is_host_vst, + const YaAudioProcessor::GetLatencySamples& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IAudioProcessor::getLatencySamples()"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaAudioProcessor::SetupProcessing& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IAudioProcessor::setupProcessing(setup = " + ")"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaAudioProcessor::SetProcessing& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IAudioProcessor::setProcessing(state = " + << (request.state ? "true" : "false") << ")"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaAudioProcessor::Process& request) { + return log_request_base( + is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { + // This is incredibly verbose, but if you're really a plugin that + // handles processing in a weird way you're going to need all of + // this + + std::ostringstream num_input_channels; + num_input_channels << "["; + for (bool is_first = true; + const auto& buffers : request.data.inputs) { + num_input_channels << (is_first ? "" : ", ") + << buffers.num_channels(); + is_first = false; + } + num_input_channels << "]"; + + std::ostringstream num_output_channels; + num_output_channels << "["; + for (bool is_first = true; + const auto& num_channels : request.data.outputs_num_channels) { + num_output_channels << (is_first ? "" : ", ") << num_channels; + is_first = false; + } + num_output_channels << "]"; + + message << request.instance_id + << ": IAudioProcessor::process(data = , output_parameter_changes = " + << (request.data.output_parameter_changes_supported + ? "" + : "nullptr") + << ", input_events = "; + if (request.data.input_events) { + message << "num_events() + << " events>"; + } else { + message << "nullptr"; + } + message << ", output_events = " + << (request.data.output_events_supported ? "" + : "nullptr") + << ", process_context = " + << (request.data.process_context ? "" + : "nullptr") + << ", process_mode = " << request.data.process_mode + << ", symbolic_sample_size = " + << request.data.symbolic_sample_size << ">)"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaAudioProcessor::GetTailSamples& request) { + return log_request_base( + is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { + message << request.instance_id + << ": IAudioProcessor::getTailSamples()"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetControllerClassId& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IComponent::getControllerClassId(&classId)"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaComponent::SetIoMode& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IComponent::setIoMode(mode = " << request.mode << ")"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetBusCount& request) { + // JUCE-based hosts will call this every processing cycle, for some reason + // (it shouldn't be allwoed to change during processing, right?) + return log_request_base( + is_host_vst, Logger::Verbosity::all_events, [&](auto& message) { + message << request.instance_id + << ": IComponent::getBusCount(type = " << request.type + << ", dir = " << request.dir << ")"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetBusInfo& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IComponent::getBusInfo(type = " << request.type + << ", dir = " << request.dir << ", index = " << request.index + << ", &bus)"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaComponent::GetRoutingInfo& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message + << request.instance_id + << ": IComponent::getRoutingInfo(inInfo = , outInfo = )"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaComponent::ActivateBus& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IComponent::activateBus(type = " << request.type + << ", dir = " << request.dir << ", index = " << request.index + << ", state = " << (request.state ? "true" : "false") << ")"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaComponent::SetActive& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id << ": IComponent::setActive(state = " + << (request.state ? "true" : "false") << ")"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const WantsConfiguration&) { return log_request_base(is_host_vst, [&](auto& message) { message << "Requesting "; @@ -593,6 +593,75 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaEditController::GetParameterInfoResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + std::string param_title = + VST3::StringConvert::convert(response.updated_info.title); + message << ", "; + } + }); +} + +void Vst3Logger::log_response( + bool is_host_vst, + const YaEditController::GetParamStringByValueResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + std::string value = VST3::StringConvert::convert(response.string); + message << ", \"" << value << "\""; + } + }); +} + +void Vst3Logger::log_response( + bool is_host_vst, + const YaEditController::GetParamValueByStringResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", " << response.value_normalized; + } + }); +} + +void Vst3Logger::log_response( + bool is_host_vst, + const YaEditController::CreateViewResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + if (response.plug_view_args) { + message << ""; + } else { + message << ""; + } + }); +} + +void Vst3Logger::log_response(bool is_host_vst, + const YaPlugView::GetSizeResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", "; + } + }); +} + +void Vst3Logger::log_response(bool is_host_vst, + const YaPluginFactory::ConstructArgs& args) { + log_response_base(is_host_vst, [&](auto& message) { + message << " with " << args.num_classes + << " registered classes"; + }); +} + void Vst3Logger::log_response( bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse& response) { @@ -684,75 +753,6 @@ void Vst3Logger::log_response( }); } -void Vst3Logger::log_response( - bool is_host_vst, - const YaEditController::GetParameterInfoResponse& response) { - log_response_base(is_host_vst, [&](auto& message) { - message << response.result.string(); - if (response.result == Steinberg::kResultOk) { - std::string param_title = - VST3::StringConvert::convert(response.updated_info.title); - message << ", "; - } - }); -} - -void Vst3Logger::log_response( - bool is_host_vst, - const YaEditController::GetParamStringByValueResponse& response) { - log_response_base(is_host_vst, [&](auto& message) { - message << response.result.string(); - if (response.result == Steinberg::kResultOk) { - std::string value = VST3::StringConvert::convert(response.string); - message << ", \"" << value << "\""; - } - }); -} - -void Vst3Logger::log_response( - bool is_host_vst, - const YaEditController::GetParamValueByStringResponse& response) { - log_response_base(is_host_vst, [&](auto& message) { - message << response.result.string(); - if (response.result == Steinberg::kResultOk) { - message << ", " << response.value_normalized; - } - }); -} - -void Vst3Logger::log_response( - bool is_host_vst, - const YaEditController::CreateViewResponse& response) { - log_response_base(is_host_vst, [&](auto& message) { - if (response.plug_view_args) { - message << ""; - } else { - message << ""; - } - }); -} - -void Vst3Logger::log_response(bool is_host_vst, - const YaPlugView::GetSizeResponse& response) { - log_response_base(is_host_vst, [&](auto& message) { - message << response.result.string(); - if (response.result == Steinberg::kResultOk) { - message << ", "; - } - }); -} - -void Vst3Logger::log_response(bool is_host_vst, - const YaPluginFactory::ConstructArgs& args) { - log_response_base(is_host_vst, [&](auto& message) { - message << " with " << args.num_classes - << " registered classes"; - }); -} - void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { log_response_base(is_host_vst, [&](auto& message) { message << ""; }); diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 689375f3..d5c237e6 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -64,27 +64,6 @@ class Vst3Logger { bool log_request(bool is_host_vst, const Vst3PluginProxy::Destruct&); bool log_request(bool is_host_vst, const Vst3PluginProxy::SetState&); bool log_request(bool is_host_vst, const Vst3PluginProxy::GetState&); - bool log_request(bool is_host_vst, - const YaAudioProcessor::SetBusArrangements&); - bool log_request(bool is_host_vst, - const YaAudioProcessor::GetBusArrangement&); - bool log_request(bool is_host_vst, - const YaAudioProcessor::CanProcessSampleSize&); - bool log_request(bool is_host_vst, - const YaAudioProcessor::GetLatencySamples&); - bool log_request(bool is_host_vst, - const YaAudioProcessor::SetupProcessing&); - bool log_request(bool is_host_vst, const YaAudioProcessor::SetProcessing&); - bool log_request(bool is_host_vst, const YaAudioProcessor::Process&); - bool log_request(bool is_host_vst, const YaAudioProcessor::GetTailSamples&); - bool log_request(bool is_host_vst, - const YaComponent::GetControllerClassId&); - bool log_request(bool is_host_vst, const YaComponent::SetIoMode&); - bool log_request(bool is_host_vst, const YaComponent::GetBusCount&); - bool log_request(bool is_host_vst, const YaComponent::GetBusInfo&); - bool log_request(bool is_host_vst, const YaComponent::GetRoutingInfo&); - bool log_request(bool is_host_vst, const YaComponent::ActivateBus&); - bool log_request(bool is_host_vst, const YaComponent::SetActive&); bool log_request(bool is_host_vst, const YaConnectionPoint::Connect&); bool log_request(bool is_host_vst, const YaConnectionPoint::Disconnect&); bool log_request(bool is_host_vst, @@ -117,6 +96,28 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); bool log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); + bool log_request(bool is_host_vst, + const YaAudioProcessor::SetBusArrangements&); + bool log_request(bool is_host_vst, + const YaAudioProcessor::GetBusArrangement&); + bool log_request(bool is_host_vst, + const YaAudioProcessor::CanProcessSampleSize&); + bool log_request(bool is_host_vst, + const YaAudioProcessor::GetLatencySamples&); + bool log_request(bool is_host_vst, + const YaAudioProcessor::SetupProcessing&); + bool log_request(bool is_host_vst, const YaAudioProcessor::SetProcessing&); + bool log_request(bool is_host_vst, const YaAudioProcessor::Process&); + bool log_request(bool is_host_vst, const YaAudioProcessor::GetTailSamples&); + bool log_request(bool is_host_vst, + const YaComponent::GetControllerClassId&); + bool log_request(bool is_host_vst, const YaComponent::SetIoMode&); + bool log_request(bool is_host_vst, const YaComponent::GetBusCount&); + bool log_request(bool is_host_vst, const YaComponent::GetBusInfo&); + bool log_request(bool is_host_vst, const YaComponent::GetRoutingInfo&); + bool log_request(bool is_host_vst, const YaComponent::ActivateBus&); + bool log_request(bool is_host_vst, const YaComponent::SetActive&); + bool log_request(bool is_host_vst, const WantsConfiguration&); bool log_request(bool is_host_vst, const YaComponentHandler::BeginEdit&); bool log_request(bool is_host_vst, const YaComponentHandler::PerformEdit&); @@ -131,15 +132,6 @@ class Vst3Logger { const std::variant&); void log_response(bool is_host_vst, const Vst3PluginProxy::GetStateResponse&); - void log_response(bool is_host_vst, - const YaAudioProcessor::GetBusArrangementResponse&); - void log_response(bool is_host_vst, - const YaAudioProcessor::ProcessResponse&); - void log_response(bool is_host_vst, - const YaComponent::GetControllerClassIdResponse&); - void log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse&); - void log_response(bool is_host_vst, - const YaComponent::GetRoutingInfoResponse&); void log_response(bool is_host_vst, const YaEditController::GetParameterInfoResponse&); void log_response(bool is_host_vst, @@ -152,6 +144,16 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); + void log_response(bool is_host_vst, + const YaAudioProcessor::GetBusArrangementResponse&); + void log_response(bool is_host_vst, + const YaAudioProcessor::ProcessResponse&); + void log_response(bool is_host_vst, + const YaComponent::GetControllerClassIdResponse&); + void log_response(bool is_host_vst, const YaComponent::GetBusInfoResponse&); + void log_response(bool is_host_vst, + const YaComponent::GetRoutingInfoResponse&); + void log_response(bool is_host_vst, const YaHostApplication::GetNameResponse&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 8b4d4f05..d41c52f3 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -64,21 +64,6 @@ using ControlRequest = std::variant; + +template +void serialize(S& s, AudioProcessorRequest& payload) { + // All of the objects in `AudioProcessorRequest` should have their own + // serialization function. + s.ext(payload, bitsery::ext::StdVariant{}); +} + /** * When we do a callback from the Wine VST host to the plugin, this encodes the * information we want or the operation we want to perform. A request of type diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 6f4830b8..91056187 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -27,7 +27,7 @@ Vst3PluginProxyImpl::Vst3PluginProxyImpl(Vst3PluginBridge& bridge, Vst3PluginProxyImpl::~Vst3PluginProxyImpl() { bridge.send_message( Vst3PluginProxy::Destruct{.instance_id = instance_id()}); - bridge.unregister_plugin_proxy(instance_id()); + bridge.unregister_plugin_proxy(*this); } tresult PLUGIN_API @@ -49,29 +49,32 @@ tresult PLUGIN_API Vst3PluginProxyImpl::setBusArrangements( int32 numOuts) { // NOTE: Ardour passes a null pointer when `numIns` or `numOuts` is 0, so we // need to work around that - return bridge.send_message(YaAudioProcessor::SetBusArrangements{ - .instance_id = instance_id(), - .inputs = (inputs ? std::vector( - inputs, &inputs[numIns]) - : std::vector()), - .num_ins = numIns, - .outputs = - (outputs ? std::vector( - outputs, &outputs[numOuts]) - : std::vector()), - .num_outs = numOuts, - }); + return bridge.send_audio_processor_message( + YaAudioProcessor::SetBusArrangements{ + .instance_id = instance_id(), + .inputs = + (inputs ? std::vector( + inputs, &inputs[numIns]) + : std::vector()), + .num_ins = numIns, + .outputs = + (outputs ? std::vector( + outputs, &outputs[numOuts]) + : std::vector()), + .num_outs = numOuts, + }); } tresult PLUGIN_API Vst3PluginProxyImpl::getBusArrangement( Steinberg::Vst::BusDirection dir, int32 index, Steinberg::Vst::SpeakerArrangement& arr) { - const GetBusArrangementResponse response = bridge.send_message( - YaAudioProcessor::GetBusArrangement{.instance_id = instance_id(), - .dir = dir, - .index = index, - .arr = arr}); + const GetBusArrangementResponse response = + bridge.send_audio_processor_message( + YaAudioProcessor::GetBusArrangement{.instance_id = instance_id(), + .dir = dir, + .index = index, + .arr = arr}); arr = response.updated_arr; @@ -80,24 +83,26 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getBusArrangement( tresult PLUGIN_API Vst3PluginProxyImpl::canProcessSampleSize(int32 symbolicSampleSize) { - return bridge.send_message(YaAudioProcessor::CanProcessSampleSize{ - .instance_id = instance_id(), - .symbolic_sample_size = symbolicSampleSize}); + return bridge.send_audio_processor_message( + YaAudioProcessor::CanProcessSampleSize{ + .instance_id = instance_id(), + .symbolic_sample_size = symbolicSampleSize}); } uint32 PLUGIN_API Vst3PluginProxyImpl::getLatencySamples() { - return bridge.send_message( + return bridge.send_audio_processor_message( YaAudioProcessor::GetLatencySamples{.instance_id = instance_id()}); } tresult PLUGIN_API Vst3PluginProxyImpl::setupProcessing(Steinberg::Vst::ProcessSetup& setup) { - return bridge.send_message(YaAudioProcessor::SetupProcessing{ - .instance_id = instance_id(), .setup = setup}); + return bridge.send_audio_processor_message( + YaAudioProcessor::SetupProcessing{.instance_id = instance_id(), + .setup = setup}); } tresult PLUGIN_API Vst3PluginProxyImpl::setProcessing(TBool state) { - return bridge.send_message(YaAudioProcessor::SetProcessing{ + return bridge.send_audio_processor_message(YaAudioProcessor::SetProcessing{ .instance_id = instance_id(), .state = state}); } @@ -105,7 +110,7 @@ tresult PLUGIN_API Vst3PluginProxyImpl::process(Steinberg::Vst::ProcessData& data) { // TODO: Check whether reusing a `YaProcessData` object make a difference in // terms of performance - ProcessResponse response = bridge.send_message( + ProcessResponse response = bridge.send_audio_processor_message( YaAudioProcessor::Process{.instance_id = instance_id(), .data = data}); response.output_data.write_back_outputs(data); @@ -114,14 +119,15 @@ Vst3PluginProxyImpl::process(Steinberg::Vst::ProcessData& data) { } uint32 PLUGIN_API Vst3PluginProxyImpl::getTailSamples() { - return bridge.send_message( + return bridge.send_audio_processor_message( YaAudioProcessor::GetTailSamples{.instance_id = instance_id()}); } tresult PLUGIN_API Vst3PluginProxyImpl::getControllerClassId(Steinberg::TUID classId) { - const GetControllerClassIdResponse response = bridge.send_message( - YaComponent::GetControllerClassId{.instance_id = instance_id()}); + const GetControllerClassIdResponse response = + bridge.send_audio_processor_message( + YaComponent::GetControllerClassId{.instance_id = instance_id()}); std::copy(response.editor_cid.begin(), response.editor_cid.end(), classId); @@ -129,14 +135,14 @@ Vst3PluginProxyImpl::getControllerClassId(Steinberg::TUID classId) { } tresult PLUGIN_API Vst3PluginProxyImpl::setIoMode(Steinberg::Vst::IoMode mode) { - return bridge.send_message( + return bridge.send_audio_processor_message( YaComponent::SetIoMode{.instance_id = instance_id(), .mode = mode}); } int32 PLUGIN_API Vst3PluginProxyImpl::getBusCount(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir) { - return bridge.send_message(YaComponent::GetBusCount{ + return bridge.send_audio_processor_message(YaComponent::GetBusCount{ .instance_id = instance_id(), .type = type, .dir = dir}); } @@ -145,7 +151,7 @@ Vst3PluginProxyImpl::getBusInfo(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir, int32 index, Steinberg::Vst::BusInfo& bus /*out*/) { - const GetBusInfoResponse response = bridge.send_message( + const GetBusInfoResponse response = bridge.send_audio_processor_message( YaComponent::GetBusInfo{.instance_id = instance_id(), .type = type, .dir = dir, @@ -159,7 +165,7 @@ Vst3PluginProxyImpl::getBusInfo(Steinberg::Vst::MediaType type, tresult PLUGIN_API Vst3PluginProxyImpl::getRoutingInfo( Steinberg::Vst::RoutingInfo& inInfo, Steinberg::Vst::RoutingInfo& outInfo /*out*/) { - const GetRoutingInfoResponse response = bridge.send_message( + const GetRoutingInfoResponse response = bridge.send_audio_processor_message( YaComponent::GetRoutingInfo{.instance_id = instance_id(), .in_info = inInfo, .out_info = outInfo}); @@ -174,7 +180,7 @@ Vst3PluginProxyImpl::activateBus(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir, int32 index, TBool state) { - return bridge.send_message( + return bridge.send_audio_processor_message( YaComponent::ActivateBus{.instance_id = instance_id(), .type = type, .dir = dir, @@ -183,7 +189,7 @@ Vst3PluginProxyImpl::activateBus(Steinberg::Vst::MediaType type, } tresult PLUGIN_API Vst3PluginProxyImpl::setActive(TBool state) { - return bridge.send_message( + return bridge.send_audio_processor_message( YaComponent::SetActive{.instance_id = instance_id(), .state = state}); } diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 681d6bd3..e318fc44 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -161,13 +161,28 @@ Steinberg::IPluginFactory* Vst3PluginBridge::get_plugin_factory() { return plugin_factory; } -void Vst3PluginBridge::register_plugin_proxy(Vst3PluginProxyImpl& component) { +void Vst3PluginBridge::register_plugin_proxy( + Vst3PluginProxyImpl& proxy_object) { std::lock_guard lock(plugin_proxies_mutex); - plugin_proxies.emplace(component.instance_id(), - std::ref(component)); + + plugin_proxies.emplace(proxy_object.instance_id(), + std::ref(proxy_object)); + + // For optimization reaons we use dedicated sockets for functions that will + // be run in the audio processing loop + if (proxy_object.YaAudioProcessor::supported() || + proxy_object.YaComponent::supported()) { + sockets.add_audio_processor_and_connect(proxy_object.instance_id()); + } } -void Vst3PluginBridge::unregister_plugin_proxy(size_t instance_id) { +void Vst3PluginBridge::unregister_plugin_proxy( + Vst3PluginProxyImpl& proxy_object) { std::lock_guard lock(plugin_proxies_mutex); - plugin_proxies.erase(instance_id); + + plugin_proxies.erase(proxy_object.instance_id()); + if (proxy_object.YaAudioProcessor::supported() || + proxy_object.YaComponent::supported()) { + sockets.remove_audio_processor(proxy_object.instance_id()); + } } diff --git a/src/plugin/bridges/vst3.h b/src/plugin/bridges/vst3.h index 613b89f1..21deb51b 100644 --- a/src/plugin/bridges/vst3.h +++ b/src/plugin/bridges/vst3.h @@ -76,13 +76,12 @@ class Vst3PluginBridge : PluginBridge> { /** * Add a `Vst3PluginProxyImpl` to the list of registered proxy objects so we * can handle host callbacks. This function is called in - * `Vst3PluginProxyImpl`'s constructor. + * `Vst3PluginProxyImpl`'s constructor. If the plugin supports the + * `IAudioProcessor` or `IComponent` interfaces, then we'll also connect to + * a dedicated audio processing socket. * - * @param instance_id The instance ID generated by the plugin host when - * instantiating the `IComponent`. Used as a stable name to refer to - * these. - * @param plugin_proxy The actual proxy object we can access its host - * context. + * @param proxy_object The proxy object so we can access its host context + * and unique instance identifier. * * @see plugin_proxies */ @@ -93,13 +92,12 @@ class Vst3PluginBridge : PluginBridge> { * registered proxy objects. Called during the object's destructor after * asking the Wine plugin host to destroy the component on its side. * - * @param instance_id The instance ID generated by the plugin host when - * instantiating the `IComponent`. Used as a stable name to refer to - * these. + * @param proxy_object The proxy object so we can access its unique instance + * identifier. * * @see plugin_proxies */ - void unregister_plugin_proxy(size_t instance_id); + void unregister_plugin_proxy(Vst3PluginProxyImpl& proxy_object); /** * Send a control message to the Wine plugin host return the response. This @@ -112,6 +110,18 @@ class Vst3PluginBridge : PluginBridge> { object, std::pair(logger, true)); } + /** + * Send an `IAudioProcessor` or `IComponent` control message to a specific + * plugin instance. This is separated from the above `send_message()` for + * performance reasons, as this way every instance has its own dedicated + * socket and thread. + */ + template + typename T::Response send_audio_processor_message(const T& object) { + return sockets.send_audio_processor_message( + object, std::pair(logger, true)); + } + /** * The logging facility used for this instance of yabridge. Wraps around * `PluginBridge::generic_logger`. diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 8f110fc1..cd95a5a9 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -116,28 +116,215 @@ void Vst3Bridge::run() { }) .get(); - if (object) { - std::lock_guard lock(object_instances_mutex); - - const size_t instance_id = generate_instance_id(); - object_instances.emplace(instance_id, std::move(object)); - - // This is where the magic happens. Here we deduce which - // interfaces are supported by this object so we can create - // a one-to-one proxy of it. - return Vst3PluginProxy::ConstructArgs( - object_instances[instance_id].object, instance_id); - } else { + if (!object) { return UniversalTResult(Steinberg::kResultFalse); } + + std::lock_guard lock(object_instances_mutex); + + const size_t instance_id = generate_instance_id(); + object_instances.emplace(instance_id, std::move(object)); + + // If the object supports `IComponent` or `IAudioProcessor`, + // then we'll set up a dedicated thread for function calls for + // those interfaces. + if (object_instances[instance_id].audio_processor || + object_instances[instance_id].component) { + std::promise socket_listening_latch; + object_instances[instance_id] + .audio_processor_handler = Win32Thread([&, + instance_id]() { + // TODO: Move this somewhere else, because this is + // obviously ridiculous + sockets.add_audio_processor_and_listen( + instance_id, socket_listening_latch, + overload{ + [&](YaAudioProcessor::SetBusArrangements& + request) + -> YaAudioProcessor::SetBusArrangements:: + Response { + return object_instances + [request.instance_id] + .audio_processor + ->setBusArrangements( + request.inputs.data(), + request.num_ins, + request.outputs.data(), + request.num_outs); + }, + [&](YaAudioProcessor::GetBusArrangement& + request) + -> YaAudioProcessor::GetBusArrangement:: + Response { + const tresult result = + object_instances + [request.instance_id] + .audio_processor + ->getBusArrangement( + request.dir, + request.index, + request.arr); + + return YaAudioProcessor:: + GetBusArrangementResponse{ + .result = result, + .updated_arr = request.arr}; + }, + [&](const YaAudioProcessor:: + CanProcessSampleSize& request) + -> YaAudioProcessor::CanProcessSampleSize:: + Response { + return object_instances + [request.instance_id] + .audio_processor + ->canProcessSampleSize( + request + .symbolic_sample_size); + }, + [&](const YaAudioProcessor::GetLatencySamples& + request) + -> YaAudioProcessor::GetLatencySamples:: + Response { + return object_instances + [request.instance_id] + .audio_processor + ->getLatencySamples(); + }, + [&](YaAudioProcessor::SetupProcessing& request) + -> YaAudioProcessor::SetupProcessing:: + Response { + return object_instances + [request.instance_id] + .audio_processor + ->setupProcessing( + request.setup); + }, + [&](const YaAudioProcessor::SetProcessing& + request) + -> YaAudioProcessor::SetProcessing:: + Response { + return object_instances + [request.instance_id] + .audio_processor + ->setProcessing( + request.state); + }, + [&](YaAudioProcessor::Process& request) + -> YaAudioProcessor::Process::Response { + const tresult result = + object_instances[request.instance_id] + .audio_processor->process( + request.data.get()); + + return YaAudioProcessor::ProcessResponse{ + .result = result, + .output_data = + request.data + .move_outputs_to_response()}; + }, + [&](const YaAudioProcessor::GetTailSamples& + request) + -> YaAudioProcessor::GetTailSamples:: + Response { + return object_instances + [request.instance_id] + .audio_processor + ->getTailSamples(); + }, + [&](const YaComponent::GetControllerClassId& + request) + -> YaComponent::GetControllerClassId:: + Response { + Steinberg::TUID cid; + const tresult result = + object_instances + [request.instance_id] + .component + ->getControllerClassId( + cid); + + return YaComponent:: + GetControllerClassIdResponse{ + .result = result, + .editor_cid = + std::to_array(cid)}; + }, + [&](const YaComponent::SetIoMode& request) + -> YaComponent::SetIoMode::Response { + return object_instances[request.instance_id] + .component->setIoMode(request.mode); + }, + [&](const YaComponent::GetBusCount& request) + -> YaComponent::GetBusCount::Response { + return object_instances[request.instance_id] + .component->getBusCount(request.type, + request.dir); + }, + [&](YaComponent::GetBusInfo& request) + -> YaComponent::GetBusInfo::Response { + const tresult result = + object_instances[request.instance_id] + .component->getBusInfo( + request.type, request.dir, + request.index, request.bus); + + return YaComponent::GetBusInfoResponse{ + .result = result, + .updated_bus = request.bus}; + }, + [&](YaComponent::GetRoutingInfo& request) + -> YaComponent::GetRoutingInfo::Response { + const tresult result = + object_instances[request.instance_id] + .component->getRoutingInfo( + request.in_info, + request.out_info); + + return YaComponent::GetRoutingInfoResponse{ + .result = result, + .updated_in_info = request.in_info, + .updated_out_info = request.out_info}; + }, + [&](const YaComponent::ActivateBus& request) + -> YaComponent::ActivateBus::Response { + return object_instances[request.instance_id] + .component->activateBus( + request.type, request.dir, + request.index, request.state); + }, + [&](const YaComponent::SetActive& request) + -> YaComponent::SetActive::Response { + return object_instances[request.instance_id] + .component->setActive(request.state); + }, + }); + }); + + // Wait for the new socket to be listening on before + // continuing. Otherwise the native plugin may try to + // connect to it before our thread is up and running. + socket_listening_latch.get_future().wait(); + } + + // This is where the magic happens. Here we deduce which + // interfaces are supported by this object so we can create + // a one-to-one proxy of it. + return Vst3PluginProxy::ConstructArgs( + object_instances[instance_id].object, instance_id); }, [&](const Vst3PluginProxy::Destruct& request) -> Vst3PluginProxy::Destruct::Response { + // Tear the dedicated audio processing socket down again if we + // created one while handling `Vst3PluginProxy::Construct` + if (object_instances[request.instance_id].audio_processor || + object_instances[request.instance_id].component) { + sockets.remove_audio_processor(request.instance_id); + } + + // Remove the instance from within the main IO context so + // removing it doesn't interfere with the Win32 message loop main_context .run_in_context([&]() { - // Remove the instance from within the main IO context - // so removing it doesn't interfere with the Win32 - // message loop std::lock_guard lock(object_instances_mutex); object_instances.erase(request.instance_id); }) @@ -181,111 +368,6 @@ void Vst3Bridge::run() { return Vst3PluginProxy::GetStateResponse{ .result = result, .updated_state = std::move(stream)}; }, - [&](YaAudioProcessor::SetBusArrangements& request) - -> YaAudioProcessor::SetBusArrangements::Response { - return object_instances[request.instance_id] - .audio_processor->setBusArrangements( - request.inputs.data(), request.num_ins, - request.outputs.data(), request.num_outs); - }, - [&](YaAudioProcessor::GetBusArrangement& request) - -> YaAudioProcessor::GetBusArrangement::Response { - const tresult result = - object_instances[request.instance_id] - .audio_processor->getBusArrangement( - request.dir, request.index, request.arr); - - return YaAudioProcessor::GetBusArrangementResponse{ - .result = result, .updated_arr = request.arr}; - }, - [&](const YaAudioProcessor::CanProcessSampleSize& request) - -> YaAudioProcessor::CanProcessSampleSize::Response { - return object_instances[request.instance_id] - .audio_processor->canProcessSampleSize( - request.symbolic_sample_size); - }, - [&](const YaAudioProcessor::GetLatencySamples& request) - -> YaAudioProcessor::GetLatencySamples::Response { - return object_instances[request.instance_id] - .audio_processor->getLatencySamples(); - }, - [&](YaAudioProcessor::SetupProcessing& request) - -> YaAudioProcessor::SetupProcessing::Response { - return object_instances[request.instance_id] - .audio_processor->setupProcessing(request.setup); - }, - [&](const YaAudioProcessor::SetProcessing& request) - -> YaAudioProcessor::SetProcessing::Response { - return object_instances[request.instance_id] - .audio_processor->setProcessing(request.state); - }, - [&](YaAudioProcessor::Process& request) - -> YaAudioProcessor::Process::Response { - const tresult result = - object_instances[request.instance_id] - .audio_processor->process(request.data.get()); - - return YaAudioProcessor::ProcessResponse{ - .result = result, - .output_data = request.data.move_outputs_to_response()}; - }, - [&](const YaAudioProcessor::GetTailSamples& request) - -> YaAudioProcessor::GetTailSamples::Response { - return object_instances[request.instance_id] - .audio_processor->getTailSamples(); - }, - [&](const YaComponent::GetControllerClassId& request) - -> YaComponent::GetControllerClassId::Response { - Steinberg::TUID cid; - const tresult result = - object_instances[request.instance_id] - .component->getControllerClassId(cid); - - return YaComponent::GetControllerClassIdResponse{ - .result = result, .editor_cid = std::to_array(cid)}; - }, - [&](const YaComponent::SetIoMode& request) - -> YaComponent::SetIoMode::Response { - return object_instances[request.instance_id] - .component->setIoMode(request.mode); - }, - [&](const YaComponent::GetBusCount& request) - -> YaComponent::GetBusCount::Response { - return object_instances[request.instance_id] - .component->getBusCount(request.type, request.dir); - }, - [&](YaComponent::GetBusInfo& request) - -> YaComponent::GetBusInfo::Response { - const tresult result = - object_instances[request.instance_id].component->getBusInfo( - request.type, request.dir, request.index, request.bus); - - return YaComponent::GetBusInfoResponse{ - .result = result, .updated_bus = request.bus}; - }, - [&](YaComponent::GetRoutingInfo& request) - -> YaComponent::GetRoutingInfo::Response { - const tresult result = - object_instances[request.instance_id] - .component->getRoutingInfo(request.in_info, - request.out_info); - - return YaComponent::GetRoutingInfoResponse{ - .result = result, - .updated_in_info = request.in_info, - .updated_out_info = request.out_info}; - }, - [&](const YaComponent::ActivateBus& request) - -> YaComponent::ActivateBus::Response { - return object_instances[request.instance_id] - .component->activateBus(request.type, request.dir, - request.index, request.state); - }, - [&](const YaComponent::SetActive& request) - -> YaComponent::SetActive::Response { - return object_instances[request.instance_id] - .component->setActive(request.state); - }, [&](const YaConnectionPoint::Connect& request) -> YaConnectionPoint::Connect::Response { // We can directly connect the underlying objects diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 026ceb53..525c2817 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -38,6 +38,13 @@ struct InstanceInterfaces { InstanceInterfaces(Steinberg::IPtr object); + /** + * A dedicated thread for handling incoming `IAudioProcessor` and + * `IComponent` calls. Will be instantiated if `object` supports either of + * those interfaces. + */ + Win32Thread audio_processor_handler; + /** * If the host passes a host context object during * `IPluginBase::initialize()`, we'll store a proxy object here and then From 5324e4142b0d63db1ff757d6b4d9b2b98f9392c9 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 21 Dec 2020 17:59:38 +0100 Subject: [PATCH 326/456] Clean up proxy object construction/destruction --- src/wine-host/bridges/vst3.cpp | 373 +++++++++++++++------------------ src/wine-host/bridges/vst3.h | 14 ++ 2 files changed, 180 insertions(+), 207 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index cd95a5a9..49b9c72d 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -120,191 +120,7 @@ void Vst3Bridge::run() { return UniversalTResult(Steinberg::kResultFalse); } - std::lock_guard lock(object_instances_mutex); - - const size_t instance_id = generate_instance_id(); - object_instances.emplace(instance_id, std::move(object)); - - // If the object supports `IComponent` or `IAudioProcessor`, - // then we'll set up a dedicated thread for function calls for - // those interfaces. - if (object_instances[instance_id].audio_processor || - object_instances[instance_id].component) { - std::promise socket_listening_latch; - object_instances[instance_id] - .audio_processor_handler = Win32Thread([&, - instance_id]() { - // TODO: Move this somewhere else, because this is - // obviously ridiculous - sockets.add_audio_processor_and_listen( - instance_id, socket_listening_latch, - overload{ - [&](YaAudioProcessor::SetBusArrangements& - request) - -> YaAudioProcessor::SetBusArrangements:: - Response { - return object_instances - [request.instance_id] - .audio_processor - ->setBusArrangements( - request.inputs.data(), - request.num_ins, - request.outputs.data(), - request.num_outs); - }, - [&](YaAudioProcessor::GetBusArrangement& - request) - -> YaAudioProcessor::GetBusArrangement:: - Response { - const tresult result = - object_instances - [request.instance_id] - .audio_processor - ->getBusArrangement( - request.dir, - request.index, - request.arr); - - return YaAudioProcessor:: - GetBusArrangementResponse{ - .result = result, - .updated_arr = request.arr}; - }, - [&](const YaAudioProcessor:: - CanProcessSampleSize& request) - -> YaAudioProcessor::CanProcessSampleSize:: - Response { - return object_instances - [request.instance_id] - .audio_processor - ->canProcessSampleSize( - request - .symbolic_sample_size); - }, - [&](const YaAudioProcessor::GetLatencySamples& - request) - -> YaAudioProcessor::GetLatencySamples:: - Response { - return object_instances - [request.instance_id] - .audio_processor - ->getLatencySamples(); - }, - [&](YaAudioProcessor::SetupProcessing& request) - -> YaAudioProcessor::SetupProcessing:: - Response { - return object_instances - [request.instance_id] - .audio_processor - ->setupProcessing( - request.setup); - }, - [&](const YaAudioProcessor::SetProcessing& - request) - -> YaAudioProcessor::SetProcessing:: - Response { - return object_instances - [request.instance_id] - .audio_processor - ->setProcessing( - request.state); - }, - [&](YaAudioProcessor::Process& request) - -> YaAudioProcessor::Process::Response { - const tresult result = - object_instances[request.instance_id] - .audio_processor->process( - request.data.get()); - - return YaAudioProcessor::ProcessResponse{ - .result = result, - .output_data = - request.data - .move_outputs_to_response()}; - }, - [&](const YaAudioProcessor::GetTailSamples& - request) - -> YaAudioProcessor::GetTailSamples:: - Response { - return object_instances - [request.instance_id] - .audio_processor - ->getTailSamples(); - }, - [&](const YaComponent::GetControllerClassId& - request) - -> YaComponent::GetControllerClassId:: - Response { - Steinberg::TUID cid; - const tresult result = - object_instances - [request.instance_id] - .component - ->getControllerClassId( - cid); - - return YaComponent:: - GetControllerClassIdResponse{ - .result = result, - .editor_cid = - std::to_array(cid)}; - }, - [&](const YaComponent::SetIoMode& request) - -> YaComponent::SetIoMode::Response { - return object_instances[request.instance_id] - .component->setIoMode(request.mode); - }, - [&](const YaComponent::GetBusCount& request) - -> YaComponent::GetBusCount::Response { - return object_instances[request.instance_id] - .component->getBusCount(request.type, - request.dir); - }, - [&](YaComponent::GetBusInfo& request) - -> YaComponent::GetBusInfo::Response { - const tresult result = - object_instances[request.instance_id] - .component->getBusInfo( - request.type, request.dir, - request.index, request.bus); - - return YaComponent::GetBusInfoResponse{ - .result = result, - .updated_bus = request.bus}; - }, - [&](YaComponent::GetRoutingInfo& request) - -> YaComponent::GetRoutingInfo::Response { - const tresult result = - object_instances[request.instance_id] - .component->getRoutingInfo( - request.in_info, - request.out_info); - - return YaComponent::GetRoutingInfoResponse{ - .result = result, - .updated_in_info = request.in_info, - .updated_out_info = request.out_info}; - }, - [&](const YaComponent::ActivateBus& request) - -> YaComponent::ActivateBus::Response { - return object_instances[request.instance_id] - .component->activateBus( - request.type, request.dir, - request.index, request.state); - }, - [&](const YaComponent::SetActive& request) - -> YaComponent::SetActive::Response { - return object_instances[request.instance_id] - .component->setActive(request.state); - }, - }); - }); - - // Wait for the new socket to be listening on before - // continuing. Otherwise the native plugin may try to - // connect to it before our thread is up and running. - socket_listening_latch.get_future().wait(); - } + const size_t instance_id = register_object_instance(object); // This is where the magic happens. Here we deduce which // interfaces are supported by this object so we can create @@ -314,28 +130,7 @@ void Vst3Bridge::run() { }, [&](const Vst3PluginProxy::Destruct& request) -> Vst3PluginProxy::Destruct::Response { - // Tear the dedicated audio processing socket down again if we - // created one while handling `Vst3PluginProxy::Construct` - if (object_instances[request.instance_id].audio_processor || - object_instances[request.instance_id].component) { - sockets.remove_audio_processor(request.instance_id); - } - - // Remove the instance from within the main IO context so - // removing it doesn't interfere with the Win32 message loop - main_context - .run_in_context([&]() { - std::lock_guard lock(object_instances_mutex); - object_instances.erase(request.instance_id); - }) - .wait(); - - // XXX: I don't think we have to wait for the object to be - // deleted most of the time, but I can imagine a situation - // where the plugin does a host callback triggered by a - // Win32 timer in between where the above closure is being - // executed and when the actual host application context on - // the plugin side gets deallocated. + unregister_object_instance(request.instance_id); return Ack{}; }, [&](Vst3PluginProxy::SetState& request) @@ -657,3 +452,167 @@ void Vst3Bridge::handle_win32_events() { size_t Vst3Bridge::generate_instance_id() { return current_instance_id.fetch_add(1); } + +size_t Vst3Bridge::register_object_instance( + Steinberg::IPtr object) { + std::lock_guard lock(object_instances_mutex); + + const size_t instance_id = generate_instance_id(); + object_instances.emplace(instance_id, std::move(object)); + + // If the object supports `IComponent` or `IAudioProcessor`, + // then we'll set up a dedicated thread for function calls for + // those interfaces. + if (object_instances[instance_id].audio_processor || + object_instances[instance_id].component) { + std::promise socket_listening_latch; + + object_instances[instance_id] + .audio_processor_handler = Win32Thread([&, instance_id]() { + sockets.add_audio_processor_and_listen( + instance_id, socket_listening_latch, + overload{ + [&](YaAudioProcessor::SetBusArrangements& request) + -> YaAudioProcessor::SetBusArrangements::Response { + return object_instances[request.instance_id] + .audio_processor->setBusArrangements( + request.inputs.data(), request.num_ins, + request.outputs.data(), request.num_outs); + }, + [&](YaAudioProcessor::GetBusArrangement& request) + -> YaAudioProcessor::GetBusArrangement::Response { + const tresult result = + object_instances[request.instance_id] + .audio_processor->getBusArrangement( + request.dir, request.index, request.arr); + + return YaAudioProcessor::GetBusArrangementResponse{ + .result = result, .updated_arr = request.arr}; + }, + [&](const YaAudioProcessor::CanProcessSampleSize& request) + -> YaAudioProcessor::CanProcessSampleSize::Response { + return object_instances[request.instance_id] + .audio_processor->canProcessSampleSize( + request.symbolic_sample_size); + }, + [&](const YaAudioProcessor::GetLatencySamples& request) + -> YaAudioProcessor::GetLatencySamples::Response { + return object_instances[request.instance_id] + .audio_processor->getLatencySamples(); + }, + [&](YaAudioProcessor::SetupProcessing& request) + -> YaAudioProcessor::SetupProcessing::Response { + return object_instances[request.instance_id] + .audio_processor->setupProcessing(request.setup); + }, + [&](const YaAudioProcessor::SetProcessing& request) + -> YaAudioProcessor::SetProcessing::Response { + return object_instances[request.instance_id] + .audio_processor->setProcessing(request.state); + }, + [&](YaAudioProcessor::Process& request) + -> YaAudioProcessor::Process::Response { + const tresult result = + object_instances[request.instance_id] + .audio_processor->process(request.data.get()); + + return YaAudioProcessor::ProcessResponse{ + .result = result, + .output_data = + request.data.move_outputs_to_response()}; + }, + [&](const YaAudioProcessor::GetTailSamples& request) + -> YaAudioProcessor::GetTailSamples::Response { + return object_instances[request.instance_id] + .audio_processor->getTailSamples(); + }, + [&](const YaComponent::GetControllerClassId& request) + -> YaComponent::GetControllerClassId::Response { + Steinberg::TUID cid; + const tresult result = + object_instances[request.instance_id] + .component->getControllerClassId(cid); + + return YaComponent::GetControllerClassIdResponse{ + .result = result, .editor_cid = std::to_array(cid)}; + }, + [&](const YaComponent::SetIoMode& request) + -> YaComponent::SetIoMode::Response { + return object_instances[request.instance_id] + .component->setIoMode(request.mode); + }, + [&](const YaComponent::GetBusCount& request) + -> YaComponent::GetBusCount::Response { + return object_instances[request.instance_id] + .component->getBusCount(request.type, request.dir); + }, + [&](YaComponent::GetBusInfo& request) + -> YaComponent::GetBusInfo::Response { + const tresult result = + object_instances[request.instance_id] + .component->getBusInfo( + request.type, request.dir, request.index, + request.bus); + + return YaComponent::GetBusInfoResponse{ + .result = result, .updated_bus = request.bus}; + }, + [&](YaComponent::GetRoutingInfo& request) + -> YaComponent::GetRoutingInfo::Response { + const tresult result = + object_instances[request.instance_id] + .component->getRoutingInfo(request.in_info, + request.out_info); + + return YaComponent::GetRoutingInfoResponse{ + .result = result, + .updated_in_info = request.in_info, + .updated_out_info = request.out_info}; + }, + [&](const YaComponent::ActivateBus& request) + -> YaComponent::ActivateBus::Response { + return object_instances[request.instance_id] + .component->activateBus(request.type, request.dir, + request.index, + request.state); + }, + [&](const YaComponent::SetActive& request) + -> YaComponent::SetActive::Response { + return object_instances[request.instance_id] + .component->setActive(request.state); + }, + }); + }); + + // Wait for the new socket to be listening on before + // continuing. Otherwise the native plugin may try to + // connect to it before our thread is up and running. + socket_listening_latch.get_future().wait(); + } + + return instance_id; +} + +void Vst3Bridge::unregister_object_instance(size_t instance_id) { + // Tear the dedicated audio processing socket down again if we + // created one while handling `Vst3PluginProxy::Construct` + if (object_instances[instance_id].audio_processor || + object_instances[instance_id].component) { + sockets.remove_audio_processor(instance_id); + } + + // Remove the instance from within the main IO context so + // removing it doesn't interfere with the Win32 message loop + // XXX: I don't think we have to wait for the object to be + // deleted most of the time, but I can imagine a situation + // where the plugin does a host callback triggered by a + // Win32 timer in between where the above closure is being + // executed and when the actual host application context on + // the plugin side gets deallocated. + main_context + .run_in_context([&, instance_id]() { + std::lock_guard lock(object_instances_mutex); + object_instances.erase(instance_id); + }) + .wait(); +} diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 525c2817..f90e45b5 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -148,6 +148,20 @@ class Vst3Bridge : public HostBridge { */ size_t generate_instance_id(); + /** + * Assign a unique identifier to an object and add it to `object_instances`. + * This will also set up listeners for `IAudioProcessor` and `IComponent` + * function calls. + */ + size_t register_object_instance( + Steinberg::IPtr object); + + /** + * Remove an object from `object_instances`. Will also tear down the + * `IAudioProcessor`/`IComponent` socket if it had one. + */ + void unregister_object_instance(size_t instance_id); + /** * The IO context used for event handling so that all events and window * message handling can be performed from a single thread, even when hosting From 0f43e21fc003d675d0568504bfe3bce320909e9f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 21 Dec 2020 18:22:40 +0100 Subject: [PATCH 327/456] Remove old todos --- src/plugin/bridges/vst3.cpp | 2 -- src/wine-host/editor.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index e318fc44..9528fe06 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -21,8 +21,6 @@ #include "vst3-impls/plugin-proxy.h" // There are still some design decisions that need some more thought -// TODO: Check whether `IPlugView::isPlatformTypeSupported` needs special -// handling. // TODO: The documentation mentions that private communication through VST3's // message system should be handled on a separate timer thread. Do we // need special handling for this on the Wine side (e.g. during the event diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index daccf194..27ab08b0 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -85,8 +85,6 @@ class WindowClass { * issues, please let me know and I'll switch to using XEmbed again. * * This workaround was inspired by LinVst. - * - * TODO: Decouple this from VST2 */ class Editor { public: From 7e34cf69fe43d42abaaa54ff289967f0e36ebe3f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 21 Dec 2020 18:24:07 +0100 Subject: [PATCH 328/456] Remove Editor::handle_win32_events This apparently isn't needed anymore. --- src/wine-host/bridges/vst2.cpp | 16 ++++++---------- src/wine-host/bridges/vst3.cpp | 2 -- src/wine-host/editor.cpp | 17 ----------------- src/wine-host/editor.h | 7 ------- 4 files changed, 6 insertions(+), 36 deletions(-) diff --git a/src/wine-host/bridges/vst2.cpp b/src/wine-host/bridges/vst2.cpp index 891bc9da..4ef03eed 100644 --- a/src/wine-host/bridges/vst2.cpp +++ b/src/wine-host/bridges/vst2.cpp @@ -355,17 +355,13 @@ void Vst2Bridge::handle_x11_events() { } void Vst2Bridge::handle_win32_events() { - if (editor) { - editor->handle_win32_events(); - } else { - MSG msg; + MSG msg; - for (int i = 0; i < max_win32_messages && - PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); - i++) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } + for (int i = 0; + i < max_win32_messages && PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); + i++) { + TranslateMessage(&msg); + DispatchMessage(&msg); } } diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 49b9c72d..4a4476f4 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -437,8 +437,6 @@ void Vst3Bridge::handle_x11_events() { } void Vst3Bridge::handle_win32_events() { - // TODO: Remove this function from the `Editor` - MSG msg; for (int i = 0; diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 1315b945..2fc56117 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -219,23 +219,6 @@ HWND Editor::get_win32_handle() const { } } -void Editor::handle_win32_events() const { - MSG msg; - - // The null value for the second argument is needed to handle interaction - // with child GUI components. So far limiting this to `max_win32_messages` - // messages has only been needed for Waves plugins as they otherwise cause - // an infinite message loop. - // TODO: If the timer is no longer needed, then we can drop this entire - // function - for (int i = 0; - i < max_win32_messages && PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); - i++) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } -} - void Editor::handle_x11_events() const { // TODO: Initiating drag-and-drop in Serum _sometimes_ causes the GUI to // update while dragging while other times it does not. From all the diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index 27ab08b0..ec8fba6a 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -125,13 +125,6 @@ class Editor { */ bool supports_ewmh_active_window() const; - /** - * Pump messages from the editor loop loop until all events are process. - * Must be run from the same thread the GUI was created in because of Win32 - * limitations. - */ - void handle_win32_events() const; - /** * Handle X11 events sent to the window our editor is embedded in. */ From f1009f19416c21a7343123b8c9eed4e5508bc705 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 21 Dec 2020 18:30:42 +0100 Subject: [PATCH 329/456] Implement IPlugView::removed() --- src/common/logging/vst3.cpp | 7 +++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + .../serialization/vst3/plug-view/plug-view.h | 16 ++++++++++++++++ .../bridges/vst3-impls/plug-view-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 15 +++++++++++++++ 6 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index f5c6983e..1add9e8f 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -257,6 +257,13 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaPlugView::Removed& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id << ": IPlugView::removed()"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPlugView::GetSize& request) { return log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index d5c237e6..b42af87c 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -90,6 +90,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPlugView::IsPlatformTypeSupported&); bool log_request(bool is_host_vst, const YaPlugView::Attached&); + bool log_request(bool is_host_vst, const YaPlugView::Removed&); bool log_request(bool is_host_vst, const YaPlugView::GetSize&); bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index d41c52f3..7fa649b6 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -79,6 +79,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(owner_instance_id); + } + }; + virtual tresult PLUGIN_API removed() override = 0; virtual tresult PLUGIN_API onWheel(float distance) override = 0; virtual tresult PLUGIN_API onKeyDown(char16 key, diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index e71f7d03..8b931003 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -59,9 +59,8 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::attached(void* parent, } tresult PLUGIN_API Vst3PlugViewProxyImpl::removed() { - // TODO: Implement - bridge.logger.log("TODO: IPlugView::removed()"); - return Steinberg::kNotImplemented; + return bridge.send_message( + YaPlugView::Removed{.owner_instance_id = owner_instance_id()}); } tresult PLUGIN_API Vst3PlugViewProxyImpl::onWheel(float distance) { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 4a4476f4..c29447f3 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -355,6 +355,21 @@ void Vst3Bridge::run() { }) .get(); }, + [&](const YaPlugView::Removed& request) + -> YaPlugView::Removed::Response { + return main_context + .run_in_context([&]() { + const tresult result = + object_instances[request.owner_instance_id] + .plug_view->removed(); + + object_instances[request.owner_instance_id] + .editor.reset(); + + return result; + }) + .get(); + }, [&](YaPlugView::GetSize& request) -> YaPlugView::GetSize::Response { const tresult result = object_instances[request.owner_instance_id] From 06c55fcdd87349477119e4b4c1df3ec3a1600dbf Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 21 Dec 2020 18:56:22 +0100 Subject: [PATCH 330/456] Implement IPlugView::onWheel() --- src/common/logging/vst3.cpp | 9 +++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + .../serialization/vst3/plug-view/plug-view.h | 19 +++++++++++++++++++ .../bridges/vst3-impls/plug-view-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 1add9e8f..7eee5a95 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -264,6 +264,15 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaPlugView::OnWheel& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id + << ": IPlugView::onWheel(distance = " << request.distance + << ")"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPlugView::GetSize& request) { return log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index b42af87c..3fd03cde 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -91,6 +91,7 @@ class Vst3Logger { const YaPlugView::IsPlatformTypeSupported&); bool log_request(bool is_host_vst, const YaPlugView::Attached&); bool log_request(bool is_host_vst, const YaPlugView::Removed&); + bool log_request(bool is_host_vst, const YaPlugView::OnWheel&); bool log_request(bool is_host_vst, const YaPlugView::GetSize&); bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 7fa649b6..e8707f28 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -80,6 +80,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(owner_instance_id); + s.value4b(distance); + } + }; + virtual tresult PLUGIN_API onWheel(float distance) override = 0; virtual tresult PLUGIN_API onKeyDown(char16 key, int16 keyCode, diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index 8b931003..130d5fad 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -64,9 +64,8 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::removed() { } tresult PLUGIN_API Vst3PlugViewProxyImpl::onWheel(float distance) { - // TODO: Implement - bridge.logger.log("TODO: IPlugView::onWheel()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaPlugView::OnWheel{ + .owner_instance_id = owner_instance_id(), .distance = distance}); } tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyDown(char16 key, diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index c29447f3..179c4574 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -370,6 +370,11 @@ void Vst3Bridge::run() { }) .get(); }, + [&](const YaPlugView::OnWheel& request) + -> YaPlugView::OnWheel::Response { + return object_instances[request.owner_instance_id] + .plug_view->onWheel(request.distance); + }, [&](YaPlugView::GetSize& request) -> YaPlugView::GetSize::Response { const tresult result = object_instances[request.owner_instance_id] From d49814d21df34b084593ac163d5b3c6c94eaf406 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 21 Dec 2020 19:11:58 +0100 Subject: [PATCH 331/456] Add todo about input focus in VST3 --- src/wine-host/editor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 2fc56117..05f66f43 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -346,6 +346,10 @@ void Editor::set_input_focus(bool grab) const { // back to that window when the user moves their mouse outside of the Wine // window while the host's window is still active (that's an important // detail, since plugins may have dialogs). + // FIXME: This should not be done for VST3 plugins since keyboard handling + // is part of `IPlugView`. Or at least, that's the idea. We have to + // figure out if plugins (and especially text input in things like + // FabFilter plugins) work without this. xcb_set_input_focus(x11_connection.get(), XCB_INPUT_FOCUS_PARENT, grab ? parent_window : topmost_window, XCB_CURRENT_TIME); From 063b480fd09d3a3cdea4d72290e8873f41a28296 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 21 Dec 2020 19:10:29 +0100 Subject: [PATCH 332/456] Implement IPlugView onKey{Up,Down} --- src/common/logging/vst3.cpp | 24 ++++++++++ src/common/logging/vst3.h | 2 + src/common/serialization/vst3.h | 2 + .../serialization/vst3/plug-view/plug-view.h | 46 +++++++++++++++++++ .../bridges/vst3-impls/plug-view-proxy.cpp | 16 ++++--- src/wine-host/bridges/vst3.cpp | 12 +++++ 6 files changed, 96 insertions(+), 6 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 7eee5a95..5dcf5be9 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -273,6 +273,30 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaPlugView::OnKeyDown& request) { + return log_request_base(is_host_vst, [&](auto& message) { + // This static cast is technically not correct of course but it's + // UTF-16, so everything's allowed + message << request.owner_instance_id << ": IPlugView::onKeyDown(key = " + << static_cast(request.key) + << ", keyCode = " << request.key_code + << ", modifiers = " << request.modifiers << ")"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaPlugView::OnKeyUp& request) { + return log_request_base(is_host_vst, [&](auto& message) { + // This static cast is technically not correct of course but it's + // UTF-16, so everything's allowed + message << request.owner_instance_id << ": IPlugView::onKeyUp(key = " + << static_cast(request.key) + << ", keyCode = " << request.key_code + << ", modifiers = " << request.modifiers << ")"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPlugView::GetSize& request) { return log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 3fd03cde..bfe3879f 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -92,6 +92,8 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPlugView::Attached&); bool log_request(bool is_host_vst, const YaPlugView::Removed&); bool log_request(bool is_host_vst, const YaPlugView::OnWheel&); + bool log_request(bool is_host_vst, const YaPlugView::OnKeyDown&); + bool log_request(bool is_host_vst, const YaPlugView::OnKeyUp&); bool log_request(bool is_host_vst, const YaPlugView::GetSize&); bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index e8707f28..4ae0dbfe 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -81,6 +81,8 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(owner_instance_id); + s.value2b(key); + s.value2b(key_code); + s.value2b(modifiers); + } + }; + virtual tresult PLUGIN_API onKeyDown(char16 key, int16 keyCode, int16 modifiers) override = 0; + + /** + * Message to pass through a call to `IPlugView::onKeyUp(key, keyCode, + * modifiers)` to the Wine plugin host. + */ + struct OnKeyUp { + using Response = UniversalTResult; + + native_size_t owner_instance_id; + + char16 key; + int16 key_code; + int16 modifiers; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.value2b(key); + s.value2b(key_code); + s.value2b(modifiers); + } + }; + virtual tresult PLUGIN_API onKeyUp(char16 key, int16 keyCode, int16 modifiers) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index 130d5fad..bf6fabb0 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -71,17 +71,21 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::onWheel(float distance) { tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyDown(char16 key, int16 keyCode, int16 modifiers) { - // TODO: Implement - bridge.logger.log("TODO: IPlugView::onKeyDown()"); - return Steinberg::kNotImplemented; + return bridge.send_message( + YaPlugView::OnKeyDown{.owner_instance_id = owner_instance_id(), + .key = key, + .key_code = keyCode, + .modifiers = modifiers}); } tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyUp(char16 key, int16 keyCode, int16 modifiers) { - // TODO: Implement - bridge.logger.log("TODO: IPlugView::onKeyUp()"); - return Steinberg::kNotImplemented; + return bridge.send_message( + YaPlugView::OnKeyUp{.owner_instance_id = owner_instance_id(), + .key = key, + .key_code = keyCode, + .modifiers = modifiers}); } tresult PLUGIN_API Vst3PlugViewProxyImpl::getSize(Steinberg::ViewRect* size) { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 179c4574..63643bb3 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -375,6 +375,18 @@ void Vst3Bridge::run() { return object_instances[request.owner_instance_id] .plug_view->onWheel(request.distance); }, + [&](const YaPlugView::OnKeyDown& request) + -> YaPlugView::OnKeyDown::Response { + return object_instances[request.owner_instance_id] + .plug_view->onKeyDown(request.key, request.key_code, + request.modifiers); + }, + [&](const YaPlugView::OnKeyUp& request) + -> YaPlugView::OnKeyUp::Response { + return object_instances[request.owner_instance_id] + .plug_view->onKeyUp(request.key, request.key_code, + request.modifiers); + }, [&](YaPlugView::GetSize& request) -> YaPlugView::GetSize::Response { const tresult result = object_instances[request.owner_instance_id] From 1aa5d5d8b45a5ddebf6e938dac79f9565fb51254 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 21 Dec 2020 22:46:59 +0100 Subject: [PATCH 333/456] Add note to input focus grabbing This shouldn't be necessary for VST3, but it is. --- src/wine-host/editor.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 05f66f43..0d7dedf6 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -346,10 +346,10 @@ void Editor::set_input_focus(bool grab) const { // back to that window when the user moves their mouse outside of the Wine // window while the host's window is still active (that's an important // detail, since plugins may have dialogs). - // FIXME: This should not be done for VST3 plugins since keyboard handling - // is part of `IPlugView`. Or at least, that's the idea. We have to - // figure out if plugins (and especially text input in things like - // FabFilter plugins) work without this. + // XXX: In theory we wouldn't have to do this for VST3 because + // `IPlugView::onKey{Down,Up}` should handle all keyboard events. But + // in practice a lot of hosts don't use that, so we still need to grab + // focus ourselves. xcb_set_input_focus(x11_connection.get(), XCB_INPUT_FOCUS_PARENT, grab ? parent_window : topmost_window, XCB_CURRENT_TIME); From 463557349d78365188b520512583ae40166a4ee1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 21 Dec 2020 23:46:29 +0100 Subject: [PATCH 334/456] Implement IPlugView::onSize() --- src/common/logging/vst3.cpp | 13 +++++++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + .../serialization/vst3/plug-view/plug-view.h | 19 +++++++++++++++++++ .../bridges/vst3-impls/plug-view-proxy.cpp | 11 ++++++++--- src/wine-host/bridges/vst3.cpp | 4 ++++ 6 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 5dcf5be9..bae28b1e 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -297,6 +297,19 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaPlugView::OnSize& request) { + return log_request_base(is_host_vst, [&](auto& message) { + // This static cast is technically not correct of course but it's + // UTF-16, so everything's allowed + message << request.owner_instance_id + << ": IPlugView::onSize(newSize = )"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPlugView::GetSize& request) { return log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index bfe3879f..668f54da 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -94,6 +94,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPlugView::OnWheel&); bool log_request(bool is_host_vst, const YaPlugView::OnKeyDown&); bool log_request(bool is_host_vst, const YaPlugView::OnKeyUp&); + bool log_request(bool is_host_vst, const YaPlugView::OnSize&); bool log_request(bool is_host_vst, const YaPlugView::GetSize&); bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 4ae0dbfe..4ec3be88 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -83,6 +83,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(owner_instance_id); + s.object(new_size); + } + }; + virtual tresult PLUGIN_API onSize(Steinberg::ViewRect* newSize) override = 0; virtual tresult PLUGIN_API onFocus(TBool state) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index bf6fabb0..7e840fe0 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -105,9 +105,14 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::getSize(Steinberg::ViewRect* size) { } tresult PLUGIN_API Vst3PlugViewProxyImpl::onSize(Steinberg::ViewRect* newSize) { - // TODO: Implement - bridge.logger.log("TODO: IPlugView::onSize()"); - return Steinberg::kNotImplemented; + if (newSize) { + return bridge.send_message(YaPlugView::OnSize{ + .owner_instance_id = owner_instance_id(), .new_size = *newSize}); + } else { + bridge.logger.log( + "WARNING: Null pointer passed to 'IPlugView::onSize()'"); + return Steinberg::kInvalidArgument; + } } tresult PLUGIN_API Vst3PlugViewProxyImpl::onFocus(TBool state) { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 63643bb3..240e5cc8 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -387,6 +387,10 @@ void Vst3Bridge::run() { .plug_view->onKeyUp(request.key, request.key_code, request.modifiers); }, + [&](YaPlugView::OnSize& request) -> YaPlugView::OnKeyUp::Response { + return object_instances[request.owner_instance_id] + .plug_view->onSize(&request.new_size); + }, [&](YaPlugView::GetSize& request) -> YaPlugView::GetSize::Response { const tresult result = object_instances[request.owner_instance_id] From d4d9746f69dcd88c6f295ecfc34ae6c0e66d73a3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 21 Dec 2020 23:52:38 +0100 Subject: [PATCH 335/456] Implement IPlugView::onFocus() --- src/common/logging/vst3.cpp | 14 ++++++++++---- src/common/logging/vst3.h | 3 ++- src/common/serialization/vst3.h | 3 ++- .../serialization/vst3/plug-view/plug-view.h | 19 +++++++++++++++++++ .../bridges/vst3-impls/plug-view-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 13 +++++++++---- 6 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index bae28b1e..3fa80143 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -297,11 +297,16 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaPlugView::GetSize& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id << ": IPlugView::getSize(size*)"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPlugView::OnSize& request) { return log_request_base(is_host_vst, [&](auto& message) { - // This static cast is technically not correct of course but it's - // UTF-16, so everything's allowed message << request.owner_instance_id << ": IPlugView::onSize(newSize = + void serialize(S& s) { + s.value8b(owner_instance_id); + s.value1b(state); + } + }; + virtual tresult PLUGIN_API onFocus(TBool state) override = 0; virtual tresult PLUGIN_API setFrame(Steinberg::IPlugFrame* frame) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index 7e840fe0..f7fb1fd9 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -116,9 +116,8 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::onSize(Steinberg::ViewRect* newSize) { } tresult PLUGIN_API Vst3PlugViewProxyImpl::onFocus(TBool state) { - // TODO: Implement - bridge.logger.log("TODO: IPlugView::onFocus()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaPlugView::OnFocus{ + .owner_instance_id = owner_instance_id(), .state = state}); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 240e5cc8..7ef4440c 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -387,10 +387,6 @@ void Vst3Bridge::run() { .plug_view->onKeyUp(request.key, request.key_code, request.modifiers); }, - [&](YaPlugView::OnSize& request) -> YaPlugView::OnKeyUp::Response { - return object_instances[request.owner_instance_id] - .plug_view->onSize(&request.new_size); - }, [&](YaPlugView::GetSize& request) -> YaPlugView::GetSize::Response { const tresult result = object_instances[request.owner_instance_id] @@ -399,6 +395,15 @@ void Vst3Bridge::run() { return YaPlugView::GetSizeResponse{ .result = result, .updated_size = request.size}; }, + [&](YaPlugView::OnSize& request) -> YaPlugView::OnSize::Response { + return object_instances[request.owner_instance_id] + .plug_view->onSize(&request.new_size); + }, + [&](const YaPlugView::OnFocus& request) + -> YaPlugView::OnFocus::Response { + return object_instances[request.owner_instance_id] + .plug_view->onFocus(request.state); + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // If we got passed a host context, we'll create a proxy object From 8c2801ce06a94e25882c17c1069f61f3e009b2b0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 13:11:54 +0100 Subject: [PATCH 336/456] Mention the effEditIdle() change in the changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f1e41d4..3882eb6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ TODO: Add the relevant entries here for yabridge's VST3 support - `libyabridge.so` is now called `libyabridge-vst2.so`. If you're using yabridgectl then nothing changes here. To avoid any confusion in the future it's adviced to remove the old `libyabridge.so` file when upgrading. +- VST2 editor idle events are now handled slightly differently. This should + result in even more responsive GUIs and I have not come across any plugins + where this caused issues, but please let me know if it does break anything for + you. - Changed part of the build process considering [this Wine bug](https://bugs.winehq.org/show_bug.cgi?id=49138). Building with Wine 5.7 and 5.8 required a change, but that change now breaks builds using Wine 6.0 From 90de46428cb94e91f1775b20a876a9b0ccbbd6f9 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 13:20:49 +0100 Subject: [PATCH 337/456] Add an implementation wrapper for IPlugFrame --- meson.build | 2 + src/common/serialization/vst3/README.md | 2 + .../vst3/plug-frame/plug-frame.cpp | 26 +++++++ .../vst3/plug-frame/plug-frame.h | 72 +++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 src/common/serialization/vst3/plug-frame/plug-frame.cpp create mode 100644 src/common/serialization/vst3/plug-frame/plug-frame.h diff --git a/meson.build b/meson.build index 416e55f8..5baeaa02 100644 --- a/meson.build +++ b/meson.build @@ -78,6 +78,7 @@ vst3_plugin_sources = [ 'src/common/logging/common.cpp', 'src/common/logging/vst3.cpp', 'src/common/serialization/vst3/plug-view/plug-view.cpp', + 'src/common/serialization/vst3/plug-frame/plug-frame.cpp', 'src/common/serialization/vst3/plugin/audio-processor.cpp', 'src/common/serialization/vst3/plugin/component.cpp', 'src/common/serialization/vst3/plugin/connection-point.cpp', @@ -129,6 +130,7 @@ if with_vst3 host_sources += [ 'src/common/logging/vst3.cpp', 'src/common/serialization/vst3/plug-view/plug-view.cpp', + 'src/common/serialization/vst3/plug-frame/plug-frame.cpp', 'src/common/serialization/vst3/plugin/audio-processor.cpp', 'src/common/serialization/vst3/plugin/component.cpp', 'src/common/serialization/vst3/plugin/connection-point.cpp', diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index efe13210..bc6837d8 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -28,6 +28,8 @@ VST3 host interfaces are implemented as follows: | `YaHostApplication` | `Vst3HostContextProxy` | `IHostApplication` | | `Vst3ComponentHandlerProxy` | | All of the below: | | `YaComponentHandler` | `Vst3ComponentHandlerProxy` | `IComponentHandler` | +| `Vst3PlugFrameProxy` | | All of the below: | +| `YaPlugFrame` | `Vst3PlugFrameProxy` | `IPlugFrame` | The following host interfaces are passed as function arguments and are thus also implemented for serialization purposes: diff --git a/src/common/serialization/vst3/plug-frame/plug-frame.cpp b/src/common/serialization/vst3/plug-frame/plug-frame.cpp new file mode 100644 index 00000000..a1cc9d46 --- /dev/null +++ b/src/common/serialization/vst3/plug-frame/plug-frame.cpp @@ -0,0 +1,26 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "plug-frame.h" + +YaPlugFrame::ConstructArgs::ConstructArgs() {} + +YaPlugFrame::ConstructArgs::ConstructArgs( + Steinberg::IPtr object) + : supported(Steinberg::FUnknownPtr(object)) {} + +YaPlugFrame::YaPlugFrame(const ConstructArgs&& args) + : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plug-frame/plug-frame.h b/src/common/serialization/vst3/plug-frame/plug-frame.h new file mode 100644 index 00000000..a0ad700e --- /dev/null +++ b/src/common/serialization/vst3/plug-frame/plug-frame.h @@ -0,0 +1,72 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "../../common.h" +#include "../base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IPlugFrame` for serialization purposes. This is instantiated as + * part of `Vst3PlugFrameProxy`. + */ +class YaPlugFrame : public Steinberg::IPlugFrame { + public: + /** + * These are the arguments for creating a `YaPlugFrame`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Check whether an existing implementation implements `IPlugFrame` and + * read arguments from it. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + template + void serialize(S& s) { + s.value1b(supported); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaPlugFrame(const ConstructArgs&& args); + + inline bool supported() const { return arguments.supported; } + + virtual tresult PLUGIN_API + resizeView(Steinberg::IPlugView* view, + Steinberg::ViewRect* newSize) override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop From f5c75da4513928f66da1e4d3e385381dca70ac75 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 13:26:54 +0100 Subject: [PATCH 338/456] Add Vst3PlugFrameProxy For proxying the `IPlugFrame*` passed to `IPlugView::setFrame()`. --- meson.build | 2 + .../vst3/component-handler-proxy.h | 8 +- .../serialization/vst3/plug-frame-proxy.cpp | 50 ++++++++++ .../serialization/vst3/plug-frame-proxy.h | 97 +++++++++++++++++++ 4 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 src/common/serialization/vst3/plug-frame-proxy.cpp create mode 100644 src/common/serialization/vst3/plug-frame-proxy.h diff --git a/meson.build b/meson.build index 5baeaa02..680cc297 100644 --- a/meson.build +++ b/meson.build @@ -93,6 +93,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/host-context-proxy.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', + 'src/common/serialization/vst3/plug-frame-proxy.cpp', 'src/common/serialization/vst3/plug-view-proxy.cpp', 'src/common/serialization/vst3/plugin-proxy.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', @@ -145,6 +146,7 @@ if with_vst3 'src/common/serialization/vst3/host-context-proxy.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', + 'src/common/serialization/vst3/plug-frame-proxy.cpp', 'src/common/serialization/vst3/plug-view-proxy.cpp', 'src/common/serialization/vst3/plugin-proxy.cpp', 'src/common/serialization/vst3/plugin-factory.cpp', diff --git a/src/common/serialization/vst3/component-handler-proxy.h b/src/common/serialization/vst3/component-handler-proxy.h index aa0a944d..eced256b 100644 --- a/src/common/serialization/vst3/component-handler-proxy.h +++ b/src/common/serialization/vst3/component-handler-proxy.h @@ -25,10 +25,10 @@ /** * An abstract class that implements `IComponentHandler`, and optionally also * all other VST3 interfaces an object passed to - * `IEditController::setComponentHandler()`. This works exactly the same as - * `Vst3PluginProxy`, but instead of proxying for an object provided by the - * plugin we are proxying for the `IComponentHandler*` argument passed to plugin - * by the host. + * `IEditController::setComponentHandler()` might implement. This works exactly + * the same as `Vst3PluginProxy`, but instead of proxying for an object provided + * by the plugin we are proxying for the `IComponentHandler*` argument passed to + * plugin by the host. */ class Vst3ComponentHandlerProxy : public YaComponentHandler { public: diff --git a/src/common/serialization/vst3/plug-frame-proxy.cpp b/src/common/serialization/vst3/plug-frame-proxy.cpp new file mode 100644 index 00000000..abf391c7 --- /dev/null +++ b/src/common/serialization/vst3/plug-frame-proxy.cpp @@ -0,0 +1,50 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "plug-frame-proxy.h" + +Vst3PlugFrameProxy::ConstructArgs::ConstructArgs() {} + +Vst3PlugFrameProxy::ConstructArgs::ConstructArgs( + Steinberg::IPtr object, + size_t owner_instance_id) + : owner_instance_id(owner_instance_id), plug_frame_args(object) {} + +Vst3PlugFrameProxy::Vst3PlugFrameProxy(const ConstructArgs&& args) + : YaPlugFrame(std::move(args.plug_frame_args)), + arguments(std::move(args)){FUNKNOWN_CTOR} + + Vst3PlugFrameProxy::~Vst3PlugFrameProxy() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(Vst3PlugFrameProxy) +#pragma GCC diagnostic pop + +tresult PLUGIN_API Vst3PlugFrameProxy::queryInterface(Steinberg::FIDString _iid, + void** obj) { + if (YaPlugFrame::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, + Steinberg::IPlugFrame) + QUERY_INTERFACE(_iid, obj, Steinberg::IPlugFrame::iid, + Steinberg::IPlugFrame) + } + + *obj = nullptr; + return Steinberg::kNoInterface; +} diff --git a/src/common/serialization/vst3/plug-frame-proxy.h b/src/common/serialization/vst3/plug-frame-proxy.h new file mode 100644 index 00000000..0a45bcb3 --- /dev/null +++ b/src/common/serialization/vst3/plug-frame-proxy.h @@ -0,0 +1,97 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "../common.h" +#include "plug-frame/plug-frame.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * An abstract class that implements `IPlugFrame`, and optionally also all other + * VST3 interfaces an object passed to `IPlugView::setFrame()` might implement. + * This works exactly the same as `Vst3PluginProxy`, but instead of proxying for + * an object provided by the plugin we are proxying for the `IPlugFrame*` + * argument passed to plugin by the host. + */ +class Vst3PlugFrameProxy : public YaPlugFrame { + public: + /** + * These are the arguments for constructing a + * `Vst3PlugFrameProxyImpl`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Read from an existing object. We will try to mimic this object, so + * we'll support any interfaces this object also supports. + */ + ConstructArgs(Steinberg::IPtr object, + size_t owner_instance_id); + + /** + * The unique instance identifier of the proxy object instance this + * component handler has been passed to and thus belongs to. This way we + * can refer to the correct 'actual' `IPlugFrame` instance when the + * plugin does a callback. + */ + native_size_t owner_instance_id; + + YaPlugFrame::ConstructArgs plug_frame_args; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.object(plug_frame_args); + } + }; + + /** + * Instantiate this instance with arguments read from an actual component + * handler. + * + * @note Since this is passed as part of `IEditController::setPlugFrame()`, + * there are no direct `Construct` or `Destruct` messages. This object's + * lifetime is bound to that of the objects they are passed to. If the + * plug view instance gets dropped, this proxy should also be dropped. + */ + Vst3PlugFrameProxy(const ConstructArgs&& args); + + /** + * The lifetime of this object should be bound to the object we created it + * for. When the `Vst3PlugViewProxy` for the object with instance with id + * `n` gets dropped, the corresponding `Vst3PlugFrameProxy` should also be + * dropped. + */ + virtual ~Vst3PlugFrameProxy(); + + DECLARE_FUNKNOWN_METHODS + + /** + * Get the instance ID of the owner of this object. + */ + inline size_t owner_instance_id() const { + return arguments.owner_instance_id; + } + + private: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop From 91c4b414b0663fa50c9bbaa0d47a9a2c113be075 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 13:37:58 +0100 Subject: [PATCH 339/456] Add a Vst3PlugFrameProxy implementation with stubs --- meson.build | 1 + src/common/serialization/vst3.h | 2 + .../bridges/vst3-impls/plug-frame-proxy.cpp | 48 +++++++++++++++++++ .../bridges/vst3-impls/plug-frame-proxy.h | 39 +++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp create mode 100644 src/wine-host/bridges/vst3-impls/plug-frame-proxy.h diff --git a/meson.build b/meson.build index 680cc297..999932bb 100644 --- a/meson.build +++ b/meson.build @@ -153,6 +153,7 @@ if with_vst3 'src/common/serialization/vst3/process-data.cpp', 'src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp', 'src/wine-host/bridges/vst3-impls/host-context-proxy.cpp', + 'src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp', 'src/wine-host/bridges/vst3.cpp', ] endif diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 79e54485..8dd51ffc 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -25,6 +25,8 @@ #include "common.h" #include "vst3/component-handler-proxy.h" #include "vst3/host-context-proxy.h" +#include "vst3/plug-frame-proxy.h" +#include "vst3/plug-view-proxy.h" #include "vst3/plugin-factory.h" #include "vst3/plugin-proxy.h" diff --git a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp new file mode 100644 index 00000000..2d005ffa --- /dev/null +++ b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp @@ -0,0 +1,48 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "plug-frame-proxy.h" + +#include + +Vst3PlugFrameProxyImpl::Vst3PlugFrameProxyImpl( + Vst3Bridge& bridge, + Vst3PlugFrameProxy::ConstructArgs&& args) + : Vst3PlugFrameProxy(std::move(args)), bridge(bridge) { + // The lifecycle is thos object is managed together with that of the plugin + // object instance instance this belongs to +} + +tresult PLUGIN_API +Vst3PlugFrameProxyImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { + // TODO: Successful queries should also be logged + const tresult result = Vst3PlugFrameProxy::queryInterface(_iid, obj); + if (result != Steinberg::kResultOk) { + std::cerr << "TODO: Implement unknown interface logging on Wine side " + "for Vst3PlugFrameProxyImpl::queryInterface" + << std::endl; + } + + return result; +} + +tresult PLUGIN_API +Vst3PlugFrameProxyImpl::resizeView(Steinberg::IPlugView* view, + Steinberg::ViewRect* newSize) { + // TODO: Implement + std::cerr << "TODO: IPlugFrame::resizeView()" << std::endl; + return Steinberg::kNotImplemented; +} diff --git a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.h b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.h new file mode 100644 index 00000000..e15066a6 --- /dev/null +++ b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.h @@ -0,0 +1,39 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "../vst3.h" + +class Vst3PlugFrameProxyImpl : public Vst3PlugFrameProxy { + public: + Vst3PlugFrameProxyImpl(Vst3Bridge& bridge, + Vst3PlugFrameProxy::ConstructArgs&& args); + + /** + * We'll override the query interface to log queries for interfaces we do + * not (yet) support. + */ + tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, + void** obj) override; + + // From `IPlugFrame` + tresult PLUGIN_API resizeView(Steinberg::IPlugView* view, + Steinberg::ViewRect* newSize) override; + + private: + Vst3Bridge& bridge; +}; From 37897da2b76663eddc6c7d275dcb847f539360a5 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 13:43:08 +0100 Subject: [PATCH 340/456] Destroy IPlugFrame proxy together with IPlugView Not that we have implemented `IPlugView::setFrame()` yet, but that should be trivial at this point. --- src/wine-host/bridges/vst3.cpp | 7 ++++++- src/wine-host/bridges/vst3.h | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 7ef4440c..50d4c861 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -22,6 +22,7 @@ #include "vst3-impls/component-handler-proxy.h" #include "vst3-impls/host-context-proxy.h" +#include "vst3-impls/plug-frame-proxy.h" InstanceInterfaces::InstanceInterfaces() {} @@ -70,9 +71,13 @@ void Vst3Bridge::run() { main_context .run_in_context([&]() { // When the pointer gets dropped by the host, we want to - // drop it here as well + // drop it here as well, along with the `IPlugFrame` + // proxy object it may have received in + // `IPlugView::setFrame()`. object_instances[request.owner_instance_id] .plug_view.reset(); + object_instances[request.owner_instance_id] + .plug_frame_proxy.reset(); }) .wait(); diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index f90e45b5..620eb4d2 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -53,6 +53,16 @@ struct InstanceInterfaces { */ Steinberg::IPtr host_context_proxy; + /** + * If the host passes an `IPlugFrame` object during `IPlugView::setFrame()`, + * then we'll store a proxy object here and then pass it to + * `plug_view->setFrame()`. Will be initialized with a null pointer until + * used. When we destroy `plug_view` while handling + * `Vst3PlugViewProxy::Destruct`, we'll also destroy (our pointer of) this + * proxy object. + */ + Steinberg::IPtr plug_frame_proxy; + /** * After a call to `IEditController::setComponentHandler()`, we'll create a * proxy of that component handler just like we did for the plugin object. From 51876a024c04f5318bf294ef7adf1cdde72b50de Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 13:54:55 +0100 Subject: [PATCH 341/456] Remove null pointer support in setComponentHandler This should be an implementation fault. --- src/common/logging/vst3.cpp | 9 ++--- .../vst3/plugin/edit-controller.h | 10 ++---- .../bridges/vst3-impls/plugin-proxy.cpp | 34 +++++++++---------- src/wine-host/bridges/vst3.cpp | 22 +++++------- 4 files changed, 28 insertions(+), 47 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 3fa80143..b259da24 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -209,13 +209,8 @@ bool Vst3Logger::log_request( const YaEditController::SetComponentHandler& request) { return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id - << ": IEditController::setComponentHandler(handler = "; - if (request.component_handler_proxy_args) { - message << ""; - } else { - message << ""; - } - message << ")"; + << ": IEditController::setComponentHandler(handler = " + ")"; }); } diff --git a/src/common/serialization/vst3/plugin/edit-controller.h b/src/common/serialization/vst3/plugin/edit-controller.h index 24853ac4..799573bd 100644 --- a/src/common/serialization/vst3/plugin/edit-controller.h +++ b/src/common/serialization/vst3/plugin/edit-controller.h @@ -345,18 +345,12 @@ class YaEditController : public Steinberg::Vst::IEditController { native_size_t instance_id; - /** - * Arguments for instantiating the proxy object. Even though it should - * never happen, if the host passed a null pointer to this function - * we'll mimic that as well. - */ - std::optional - component_handler_proxy_args; + Vst3ComponentHandlerProxy::ConstructArgs component_handler_proxy_args; template void serialize(S& s) { s.value8b(instance_id); - s.ext(component_handler_proxy_args, bitsery::ext::StdOptional{}); + s.object(component_handler_proxy_args); } }; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 91056187..6e6861b3 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -337,28 +337,26 @@ Vst3PluginProxyImpl::setParamNormalized(Steinberg::Vst::ParamID id, tresult PLUGIN_API Vst3PluginProxyImpl::setComponentHandler( Steinberg::Vst::IComponentHandler* handler) { - // We'll store the pointer for when the plugin later makes a callback to - // this component handler - component_handler = handler; - - // Automatically converted smart pointers for when the plugin performs a - // callback later - host_application = host_context; - - std::optional - component_handler_proxy_args = std::nullopt; if (handler) { - component_handler_proxy_args = Vst3ComponentHandlerProxy::ConstructArgs( - component_handler, instance_id()); + // We'll store the pointer for when the plugin later makes a callback to + // this component handler + component_handler = handler; + + // Automatically converted smart pointers for when the plugin performs a + // callback later + host_application = host_context; + + return bridge.send_message(YaEditController::SetComponentHandler{ + .instance_id = instance_id(), + .component_handler_proxy_args = + Vst3ComponentHandlerProxy::ConstructArgs(component_handler, + instance_id())}); } else { bridge.logger.log( - "Null pointer passed to 'IEditController::setComponentHandler()'"); + "WARNING: Null pointer passed to " + "'IEditController::setComponentHandler()'"); + return Steinberg::kInvalidArgument; } - - return bridge.send_message(YaEditController::SetComponentHandler{ - .instance_id = instance_id(), - .component_handler_proxy_args = - std::move(component_handler_proxy_args)}); } Steinberg::IPlugView* PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 50d4c861..0b0e173a 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -255,22 +255,16 @@ void Vst3Bridge::run() { }, [&](YaEditController::SetComponentHandler& request) -> YaEditController::SetComponentHandler::Response { - // If we got passed a component handler, we'll create a proxy - // object and pass that to the initialize function. The lifetime - // of this object is tied to that of the actual plugin object - // we're proxying for. + // We'll create a proxy object for the component handler and + // pass that to the initialize function. The lifetime of this + // object is tied to that of the actual plugin object we're + // proxying for. // TODO: Does this have to be run from the UI thread? Figure out // if it does - if (request.component_handler_proxy_args) { - object_instances[request.instance_id] - .component_handler_proxy = - Steinberg::owned(new Vst3ComponentHandlerProxyImpl( - *this, - std::move(*request.component_handler_proxy_args))); - } else { - object_instances[request.instance_id] - .component_handler_proxy = nullptr; - } + object_instances[request.instance_id].component_handler_proxy = + Steinberg::owned(new Vst3ComponentHandlerProxyImpl( + *this, + std::move(request.component_handler_proxy_args))); return object_instances[request.instance_id] .edit_controller->setComponentHandler( From 3b96ffa349110936eecd1e91914f41047389b74f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 13:59:47 +0100 Subject: [PATCH 342/456] Remove null pointer supported in initialize() --- src/common/logging/vst3.cpp | 8 +------ .../serialization/vst3/plugin/plugin-base.h | 5 ++-- .../bridges/vst3-impls/plugin-proxy.cpp | 24 +++++++++---------- src/wine-host/bridges/vst3.cpp | 21 ++++++---------- 4 files changed, 21 insertions(+), 37 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index b259da24..d75c4e8a 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -322,13 +322,7 @@ bool Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id - << ": IPluginBase::initialize(context = "; - if (request.host_context_args) { - message << ""; - } else { - message << ""; - } - message << ")"; + << ": IPluginBase::initialize(context = )"; }); } diff --git a/src/common/serialization/vst3/plugin/plugin-base.h b/src/common/serialization/vst3/plugin/plugin-base.h index ad89d37f..5958ac9b 100644 --- a/src/common/serialization/vst3/plugin/plugin-base.h +++ b/src/common/serialization/vst3/plugin/plugin-base.h @@ -16,7 +16,6 @@ #pragma once -#include #include #include "../../common.h" @@ -77,12 +76,12 @@ class YaPluginBase : public Steinberg::IPluginBase { native_size_t instance_id; - std::optional host_context_args; + Vst3HostContextProxy::ConstructArgs host_context_args; template void serialize(S& s) { s.value8b(instance_id); - s.ext(host_context_args, bitsery::ext::StdOptional{}); + s.object(host_context_args); } }; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 6e6861b3..6f2c2ed6 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -394,23 +394,21 @@ tresult PLUGIN_API Vst3PluginProxyImpl::openAboutBox(TBool onlyCheck) { } tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* context) { - // We will create a proxy object that that supports all the same interfaces - // as `context`, and then we'll store `context` in this object. We can then - // use it to handle callbacks made by the Windows VST3 plugin to this - // context. - host_context = context; + if (context) { + // We will create a proxy object that that supports all the same + // interfaces as `context`, and then we'll store `context` in this + // object. We can then use it to handle callbacks made by the Windows + // VST3 plugin to this context. + host_context = context; - std::optional host_context_args{}; - if (host_context) { - host_context_args = - Vst3HostContextProxy::ConstructArgs(host_context, instance_id()); + return bridge.send_message(YaPluginBase::Initialize{ + .instance_id = instance_id(), + .host_context_args = Vst3HostContextProxy::ConstructArgs( + host_context, instance_id())}); } else { bridge.logger.log("Null pointer passed to 'IPluginBase::initialize()'"); + return Steinberg::kInvalidArgument; } - - return bridge.send_message(YaPluginBase::Initialize{ - .instance_id = instance_id(), - .host_context_args = std::move(host_context_args)}); } tresult PLUGIN_API Vst3PluginProxyImpl::terminate() { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 0b0e173a..8a289086 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -405,22 +405,15 @@ void Vst3Bridge::run() { }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { - // If we got passed a host context, we'll create a proxy object - // and pass that to the initialize function. The lifetime of - // this object is tied to that of the actual plugin object we're - // proxying for. + // We'll create a proxy object for the host context passed by + // the host and pass that to the initialize function. The + // lifetime of this object is tied to that of the actual plugin + // object we're proxying for. // TODO: This needs changing if it turns out we need a // `Vst3HostProxy` - // TODO: Does this have to be run from the UI thread? Figure out - // if it does - if (request.host_context_args) { - object_instances[request.instance_id].host_context_proxy = - Steinberg::owned(new Vst3HostContextProxyImpl( - *this, std::move(*request.host_context_args))); - } else { - object_instances[request.instance_id].host_context_proxy = - nullptr; - } + object_instances[request.instance_id].host_context_proxy = + Steinberg::owned(new Vst3HostContextProxyImpl( + *this, std::move(request.host_context_args))); // XXX: Should `IPlugView::{initialize,terminate}` be run from // the main UI thread? I can see how plugins would want to From 9288cdcb5975eddc65243f4a3c6b0f601c0d7fdf Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 14:03:56 +0100 Subject: [PATCH 343/456] Remove support for null pointers in setHostContext Like the other functions, null pointers are never valid here so we shouldn't bother passing them as it only increases complexity. --- src/common/logging/vst3.cpp | 8 +---- .../serialization/vst3/plugin-factory.h | 8 ++--- .../bridges/vst3-impls/plugin-factory.cpp | 31 +++++++++---------- .../bridges/vst3-impls/plugin-proxy.cpp | 3 +- src/wine-host/bridges/vst3.cpp | 12 +++---- 5 files changed, 24 insertions(+), 38 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index d75c4e8a..9a73988a 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -342,13 +342,7 @@ bool Vst3Logger::log_request(bool is_host_vst, bool Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::SetHostContext& request) { return log_request_base(is_host_vst, [&](auto& message) { - message << "IPluginFactory3::setHostContext("; - if (request.host_context_args) { - message << ""; - } else { - message << ""; - } - message << ")"; + message << "IPluginFactory3::setHostContext()"; }); } diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index ac78342e..fdc572c7 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -162,15 +162,11 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { struct SetHostContext { using Response = UniversalTResult; - /** - * Arguments for creating a proxy host context object. If we got passed - * an null pointer we'll reflect that. - */ - std::optional host_context_args; + Vst3HostContextProxy::ConstructArgs host_context_args; template void serialize(S& s) { - s.ext(host_context_args, bitsery::ext::StdOptional{}); + s.object(host_context_args); } }; diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index 988bf06a..a795a5fe 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -100,25 +100,24 @@ YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, tresult PLUGIN_API YaPluginFactoryImpl::setHostContext(Steinberg::FUnknown* context) { - // We will create a proxy object that that supports all the same interfaces - // as `context`, and then we'll store `context` in this object. We can then - // use it to handle callbacks made by the Windows VST3 plugin to this - // context. - host_context = context; + if (context) { + // We will create a proxy object that that supports all the same + // interfaces as `context`, and then we'll store `context` in this + // object. We can then use it to handle callbacks made by the Windows + // VST3 plugin to this context. + host_context = context; - // Automatically converted smart pointers for when the plugin performs a - // callback later - host_application = host_context; + // Automatically converted smart pointers for when the plugin performs a + // callback later + host_application = host_context; - std::optional host_context_args{}; - if (host_context) { - host_context_args = - Vst3HostContextProxy::ConstructArgs(host_context, std::nullopt); + return bridge.send_message(YaPluginFactory::SetHostContext{ + .host_context_args = Vst3HostContextProxy::ConstructArgs( + host_context, std::nullopt)}); } else { bridge.logger.log( - "Null pointer passed to 'IPluginFactory3::setHostContext()'"); + "WARNING: Null pointer passed to " + "'IPluginFactory3::setHostContext()'"); + return Steinberg::kInvalidArgument; } - - return bridge.send_message(YaPluginFactory::SetHostContext{ - .host_context_args = std::move(host_context_args)}); } diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 6f2c2ed6..2b196590 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -406,7 +406,8 @@ tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* context) { .host_context_args = Vst3HostContextProxy::ConstructArgs( host_context, instance_id())}); } else { - bridge.logger.log("Null pointer passed to 'IPluginBase::initialize()'"); + bridge.logger.log( + "WARNING: Null pointer passed to 'IPluginBase::initialize()'"); return Steinberg::kInvalidArgument; } } diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 8a289086..261e274e 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -443,18 +443,14 @@ void Vst3Bridge::run() { }, [&](YaPluginFactory::SetHostContext& request) -> YaPluginFactory::SetHostContext::Response { - if (request.host_context_args) { - plugin_factory_host_context = - Steinberg::owned(new Vst3HostContextProxyImpl( - *this, std::move(*request.host_context_args))); - } else { - plugin_factory_host_context = nullptr; - } + plugin_factory_host_context = + Steinberg::owned(new Vst3HostContextProxyImpl( + *this, std::move(request.host_context_args))); Steinberg::FUnknownPtr factory_3( module->getFactory().get()); - assert(factory_3); + return factory_3->setHostContext(plugin_factory_host_context); }}); } From da6ddccf077fd5a3a0716956eddc4584840d3673 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 14:16:00 +0100 Subject: [PATCH 344/456] Implement IPlugView::setFrame() --- README.md | 3 ++- src/common/logging/vst3.cpp | 8 +++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + .../serialization/vst3/plug-view/plug-view.h | 24 +++++++++++++++++++ .../bridges/vst3-impls/plug-view-proxy.cpp | 17 ++++++++++--- .../bridges/vst3-impls/plug-view-proxy.h | 7 ++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 4 ---- src/wine-host/bridges/vst3.cpp | 17 +++++++++++++ src/wine-host/bridges/vst3.h | 18 +++++++------- 10 files changed, 83 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 06d7930e..50902361 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ incomplete list of things that still have to be done before this can be used: - `IHostApplication::createComponent()` - `IConnectionPoint::notify()`, and support for indirectly connecting components through message passing proxies - - `IPlugView` + - The rest of `IPlugView` - `IEditController2` + - `IPlugFrame` - All other mandatory interfaces - All other optional interfaces - Fully implemented: see [this diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 9a73988a..dcea2046 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -318,6 +318,14 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaPlugView::SetFrame& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id + << ": IPlugView::setFrame(frame = )"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { return log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index a14fd01e..9eacc48c 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -97,6 +97,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPlugView::GetSize&); bool log_request(bool is_host_vst, const YaPlugView::OnSize&); bool log_request(bool is_host_vst, const YaPlugView::OnFocus&); + bool log_request(bool is_host_vst, const YaPlugView::SetFrame&); bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 8dd51ffc..1e399523 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -88,6 +88,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(owner_instance_id); + s.object(plug_frame_args); + } + }; + virtual tresult PLUGIN_API setFrame(Steinberg::IPlugFrame* frame) override = 0; virtual tresult PLUGIN_API canResize() override = 0; diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index f7fb1fd9..eaa09a64 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -122,9 +122,20 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::onFocus(TBool state) { tresult PLUGIN_API Vst3PlugViewProxyImpl::setFrame(Steinberg::IPlugFrame* frame) { - // TODO: Implement - bridge.logger.log("TODO: IPlugView::setFrame()"); - return Steinberg::kNotImplemented; + if (frame) { + // We'll store the pointer for when the plugin later makes a callback to + // this component handler + plug_frame = frame; + + return bridge.send_message(YaPlugView::SetFrame{ + .owner_instance_id = owner_instance_id(), + .plug_frame_args = Vst3PlugFrameProxy::ConstructArgs( + plug_frame, owner_instance_id())}); + } else { + bridge.logger.log( + "WARNING: Null pointer passed to 'IPlugView::setFrame()'"); + return Steinberg::kInvalidArgument; + } } tresult PLUGIN_API Vst3PlugViewProxyImpl::canResize() { diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.h b/src/plugin/bridges/vst3-impls/plug-view-proxy.h index 4e497621..0a9711f0 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.h +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.h @@ -57,6 +57,13 @@ class Vst3PlugViewProxyImpl : public Vst3PlugViewProxy { tresult PLUGIN_API canResize() override; tresult PLUGIN_API checkSizeConstraint(Steinberg::ViewRect* rect) override; + /** + * The `IPlugFrame` object passed by the host passed to us in + * `IPlugView::setFrame()`. When the plugin makes a callback on the + * `IPlugFrame` proxy object, we'll pass the call through to this object. + */ + Steinberg::IPtr plug_frame; + private: Vst3PluginBridge& bridge; }; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 2b196590..58ded7a1 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -342,10 +342,6 @@ tresult PLUGIN_API Vst3PluginProxyImpl::setComponentHandler( // this component handler component_handler = handler; - // Automatically converted smart pointers for when the plugin performs a - // callback later - host_application = host_context; - return bridge.send_message(YaEditController::SetComponentHandler{ .instance_id = instance_id(), .component_handler_proxy_args = diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 261e274e..7df6f3ed 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -403,6 +403,23 @@ void Vst3Bridge::run() { return object_instances[request.owner_instance_id] .plug_view->onFocus(request.state); }, + [&](YaPlugView::SetFrame& request) + -> YaPlugView::SetFrame::Response { + // We'll create a proxy object for the `IPlugFrame` object and + // pass that to the `setFrame()` function. The lifetime of this + // object is tied to that of the actual `IPlugFrame` object + // we're passing this proxy to. + // TODO: Does this have to be run from the UI thread? Figure out + // if it does + object_instances[request.owner_instance_id].plug_frame_proxy = + Steinberg::owned(new Vst3PlugFrameProxyImpl( + *this, std::move(request.plug_frame_args))); + + return object_instances[request.owner_instance_id] + .plug_view->setFrame( + object_instances[request.owner_instance_id] + .plug_frame_proxy); + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // We'll create a proxy object for the host context passed by diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 620eb4d2..6f47761a 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -53,6 +53,15 @@ struct InstanceInterfaces { */ Steinberg::IPtr host_context_proxy; + /** + * After a call to `IEditController::setComponentHandler()`, we'll create a + * proxy of that component handler just like we did for the plugin object. + * When the plugin calls a function on this object, we make a callback to + * the original object provided by the host. Will be initialized with a null + * pointer until used. + */ + Steinberg::IPtr component_handler_proxy; + /** * If the host passes an `IPlugFrame` object during `IPlugView::setFrame()`, * then we'll store a proxy object here and then pass it to @@ -63,15 +72,6 @@ struct InstanceInterfaces { */ Steinberg::IPtr plug_frame_proxy; - /** - * After a call to `IEditController::setComponentHandler()`, we'll create a - * proxy of that component handler just like we did for the plugin object. - * When the plugin calls a function on this object, we make a callback to - * the original object provided by the host. Will be initialized with a null - * pointer until used. - */ - Steinberg::IPtr component_handler_proxy; - /** * The base object we cast from. */ From 3bc340992980cd3168be9069d70cf8792de9c19d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 14:27:56 +0100 Subject: [PATCH 345/456] Keep track of the last created plugin view For implementing `IPlugView::resizeView()`. This approach is safe because there's only a single defined view type. --- src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 11 +++++++++-- src/plugin/bridges/vst3-impls/plugin-proxy.h | 12 ++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 58ded7a1..49593d06 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -363,8 +363,15 @@ Vst3PluginProxyImpl::createView(Steinberg::FIDString name) { if (response.plug_view_args) { // The host should manage this. Returning raw pointers feels scary. - return new Vst3PlugViewProxyImpl(bridge, - std::move(*response.plug_view_args)); + auto plug_view_proxy = new Vst3PlugViewProxyImpl( + bridge, std::move(*response.plug_view_args)); + + // We also need to store an (unmanaged, since we don't want to affect + // the reference counting) pointer to this to be able to handle calls to + // `IPlugFrame::resizeView()` in the future + last_created_plug_view = plug_view_proxy; + + return plug_view_proxy; } else { return nullptr; } diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index 0d6e856f..6d404975 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -123,6 +123,18 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { tresult PLUGIN_API initialize(FUnknown* context) override; tresult PLUGIN_API terminate() override; + /** + * An unmanaged, raw pointer to the `IPlugView` instance returned in our + * implementation of `IEditController::createView()`. We need this to handle + * `IPlugFrame::resizeView()`, since that expects a pointer to the view that + * gets resized. + * + * XXX: This approach of course won't work with multiple views, but the SDK + * currently only defines a single type of view so that shouldn't be an + * issue + */ + Steinberg::IPlugView* last_created_plug_view = nullptr; + /** * The component handler the host passed to us during * `IEditController::setComponentHandler()`. When the plugin makes a From 656f6d3f6c9f180b01030a93f3f0b6e0862abac3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 15:09:33 +0100 Subject: [PATCH 346/456] Implement IPlugFrame::resizeView() The base IPlugFrame only contains this single function. --- README.md | 1 - src/common/logging/vst3.cpp | 14 ++++- src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 3 +- .../vst3/plug-frame/plug-frame.h | 22 ++++++++ src/plugin/bridges/vst3-impls/plugin-proxy.h | 3 +- src/plugin/bridges/vst3.cpp | 51 ++++++++++++------- .../bridges/vst3-impls/plug-frame-proxy.cpp | 17 +++++-- 8 files changed, 86 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 50902361..7b2338ae 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ incomplete list of things that still have to be done before this can be used: components through message passing proxies - The rest of `IPlugView` - `IEditController2` - - `IPlugFrame` - All other mandatory interfaces - All other optional interfaces - Fully implemented: see [this diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index dcea2046..85839b02 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -348,7 +348,7 @@ bool Vst3Logger::log_request(bool is_host_vst, } bool Vst3Logger::log_request(bool is_host_vst, - const YaPluginFactory::SetHostContext& request) { + const YaPluginFactory::SetHostContext&) { return log_request_base(is_host_vst, [&](auto& message) { message << "IPluginFactory3::setHostContext()"; }); @@ -612,6 +612,18 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaPlugFrame::ResizeView& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id + << ": IPlugFrame::resizeView(view = , newSize = " + ")"; + }); +} + void Vst3Logger::log_response(bool is_host_vst, const Ack&) { log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 9eacc48c..e9f3dc03 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -132,6 +132,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaComponentHandler::RestartComponent&); bool log_request(bool is_host_vst, const YaHostApplication::GetName&); + bool log_request(bool is_host_vst, const YaPlugFrame::ResizeView&); void log_response(bool is_host_vst, const Ack&); void log_response( diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 1e399523..65222858 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -140,7 +140,8 @@ using CallbackRequest = std::variant; + YaHostApplication::GetName, + YaPlugFrame::ResizeView>; template void serialize(S& s, CallbackRequest& payload) { diff --git a/src/common/serialization/vst3/plug-frame/plug-frame.h b/src/common/serialization/vst3/plug-frame/plug-frame.h index a0ad700e..89504638 100644 --- a/src/common/serialization/vst3/plug-frame/plug-frame.h +++ b/src/common/serialization/vst3/plug-frame/plug-frame.h @@ -61,6 +61,28 @@ class YaPlugFrame : public Steinberg::IPlugFrame { inline bool supported() const { return arguments.supported; } + /** + * Message to pass through a call to `IPlugFrame::resizeView` to the + * `IPlugView` object provided by the host. + * + * XXX: Since we don't support multiple `IPlugView`s right now (as it's not + * used the SDK's current version), we'll just assume that `view` is + * the view stored in `Vst3PluginProxyImpl::plug_view` + */ + struct ResizeView { + using Response = UniversalTResult; + + native_size_t owner_instance_id; + + Steinberg::ViewRect new_size; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.object(new_size); + } + }; + virtual tresult PLUGIN_API resizeView(Steinberg::IPlugView* view, Steinberg::ViewRect* newSize) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index 6d404975..621c0d70 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -17,6 +17,7 @@ #pragma once #include "../vst3.h" +#include "plug-view-proxy.h" class Vst3PluginProxyImpl : public Vst3PluginProxy { public: @@ -133,7 +134,7 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { * currently only defines a single type of view so that shouldn't be an * issue */ - Steinberg::IPlugView* last_created_plug_view = nullptr; + Vst3PlugViewProxyImpl* last_created_plug_view = nullptr; /** * The component handler the host passed to us during diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 9528fe06..6712019f 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -81,24 +81,6 @@ Vst3PluginBridge::Vst3PluginBridge() [&](const WantsConfiguration&) -> WantsConfiguration::Response { return config; }, - [&](const YaHostApplication::GetName& request) - -> YaHostApplication::GetName::Response { - tresult result; - Steinberg::Vst::String128 name{0}; - if (request.owner_instance_id) { - result = plugin_proxies.at(*request.owner_instance_id) - .get() - .host_application->getName(name); - } else { - result = - plugin_factory->host_application->getName(name); - } - - return YaHostApplication::GetNameResponse{ - .result = result, - .name = tchar_pointer_to_u16string(name), - }; - }, [&](const YaComponentHandler::BeginEdit& request) -> YaComponentHandler::BeginEdit::Response { return plugin_proxies.at(request.owner_instance_id) @@ -124,6 +106,39 @@ Vst3PluginBridge::Vst3PluginBridge() .get() .component_handler->restartComponent(request.flags); }, + [&](const YaHostApplication::GetName& request) + -> YaHostApplication::GetName::Response { + tresult result; + Steinberg::Vst::String128 name{0}; + if (request.owner_instance_id) { + result = plugin_proxies.at(*request.owner_instance_id) + .get() + .host_application->getName(name); + } else { + result = + plugin_factory->host_application->getName(name); + } + + return YaHostApplication::GetNameResponse{ + .result = result, + .name = tchar_pointer_to_u16string(name), + }; + }, + [&](YaPlugFrame::ResizeView& request) + -> YaPlugFrame::ResizeView::Response { + // XXX: As mentioned elsewhere, since VST3 only supports a + // single plug view type at the moment we'll just + // assume that this function is called from the last + // (and only) `IPlugView*` instance returned by the + // plugin. + Vst3PlugViewProxyImpl* plug_view = + plugin_proxies.at(request.owner_instance_id) + .get() + .last_created_plug_view; + + return plug_view->plug_frame->resizeView(plug_view, + &request.new_size); + }, }); }); } diff --git a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp index 2d005ffa..484ca189 100644 --- a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp @@ -40,9 +40,18 @@ Vst3PlugFrameProxyImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { } tresult PLUGIN_API -Vst3PlugFrameProxyImpl::resizeView(Steinberg::IPlugView* view, +Vst3PlugFrameProxyImpl::resizeView(Steinberg::IPlugView* /*view*/, Steinberg::ViewRect* newSize) { - // TODO: Implement - std::cerr << "TODO: IPlugFrame::resizeView()" << std::endl; - return Steinberg::kNotImplemented; + if (newSize) { + // XXX: Since VST3 currently only support a single view type we'll + // assume `view` is the `IPlugView*` returned by the last call to + // `IEditController::createView()` + return bridge.send_message(YaPlugFrame::ResizeView{ + .owner_instance_id = owner_instance_id(), .new_size = *newSize}); + } else { + std::cerr + << "WARNING: Null pointer passed to 'IPlugFrame::resizeView()'" + << std::endl; + return Steinberg::kInvalidArgument; + } } From 5ef7e73725bb83d2c57404bd6e9585b8fa877830 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 15:37:06 +0100 Subject: [PATCH 347/456] Implement IPlugView::canResize() --- src/common/logging/vst3.cpp | 7 +++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + .../serialization/vst3/plug-view/plug-view.h | 16 ++++++++++++++++ .../bridges/vst3-impls/plug-view-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 85839b02..af419ffd 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -326,6 +326,13 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaPlugView::CanResize& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id << ": IPlugView::canResize()"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { return log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index e9f3dc03..ca359e17 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -98,6 +98,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPlugView::OnSize&); bool log_request(bool is_host_vst, const YaPlugView::OnFocus&); bool log_request(bool is_host_vst, const YaPlugView::SetFrame&); + bool log_request(bool is_host_vst, const YaPlugView::CanResize&); bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 65222858..3a6f1c89 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -89,6 +89,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(owner_instance_id); + } + }; + virtual tresult PLUGIN_API canResize() override = 0; virtual tresult PLUGIN_API checkSizeConstraint(Steinberg::ViewRect* rect) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index eaa09a64..aaa14bbb 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -139,9 +139,8 @@ Vst3PlugViewProxyImpl::setFrame(Steinberg::IPlugFrame* frame) { } tresult PLUGIN_API Vst3PlugViewProxyImpl::canResize() { - // TODO: Implement - bridge.logger.log("TODO: IPlugView::canResize()"); - return Steinberg::kNotImplemented; + return bridge.send_message( + YaPlugView::CanResize{.owner_instance_id = owner_instance_id()}); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 7df6f3ed..1b4b7276 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -420,6 +420,11 @@ void Vst3Bridge::run() { object_instances[request.owner_instance_id] .plug_frame_proxy); }, + [&](YaPlugView::CanResize& request) + -> YaPlugView::CanResize::Response { + return object_instances[request.owner_instance_id] + .plug_view->canResize(); + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // We'll create a proxy object for the host context passed by From 9bb90388c19c9c1d5d0a4a8901d04f7816b2f8d5 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 15:42:41 +0100 Subject: [PATCH 348/456] Implement IPlugView::checkSizeConstraint() With this the whole of `IPlugView` and thus also `IEditController` is implemented. --- README.md | 1 - src/common/logging/vst3.cpp | 12 ++++++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + .../serialization/vst3/plug-view/plug-view.h | 19 +++++++++++++++++++ .../bridges/vst3-impls/plug-view-proxy.cpp | 12 +++++++++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 7 files changed, 47 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7b2338ae..4ea68487 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ incomplete list of things that still have to be done before this can be used: - `IHostApplication::createComponent()` - `IConnectionPoint::notify()`, and support for indirectly connecting components through message passing proxies - - The rest of `IPlugView` - `IEditController2` - All other mandatory interfaces - All other optional interfaces diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index af419ffd..53e412c6 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -333,6 +333,18 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaPlugView::CheckSizeConstraint& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id + << ": IPlugView::checkSizeConstraint(rect = " + ")"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaPluginBase::Initialize& request) { return log_request_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index ca359e17..e317768c 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -99,6 +99,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPlugView::OnFocus&); bool log_request(bool is_host_vst, const YaPlugView::SetFrame&); bool log_request(bool is_host_vst, const YaPlugView::CanResize&); + bool log_request(bool is_host_vst, const YaPlugView::CheckSizeConstraint&); bool log_request(bool is_host_vst, const YaPluginBase::Initialize&); bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 3a6f1c89..fe6fe52f 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -90,6 +90,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(owner_instance_id); + s.object(rect); + } + }; + virtual tresult PLUGIN_API checkSizeConstraint(Steinberg::ViewRect* rect) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp index aaa14bbb..3485b73e 100644 --- a/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plug-view-proxy.cpp @@ -145,7 +145,13 @@ tresult PLUGIN_API Vst3PlugViewProxyImpl::canResize() { tresult PLUGIN_API Vst3PlugViewProxyImpl::checkSizeConstraint(Steinberg::ViewRect* rect) { - // TODO: Implement - bridge.logger.log("TODO: IPlugView::checkSizeConstraint()"); - return Steinberg::kNotImplemented; + if (rect) { + return bridge.send_message(YaPlugView::CheckSizeConstraint{ + .owner_instance_id = owner_instance_id(), .rect = *rect}); + } else { + bridge.logger.log( + "WARNING: Null pointer passed to " + "'IPlugView::checkSizeConstraint()'"); + return Steinberg::kInvalidArgument; + } } diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 1b4b7276..0d77e751 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -425,6 +425,11 @@ void Vst3Bridge::run() { return object_instances[request.owner_instance_id] .plug_view->canResize(); }, + [&](YaPlugView::CheckSizeConstraint& request) + -> YaPlugView::CheckSizeConstraint::Response { + return object_instances[request.owner_instance_id] + .plug_view->checkSizeConstraint(&request.rect); + }, [&](YaPluginBase::Initialize& request) -> YaPluginBase::Initialize::Response { // We'll create a proxy object for the host context passed by From 9ee7982591880de2665811d426554a04ff86c27d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 15:47:57 +0100 Subject: [PATCH 349/456] Call IPlugView::on* from the UI thread Since these can trigger a redraw. --- src/wine-host/bridges/vst3.cpp | 46 +++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 0d77e751..fa9775b1 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -371,20 +371,34 @@ void Vst3Bridge::run() { }, [&](const YaPlugView::OnWheel& request) -> YaPlugView::OnWheel::Response { - return object_instances[request.owner_instance_id] - .plug_view->onWheel(request.distance); + // Since all of these `IPlugView::on*` functions can cause a + // redraw, they all have to be called from the UI thread + return main_context + .run_in_context([&]() { + return object_instances[request.owner_instance_id] + .plug_view->onWheel(request.distance); + }) + .get(); }, [&](const YaPlugView::OnKeyDown& request) -> YaPlugView::OnKeyDown::Response { - return object_instances[request.owner_instance_id] - .plug_view->onKeyDown(request.key, request.key_code, - request.modifiers); + return main_context + .run_in_context([&]() { + return object_instances[request.owner_instance_id] + .plug_view->onKeyDown(request.key, request.key_code, + request.modifiers); + }) + .get(); }, [&](const YaPlugView::OnKeyUp& request) -> YaPlugView::OnKeyUp::Response { - return object_instances[request.owner_instance_id] - .plug_view->onKeyUp(request.key, request.key_code, - request.modifiers); + return main_context + .run_in_context([&]() { + return object_instances[request.owner_instance_id] + .plug_view->onKeyUp(request.key, request.key_code, + request.modifiers); + }) + .get(); }, [&](YaPlugView::GetSize& request) -> YaPlugView::GetSize::Response { const tresult result = @@ -395,13 +409,21 @@ void Vst3Bridge::run() { .result = result, .updated_size = request.size}; }, [&](YaPlugView::OnSize& request) -> YaPlugView::OnSize::Response { - return object_instances[request.owner_instance_id] - .plug_view->onSize(&request.new_size); + return main_context + .run_in_context([&]() { + return object_instances[request.owner_instance_id] + .plug_view->onSize(&request.new_size); + }) + .get(); }, [&](const YaPlugView::OnFocus& request) -> YaPlugView::OnFocus::Response { - return object_instances[request.owner_instance_id] - .plug_view->onFocus(request.state); + return main_context + .run_in_context([&]() { + return object_instances[request.owner_instance_id] + .plug_view->onFocus(request.state); + }) + .get(); }, [&](YaPlugView::SetFrame& request) -> YaPlugView::SetFrame::Response { From 3beaaf2312cea31f8f2e43f01dba1c49ac8bbe14 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 22 Dec 2020 17:36:30 +0100 Subject: [PATCH 350/456] Always handle IPlugView::onSize() from UI thread This requires a super hacky workaround because the UI thread can be currently blocked by the plugin calling `IPlugFrame::resizeView()` from the Win32 message loop. --- .../bridges/vst3-impls/plug-frame-proxy.cpp | 77 ++++++++++++++++++- .../bridges/vst3-impls/plug-frame-proxy.h | 48 ++++++++++++ src/wine-host/bridges/vst3.cpp | 43 ++++++++--- src/wine-host/bridges/vst3.h | 10 +++ 4 files changed, 167 insertions(+), 11 deletions(-) diff --git a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp index 484ca189..888e1ef3 100644 --- a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp @@ -46,8 +46,81 @@ Vst3PlugFrameProxyImpl::resizeView(Steinberg::IPlugView* /*view*/, // XXX: Since VST3 currently only support a single view type we'll // assume `view` is the `IPlugView*` returned by the last call to // `IEditController::createView()` - return bridge.send_message(YaPlugFrame::ResizeView{ - .owner_instance_id = owner_instance_id(), .new_size = *newSize}); + + // HACK: This ia bit of a weird one and requires special handling. A + // plugin will call this function from the Win32 message loop so + // the call blocks the loop. The host will then check with the + // plugin if it can actually resize itself to `*newSize`, and it + // will then call `IPlugView::onSize()` with the new size. The + // issue is that the `IPlugView::onSize()` call also has to be + // called from within the UI thread, but that thread is currently + // being blocked by the call to this function. + // As a workaround, we'll send the message for the call to + // `IPlugFrame::resizeView()` on another thread. We then wait for + // either that request to finish immediately (meaning the host + // hasn't resized the window), or for `IPlugView::onSize()` to be + // called by the host. If the host does call `IPlugView::onSize()` + // while the other thread is handling `IPlugFrame::resizeView()`, + // then we'll awaken this thread using a condition variable so we + // can do the actual call to `IPlugView::onSize()` from here, from + // within the Win32 loop. + // TODO: Can we someone use Boost.Asio strands to make this cleaner? + { + std::lock_guard lock(on_size_interrupt_mutex); + on_size_interrupt_waiting = true; + on_size_interrupt.reset(); + on_size_interrupt_result.reset(); + } + + std::promise resize_result_promise{}; + std::future resize_result_future = + resize_result_promise.get_future(); + Win32Thread resize_thread([&]() { + const tresult result = bridge.send_message(YaPlugFrame::ResizeView{ + .owner_instance_id = owner_instance_id(), + .new_size = *newSize}); + + resize_result_promise.set_value(result); + + { + std::lock_guard lock(on_size_interrupt_mutex); + if (BOOST_LIKELY(!on_size_interrupt_waiting)) { + return; + } + + // If the call to `IPlugFrame::resizeView()` finish without the + // host calling `IPlugView::onSize()` (e.g. when the call + // failed), then we'll have to manually unblock the thread below + // by providing a noop function. + on_size_interrupt = []() { return Steinberg::kResultOk; }; + } + on_size_interrupt_cv.notify_one(); + + // We don't need this result value, but we should still wait for + // it + std::unique_lock lock(on_size_interrupt_mutex); + on_size_interrupt_cv.wait( + lock, [&]() { return on_size_interrupt_result.has_value(); }); + }); + + // Wait for `IPlugView::onSize()` to be called by the host and handle + // the call here on this thread, or wait for the call to + // `IPlugFrame::resizeView()` in the above thread to finish. In the last + // case we'll execute a noop function. + std::unique_lock lock(on_size_interrupt_mutex); + on_size_interrupt_cv.wait( + lock, [&]() { return on_size_interrupt.has_value(); }); + + // When we get a function through one of the two above means, we'll + // store the result and then awake the calling thread again so it can + // access the result + on_size_interrupt_result = (*on_size_interrupt)(); + on_size_interrupt_waiting = false; + + lock.unlock(); + on_size_interrupt_cv.notify_one(); + + return resize_result_future.get(); } else { std::cerr << "WARNING: Null pointer passed to 'IPlugFrame::resizeView()'" diff --git a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.h b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.h index e15066a6..e43d7e9b 100644 --- a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.h +++ b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.h @@ -30,10 +30,58 @@ class Vst3PlugFrameProxyImpl : public Vst3PlugFrameProxy { tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, void** obj) override; + /** + * This is needed to be able to handle a call to `IPlugView::onSize()` from + * the UI thread while the plugin is currently calling + * `IPlugFrame::resizeView()` from that same thread.. This is probably the + * hackiest (and most error prone, probably) part of this VST3 + * implementation. The details on how this works and why it is necessary are + * explained in the comment in `YaPlugFrameProxyImpl::resizeView()`. + * + * If there is currently a call to `resizeView()` being processed, then this + * will run `on_size` from the same thread that's currently processing a + * call to `resizeView()` and return the result from the function call. + * Otherwise this will return a nullopt and `on_size` should be passed to + * `main_context.run_in_context()`. + */ + template + std::optional maybe_run_on_size_from_ui_thread(F on_size) { + { + std::lock_guard lock(on_size_interrupt_mutex); + if (!on_size_interrupt_waiting) { + return std::nullopt; + } + + on_size_interrupt = std::move(on_size); + } + on_size_interrupt_cv.notify_one(); + + // Since `on_size` is run from another thread, we now have to wait to be + // woken up again when the result is ready + std::unique_lock lock(on_size_interrupt_mutex); + on_size_interrupt_cv.wait( + lock, [&]() { return on_size_interrupt_result.has_value(); }); + + return *on_size_interrupt_result; + } + // From `IPlugFrame` tresult PLUGIN_API resizeView(Steinberg::IPlugView* view, Steinberg::ViewRect* newSize) override; private: Vst3Bridge& bridge; + + /** + * A function that will be used to run `IPlugView::onSize()` on the thread + * that originally called `IPlugFrame::resizeView()` to work around a Win32 + * limitation, along with its result, and whether or not we're currently + * waiting for this function to be provided by some other thread. See the + * comment in `onSize()` for more information. + */ + bool on_size_interrupt_waiting = false; + std::optional> on_size_interrupt; + std::optional on_size_interrupt_result; + std::condition_variable on_size_interrupt_cv; + std::mutex on_size_interrupt_mutex; }; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index fa9775b1..6d9e91f6 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -409,12 +409,31 @@ void Vst3Bridge::run() { .result = result, .updated_size = request.size}; }, [&](YaPlugView::OnSize& request) -> YaPlugView::OnSize::Response { - return main_context - .run_in_context([&]() { - return object_instances[request.owner_instance_id] + auto call_on_size = [&]() { + const auto result = + object_instances[request.owner_instance_id] .plug_view->onSize(&request.new_size); - }) - .get(); + return result; + }; + + // HACK: As explained in the docstring of + // `YaPlugFrameProxyImpl::resizeView()`, calls to + // `IPlugView::onSize()` have to be handled from the UI + // thread, even if that thread is currently making a call + // to `IPlugFrame::resizeView()`. We use some special + // handling to execute `call_on_size()` on that thread if + // it's currently waiting for a call to + // `IPlugFrame::resizeView()`, and we'll just run in + // within the main context as usual otherwise. + if (auto result = + object_instances[request.owner_instance_id] + .plug_frame_proxy_impl + ->maybe_run_on_size_from_ui_thread(call_on_size)) { + return *result; + } else { + return main_context.run_in_context(call_on_size) + .get(); + } }, [&](const YaPlugView::OnFocus& request) -> YaPlugView::OnFocus::Response { @@ -431,12 +450,18 @@ void Vst3Bridge::run() { // pass that to the `setFrame()` function. The lifetime of this // object is tied to that of the actual `IPlugFrame` object // we're passing this proxy to. + // We'll also store an unmanaged pointer to the actual + // implementation so we can do some sneakiness for + // `IPlugView::onSize()`. + object_instances[request.owner_instance_id] + .plug_frame_proxy_impl = new Vst3PlugFrameProxyImpl( + *this, std::move(request.plug_frame_args)); + object_instances[request.owner_instance_id].plug_frame_proxy = + Steinberg::owned(object_instances[request.owner_instance_id] + .plug_frame_proxy_impl); + // TODO: Does this have to be run from the UI thread? Figure out // if it does - object_instances[request.owner_instance_id].plug_frame_proxy = - Steinberg::owned(new Vst3PlugFrameProxyImpl( - *this, std::move(request.plug_frame_args))); - return object_instances[request.owner_instance_id] .plug_view->setFrame( object_instances[request.owner_instance_id] diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 6f47761a..9b7c5bf8 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -25,6 +25,9 @@ #include "../editor.h" #include "common.h" +// Forward declarations +class Vst3PlugFrameProxyImpl; + /** * A holder for plugin object instance created from the factory. This stores all * relevant interface smart pointers to that object so we can handle control @@ -72,6 +75,13 @@ struct InstanceInterfaces { */ Steinberg::IPtr plug_frame_proxy; + /** + * An unmanaged raw pointer for the actual implementation behind + * `plug_frame_proxy`. This is needed for some special handling for + * `IPlugView::onSize()`. + */ + Vst3PlugFrameProxyImpl* plug_frame_proxy_impl; + /** * The base object we cast from. */ From 216f65d2a1ccd2e9e4b82c18af40a1cc6c131db2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 23 Dec 2020 11:53:09 +0100 Subject: [PATCH 351/456] Unify log format further --- src/common/logging/vst3.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 53e412c6..6470613c 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -485,14 +485,14 @@ bool Vst3Logger::log_request(bool is_host_vst, << request.data.input_events->num_events() << " events>"; } else { - message << "nullptr"; + message << ""; } message << ", output_events = " << (request.data.output_events_supported ? "" - : "nullptr") + : "") << ", process_context = " << (request.data.process_context ? "" - : "nullptr") + : "") << ", process_mode = " << request.data.process_mode << ", symbolic_sample_size = " << request.data.symbolic_sample_size << ">)"; From eae77d4dbf4df05275483d9f437aeb60361bfdda Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 23 Dec 2020 12:00:31 +0100 Subject: [PATCH 352/456] Fix num samples in processing log message --- src/common/logging/vst3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 6470613c..0a510633 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -472,7 +472,7 @@ bool Vst3Logger::log_request(bool is_host_vst, "input_channels = " << num_input_channels.str() << ", output_channels = " << num_output_channels.str() - << ", num_samples = " << request.data.process_mode + << ", num_samples = " << request.data.num_samples << ", input_parameter_changes = , output_parameter_changes = " From 7a55fc3ec0ceeb1edcdee1063a9bbc40f98bdafa Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 23 Dec 2020 15:31:32 +0100 Subject: [PATCH 353/456] Significantly clean up mutual recursion workaround This has much fewer moving parts, and it's probably more understandable. There was also a race condition in the previous implementation, so there's that. --- .../bridges/vst3-impls/plug-frame-proxy.cpp | 79 +---------- .../bridges/vst3-impls/plug-frame-proxy.h | 48 ------- src/wine-host/bridges/vst3.cpp | 53 +++----- src/wine-host/bridges/vst3.h | 125 +++++++++++++++++- src/wine-host/utils.h | 32 +---- 5 files changed, 146 insertions(+), 191 deletions(-) diff --git a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp index 888e1ef3..3ceff0de 100644 --- a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp @@ -47,80 +47,11 @@ Vst3PlugFrameProxyImpl::resizeView(Steinberg::IPlugView* /*view*/, // assume `view` is the `IPlugView*` returned by the last call to // `IEditController::createView()` - // HACK: This ia bit of a weird one and requires special handling. A - // plugin will call this function from the Win32 message loop so - // the call blocks the loop. The host will then check with the - // plugin if it can actually resize itself to `*newSize`, and it - // will then call `IPlugView::onSize()` with the new size. The - // issue is that the `IPlugView::onSize()` call also has to be - // called from within the UI thread, but that thread is currently - // being blocked by the call to this function. - // As a workaround, we'll send the message for the call to - // `IPlugFrame::resizeView()` on another thread. We then wait for - // either that request to finish immediately (meaning the host - // hasn't resized the window), or for `IPlugView::onSize()` to be - // called by the host. If the host does call `IPlugView::onSize()` - // while the other thread is handling `IPlugFrame::resizeView()`, - // then we'll awaken this thread using a condition variable so we - // can do the actual call to `IPlugView::onSize()` from here, from - // within the Win32 loop. - // TODO: Can we someone use Boost.Asio strands to make this cleaner? - { - std::lock_guard lock(on_size_interrupt_mutex); - on_size_interrupt_waiting = true; - on_size_interrupt.reset(); - on_size_interrupt_result.reset(); - } - - std::promise resize_result_promise{}; - std::future resize_result_future = - resize_result_promise.get_future(); - Win32Thread resize_thread([&]() { - const tresult result = bridge.send_message(YaPlugFrame::ResizeView{ - .owner_instance_id = owner_instance_id(), - .new_size = *newSize}); - - resize_result_promise.set_value(result); - - { - std::lock_guard lock(on_size_interrupt_mutex); - if (BOOST_LIKELY(!on_size_interrupt_waiting)) { - return; - } - - // If the call to `IPlugFrame::resizeView()` finish without the - // host calling `IPlugView::onSize()` (e.g. when the call - // failed), then we'll have to manually unblock the thread below - // by providing a noop function. - on_size_interrupt = []() { return Steinberg::kResultOk; }; - } - on_size_interrupt_cv.notify_one(); - - // We don't need this result value, but we should still wait for - // it - std::unique_lock lock(on_size_interrupt_mutex); - on_size_interrupt_cv.wait( - lock, [&]() { return on_size_interrupt_result.has_value(); }); - }); - - // Wait for `IPlugView::onSize()` to be called by the host and handle - // the call here on this thread, or wait for the call to - // `IPlugFrame::resizeView()` in the above thread to finish. In the last - // case we'll execute a noop function. - std::unique_lock lock(on_size_interrupt_mutex); - on_size_interrupt_cv.wait( - lock, [&]() { return on_size_interrupt.has_value(); }); - - // When we get a function through one of the two above means, we'll - // store the result and then awake the calling thread again so it can - // access the result - on_size_interrupt_result = (*on_size_interrupt)(); - on_size_interrupt_waiting = false; - - lock.unlock(); - on_size_interrupt_cv.notify_one(); - - return resize_result_future.get(); + // We have to use this special sending function here so we can handle + // calls to `IPlugView::onSize()` from this same thread (the UI thread). + // See the docstring for more information. + return bridge.send_mutually_recursive_message(YaPlugFrame::ResizeView{ + .owner_instance_id = owner_instance_id(), .new_size = *newSize}); } else { std::cerr << "WARNING: Null pointer passed to 'IPlugFrame::resizeView()'" diff --git a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.h b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.h index e43d7e9b..e15066a6 100644 --- a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.h +++ b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.h @@ -30,58 +30,10 @@ class Vst3PlugFrameProxyImpl : public Vst3PlugFrameProxy { tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, void** obj) override; - /** - * This is needed to be able to handle a call to `IPlugView::onSize()` from - * the UI thread while the plugin is currently calling - * `IPlugFrame::resizeView()` from that same thread.. This is probably the - * hackiest (and most error prone, probably) part of this VST3 - * implementation. The details on how this works and why it is necessary are - * explained in the comment in `YaPlugFrameProxyImpl::resizeView()`. - * - * If there is currently a call to `resizeView()` being processed, then this - * will run `on_size` from the same thread that's currently processing a - * call to `resizeView()` and return the result from the function call. - * Otherwise this will return a nullopt and `on_size` should be passed to - * `main_context.run_in_context()`. - */ - template - std::optional maybe_run_on_size_from_ui_thread(F on_size) { - { - std::lock_guard lock(on_size_interrupt_mutex); - if (!on_size_interrupt_waiting) { - return std::nullopt; - } - - on_size_interrupt = std::move(on_size); - } - on_size_interrupt_cv.notify_one(); - - // Since `on_size` is run from another thread, we now have to wait to be - // woken up again when the result is ready - std::unique_lock lock(on_size_interrupt_mutex); - on_size_interrupt_cv.wait( - lock, [&]() { return on_size_interrupt_result.has_value(); }); - - return *on_size_interrupt_result; - } - // From `IPlugFrame` tresult PLUGIN_API resizeView(Steinberg::IPlugView* view, Steinberg::ViewRect* newSize) override; private: Vst3Bridge& bridge; - - /** - * A function that will be used to run `IPlugView::onSize()` on the thread - * that originally called `IPlugFrame::resizeView()` to work around a Win32 - * limitation, along with its result, and whether or not we're currently - * waiting for this function to be provided by some other thread. See the - * comment in `onSize()` for more information. - */ - bool on_size_interrupt_waiting = false; - std::optional> on_size_interrupt; - std::optional on_size_interrupt_result; - std::condition_variable on_size_interrupt_cv; - std::mutex on_size_interrupt_mutex; }; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 6d9e91f6..756f948e 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -69,7 +69,7 @@ void Vst3Bridge::run() { -> Vst3PlugViewProxy::Destruct::Response { // XXX: Not sure if his has to be run form the UI thread main_context - .run_in_context([&]() { + .run_in_context([&]() { // When the pointer gets dropped by the host, we want to // drop it here as well, along with the `IPlugFrame` // proxy object it may have received in @@ -275,7 +275,7 @@ void Vst3Bridge::run() { -> YaEditController::CreateView::Response { // Instantiate the object from the GUI thread main_context - .run_in_context([&]() { + .run_in_context([&]() { object_instances[request.instance_id].plug_view = Steinberg::owned( object_instances[request.instance_id] @@ -409,31 +409,20 @@ void Vst3Bridge::run() { .result = result, .updated_size = request.size}; }, [&](YaPlugView::OnSize& request) -> YaPlugView::OnSize::Response { - auto call_on_size = [&]() { - const auto result = - object_instances[request.owner_instance_id] + // HACK: This function has to be run from the UI thread since + // the plugin probably wants to redraw when it gets + // resized. The issue here is that this function can be + // called in response to a call to + // `IPlugFrame::resizeView()`. That function is always + // called from the UI thread, so we need some way to run + // code on the same thread that's currently waiting for a + // response to the message it sent. See the docstring of + // this function for more information on how this works. + return do_mutual_recursion_or_handle_in_main_context( + [&]() { + return object_instances[request.owner_instance_id] .plug_view->onSize(&request.new_size); - return result; - }; - - // HACK: As explained in the docstring of - // `YaPlugFrameProxyImpl::resizeView()`, calls to - // `IPlugView::onSize()` have to be handled from the UI - // thread, even if that thread is currently making a call - // to `IPlugFrame::resizeView()`. We use some special - // handling to execute `call_on_size()` on that thread if - // it's currently waiting for a call to - // `IPlugFrame::resizeView()`, and we'll just run in - // within the main context as usual otherwise. - if (auto result = - object_instances[request.owner_instance_id] - .plug_frame_proxy_impl - ->maybe_run_on_size_from_ui_thread(call_on_size)) { - return *result; - } else { - return main_context.run_in_context(call_on_size) - .get(); - } + }); }, [&](const YaPlugView::OnFocus& request) -> YaPlugView::OnFocus::Response { @@ -450,15 +439,9 @@ void Vst3Bridge::run() { // pass that to the `setFrame()` function. The lifetime of this // object is tied to that of the actual `IPlugFrame` object // we're passing this proxy to. - // We'll also store an unmanaged pointer to the actual - // implementation so we can do some sneakiness for - // `IPlugView::onSize()`. - object_instances[request.owner_instance_id] - .plug_frame_proxy_impl = new Vst3PlugFrameProxyImpl( - *this, std::move(request.plug_frame_args)); object_instances[request.owner_instance_id].plug_frame_proxy = - Steinberg::owned(object_instances[request.owner_instance_id] - .plug_frame_proxy_impl); + Steinberg::owned(new Vst3PlugFrameProxyImpl( + *this, std::move(request.plug_frame_args))); // TODO: Does this have to be run from the UI thread? Figure out // if it does @@ -711,7 +694,7 @@ void Vst3Bridge::unregister_object_instance(size_t instance_id) { // executed and when the actual host application context on // the plugin side gets deallocated. main_context - .run_in_context([&, instance_id]() { + .run_in_context([&, instance_id]() { std::lock_guard lock(object_instances_mutex); object_instances.erase(instance_id); }) diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 9b7c5bf8..0f5927dc 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include @@ -75,13 +76,6 @@ struct InstanceInterfaces { */ Steinberg::IPtr plug_frame_proxy; - /** - * An unmanaged raw pointer for the actual implementation behind - * `plug_frame_proxy`. This is needed for some special handling for - * `IPlugView::onSize()`. - */ - Vst3PlugFrameProxyImpl* plug_frame_proxy_impl; - /** * The base object we cast from. */ @@ -160,6 +154,106 @@ class Vst3Bridge : public HostBridge { return sockets.vst_host_callback.send_message(object, std::nullopt); } + /** + * Spawn a new thread and call `send_message()` from there, and then handle + * functions passed by calls to + * `do_mutual_recursion_or_handle_in_main_context()` on this thread until + * the original message we're trying to send has succeeded. This is a very + * specific solution to a very specific problem. When a plugin wants to + * resize itself, it will call `IPlugFrame::resizeView()` from within the + * WIn32 message loop. The host will then call `IPlugView::onSize()` on the + * plugin's `IPlugView` to actually resize the plugin. The issue is that + * that call to `IPlugView::onSize()` has to be handled from the UI thread, + * but in this sequence that thread is being blocked by a call to + * `IPlugFrame::resizeView()`. + * + * The hacky solution here is to send the message from another thread, and + * to then allow this thread to execute other functions submitted to an IO + * context. + */ + template + typename T::Response send_mutually_recursive_message(const T& object) { + using TResponse = typename T::Response; + + // This IO context will accept incoming calls from + // `do_mutual_recursion_or_handle_in_main_context()` until we receive a + // response + { + std::unique_lock lock(mutual_recursion_context_mutex); + + // In case some other thread is already calling + // `send_mutually_recursive_message()` (which should never be the + // case since this should only be called from the UI thread), we'll + // wait for that function to finish + if (mutual_recursion_context) { + mutual_recursion_context_cv.wait(lock, [&]() { + return !mutual_recursion_context.has_value(); + }); + } + + mutual_recursion_context.emplace(); + } + + // We will call the function from another thread so we can handle calls + // to from this thread + std::promise response_promise{}; + Win32Thread sending_thread([&]() { + const TResponse response = send_message(object); + + // Stop accepting additional work to be run from the calling thread + // once we receive a response + { + std::lock_guard lock(mutual_recursion_context_mutex); + mutual_recursion_context->stop(); + mutual_recursion_context.reset(); + } + mutual_recursion_context_cv.notify_one(); + + response_promise.set_value(response); + }); + + // Accept work from the other thread until we receive a response, at + // which point the context will be stopped + auto work_guard = + boost::asio::make_work_guard(*mutual_recursion_context); + mutual_recursion_context->run(); + + return response_promise.get_future().get(); + } + + /** + * Crazy functions ask for crazy naming. This is the other part of + * `send_mutually_recursive_message()`. If another thread is currently + * calling that function (from the UI thread), then we'll execute `f` from + * the UI thread using the IO context started in the above function. + * Otherwise `f` will be run on the UI thread through `main_context` as + * usual. + * + * @see Vst3Bridge::send_mutually_recursive_message + */ + template + T do_mutual_recursion_or_handle_in_main_context(F f) { + std::packaged_task do_call(f); + std::future do_call_response = do_call.get_future(); + + // If the above function is currently being called from some thread, + // then we'll submit the task to the IO context created there so it can + // be handled on that same thread. Otherwise we'll just submit it to the + // main IO context. Neither of these two functions block until `do_call` + // finish executing. + { + std::lock_guard lock(mutual_recursion_context_mutex); + if (mutual_recursion_context) { + boost::asio::dispatch(*mutual_recursion_context, + std::move(do_call)); + } else { + main_context.schedule_task(std::move(do_call)); + } + } + + return do_call_response.get(); + } + private: /** * Generate a nique instance identifier using an atomic fetch-and-add. This @@ -232,4 +326,21 @@ class Vst3Bridge : public HostBridge { */ std::map object_instances; std::mutex object_instances_mutex; + + /** + * The IO context used in `send_mutually_recursive_message()` to be able to + * execute functions from that same calling thread while we're waiting for a + * response. See the docstring there for more information. When this doesn't + * contain an IO context, this function is not being called and + * `do_mutual_recursion_or_handle_in_main_context()` should post the task + * directly to the main IO context. + */ + std::optional mutual_recursion_context; + std::mutex mutual_recursion_context_mutex; + /** + * Used to make sure only a single call to + * `send_mutually_recursive_message()` at a time can be processed (this + * should never happen, but better safe tha nsorry). + */ + std::condition_variable mutual_recursion_context_cv; }; diff --git a/src/wine-host/utils.h b/src/wine-host/utils.h index c87fc652..ef6e6355 100644 --- a/src/wine-host/utils.h +++ b/src/wine-host/utils.h @@ -71,33 +71,11 @@ class MainContext { */ template std::future run_in_context(F fn) { - std::promise result{}; - std::future future = result.get_future(); - boost::asio::dispatch(context, - [result = std::move(result), fn]() mutable { - result.set_value(fn()); - }); + std::packaged_task call_fn(std::move(fn)); + std::future response = call_fn.get_future(); + boost::asio::dispatch(context, std::move(call_fn)); - return future; - } - - /** - * The same as the above, but without returning a value. This allows us to - * wait for the task to have been run. - * - * @overload - */ - template - std::future run_in_context(F fn) { - std::promise result{}; - std::future future = result.get_future(); - boost::asio::dispatch(context, - [result = std::move(result), fn]() mutable { - fn(); - result.set_value(); - }); - - return future; + return response; } /** @@ -107,7 +85,7 @@ class MainContext { */ template void schedule_task(F fn) { - boost::asio::post(context, fn); + boost::asio::post(context, std::move(fn)); } /** From a7d284469a4c8a4e57ec472caf531b65bde3b726 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 23 Dec 2020 17:12:28 +0100 Subject: [PATCH 354/456] [yabridgectl] Locate libyabridge-vst3.so And unify how finding files in yabridgectl works. --- tools/yabridgectl/src/actions.rs | 42 +++++++---- tools/yabridgectl/src/config.rs | 121 ++++++++++++++++++++----------- tools/yabridgectl/src/utils.rs | 22 +++--- 3 files changed, 119 insertions(+), 66 deletions(-) diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index b2741cd9..9a89d736 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -21,7 +21,7 @@ use colored::Colorize; use std::fs; use std::path::{Path, PathBuf}; -use crate::config::{Config, InstallationMethod}; +use crate::config::{Config, InstallationMethod, YabridgeFiles}; use crate::files; use crate::files::FoundFile; use crate::utils; @@ -95,15 +95,27 @@ pub fn show_status(config: &Config) -> Result<()> { .map(|path| format!("'{}'", path.display())) .unwrap_or_else(|| String::from("")) ); - println!( - "libyabridge-vst2.so: {}", - config - .libyabridge_vst2() - .map(|path| format!("'{}'", path.display())) - .unwrap_or_else(|_| format!("{}", "".red())) - ); - println!("installation method: {}", config.method); + match config.files() { + Ok(files) => { + println!( + "libyabridge-vst2.so: '{}'", + files.libyabridge_vst2.display() + ); + println!( + "libyabridge-vst3.so: {}\n", + files + .libyabridge_vst3 + .map(|path| format!("'{}'", path.display())) + .unwrap_or_else(|| "".red().to_string()) + ); + } + Err(err) => { + println!("Could not find yabridge's files files: {}\n", err); + } + } + + println!("installation method: {}", config.method); for (path, search_results) in results { println!("\n{}:", path.display()); @@ -154,9 +166,9 @@ pub struct SyncOptions { /// Set up yabridge for all Windows VST2 plugins in the plugin directories. Will also remove orphan /// `.so` files if the prune option is set. pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { - let libyabridge_vst2_path = config.libyabridge_vst2()?; - let libyabridge_vst2_hash = utils::hash_file(&libyabridge_vst2_path)?; - println!("Using '{}'\n", libyabridge_vst2_path.display()); + let files: YabridgeFiles = config.files()?; + let libyabridge_vst2_hash = utils::hash_file(&files.libyabridge_vst2)?; + println!("Using '{}'\n", files.libyabridge_vst2.display()); let results = config .index_directories() @@ -200,7 +212,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { // If the target file is already a symlink to `libyabridge-vst2.so`, then we // can skip this file if metadata.file_type().is_symlink() - && target_path.read_link()? == libyabridge_vst2_path + && target_path.read_link()? == files.libyabridge_vst2 { continue; } @@ -217,10 +229,10 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { num_new += 1; match config.method { InstallationMethod::Copy => { - utils::copy(&libyabridge_vst2_path, &target_path)?; + utils::copy(&files.libyabridge_vst2, &target_path)?; } InstallationMethod::Symlink => { - utils::symlink(&libyabridge_vst2_path, &target_path)?; + utils::symlink(&files.libyabridge_vst2, &target_path)?; } } diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index 6f993bad..a0e0c1d7 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -28,14 +28,16 @@ use xdg::BaseDirectories; use crate::files::{self, SearchResults}; -/// The name of the config file, relative to `$XDG_CONFIG_HOME/CONFIG_PREFIX`. +/// The name of the config file, relative to `$XDG_CONFIG_HOME/YABRIDGECTL_PREFIX`. pub const CONFIG_FILE_NAME: &str = "config.toml"; /// The name of the XDG base directory prefix for yabridgectl, relative to `$XDG_CONFIG_HOME` and /// `$XDG_DATA_HOME`. const YABRIDGECTL_PREFIX: &str = "yabridgectl"; -/// The name of the library file we're searching for. +/// The name of yabridge's VST2 library. pub const LIBYABRIDGE_VST2_NAME: &str = "libyabridge-vst2.so"; +/// The name of yabridge's VST3 library. +pub const LIBYABRIDGE_VST3_NAME: &str = "libyabridge-vst3.so"; /// The name of the script we're going to run to verify that everything's working correctly. pub const YABRIDGE_HOST_EXE_NAME: &str = "yabridge-host.exe"; /// The name of the XDG base directory prefix for yabridge's own files, relative to @@ -66,13 +68,13 @@ pub struct Config { #[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum InstallationMethod { - /// Create a copy of `libyabridge-vst2.so` for every Windows VST2 plugin .dll file found. After - /// updating yabridge, the user will have to rerun `yabridgectl sync` to copy over the new - /// version. + /// Create a copy of `libyabridge-{vst2,vst3}.so` for every Windows VST2 plugin .dll file or + /// VST3 module found. After updating yabridge, the user will have to rerun `yabridgectl sync` + /// to copy over the new version. Copy, - /// This will create a symlink to `libyabridge-vst2.so` for every VST2 .dll file in the plugin - /// directories. As explained in the readme, this makes updating easier and remvoes the need to - /// modify the `PATH` environment variable. + /// This will create a symlink to `libyabridge-{vst2,vst3}.so` for every VST2 plugin .dll file + /// or VST3 module in the plugin directories. Now that yabridge also searches in + /// `~/.local/share/yabridge` since yabridge 2.1 this option is not really needed anymore. Symlink, } @@ -117,6 +119,23 @@ pub struct KnownConfig { pub yabridge_host_hash: i64, } +/// Paths to all of yabridge's files based on the `yabridge_home` setting. Created by +/// `Config::files`. +#[derive(Debug)] +pub struct YabridgeFiles { + /// The path to `libyabridge-vst2.so` we should use. + pub libyabridge_vst2: PathBuf, + /// The path to `libyabridge-vst3.so` we should use, if yabridge has been compiled with VST3 + /// support. + pub libyabridge_vst3: Option, + /// The path to `yabridge-host.exe`. This is the path yabridge will actually use, and it does + /// not have to be relative to `yabridge_home`. + pub yabridge_host_exe: PathBuf, + /// The actual Winelib binary for `yabridge-host.exe`. Will be hashed to check whether the user + /// has updated yabridge. + pub yabridge_host_exe_so: PathBuf, +} + impl Config { /// Try to read the config file, creating a new default file if necessary. This will fail if the /// file could not be created or if it could not be parsed. @@ -158,21 +177,23 @@ impl Config { .with_context(|| format!("Failed to write config file to '{}'", config_path.display())) } - /// Return the path to `libyabridge-vst2.so`, or a descriptive error if it can't be found. If - /// `yabridge_home` is `None`, then we'll search in both `/usr/lib` and - /// `$XDG_DATA_HOME/yabridge`. - pub fn libyabridge_vst2(&self) -> Result { - match &self.yabridge_home { + /// Find all of yabridge's files based on `yabridge_home`. For the binaries we'll search for + /// them the exact same way as yabridge itself will. + pub fn files(&self) -> Result { + let xdg_dirs = yabridge_directories()?; + + // First find `libyabridge-vst2.so` + let libyabridge_vst2: PathBuf = match &self.yabridge_home { Some(directory) => { let candidate = directory.join(LIBYABRIDGE_VST2_NAME); if candidate.exists() { - Ok(candidate) + candidate } else { - Err(anyhow!( + return Err(anyhow!( "Could not find '{}' in '{}'", LIBYABRIDGE_VST2_NAME, directory.display() - )) + )); } } None => { @@ -182,42 +203,58 @@ impl Config { // when `libyabridge-vst2.so` can't be found. let system_path = Path::new("/usr/lib"); let system_path_alt = Path::new("/usr/local/lib"); - let user_path = yabridge_directories()?.get_data_home(); - for directory in &[system_path, system_path_alt, &user_path] { - let candidate = directory.join(LIBYABRIDGE_VST2_NAME); - if candidate.exists() { - return Ok(candidate); + let user_path = xdg_dirs.get_data_home(); + let directories = [system_path, system_path_alt, &user_path]; + let mut candidates = directories + .iter() + .map(|directory| directory.join(LIBYABRIDGE_VST2_NAME)); + match candidates.find(|directory| directory.exists()) { + Some(candidate) => candidate, + _ => { + return Err(anyhow!( + "Could not find '{}' in either '{}' or '{}'. You can override the \ + default search path using 'yabridgectl set --path='.", + LIBYABRIDGE_VST2_NAME, + system_path.display(), + user_path.display() + )); } } - - Err(anyhow!( - "Could not find '{}' in either '{}' or '{}'. You can override the default \ - search path using 'yabridgectl set --path='.", - LIBYABRIDGE_VST2_NAME, - system_path.display(), - user_path.display() - )) } - } - } + }; - /// Return the path to `yabridge-host.exe`, or a descriptive error if it can't be found. This - /// will first search alongside `libyabridge-vst2.so` and then search through the search path. - pub fn yabridge_host_exe(&self) -> Result { - let yabridge_path = self.libyabridge_vst2()?; + // Based on that we can check if `libyabridge-vst3.so` exists, since yabridge can be + // compiled without VST3 support + let libyabridge_vst3 = match libyabridge_vst2.with_file_name(LIBYABRIDGE_VST3_NAME) { + path if path.exists() => Some(path), + _ => None, + }; - let yabridge_host_exe_candidate = yabridge_path.with_file_name(YABRIDGE_HOST_EXE_NAME); - if yabridge_host_exe_candidate.exists() { - return Ok(yabridge_host_exe_candidate); - } + // `yabridge-host.exe` should either be in the search path, or it should be in + // `~/.local/share/yabridge` + let yabridge_host_exe = match which(YABRIDGE_HOST_EXE_NAME) + .ok() + .or_else(|| xdg_dirs.find_data_file(YABRIDGE_HOST_EXE_NAME)) + { + Some(path) => path, + _ => { + return Err(anyhow!("Could not locate '{}'.", YABRIDGE_HOST_EXE_NAME)); + } + }; + let yabridge_host_exe_so = yabridge_host_exe.with_extension("exe.so"); - // Normally we wouldn't need the full absolute path to `yabridge-host.exe`, but it's useful - // for the error messages - Ok(which(YABRIDGE_HOST_EXE_NAME)?) + Ok(YabridgeFiles { + libyabridge_vst2, + libyabridge_vst3, + yabridge_host_exe, + yabridge_host_exe_so, + }) } /// Search for VST2 plugins in all of the registered plugins directories. This will return an /// error if `winedump` could not be called. + /// + /// TODO: Next step is including VST3 modules in the search pub fn index_directories(&self) -> Result> { self.plugin_dirs .par_iter() diff --git a/tools/yabridgectl/src/utils.rs b/tools/yabridgectl/src/utils.rs index 3e241a2a..f73c6294 100644 --- a/tools/yabridgectl/src/utils.rs +++ b/tools/yabridgectl/src/utils.rs @@ -90,17 +90,21 @@ pub fn hash_file(file: &Path) -> Result { /// In the last case we'll just print a warning since we don't know how to invoke the shell as a /// login shell. This is needed when using copies to ensure that yabridge can find the host binaries /// when the VST host is launched from the desktop enviornment. +/// +/// This is a bit messy, and with yabridge 2.1 automatically searching in `~/.local/share/yabridge` +/// it's probably not really needed anymore, but it could still be useful in some edge case +/// scenarios. pub fn verify_path_setup(config: &Config) -> Result { // First we'll check `~/.local/share/yabridge`, since that's a special location where yabridge // will always search - if config::yabridge_directories() + let xdg_data_yabridge_exists = config::yabridge_directories() .map(|dirs| { dirs.get_data_home() .join(YABRIDGE_HOST_EXE_NAME) .is_executable() }) - .unwrap_or(false) - { + .unwrap_or(false); + if xdg_data_yabridge_exists { return Ok(true); } @@ -179,7 +183,7 @@ pub fn verify_path_setup(config: &Config) -> Result { reboot your system to complete the setup.\n\ \n\ https://github.com/robbert-vdh/yabridge#troubleshooting-common-issues", - config.libyabridge_vst2()?.parent().unwrap().display(), + config.files()?.libyabridge_vst2.parent().unwrap().display(), shell.bright_white(), "PATH".bright_white() )) @@ -230,13 +234,13 @@ pub fn verify_wine_setup(config: &mut Config) -> Result<()> { let mut wine_version = String::from_utf8(wine_version_output)?; wine_version.pop().unwrap(); - let yabridge_host_exe_path = config - .yabridge_host_exe() + let files = config + .files() .context(format!("Could not find '{}'", YABRIDGE_HOST_EXE_NAME))?; // Hash the contents of `yabridge-host.exe.so` since `yabridge-host.exe` is only a Wine // generated shell script - let yabridge_host_hash = hash_file(&yabridge_host_exe_path.with_extension("exe.so"))?; + let yabridge_host_hash = hash_file(&files.yabridge_host_exe_so)?; // Since these checks can take over a second if wineserver isn't already running we'll only // perform them when something has changed @@ -248,9 +252,9 @@ pub fn verify_wine_setup(config: &mut Config) -> Result<()> { return Ok(()); } - let output = Command::new(&yabridge_host_exe_path) + let output = Command::new(&files.yabridge_host_exe) .output() - .with_context(|| format!("Could not run '{}'", yabridge_host_exe_path.display()))?; + .with_context(|| format!("Could not run '{}'", files.yabridge_host_exe.display()))?; let stderr = String::from_utf8(output.stderr)?; // There are three scenarios here: From 3dc1b1585b473939239a09df590dbfb3aa37feb7 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 23 Dec 2020 17:41:16 +0100 Subject: [PATCH 355/456] [yabridgectl] Add field for indexing VST3 modules --- CHANGELOG.md | 8 +++++ tools/yabridgectl/src/actions.rs | 8 ++--- tools/yabridgectl/src/config.rs | 16 +++++----- tools/yabridgectl/src/files.rs | 52 +++++++++++++++++++++----------- 4 files changed, 55 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3882eb6d..5bd784c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,14 @@ TODO: Add the relevant entries here for yabridge's VST3 support and 5.8 required a change, but that change now breaks builds using Wine 6.0 and up, so this change has been reverted. +### yabridgectl + +- Updated for the changes in yabridge 3.0. Yabridgectl now allows you to set up + yabridge for VST3 plugins. Since `libyabridge.so` got renamed to + `libyabridge-vst2.so` in this version, it's advised to carefully remove the + old `libyabridge.so` and `yabridgectl` files before upgrading to avoid + confusing situations. + ## [2.2.1] - 2020-12-12 ### Fixed diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 9a89d736..87127956 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -23,7 +23,7 @@ use std::path::{Path, PathBuf}; use crate::config::{Config, InstallationMethod, YabridgeFiles}; use crate::files; -use crate::files::FoundFile; +use crate::files::NativeSoFile; use crate::utils; use crate::utils::{verify_path_setup, verify_wine_setup}; @@ -121,8 +121,8 @@ pub fn show_status(config: &Config) -> Result<()> { for (plugin, status) in search_results.installation_status() { let status_str = match status { - Some(FoundFile::Regular(_)) => "copy".green(), - Some(FoundFile::Symlink(_)) => "symlink".green(), + Some(NativeSoFile::Regular(_)) => "copy".green(), + Some(NativeSoFile::Symlink(_)) => "symlink".green(), None => "not installed".red(), }; @@ -178,7 +178,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { let mut num_installed = 0; let mut num_new = 0; let mut skipped_dll_files: Vec = Vec::new(); - let mut orphan_so_files: Vec = Vec::new(); + let mut orphan_so_files: Vec = Vec::new(); for (path, search_results) in results { num_installed += search_results.vst2_files.len(); orphan_so_files.extend(search_results.orphans().into_iter().cloned()); diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index a0e0c1d7..f891e7c7 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -55,8 +55,10 @@ pub struct Config { /// yabridgectl will look in `/usr/lib` and `$XDG_DATA_HOME/yabridge` since those are the /// expected locations for yabridge to be installed in. pub yabridge_home: Option, - /// Directories to search for Windows VST plugins. We're using an ordered set here out of - /// convenience so we can't get duplicates and the config file is always sorted. + /// Directories to search for Windows VST plugins. These directories can contain both VST2 + /// plugin `.dll` files and VST3 modules (which should be located in `/drive_c/Program + /// Files/Common/VST3`). We're using an ordered set here out of convenience so we can't get + /// duplicates and the config file is always sorted. pub plugin_dirs: BTreeSet, /// The last known combination of Wine and yabridge versions that would work together properly. /// This is mostly to diagnose issues with older Wine versions (such as those in Ubuntu's repos) @@ -68,11 +70,11 @@ pub struct Config { #[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum InstallationMethod { - /// Create a copy of `libyabridge-{vst2,vst3}.so` for every Windows VST2 plugin .dll file or + /// Create a copy of `libyabridge-{vst2,vst3}.so` for every Windows VST2 plugin `.dll` file or /// VST3 module found. After updating yabridge, the user will have to rerun `yabridgectl sync` /// to copy over the new version. Copy, - /// This will create a symlink to `libyabridge-{vst2,vst3}.so` for every VST2 plugin .dll file + /// This will create a symlink to `libyabridge-{vst2,vst3}.so` for every VST2 plugin `.dll` file /// or VST3 module in the plugin directories. Now that yabridge also searches in /// `~/.local/share/yabridge` since yabridge 2.1 this option is not really needed anymore. Symlink, @@ -251,10 +253,8 @@ impl Config { }) } - /// Search for VST2 plugins in all of the registered plugins directories. This will return an - /// error if `winedump` could not be called. - /// - /// TODO: Next step is including VST3 modules in the search + /// Search for VST2 and VST3 plugins in all of the registered plugins directories. This will + /// return an error if `winedump` could not be called. pub fn index_directories(&self) -> Result> { self.plugin_dirs .par_iter() diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index 3544ff34..7209eade 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -25,35 +25,51 @@ use std::path::{Path, PathBuf}; use std::process::Command; use walkdir::WalkDir; -/// Stores the results from searching for Windows VST plugin `.dll` files and native Linux `.so` -/// files inside of a directory. These `.so` files are kept track of so we can report the current -/// installation status and to be able to prune orphan files. +/// Stores the results from searching through a directory. We'll search for Windows VST2 plugin +/// `.dll` files, Windows VST3 plugin modules, and native Linux `.so` files inside of a directory. +/// These `.so` files are kept track of so we can report the current installation status of VST2 +/// plugins and to be able to prune orphan files. Since VST3 plugins have to be instaleld in +/// `~/.vst3`, these orphan files are only relevant for VST2 plugins. #[derive(Debug)] pub struct SearchResults { /// Absolute paths to the found VST2 `.dll` files. pub vst2_files: Vec, + /// Absolute paths to found VST3 modules. Either legacy `.vst3` DLL files or VST 3.6.10 bundles. + pub vst3_modules: Vec, /// `.dll` files skipped over during the serach. Used for printing statistics and shown when /// running `yabridgectl sync --verbose`. pub skipped_files: Vec, /// Absolute paths to any `.so` files inside of the directory, and whether they're a symlink or /// a regular file. - pub so_files: Vec, + pub so_files: Vec, } +/// Native `.so` files we found during a search. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum FoundFile { +pub enum NativeSoFile { Symlink(PathBuf), Regular(PathBuf), } +/// VST3 modules we found during a serach. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Vst3Module { + /// Old, pre-VST 3.6.10 style `.vst3` modules. These are simply `.dll` files with a different p + /// refix. Even though this is a legacy format, almost all VST3 plugins in the wild still use + /// this format. + Legacy(PathBuf), + /// A VST 3.6.10 bundle, with the same format as the VST3 bundles used on Linux and macOS. + Bundle(PathBuf), +} + impl SearchResults { /// For every found VST2 plugin, find the associated copy or symlink of `libyabridge-vst2.so`. /// The returned hashmap will contain a `None` value for plugins that have not yet been set up. /// /// These two functions could be combined into a single function, but speed isn't really an /// issue here and it's a bit more organized this way. - pub fn installation_status(&self) -> BTreeMap<&Path, Option<&FoundFile>> { - let so_files: HashMap<&Path, &FoundFile> = self + pub fn installation_status(&self) -> BTreeMap<&Path, Option<&NativeSoFile>> { + let so_files: HashMap<&Path, &NativeSoFile> = self .so_files .iter() .map(|file| (file.path(), file)) @@ -71,9 +87,9 @@ impl SearchResults { } /// Find all `.so` files in the search results that do not belong to a VST2 plugin `.dll` file. - pub fn orphans(&self) -> Vec<&FoundFile> { + pub fn orphans(&self) -> Vec<&NativeSoFile> { // We need to store these in a map so we can easily entries with corresponding `.dll` files - let mut orphans: HashMap<&Path, &FoundFile> = self + let mut orphans: HashMap<&Path, &NativeSoFile> = self .so_files .iter() .map(|file| (file.path(), file)) @@ -86,12 +102,12 @@ impl SearchResults { } } -impl FoundFile { +impl NativeSoFile { /// Return the path of a found `.so` file. pub fn path(&self) -> &Path { match &self { - FoundFile::Symlink(path) => path, - FoundFile::Regular(path) => path, + NativeSoFile::Symlink(path) => path, + NativeSoFile::Regular(path) => path, } } } @@ -142,12 +158,14 @@ pub fn index(directory: &Path) -> Result { vst2_files, skipped_files, so_files, + // TODO: Search for VST3 modules + vst3_modules: Vec::new(), }) } /// THe same as [index()](index), but only report found `.so` files. This avoids unnecesarily /// filtering the found `.dll` files. -pub fn index_so_files(directory: &Path) -> Vec { +pub fn index_so_files(directory: &Path) -> Vec { let (_, so_files) = find_files(directory); so_files @@ -155,9 +173,9 @@ pub fn index_so_files(directory: &Path) -> Vec { /// Find all `.dll` and `.so` files under a directory. The results are a pair of `(dll_files, /// so_files)`. -fn find_files(directory: &Path) -> (Vec, Vec) { +fn find_files(directory: &Path) -> (Vec, Vec) { let mut dll_files: Vec = Vec::new(); - let mut so_files: Vec = Vec::new(); + let mut so_files: Vec = Vec::new(); // XXX: We're silently skipping directories and files we don't have permission to read. This // sounds like the expected behavior, but I"m not entirely sure. for (file_idx, entry) in WalkDir::new(directory) @@ -182,9 +200,9 @@ fn find_files(directory: &Path) -> (Vec, Vec) { Some("dll") => dll_files.push(entry.into_path()), Some("so") => { if entry.path_is_symlink() { - so_files.push(FoundFile::Symlink(entry.into_path())); + so_files.push(NativeSoFile::Symlink(entry.into_path())); } else { - so_files.push(FoundFile::Regular(entry.into_path())); + so_files.push(NativeSoFile::Regular(entry.into_path())); } } _ => (), From 3d27426b9d831204e534b687706bebd558da1a43 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 23 Dec 2020 18:39:22 +0100 Subject: [PATCH 356/456] [yabridgectl] Index .vst3 files It doesn't actually identify VST3 modules yet though. --- tools/yabridgectl/src/actions.rs | 6 +- tools/yabridgectl/src/config.rs | 8 +- tools/yabridgectl/src/files.rs | 148 +++++++++++++++++-------------- 3 files changed, 92 insertions(+), 70 deletions(-) diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 87127956..5b5a4a47 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -43,7 +43,7 @@ pub fn remove_directory(config: &mut Config, path: &Path) -> Result<()> { // Ask the user to remove any leftover files to prevent possible future problems and out of date // copies - let orphan_files = files::index_so_files(path); + let orphan_files = files::index(path).so_files; if !orphan_files.is_empty() { println!( "Warning: Found {} leftover .so files still in this directory:", @@ -84,7 +84,7 @@ pub fn list_directories(config: &Config) -> Result<()> { /// Print the current configuration and the installation status for all found plugins. pub fn show_status(config: &Config) -> Result<()> { let results = config - .index_directories() + .search_directories() .context("Failure while searching for plugins")?; println!( @@ -171,7 +171,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { println!("Using '{}'\n", files.libyabridge_vst2.display()); let results = config - .index_directories() + .search_directories() .context("Failure while searching for plugins")?; // Keep track of some global statistics diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index f891e7c7..b24510fd 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -255,10 +255,14 @@ impl Config { /// Search for VST2 and VST3 plugins in all of the registered plugins directories. This will /// return an error if `winedump` could not be called. - pub fn index_directories(&self) -> Result> { + pub fn search_directories(&self) -> Result> { self.plugin_dirs .par_iter() - .map(|path| files::index(path).map(|search_results| (path.as_path(), search_results))) + .map(|path| { + files::index(path) + .search() + .map(|search_results| (path.as_path(), search_results)) + }) .collect() } } diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index 7209eade..15523948 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -36,9 +36,24 @@ pub struct SearchResults { pub vst2_files: Vec, /// Absolute paths to found VST3 modules. Either legacy `.vst3` DLL files or VST 3.6.10 bundles. pub vst3_modules: Vec, - /// `.dll` files skipped over during the serach. Used for printing statistics and shown when + /// `.dll` files skipped over during the search. Used for printing statistics and shown when /// running `yabridgectl sync --verbose`. pub skipped_files: Vec, + + /// Absolute paths to any `.so` files inside of the directory, and whether they're a symlink or + /// a regular file. + pub so_files: Vec, +} + +/// The results of the first step of the search process. We'll first index all possibly relevant +/// files in a directory before filtering them down to a `SearchResults` object. +#[derive(Debug)] +pub struct SearchIndex { + /// Any `.dll` file. + pub dll_files: Vec, + /// Any `.vst3` file or directory. This can be either a legacy `.vst3` DLL module or a VST + /// 3.6.10 module (or some kind of random other file, of course). + pub vst3_files: Vec, /// Absolute paths to any `.so` files inside of the directory, and whether they're a symlink or /// a regular file. pub so_files: Vec, @@ -68,6 +83,8 @@ impl SearchResults { /// /// These two functions could be combined into a single function, but speed isn't really an /// issue here and it's a bit more organized this way. + /// + /// TODO: Do this for VST3 plugins pub fn installation_status(&self) -> BTreeMap<&Path, Option<&NativeSoFile>> { let so_files: HashMap<&Path, &NativeSoFile> = self .so_files @@ -87,6 +104,8 @@ impl SearchResults { } /// Find all `.so` files in the search results that do not belong to a VST2 plugin `.dll` file. + /// + /// TODO: Also do something similar for VST3 plugins pub fn orphans(&self) -> Vec<&NativeSoFile> { // We need to store these in a map so we can easily entries with corresponding `.dll` files let mut orphans: HashMap<&Path, &NativeSoFile> = self @@ -98,7 +117,7 @@ impl SearchResults { orphans.remove(vst2_path.with_extension("so").as_path()); } - orphans.into_iter().map(|(_, file)| file).collect() + orphans.values().cloned().collect() } } @@ -112,69 +131,11 @@ impl NativeSoFile { } } -/// Search for Windows VST2 plugins and .so files under a directory. This will return an error if -/// the directory does not exist, or if `winedump` could not be found. -pub fn index(directory: &Path) -> Result { - // First we'll find all .dll and .so files in the directory - let (dll_files, so_files) = find_files(directory); - - lazy_static! { - static ref VST2_AUTOMATON: AhoCorasick = - AhoCorasick::new_auto_configured(&["VSTPluginMain", "main", "main_plugin"]); - } - - // THne we'll figure out which `.dll` files are VST2 plugins and which should be skipped by - // checking whether the file contains one of the VST2 entry point functions. The boolean flag in - // this vector indicates whether it is a VST2 plugin. - let dll_files: Vec<(PathBuf, bool)> = dll_files - .into_par_iter() - .map(|path| { - let exported_functions = Command::new("winedump") - .arg("-j") - .arg("export") - .arg(&path) - .output() - .context( - "Could not find 'winedump'. In some distributions this is part of a seperate \ - Wine tools package.", - )? - .stdout; - - Ok((path, VST2_AUTOMATON.is_match(exported_functions))) - }) - .collect::>()?; - - let mut vst2_files = Vec::new(); - let mut skipped_files = Vec::new(); - for (path, is_vst2_plugin) in dll_files { - if is_vst2_plugin { - vst2_files.push(path); - } else { - skipped_files.push(path); - } - } - - Ok(SearchResults { - vst2_files, - skipped_files, - so_files, - // TODO: Search for VST3 modules - vst3_modules: Vec::new(), - }) -} - -/// THe same as [index()](index), but only report found `.so` files. This avoids unnecesarily -/// filtering the found `.dll` files. -pub fn index_so_files(directory: &Path) -> Vec { - let (_, so_files) = find_files(directory); - - so_files -} - -/// Find all `.dll` and `.so` files under a directory. The results are a pair of `(dll_files, -/// so_files)`. -fn find_files(directory: &Path) -> (Vec, Vec) { +/// Find all `.dll`, `.vst3` and `.so` files under a directory. These results can be filtered down +/// to actual VST2 plugins and VST3 modules using `search()`. +pub fn index(directory: &Path) -> SearchIndex { let mut dll_files: Vec = Vec::new(); + let mut vst3_files: Vec = Vec::new(); let mut so_files: Vec = Vec::new(); // XXX: We're silently skipping directories and files we don't have permission to read. This // sounds like the expected behavior, but I"m not entirely sure. @@ -198,6 +159,7 @@ fn find_files(directory: &Path) -> (Vec, Vec) { match entry.path().extension().and_then(|os| os.to_str()) { Some("dll") => dll_files.push(entry.into_path()), + Some("vst3") => vst3_files.push(entry.into_path()), Some("so") => { if entry.path_is_symlink() { so_files.push(NativeSoFile::Symlink(entry.into_path())); @@ -209,5 +171,61 @@ fn find_files(directory: &Path) -> (Vec, Vec) { } } - (dll_files, so_files) + SearchIndex { + dll_files, + vst3_files, + so_files, + } +} + +impl SearchIndex { + /// Filter these indexing results down to actual VST2 plugins and VST3 modules. This will skip + /// all invalid files, such as regular `.dll` libraries. Will return an error if `winedump` + /// could not be found. + pub fn search(self) -> Result { + lazy_static! { + static ref VST2_AUTOMATON: AhoCorasick = + AhoCorasick::new_auto_configured(&["VSTPluginMain", "main", "main_plugin"]); + } + + // THne we'll figure out which `.dll` files are VST2 plugins and which should be skipped by + // checking whether the file contains one of the VST2 entry point functions. The boolean flag in + // this vector indicates whether it is a VST2 plugin. + let dll_files: Vec<(PathBuf, bool)> = self + .dll_files + .into_par_iter() + .map(|path| { + let exported_functions = Command::new("winedump") + .arg("-j") + .arg("export") + .arg(&path) + .output() + .context( + "Could not find 'winedump'. In some distributions this is part of a seperate \ + Wine tools package.", + )? + .stdout; + + Ok((path, VST2_AUTOMATON.is_match(exported_functions))) + }) + .collect::>()?; + + let mut vst2_files: Vec = Vec::new(); + let mut skipped_files: Vec = Vec::new(); + for (path, is_vst2_plugin) in dll_files { + if is_vst2_plugin { + vst2_files.push(path); + } else { + skipped_files.push(path); + } + } + + Ok(SearchResults { + vst2_files, + // TODO: Search for VST3 modules + vst3_modules: Vec::new(), + skipped_files, + so_files: self.so_files, + }) + } } From 8cb15180230d000174cba45d81750e976cbd6949 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 23 Dec 2020 21:08:24 +0100 Subject: [PATCH 357/456] [yabridgectl] Index and categorize VST3 modules --- tools/yabridgectl/src/files.rs | 141 +++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 25 deletions(-) diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index 15523948..706dbe32 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -72,9 +72,29 @@ pub enum Vst3Module { /// Old, pre-VST 3.6.10 style `.vst3` modules. These are simply `.dll` files with a different p /// refix. Even though this is a legacy format, almost all VST3 plugins in the wild still use /// this format. - Legacy(PathBuf), - /// A VST 3.6.10 bundle, with the same format as the VST3 bundles used on Linux and macOS. - Bundle(PathBuf), + Legacy(PathBuf, LibArchitecture), + /// A VST 3.6.10 bundle, with the same format as the VST3 bundles used on Linux and macOS. These + /// kinds of bundles can come with resource files and presets, which should also be symlinked to + /// `~/.vst3/` + Bundle(PathBuf, LibArchitecture), +} + +/// The architecture of a `.dll` file. Needed so we can create a merged bundle for VST3 plugins. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum LibArchitecture { + Dll32, + Dll64, +} + +impl LibArchitecture { + /// Get the corresponding VST3 architecture directory name. See + /// https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html. + pub fn vst_arch(&self) -> &str { + match &self { + LibArchitecture::Dll32 => "x86-win", + LibArchitecture::Dll64 => "x86_64-win", + } + } } impl SearchResults { @@ -186,44 +206,115 @@ impl SearchIndex { lazy_static! { static ref VST2_AUTOMATON: AhoCorasick = AhoCorasick::new_auto_configured(&["VSTPluginMain", "main", "main_plugin"]); + static ref VST3_AUTOMATON: AhoCorasick = + AhoCorasick::new_auto_configured(&["GetPluginFactory"]); + static ref DLL32_AUTOMATON: AhoCorasick = + AhoCorasick::new_auto_configured(&["Machine: 014C"]); } - // THne we'll figure out which `.dll` files are VST2 plugins and which should be skipped by - // checking whether the file contains one of the VST2 entry point functions. The boolean flag in - // this vector indicates whether it is a VST2 plugin. - let dll_files: Vec<(PathBuf, bool)> = self - .dll_files - .into_par_iter() - .map(|path| { - let exported_functions = Command::new("winedump") - .arg("-j") - .arg("export") - .arg(&path) + let winedump = |args: &[&str], path: &Path| { + Command::new("winedump") + .args(args) + .arg(path) .output() .context( "Could not find 'winedump'. In some distributions this is part of a seperate \ Wine tools package.", - )? - .stdout; + ) + .map(|output| output.stdout) + }; + let pe32_info = |path: &Path| winedump(&[], path); + let exported_functions = |path: &Path| winedump(&["-j", "export"], path); - Ok((path, VST2_AUTOMATON.is_match(exported_functions))) + // We'll have to figure out which `.dll` files are VST2 plugins and which should be skipped + // by checking whether the file contains one of the VST2 entry point functions. This vector + // will contain an `Err(path)` if `path` was not a valid VST2 plugin. + let is_vst2_plugin: Vec> = self + .dll_files + .into_par_iter() + .map(|path| { + if VST2_AUTOMATON.is_match(exported_functions(&path)?) { + Ok(Ok(path)) + } else { + Ok(Err(path)) + } + }) + .collect::>()?; + + // We need to do the same thing with VST3 plugins. The added difficulty here is that we have + // to figure out of the `.vst3` file is a legacy standalone VST3 module, or part of a VST + // 3.6.10 bundle. We also need to know the plugin's architecture because we're going to + // create a univeral VST3 bundle. + let is_vst3_module: Vec> = self + .vst3_files + .into_par_iter() + .map(|module_path| { + let architecture = if DLL32_AUTOMATON.is_match(pe32_info(&module_path)?) { + LibArchitecture::Dll32 + } else { + LibArchitecture::Dll64 + }; + + if VST3_AUTOMATON.is_match(exported_functions(&module_path)?) { + // Now we'll have to figure out if the plugin is part of a VST 3.6.10 style + // bundle or a legacy `.vst3` DLL file. A WIndows VST3 bundle contains at least + // `.vst3/Contents//.vst3`, so + // we'll just go up a few directories and then reconstruct that bundle. + let module_name = module_path.file_name(); + let bundle_root = module_path + .parent() + .and_then(|arch_dir| arch_dir.parent()) + .and_then(|contents_dir| contents_dir.parent()); + let module_is_in_bundle = bundle_root + .and_then(|bundle_root| bundle_root.parent()) + .zip(module_name) + .map(|(path, module_name)| { + // Now reconstruct the path to the original file again as if it were in + // a bundle + let mut reconstructed_path = path.join(module_name); + reconstructed_path.push("Contents"); + reconstructed_path.push(architecture.vst_arch()); + reconstructed_path.push(module_name); + + return reconstructed_path.exists(); + }) + .unwrap_or(false); + + if module_is_in_bundle { + Ok(Ok(Vst3Module::Bundle( + bundle_root.unwrap().to_owned(), + architecture, + ))) + } else { + Ok(Ok(Vst3Module::Legacy(module_path, architecture))) + } + } else { + Ok(Err(module_path)) + } }) .collect::>()?; - let mut vst2_files: Vec = Vec::new(); let mut skipped_files: Vec = Vec::new(); - for (path, is_vst2_plugin) in dll_files { - if is_vst2_plugin { - vst2_files.push(path); - } else { - skipped_files.push(path); + + let mut vst2_files: Vec = Vec::new(); + for dandidate in is_vst2_plugin { + match dandidate { + Ok(path) => vst2_files.push(path), + Err(path) => skipped_files.push(path), + } + } + + let mut vst3_modules: Vec = Vec::new(); + for candidate in is_vst3_module { + match candidate { + Ok(module) => vst3_modules.push(module), + Err(path) => skipped_files.push(path), } } Ok(SearchResults { vst2_files, - // TODO: Search for VST3 modules - vst3_modules: Vec::new(), + vst3_modules, skipped_files, so_files: self.so_files, }) From 5e476a2f9b19335a9be2d0334f4804405b080de6 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 23 Dec 2020 21:40:11 +0100 Subject: [PATCH 358/456] [yabridgectl] Add utilities for VST3 paths --- tools/yabridgectl/src/config.rs | 13 ++++++ tools/yabridgectl/src/files.rs | 72 +++++++++++++++++++++++++++------ 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index b24510fd..9bca24f3 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -20,6 +20,7 @@ use anyhow::{anyhow, Context, Result}; use rayon::prelude::*; use serde_derive::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; +use std::env; use std::fmt::Display; use std::fs; use std::path::{Path, PathBuf}; @@ -44,6 +45,11 @@ pub const YABRIDGE_HOST_EXE_NAME: &str = "yabridge-host.exe"; /// `$XDG_CONFIG_HOME` and `$XDG_DATA_HOME`. const YABRIDGE_PREFIX: &str = "yabridge"; +/// The path relative to `$HOME` that VST3 modules bridged by yabridgectl life in. By putting this +/// in a subdirectory we can easily clean up any orphan files without interfering with other native +/// plugins. +const YABRIDGE_VST3_HOME: &str = ".vst3/yabridge"; + /// The configuration used for yabridgectl. This will be serialized to and deserialized from /// `$XDG_CONFIG_HOME/yabridge/config.toml`. #[derive(Deserialize, Serialize, Debug)] @@ -279,3 +285,10 @@ pub fn yabridge_directories() -> Result { pub fn yabridgectl_directories() -> Result { BaseDirectories::with_prefix(YABRIDGECTL_PREFIX).context("Error while parsing base directories") } + +/// Get the path where VST3 modules bridged by yabridgectl should be placed in. This is a +/// subdirectory of `~/.vst3` so we can easily clean up leftover files without interfering with +/// other native plugins. +pub fn yabridge_vst3_home() -> PathBuf { + Path::new(&env::var("HOME").expect("$HOME is not set")).join(YABRIDGE_VST3_HOME) +} diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index 706dbe32..897d66b2 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -25,6 +25,8 @@ use std::path::{Path, PathBuf}; use std::process::Command; use walkdir::WalkDir; +use crate::config::yabridge_vst3_home; + /// Stores the results from searching through a directory. We'll search for Windows VST2 plugin /// `.dll` files, Windows VST3 plugin modules, and native Linux `.so` files inside of a directory. /// These `.so` files are kept track of so we can report the current installation status of VST2 @@ -66,6 +68,16 @@ pub enum NativeSoFile { Regular(PathBuf), } +impl NativeSoFile { + /// Return the path of a found `.so` file. + pub fn path(&self) -> &Path { + match &self { + NativeSoFile::Symlink(path) => path, + NativeSoFile::Regular(path) => path, + } + } +} + /// VST3 modules we found during a serach. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Vst3Module { @@ -79,8 +91,54 @@ pub enum Vst3Module { Bundle(PathBuf, LibArchitecture), } +impl Vst3Module { + /// The architecture of this VST3 module. + pub fn architecture(&self) -> LibArchitecture { + match &self { + Vst3Module::Legacy(_, architecture) | Vst3Module::Bundle(_, architecture) => { + *architecture + } + } + } + + /// Get the path to the `libyabridge.so` file in `~/.vst3` corresponding to the bridged version + /// of this module. + pub fn libyabridge_location(&self) -> PathBuf { + let mut path = yabridge_vst3_home(); + path.push(self.module_name()); + path.push("Contents"); + path.push("x86_64-linux"); + path.push(self.native_module_name()); + path + } + + /// Get the name of the module as a string. Should be in the format `Plugin Name.vst3`. + pub fn module_name(&self) -> &str { + match &self { + Vst3Module::Legacy(path, _) | Vst3Module::Bundle(path, _) => path + .file_name() + .unwrap() + .to_str() + .expect("VST3 module name contains invalid UTF-8"), + } + } + + /// `module_name()` but with a `.so` file extension isntead of `.vst3`. + pub fn native_module_name(&self) -> String { + match &self { + Vst3Module::Legacy(path, _) | Vst3Module::Bundle(path, _) => path + .with_extension("so") + .file_name() + .unwrap() + .to_str() + .expect("VST3 module name contains invalid UTF-8") + .to_owned(), + } + } +} + /// The architecture of a `.dll` file. Needed so we can create a merged bundle for VST3 plugins. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum LibArchitecture { Dll32, Dll64, @@ -116,7 +174,7 @@ impl SearchResults { .iter() .map( |path| match so_files.get(path.with_extension("so").as_path()) { - Some(&file) => (path.as_path(), Some(file)), + Some(&file_type) => (path.as_path(), Some(file_type)), None => (path.as_path(), None), }, ) @@ -141,16 +199,6 @@ impl SearchResults { } } -impl NativeSoFile { - /// Return the path of a found `.so` file. - pub fn path(&self) -> &Path { - match &self { - NativeSoFile::Symlink(path) => path, - NativeSoFile::Regular(path) => path, - } - } -} - /// Find all `.dll`, `.vst3` and `.so` files under a directory. These results can be filtered down /// to actual VST2 plugins and VST3 modules using `search()`. pub fn index(directory: &Path) -> SearchIndex { From bc9801c932ae246513b4b62ab46a2ea023502aa2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Wed, 23 Dec 2020 21:55:14 +0100 Subject: [PATCH 359/456] [yabridgectl] Add VST3 modules to the status --- tools/yabridgectl/src/files.rs | 43 +++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index 897d66b2..cad40cce 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -135,6 +135,14 @@ impl Vst3Module { .to_owned(), } } + + /// Get the path to the module. This can be either a file or a directory depending on the type + /// of moudle. + pub fn path(&self) -> &Path { + match &self { + Vst3Module::Legacy(path, _) | Vst3Module::Bundle(path, _) => path, + } + } } /// The architecture of a `.dll` file. Needed so we can create a merged bundle for VST3 plugins. @@ -156,29 +164,42 @@ impl LibArchitecture { } impl SearchResults { - /// For every found VST2 plugin, find the associated copy or symlink of `libyabridge-vst2.so`. - /// The returned hashmap will contain a `None` value for plugins that have not yet been set up. - /// - /// These two functions could be combined into a single function, but speed isn't really an - /// issue here and it's a bit more organized this way. - /// - /// TODO: Do this for VST3 plugins - pub fn installation_status(&self) -> BTreeMap<&Path, Option<&NativeSoFile>> { + /// For every found VST2 plugin and VST3 module, find the associated copy or symlink of + /// `libyabridge-{vst2,vst3}.so`. The returned hashmap will contain a `None` value for plugins + /// that have not yet been set up. + pub fn installation_status(&self) -> BTreeMap<&Path, Option> { let so_files: HashMap<&Path, &NativeSoFile> = self .so_files .iter() .map(|file| (file.path(), file)) .collect(); - self.vst2_files + // Do this for the VST2 plugins + let mut installation_status: BTreeMap<&Path, Option> = self + .vst2_files .iter() .map( |path| match so_files.get(path.with_extension("so").as_path()) { - Some(&file_type) => (path.as_path(), Some(file_type)), + Some(&file_type) => (path.as_path(), Some(file_type.clone())), None => (path.as_path(), None), }, ) - .collect() + .collect(); + + // And for VST3 modules. We have not stored the paths to the corresponding `.so` files yet + // because they are not in any of the directories we're indexing. + installation_status.extend(self.vst3_modules.iter().map(|module| { + let install_path = module.libyabridge_location(); + match install_path.metadata() { + Ok(metadata) if metadata.file_type().is_symlink() => { + (module.path(), Some(NativeSoFile::Symlink(install_path))) + } + Ok(_) => (module.path(), Some(NativeSoFile::Regular(install_path))), + Err(_) => (module.path(), None), + } + })); + + installation_status } /// Find all `.so` files in the search results that do not belong to a VST2 plugin `.dll` file. From 55957ca7985350acb5e5dd28fc2f3024af6ac82f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 00:04:05 +0100 Subject: [PATCH 360/456] [yabridgectl] Allow setting up VST3 plugins This is still missing checks for removing leftover files, symlinks for resources and presets, and a way to differentiate between plugins with the same name from different prefixes. --- tools/yabridgectl/src/actions.rs | 196 ++++++++++++++++++++++--------- tools/yabridgectl/src/config.rs | 2 +- tools/yabridgectl/src/files.rs | 121 +++++++++++-------- tools/yabridgectl/src/utils.rs | 26 ++++ 4 files changed, 242 insertions(+), 103 deletions(-) diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 5b5a4a47..215a74cb 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -23,7 +23,7 @@ use std::path::{Path, PathBuf}; use crate::config::{Config, InstallationMethod, YabridgeFiles}; use crate::files; -use crate::files::NativeSoFile; +use crate::files::NativeFile; use crate::utils; use crate::utils::{verify_path_setup, verify_wine_setup}; @@ -34,10 +34,9 @@ pub fn add_directory(config: &mut Config, path: PathBuf) -> Result<()> { } /// Remove a direcotry to the plugin locations. The path is assumed to be part of -/// `config.plugin_dirs`, otherwise this si silently ignored. +/// `config.plugin_dirs`, otherwise this is silently ignored. pub fn remove_directory(config: &mut Config, path: &Path) -> Result<()> { // We've already verified that this path is in `config.plugin_dirs` - // XXS: Would it be a good idea to warn about leftover .so files? config.plugin_dirs.remove(path); config.write()?; @@ -121,8 +120,9 @@ pub fn show_status(config: &Config) -> Result<()> { for (plugin, status) in search_results.installation_status() { let status_str = match status { - Some(NativeSoFile::Regular(_)) => "copy".green(), - Some(NativeSoFile::Symlink(_)) => "symlink".green(), + Some(NativeFile::Regular(_)) => "copy".green(), + Some(NativeFile::Symlink(_)) => "symlink".green(), + Some(NativeFile::Directory(_)) => "invalid".red(), None => "not installed".red(), }; @@ -168,20 +168,47 @@ pub struct SyncOptions { pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { let files: YabridgeFiles = config.files()?; let libyabridge_vst2_hash = utils::hash_file(&files.libyabridge_vst2)?; - println!("Using '{}'\n", files.libyabridge_vst2.display()); + let libyabridge_vst3_hash = match &files.libyabridge_vst3 { + Some(path) => Some(utils::hash_file(path)?), + None => None, + }; + + if let Some(libyabridge_vst3_path) = &files.libyabridge_vst3 { + println!("Setting up VST2 and VST3 plugins using:"); + println!("- {}", files.libyabridge_vst2.display()); + println!("- {}\n", libyabridge_vst3_path.display()); + } else { + println!("Setting up VST2 plugins using:"); + println!("- {}\n", files.libyabridge_vst2.display()); + } let results = config .search_directories() .context("Failure while searching for plugins")?; // Keep track of some global statistics + // The number of plugins we set up yabridge for let mut num_installed = 0; + // The number of plugins we create a (new) copy of `libyabridge-{vst2,vst3}.so` for let mut num_new = 0; + // The files we skipped during the scan because they turned out to not be plugins let mut skipped_dll_files: Vec = Vec::new(); - let mut orphan_so_files: Vec = Vec::new(); + // `.so` files we found during scanning that didn't have a corresponding copy or symlink of + // `libyabridge-vst2.so` + let mut orphan_vst2_so_files: Vec = Vec::new(); + // All the VST3 modules we have set up yabridge for. We need this to detect leftover VST3 + // modules in `~/.vst3/yabridge`. + let mut yabridge_vst3_bundles: Vec = Vec::new(); for (path, search_results) in results { num_installed += search_results.vst2_files.len(); - orphan_so_files.extend(search_results.orphans().into_iter().cloned()); + num_installed += search_results.vst3_modules.len(); + orphan_vst2_so_files.extend(search_results.vst2_orphans().into_iter().cloned()); + yabridge_vst3_bundles.extend( + search_results + .vst3_modules + .iter() + .map(|module| module.yabridge_bundle_home()), + ); skipped_dll_files.extend(search_results.skipped_files); if options.verbose { @@ -190,56 +217,58 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { for plugin in search_results.vst2_files { let target_path = plugin.with_extension("so"); - // We'll only recreate existing files when updating yabridge, when switching between the - // symlink and copy installation methods, or when the `force` option is set. If the - // target file already exists and does not require updating, we'll just skip the file - // since some DAWs will otherwise unnecessarily reindex the file. - // We check `std::fs::symlink_metadata` instead of `Path::exists()` because the latter - // reports false for broken symlinks. - if let Ok(metadata) = fs::symlink_metadata(&target_path) { - match (options.force, &config.method) { - (false, InstallationMethod::Copy) => { - // If the target file is already a real file (not a symlink) and its hash is - // the same as the `libyabridge-vst2.so` file we're trying to copy there, - // then we don't have to do anything - if metadata.file_type().is_file() - && utils::hash_file(&target_path)? == libyabridge_vst2_hash - { - continue; - } - } - (false, InstallationMethod::Symlink) => { - // If the target file is already a symlink to `libyabridge-vst2.so`, then we - // can skip this file - if metadata.file_type().is_symlink() - && target_path.read_link()? == files.libyabridge_vst2 - { - continue; - } - } - // With the force option we always want to recreate existing .so files - (true, _) => (), - } - - utils::remove_file(&target_path)?; - }; - // Since we skip some files, we'll also keep track of how many new file we've actually // set up - num_new += 1; - match config.method { - InstallationMethod::Copy => { - utils::copy(&files.libyabridge_vst2, &target_path)?; - } - InstallationMethod::Symlink => { - utils::symlink(&files.libyabridge_vst2, &target_path)?; - } + if install_file( + options.force, + config.method, + &files.libyabridge_vst2, + libyabridge_vst2_hash, + &target_path, + )? { + num_new += 1; } if options.verbose { println!(" {}", plugin.display()); } } + if let Some(libyabridge_vst3_hash) = libyabridge_vst3_hash { + for module in search_results.vst3_modules { + let native_module_path = module.yabridge_native_module_path(); + + // For VST3 plugins we'll first have to create the bundle structure + utils::create_dir_all(native_module_path.parent().unwrap())?; + + // We'll then symlink the Windows VST3 module to that bundle to create a merged + // bundle: https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles + let windows_module_path = module.yabridge_windows_module_path(); + utils::create_dir_all(windows_module_path.parent().unwrap())?; + install_file( + true, + InstallationMethod::Symlink, + &module.original_module_path(), + 0, + &windows_module_path, + )?; + + // TODO: Symlink resources and presets + + if install_file( + options.force, + config.method, + files.libyabridge_vst3.as_ref().unwrap(), + libyabridge_vst3_hash, + &native_module_path, + )? { + num_new += 1; + } + + if options.verbose { + println!(" {}", module.original_path().display()); + } + } + } if options.verbose { println!(); } @@ -255,19 +284,24 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { println!(); } - // Always warn about leftover files sicne those might cause warnings or errors when a VST host + // TODO: Remove leftover files for VST3 plugins + + // Always warn about leftover files since those might cause warnings or errors when a VST host // tries to load them - if !orphan_so_files.is_empty() { + if !orphan_vst2_so_files.is_empty() { if options.prune { - println!("Removing {} leftover .so files:", orphan_so_files.len()); + println!( + "Removing {} leftover .so files:", + orphan_vst2_so_files.len() + ); } else { println!( "Found {} leftover .so files, rerun with the '--prune' option to remove them:", - orphan_so_files.len() + orphan_vst2_so_files.len() ); } - for file in orphan_so_files { + for file in orphan_vst2_so_files { let path = file.path(); println!("- {}", path.display()); @@ -291,6 +325,8 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { return Ok(()); } + // The path setup is to make sure that the `libyabridge-{vst2,vst3}.so` copies can find + // `yabridge-host.exe` if config.method == InstallationMethod::Copy { verify_path_setup(config)?; } @@ -300,3 +336,53 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { Ok(()) } + +/// Create a copy or symlink of `from` to `to`. Depending on `force`, we might not actually create a +/// new copy or symlink if `to` matches `from_hash`. +fn install_file( + force: bool, + method: InstallationMethod, + from: &Path, + from_hash: i64, + to: &Path, +) -> Result { + // We'll only recreate existing files when updating yabridge, when switching between the symlink + // and copy installation methods, or when the `force` option is set. If the target file already + // exists and does not require updating, we'll just skip the file since some DAWs will otherwise + // unnecessarily reindex the file. We check `std::fs::symlink_metadata` instead of + // `Path::exists()` because the latter reports false for broken symlinks. + if let Ok(metadata) = fs::symlink_metadata(&to) { + match (force, &method) { + (false, InstallationMethod::Copy) => { + // If the target file is already a real file (not a symlink) and its hash is + // the same as the `libyabridge-vst2.so` file we're trying to copy there, + // then we don't have to do anything + if metadata.file_type().is_file() && utils::hash_file(to)? == from_hash { + return Ok(false); + } + } + (false, InstallationMethod::Symlink) => { + // If the target file is already a symlink to `libyabridge-vst2.so`, then we + // can skip this file + if metadata.file_type().is_symlink() && to.read_link()? == from { + return Ok(false); + } + } + // With the force option we always want to recreate existing .so files + (true, _) => (), + } + + utils::remove_file(&to)?; + }; + + match method { + InstallationMethod::Copy => { + utils::copy(from, to)?; + } + InstallationMethod::Symlink => { + utils::symlink(from, to)?; + } + } + + Ok(true) +} diff --git a/tools/yabridgectl/src/config.rs b/tools/yabridgectl/src/config.rs index 9bca24f3..0669e109 100644 --- a/tools/yabridgectl/src/config.rs +++ b/tools/yabridgectl/src/config.rs @@ -73,7 +73,7 @@ pub struct Config { } /// Specifies how yabridge will be set up for the found plugins. -#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Copy)] #[serde(rename_all = "snake_case")] pub enum InstallationMethod { /// Create a copy of `libyabridge-{vst2,vst3}.so` for every Windows VST2 plugin `.dll` file or diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index cad40cce..9eb81ebd 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -26,6 +26,7 @@ use std::process::Command; use walkdir::WalkDir; use crate::config::yabridge_vst3_home; +use crate::utils::get_file_type; /// Stores the results from searching through a directory. We'll search for Windows VST2 plugin /// `.dll` files, Windows VST3 plugin modules, and native Linux `.so` files inside of a directory. @@ -44,7 +45,7 @@ pub struct SearchResults { /// Absolute paths to any `.so` files inside of the directory, and whether they're a symlink or /// a regular file. - pub so_files: Vec, + pub so_files: Vec, } /// The results of the first step of the search process. We'll first index all possibly relevant @@ -58,22 +59,24 @@ pub struct SearchIndex { pub vst3_files: Vec, /// Absolute paths to any `.so` files inside of the directory, and whether they're a symlink or /// a regular file. - pub so_files: Vec, + pub so_files: Vec, } -/// Native `.so` files we found during a search. +/// Native `.so` files and VST3 bundle directories we found during a search. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum NativeSoFile { +pub enum NativeFile { Symlink(PathBuf), Regular(PathBuf), + Directory(PathBuf), } -impl NativeSoFile { +impl NativeFile { /// Return the path of a found `.so` file. pub fn path(&self) -> &Path { match &self { - NativeSoFile::Symlink(path) => path, - NativeSoFile::Regular(path) => path, + NativeFile::Symlink(path) | NativeFile::Regular(path) | NativeFile::Directory(path) => { + path + } } } } @@ -101,19 +104,16 @@ impl Vst3Module { } } - /// Get the path to the `libyabridge.so` file in `~/.vst3` corresponding to the bridged version - /// of this module. - pub fn libyabridge_location(&self) -> PathBuf { - let mut path = yabridge_vst3_home(); - path.push(self.module_name()); - path.push("Contents"); - path.push("x86_64-linux"); - path.push(self.native_module_name()); - path + /// Get the path to the Windows VST3 plugin. This can be either a file or a directory depending + /// on the type of moudle. + pub fn original_path(&self) -> &Path { + match &self { + Vst3Module::Legacy(path, _) | Vst3Module::Bundle(path, _) => path, + } } /// Get the name of the module as a string. Should be in the format `Plugin Name.vst3`. - pub fn module_name(&self) -> &str { + pub fn original_module_name(&self) -> &str { match &self { Vst3Module::Legacy(path, _) | Vst3Module::Bundle(path, _) => path .file_name() @@ -123,9 +123,32 @@ impl Vst3Module { } } - /// `module_name()` but with a `.so` file extension isntead of `.vst3`. - pub fn native_module_name(&self) -> String { + /// Get the path to the actual `.vst3` module file. + pub fn original_module_path(&self) -> PathBuf { match &self { + Vst3Module::Legacy(path, _) => path.to_owned(), + Vst3Module::Bundle(bundle_home, architecture) => { + let mut path = bundle_home.join("Contents"); + path.push(architecture.vst_arch()); + path.push(self.original_module_name()); + + path + } + } + } + + /// Get the path to the bundle in `~/.vst3` corresponding to the bridged version of this module. + /// + /// FIXME: How do we solve naming clashes from the same VST3 plugin being installed to multiple + /// Wine prefixes? + pub fn yabridge_bundle_home(&self) -> PathBuf { + yabridge_vst3_home().join(self.original_module_name()) + } + + /// Get the path to the `libyabridge.so` file in `~/.vst3` corresponding to the bridged version + /// of this module. + pub fn yabridge_native_module_path(&self) -> PathBuf { + let native_module_name = match &self { Vst3Module::Legacy(path, _) | Vst3Module::Bundle(path, _) => path .with_extension("so") .file_name() @@ -133,15 +156,23 @@ impl Vst3Module { .to_str() .expect("VST3 module name contains invalid UTF-8") .to_owned(), - } + }; + + let mut path = self.yabridge_bundle_home(); + path.push("Contents"); + path.push("x86_64-linux"); + path.push(native_module_name); + path } - /// Get the path to the module. This can be either a file or a directory depending on the type - /// of moudle. - pub fn path(&self) -> &Path { - match &self { - Vst3Module::Legacy(path, _) | Vst3Module::Bundle(path, _) => path, - } + /// Get the path to where we'll symlink `original_module_path`. This is part of the merged VST3 + /// bundle in `~/.vst3/yabridge`. + pub fn yabridge_windows_module_path(&self) -> PathBuf { + let mut path = self.yabridge_bundle_home(); + path.push("Contents"); + path.push(self.architecture().vst_arch()); + path.push(self.original_module_name()); + path } } @@ -167,21 +198,21 @@ impl SearchResults { /// For every found VST2 plugin and VST3 module, find the associated copy or symlink of /// `libyabridge-{vst2,vst3}.so`. The returned hashmap will contain a `None` value for plugins /// that have not yet been set up. - pub fn installation_status(&self) -> BTreeMap<&Path, Option> { - let so_files: HashMap<&Path, &NativeSoFile> = self + pub fn installation_status(&self) -> BTreeMap> { + let so_files: HashMap<&Path, &NativeFile> = self .so_files .iter() .map(|file| (file.path(), file)) .collect(); // Do this for the VST2 plugins - let mut installation_status: BTreeMap<&Path, Option> = self + let mut installation_status: BTreeMap> = self .vst2_files .iter() .map( |path| match so_files.get(path.with_extension("so").as_path()) { - Some(&file_type) => (path.as_path(), Some(file_type.clone())), - None => (path.as_path(), None), + Some(&file_type) => (path.clone(), Some(file_type.clone())), + None => (path.clone(), None), }, ) .collect(); @@ -189,29 +220,25 @@ impl SearchResults { // And for VST3 modules. We have not stored the paths to the corresponding `.so` files yet // because they are not in any of the directories we're indexing. installation_status.extend(self.vst3_modules.iter().map(|module| { - let install_path = module.libyabridge_location(); - match install_path.metadata() { - Ok(metadata) if metadata.file_type().is_symlink() => { - (module.path(), Some(NativeSoFile::Symlink(install_path))) - } - Ok(_) => (module.path(), Some(NativeSoFile::Regular(install_path))), - Err(_) => (module.path(), None), - } + let module_path = module.yabridge_native_module_path(); + let install_type = get_file_type(&module_path); + (module_path, install_type) })); installation_status } /// Find all `.so` files in the search results that do not belong to a VST2 plugin `.dll` file. - /// - /// TODO: Also do something similar for VST3 plugins - pub fn orphans(&self) -> Vec<&NativeSoFile> { + /// We cannot yet do the same thing for VST3 plguins because they will all be installed in + /// `~/.vst3`. + pub fn vst2_orphans(&self) -> Vec<&NativeFile> { // We need to store these in a map so we can easily entries with corresponding `.dll` files - let mut orphans: HashMap<&Path, &NativeSoFile> = self + let mut orphans: HashMap<&Path, &NativeFile> = self .so_files .iter() - .map(|file| (file.path(), file)) + .map(|file_type| (file_type.path(), file_type)) .collect(); + for vst2_path in &self.vst2_files { orphans.remove(vst2_path.with_extension("so").as_path()); } @@ -225,7 +252,7 @@ impl SearchResults { pub fn index(directory: &Path) -> SearchIndex { let mut dll_files: Vec = Vec::new(); let mut vst3_files: Vec = Vec::new(); - let mut so_files: Vec = Vec::new(); + let mut so_files: Vec = Vec::new(); // XXX: We're silently skipping directories and files we don't have permission to read. This // sounds like the expected behavior, but I"m not entirely sure. for (file_idx, entry) in WalkDir::new(directory) @@ -251,9 +278,9 @@ pub fn index(directory: &Path) -> SearchIndex { Some("vst3") => vst3_files.push(entry.into_path()), Some("so") => { if entry.path_is_symlink() { - so_files.push(NativeSoFile::Symlink(entry.into_path())); + so_files.push(NativeFile::Symlink(entry.into_path())); } else { - so_files.push(NativeSoFile::Regular(entry.into_path())); + so_files.push(NativeFile::Regular(entry.into_path())); } } _ => (), diff --git a/tools/yabridgectl/src/utils.rs b/tools/yabridgectl/src/utils.rs index f73c6294..ff2ede7c 100644 --- a/tools/yabridgectl/src/utils.rs +++ b/tools/yabridgectl/src/utils.rs @@ -30,6 +30,7 @@ use std::process::{Command, Stdio}; use textwrap::Wrapper; use crate::config::{self, Config, KnownConfig, YABRIDGE_HOST_EXE_NAME}; +use crate::files::NativeFile; /// (Part of) the expected output when running `yabridge-host.exe`. Used to verify that everything's /// working correctly. We'll only match this prefix so we can modify the exact output at a later @@ -47,6 +48,17 @@ pub fn copy, Q: AsRef>(from: P, to: Q) -> Result { }) } +/// Wrapper around [`std::fs::create_dir_all()`](std::fs::create_dir_all) with a human readable +/// error message. +pub fn create_dir_all>(path: P) -> Result<()> { + fs::create_dir_all(&path).with_context(|| { + format!( + "Error creating directories for '{}'", + path.as_ref().display(), + ) + }) +} + /// Wrapper around [`std::fs::remove_file()`](std::fs::remove_file) with a human readable error /// message. pub fn remove_file>(path: P) -> Result<()> { @@ -66,6 +78,20 @@ pub fn symlink, Q: AsRef>(src: P, dst: Q) -> Result<()> { }) } +/// Get the type of a file, if it exists. +pub fn get_file_type(path: &Path) -> Option { + match path.metadata() { + Ok(metadata) if metadata.file_type().is_symlink() => { + Some(NativeFile::Symlink(path.to_owned())) + } + Ok(metadata) if metadata.file_type().is_dir() => { + Some(NativeFile::Directory(path.to_owned())) + } + Ok(_) => Some(NativeFile::Regular(path.to_owned())), + Err(_) => None, + } +} + /// Hash the conetnts of a file as an `i64` using Rust's built in hasher. Collisions are not a big /// issue in our situation so we can get away with this. /// From e19cbca5d1eaf7a9ac65d5cab0c578b9775b53f6 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 00:14:58 +0100 Subject: [PATCH 361/456] Actually create the IHostApplication smart pointer --- src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 49593d06..66a5b826 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -404,6 +404,10 @@ tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* context) { // VST3 plugin to this context. host_context = context; + // Automatically converted smart pointers for when the plugin performs a + // callback later + host_application = host_context; + return bridge.send_message(YaPluginBase::Initialize{ .instance_id = instance_id(), .host_context_args = Vst3HostContextProxy::ConstructArgs( From 68c95e9527934089d751703243ad8851af1d544e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 11:54:45 +0100 Subject: [PATCH 362/456] [yabridgectl] Allow skipping hash checks --- tools/yabridgectl/src/actions.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 215a74cb..84098b52 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -223,7 +223,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { options.force, config.method, &files.libyabridge_vst2, - libyabridge_vst2_hash, + Some(libyabridge_vst2_hash), &target_path, )? { num_new += 1; @@ -248,7 +248,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { true, InstallationMethod::Symlink, &module.original_module_path(), - 0, + None, &windows_module_path, )?; @@ -258,7 +258,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { options.force, config.method, files.libyabridge_vst3.as_ref().unwrap(), - libyabridge_vst3_hash, + Some(libyabridge_vst3_hash), &native_module_path, )? { num_new += 1; @@ -343,7 +343,7 @@ fn install_file( force: bool, method: InstallationMethod, from: &Path, - from_hash: i64, + from_hash: Option, to: &Path, ) -> Result { // We'll only recreate existing files when updating yabridge, when switching between the symlink @@ -354,16 +354,17 @@ fn install_file( if let Ok(metadata) = fs::symlink_metadata(&to) { match (force, &method) { (false, InstallationMethod::Copy) => { - // If the target file is already a real file (not a symlink) and its hash is - // the same as the `libyabridge-vst2.so` file we're trying to copy there, - // then we don't have to do anything - if metadata.file_type().is_file() && utils::hash_file(to)? == from_hash { - return Ok(false); + // If the target file is already a real file (not a symlink) and its hash is the + // same as that of the `from` file we're trying to copy there, then we don't have to + // do anything + if let Some(hash) = from_hash { + if metadata.file_type().is_file() && utils::hash_file(to)? == hash { + return Ok(false); + } } } (false, InstallationMethod::Symlink) => { - // If the target file is already a symlink to `libyabridge-vst2.so`, then we - // can skip this file + // If the target file is already a symlink to `from`, then we can skip this file if metadata.file_type().is_symlink() && to.read_link()? == from { return Ok(false); } From 9d33cafd3789942560c0fd594cf7862403fea236 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 12:12:48 +0100 Subject: [PATCH 363/456] [yabridgectl] Allow removing orphan VST3 modules --- tools/yabridgectl/src/actions.rs | 66 +++++++++++++++++++++++--------- tools/yabridgectl/src/files.rs | 4 +- tools/yabridgectl/src/utils.rs | 21 +++++----- 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 84098b52..f2b6ca9a 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -18,10 +18,11 @@ use anyhow::{Context, Result}; use colored::Colorize; +use std::collections::BTreeSet; use std::fs; use std::path::{Path, PathBuf}; -use crate::config::{Config, InstallationMethod, YabridgeFiles}; +use crate::config::{yabridge_vst3_home, Config, InstallationMethod, YabridgeFiles}; use crate::files; use crate::files::NativeFile; use crate::utils; @@ -193,16 +194,16 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { let mut num_new = 0; // The files we skipped during the scan because they turned out to not be plugins let mut skipped_dll_files: Vec = Vec::new(); - // `.so` files we found during scanning that didn't have a corresponding copy or symlink of - // `libyabridge-vst2.so` - let mut orphan_vst2_so_files: Vec = Vec::new(); + // `.so` files and unused VST3 modules we found during scanning that didn't have a corresponding + // copy or symlink of `libyabridge-vst2.so` + let mut orphan_files: Vec = Vec::new(); // All the VST3 modules we have set up yabridge for. We need this to detect leftover VST3 // modules in `~/.vst3/yabridge`. - let mut yabridge_vst3_bundles: Vec = Vec::new(); + let mut yabridge_vst3_bundles: BTreeSet = BTreeSet::new(); for (path, search_results) in results { num_installed += search_results.vst2_files.len(); num_installed += search_results.vst3_modules.len(); - orphan_vst2_so_files.extend(search_results.vst2_orphans().into_iter().cloned()); + orphan_files.extend(search_results.vst2_orphans().into_iter().cloned()); yabridge_vst3_bundles.extend( search_results .vst3_modules @@ -214,6 +215,8 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { if options.verbose { println!("{}:", path.display()); } + + // We'll set up the copies or symlinks for VST2 plugins for plugin in search_results.vst2_files { let target_path = plugin.with_extension("so"); @@ -233,6 +236,9 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { println!(" {}", plugin.display()); } } + + // And then create merged bundles for the VST3 plugins: + // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles if let Some(libyabridge_vst3_hash) = libyabridge_vst3_hash { for module in search_results.vst3_modules { let native_module_path = module.yabridge_native_module_path(); @@ -269,6 +275,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { } } } + if options.verbose { println!(); } @@ -284,29 +291,50 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { println!(); } - // TODO: Remove leftover files for VST3 plugins + if let Ok(dirs) = fs::read_dir(yabridge_vst3_home()) { + orphan_files.extend( + dirs.filter_map(|entry| entry.ok()) + .map(|entry| entry.path()) + .filter_map(|path| { + // Add all files and directories in `~/.vst3/yabridge` to `orphan_files` if they + // are not a VST3 module we just created + if !yabridge_vst3_bundles.contains(&path) { + utils::get_file_type(path) + } else { + None + } + }), + ); + } // Always warn about leftover files since those might cause warnings or errors when a VST host // tries to load them - if !orphan_vst2_so_files.is_empty() { + if !orphan_files.is_empty() { + let leftover_files_str = if orphan_files.len() == 1 { + format!("{} leftover file", orphan_files.len()) + } else { + format!("{} leftover files", orphan_files.len()) + }; if options.prune { - println!( - "Removing {} leftover .so files:", - orphan_vst2_so_files.len() - ); + println!("Removing {}:", leftover_files_str); } else { println!( - "Found {} leftover .so files, rerun with the '--prune' option to remove them:", - orphan_vst2_so_files.len() + "Found {}, rerun with the '--prune' option to remove them:", + leftover_files_str ); } - for file in orphan_vst2_so_files { - let path = file.path(); - - println!("- {}", path.display()); + for file in orphan_files { + println!("- {}", file.path().display()); if options.prune { - utils::remove_file(path)?; + match file { + NativeFile::Regular(path) | NativeFile::Symlink(path) => { + utils::remove_file(path)?; + } + NativeFile::Directory(path) => { + utils::remove_dir_all(path)?; + } + } } } diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index 9eb81ebd..af284480 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -221,7 +221,7 @@ impl SearchResults { // because they are not in any of the directories we're indexing. installation_status.extend(self.vst3_modules.iter().map(|module| { let module_path = module.yabridge_native_module_path(); - let install_type = get_file_type(&module_path); + let install_type = get_file_type(module_path.clone()); (module_path, install_type) })); @@ -372,7 +372,7 @@ impl SearchIndex { reconstructed_path.push(architecture.vst_arch()); reconstructed_path.push(module_name); - return reconstructed_path.exists(); + reconstructed_path.exists() }) .unwrap_or(false); diff --git a/tools/yabridgectl/src/utils.rs b/tools/yabridgectl/src/utils.rs index ff2ede7c..b50dfc41 100644 --- a/tools/yabridgectl/src/utils.rs +++ b/tools/yabridgectl/src/utils.rs @@ -25,7 +25,7 @@ use std::fs; use std::hash::Hasher; use std::os::unix::fs as unix_fs; use std::os::unix::process::CommandExt; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use textwrap::Wrapper; @@ -59,6 +59,13 @@ pub fn create_dir_all>(path: P) -> Result<()> { }) } +/// Wrapper around [`std::fs::remove_dir_all()`](std::fs::remove_dir_all) with a human readable +/// error message. +pub fn remove_dir_all>(path: P) -> Result<()> { + fs::remove_dir_all(&path) + .with_context(|| format!("Could not remove directory '{}'", path.as_ref().display())) +} + /// Wrapper around [`std::fs::remove_file()`](std::fs::remove_file) with a human readable error /// message. pub fn remove_file>(path: P) -> Result<()> { @@ -79,15 +86,11 @@ pub fn symlink, Q: AsRef>(src: P, dst: Q) -> Result<()> { } /// Get the type of a file, if it exists. -pub fn get_file_type(path: &Path) -> Option { +pub fn get_file_type(path: PathBuf) -> Option { match path.metadata() { - Ok(metadata) if metadata.file_type().is_symlink() => { - Some(NativeFile::Symlink(path.to_owned())) - } - Ok(metadata) if metadata.file_type().is_dir() => { - Some(NativeFile::Directory(path.to_owned())) - } - Ok(_) => Some(NativeFile::Regular(path.to_owned())), + Ok(metadata) if metadata.file_type().is_symlink() => Some(NativeFile::Symlink(path)), + Ok(metadata) if metadata.file_type().is_dir() => Some(NativeFile::Directory(path)), + Ok(_) => Some(NativeFile::Regular(path)), Err(_) => None, } } From a3e76b337021693030fbce17b70bb961217c099f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 12:30:38 +0100 Subject: [PATCH 364/456] [yabridgectl] Warn for duplicate VST3 plugins Since we can't have multiple plugins with the same name this way. --- tools/yabridgectl/src/actions.rs | 37 ++++++++++++++++++++++---------- tools/yabridgectl/src/files.rs | 12 ++++++++++- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index f2b6ca9a..7aa565d7 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -18,13 +18,12 @@ use anyhow::{Context, Result}; use colored::Colorize; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::fs; use std::path::{Path, PathBuf}; use crate::config::{yabridge_vst3_home, Config, InstallationMethod, YabridgeFiles}; -use crate::files; -use crate::files::NativeFile; +use crate::files::{self, LibArchitecture, NativeFile}; use crate::utils; use crate::utils::{verify_path_setup, verify_wine_setup}; @@ -199,17 +198,11 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { let mut orphan_files: Vec = Vec::new(); // All the VST3 modules we have set up yabridge for. We need this to detect leftover VST3 // modules in `~/.vst3/yabridge`. - let mut yabridge_vst3_bundles: BTreeSet = BTreeSet::new(); + let mut yabridge_vst3_bundles: BTreeMap> = BTreeMap::new(); for (path, search_results) in results { num_installed += search_results.vst2_files.len(); num_installed += search_results.vst3_modules.len(); orphan_files.extend(search_results.vst2_orphans().into_iter().cloned()); - yabridge_vst3_bundles.extend( - search_results - .vst3_modules - .iter() - .map(|module| module.yabridge_bundle_home()), - ); skipped_dll_files.extend(search_results.skipped_files); if options.verbose { @@ -241,6 +234,28 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles if let Some(libyabridge_vst3_hash) = libyabridge_vst3_hash { for module in search_results.vst3_modules { + // Check if we already set up the same architecture version of the plugin (since + // 32-bit and 64-bit versions of the plugin cna live inside of the same bundle), and + // show a warning if we come across any duplicates. + let already_installed_architectures = yabridge_vst3_bundles + .entry(module.yabridge_bundle_home()) + .or_insert_with(|| BTreeSet::new()); + if !already_installed_architectures.insert(module.architecture()) { + eprintln!( + "{}", + utils::wrap(&format!( + "{}: The {} version of '{}' has already been provided by another Wine \ + prefix, skipping '{}'\n", + "WARNING".red(), + module.architecture(), + module.yabridge_bundle_home().display(), + module.original_module_path().display(), + )) + ); + + continue; + } + let native_module_path = module.yabridge_native_module_path(); // For VST3 plugins we'll first have to create the bundle structure @@ -298,7 +313,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { .filter_map(|path| { // Add all files and directories in `~/.vst3/yabridge` to `orphan_files` if they // are not a VST3 module we just created - if !yabridge_vst3_bundles.contains(&path) { + if !yabridge_vst3_bundles.contains_key(&path) { utils::get_file_type(path) } else { None diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index af284480..1c82331b 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -21,6 +21,7 @@ use anyhow::{Context, Result}; use lazy_static::lazy_static; use rayon::prelude::*; use std::collections::{BTreeMap, HashMap}; +use std::fmt::Display; use std::path::{Path, PathBuf}; use std::process::Command; use walkdir::WalkDir; @@ -177,12 +178,21 @@ impl Vst3Module { } /// The architecture of a `.dll` file. Needed so we can create a merged bundle for VST3 plugins. -#[derive(Debug, Clone, PartialEq, Eq, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)] pub enum LibArchitecture { Dll32, Dll64, } +impl Display for LibArchitecture { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + LibArchitecture::Dll32 => write!(f, "32-bit"), + LibArchitecture::Dll64 => write!(f, "64-bit"), + } + } +} + impl LibArchitecture { /// Get the corresponding VST3 architecture directory name. See /// https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html. From a0098034ed2111a42b55c0a7ed44fceb0d2d6971 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 12:38:47 +0100 Subject: [PATCH 365/456] [yabridgectl] Rename the Vst3Module functions Creating a clear naming scheme here is more difficult than it should be. --- tools/yabridgectl/src/actions.rs | 31 +++++++++++++++---------------- tools/yabridgectl/src/files.rs | 12 ++++++------ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 7aa565d7..ea28663f 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -238,7 +238,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { // 32-bit and 64-bit versions of the plugin cna live inside of the same bundle), and // show a warning if we come across any duplicates. let already_installed_architectures = yabridge_vst3_bundles - .entry(module.yabridge_bundle_home()) + .entry(module.target_bundle_home()) .or_insert_with(|| BTreeSet::new()); if !already_installed_architectures.insert(module.architecture()) { eprintln!( @@ -248,7 +248,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { prefix, skipping '{}'\n", "WARNING".red(), module.architecture(), - module.yabridge_bundle_home().display(), + module.target_bundle_home().display(), module.original_module_path().display(), )) ); @@ -256,14 +256,23 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { continue; } - let native_module_path = module.yabridge_native_module_path(); - - // For VST3 plugins we'll first have to create the bundle structure + // We're building a merged VST3 bundle containing both a copy or symlink to + // `libyabridge-vst3.so` and the Windows VST3 plugin + let native_module_path = module.target_native_module_path(); utils::create_dir_all(native_module_path.parent().unwrap())?; + if install_file( + options.force, + config.method, + files.libyabridge_vst3.as_ref().unwrap(), + Some(libyabridge_vst3_hash), + &native_module_path, + )? { + num_new += 1; + } // We'll then symlink the Windows VST3 module to that bundle to create a merged // bundle: https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles - let windows_module_path = module.yabridge_windows_module_path(); + let windows_module_path = module.target_windows_module_path(); utils::create_dir_all(windows_module_path.parent().unwrap())?; install_file( true, @@ -275,16 +284,6 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { // TODO: Symlink resources and presets - if install_file( - options.force, - config.method, - files.libyabridge_vst3.as_ref().unwrap(), - Some(libyabridge_vst3_hash), - &native_module_path, - )? { - num_new += 1; - } - if options.verbose { println!(" {}", module.original_path().display()); } diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index 1c82331b..1e7fe1d5 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -142,13 +142,13 @@ impl Vst3Module { /// /// FIXME: How do we solve naming clashes from the same VST3 plugin being installed to multiple /// Wine prefixes? - pub fn yabridge_bundle_home(&self) -> PathBuf { + pub fn target_bundle_home(&self) -> PathBuf { yabridge_vst3_home().join(self.original_module_name()) } /// Get the path to the `libyabridge.so` file in `~/.vst3` corresponding to the bridged version /// of this module. - pub fn yabridge_native_module_path(&self) -> PathBuf { + pub fn target_native_module_path(&self) -> PathBuf { let native_module_name = match &self { Vst3Module::Legacy(path, _) | Vst3Module::Bundle(path, _) => path .with_extension("so") @@ -159,7 +159,7 @@ impl Vst3Module { .to_owned(), }; - let mut path = self.yabridge_bundle_home(); + let mut path = self.target_bundle_home(); path.push("Contents"); path.push("x86_64-linux"); path.push(native_module_name); @@ -168,8 +168,8 @@ impl Vst3Module { /// Get the path to where we'll symlink `original_module_path`. This is part of the merged VST3 /// bundle in `~/.vst3/yabridge`. - pub fn yabridge_windows_module_path(&self) -> PathBuf { - let mut path = self.yabridge_bundle_home(); + pub fn target_windows_module_path(&self) -> PathBuf { + let mut path = self.target_bundle_home(); path.push("Contents"); path.push(self.architecture().vst_arch()); path.push(self.original_module_name()); @@ -230,7 +230,7 @@ impl SearchResults { // And for VST3 modules. We have not stored the paths to the corresponding `.so` files yet // because they are not in any of the directories we're indexing. installation_status.extend(self.vst3_modules.iter().map(|module| { - let module_path = module.yabridge_native_module_path(); + let module_path = module.target_native_module_path(); let install_type = get_file_type(module_path.clone()); (module_path, install_type) })); From d79ccc75e6edef98cf923ac876c9a264783cf2c1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 12:45:14 +0100 Subject: [PATCH 366/456] [yabridgectl] Symlink VST 3.6.10 bundle resources Although I haven't run into any of these 'new' bundles yet. Everything's still in the legacy format. --- tools/yabridgectl/src/actions.rs | 14 +++++++++++++- tools/yabridgectl/src/files.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index ea28663f..c2ab4212 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -282,7 +282,19 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { &windows_module_path, )?; - // TODO: Symlink resources and presets + // If `module` is a bundle, then it may contain a `Resources` directory with + // screenshots and documentation + // TODO: Also symlink presets, but this is a bit more involved. See + // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#win7preset + if let Some(original_resources_dir) = module.original_resources_dir() { + install_file( + false, + InstallationMethod::Symlink, + &original_resources_dir, + None, + &module.target_resources_dir(), + )?; + } if options.verbose { println!(" {}", module.original_path().display()); diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index 1e7fe1d5..81243034 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -138,6 +138,23 @@ impl Vst3Module { } } + /// If this was a VST 3.6.10 style bundle, then return the path to the `Resources` directory if + /// it has one. + pub fn original_resources_dir(&self) -> Option { + match &self { + Vst3Module::Bundle(bundle_home, _) => { + let mut path = bundle_home.join("Contents"); + path.push("Resources"); + if path.exists() { + Some(path) + } else { + None + } + } + Vst3Module::Legacy(_, _) => None, + } + } + /// Get the path to the bundle in `~/.vst3` corresponding to the bridged version of this module. /// /// FIXME: How do we solve naming clashes from the same VST3 plugin being installed to multiple @@ -175,6 +192,16 @@ impl Vst3Module { path.push(self.original_module_name()); path } + + /// If the Windows VST3 plugin we're bridging was in a VST 3.6.10 style bundle and had a + /// resources directory, then we'll symlink that directory to here so the host can access all + /// its original resources. + pub fn target_resources_dir(&self) -> PathBuf { + let mut path = self.target_bundle_home(); + path.push("Contents"); + path.push("Resources"); + path + } } /// The architecture of a `.dll` file. Needed so we can create a merged bundle for VST3 plugins. From 9ac437f02bbf93e734c5ed415f0b20143e235250 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 13:04:30 +0100 Subject: [PATCH 367/456] [yabridgectl] Fix VST3 installation status display --- tools/yabridgectl/src/files.rs | 7 ++++--- tools/yabridgectl/src/utils.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/yabridgectl/src/files.rs b/tools/yabridgectl/src/files.rs index 81243034..8e6426d7 100644 --- a/tools/yabridgectl/src/files.rs +++ b/tools/yabridgectl/src/files.rs @@ -257,9 +257,10 @@ impl SearchResults { // And for VST3 modules. We have not stored the paths to the corresponding `.so` files yet // because they are not in any of the directories we're indexing. installation_status.extend(self.vst3_modules.iter().map(|module| { - let module_path = module.target_native_module_path(); - let install_type = get_file_type(module_path.clone()); - (module_path, install_type) + ( + module.original_path().to_owned(), + get_file_type(module.target_native_module_path()), + ) })); installation_status diff --git a/tools/yabridgectl/src/utils.rs b/tools/yabridgectl/src/utils.rs index b50dfc41..7e70be33 100644 --- a/tools/yabridgectl/src/utils.rs +++ b/tools/yabridgectl/src/utils.rs @@ -87,7 +87,7 @@ pub fn symlink, Q: AsRef>(src: P, dst: Q) -> Result<()> { /// Get the type of a file, if it exists. pub fn get_file_type(path: PathBuf) -> Option { - match path.metadata() { + match path.symlink_metadata() { Ok(metadata) if metadata.file_type().is_symlink() => Some(NativeFile::Symlink(path)), Ok(metadata) if metadata.file_type().is_dir() => Some(NativeFile::Directory(path)), Ok(_) => Some(NativeFile::Regular(path)), From 4f8bfbcda641a8fb962e0eece39b8618b64274a7 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 13:12:19 +0100 Subject: [PATCH 368/456] [yabridgectl] Fix clippy lints --- tools/yabridgectl/src/actions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index c2ab4212..59bc2e20 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -239,7 +239,7 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { // show a warning if we come across any duplicates. let already_installed_architectures = yabridge_vst3_bundles .entry(module.target_bundle_home()) - .or_insert_with(|| BTreeSet::new()); + .or_insert_with(BTreeSet::new); if !already_installed_architectures.insert(module.architecture()) { eprintln!( "{}", From 1186e7d775c2315c757581fa3036f31ec2480a59 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 13:22:54 +0100 Subject: [PATCH 369/456] Store whether two objects are connected directly In the host context. So when the plugin wants to create an `IMessage` object to send a message to the other object, we don't have to go through the host. --- src/common/serialization/vst3/host-context-proxy.h | 9 +++++++++ src/wine-host/bridges/vst3.cpp | 12 +++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/common/serialization/vst3/host-context-proxy.h b/src/common/serialization/vst3/host-context-proxy.h index e2afe641..0aab8530 100644 --- a/src/common/serialization/vst3/host-context-proxy.h +++ b/src/common/serialization/vst3/host-context-proxy.h @@ -97,6 +97,15 @@ class Vst3HostContextProxy : public YaHostApplication { return arguments.owner_instance_id; } + /** + * Used to shortcut calls to + * `IHostApplication::createInstance(IMessage::iid, IMessage::iid, &obj)` + * when two objects (a processor and a controller instance, for example) are + * directly connected. This way we don't have to proxy the message created + * by the host, which can save a lot of resoruces. + */ + std::atomic_bool are_objects_directly_connected = false; + private: ConstructArgs arguments; }; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 756f948e..ae60b580 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -170,9 +170,19 @@ void Vst3Bridge::run() { }, [&](const YaConnectionPoint::Connect& request) -> YaConnectionPoint::Connect::Response { - // We can directly connect the underlying objects + // We can directly connect the underlying objects. We'll mark + // that we're using a direct connection on our host context + // proxy so that when the plugin wants to create an `IMessage` + // object, we can keep everything local and we Don't have to go + // through the host. // TODO: Add support for connecting objects through a proxy // object provided by the host + if (object_instances[request.instance_id].host_context_proxy) { + object_instances[request.instance_id] + .host_context_proxy->are_objects_directly_connected = + true; + } + return object_instances[request.instance_id] .connection_point->connect( object_instances[request.other_instance_id] From 1be9f53bb327f57686212610a6bd090a766df1c3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 13:47:01 +0100 Subject: [PATCH 370/456] Add missing compile flags --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 999932bb..1c7b9d38 100644 --- a/meson.build +++ b/meson.build @@ -277,7 +277,7 @@ if with_vst3 # This does mean that we now have a lot of defines in our code, but the # alternative would be patching every location in the SDK where they include # `windows.h` - compile_args : vst3_wine_compiler_options, + compile_args : vst3_compiler_options + vst3_wine_compiler_options, ) # And another time for the 32-bit version From a86c37a21da7f7ca0e161d157a9ade980e5ab584 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 13:48:31 +0100 Subject: [PATCH 371/456] Partially implement IHostApplication For now only works for directly connected components. --- README.md | 6 +-- .../bridges/vst3-impls/host-context-proxy.cpp | 48 +++++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4ea68487..58df5366 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ This branch is still very far removed from being in a usable state. Below is an incomplete list of things that still have to be done before this can be used: - Interfaces left to implement: - - `IHostApplication::createComponent()` - - `IConnectionPoint::notify()`, and support for indirectly connecting - components through message passing proxies + - `IHostApplication::createComponent()` for indirectly connected objects + - `IConnectionPoint::notify()`, and support for indirectly connecting objects + through connction proxies - `IEditController2` - All other mandatory interfaces - All other optional interfaces diff --git a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp index c3661e16..add3ca13 100644 --- a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp @@ -18,6 +18,8 @@ #include +#include + Vst3HostContextProxyImpl::Vst3HostContextProxyImpl( Vst3Bridge& bridge, Vst3HostContextProxy::ConstructArgs&& args) @@ -54,10 +56,48 @@ Vst3HostContextProxyImpl::getName(Steinberg::Vst::String128 name) { } tresult PLUGIN_API -Vst3HostContextProxyImpl::createInstance(Steinberg::TUID cid, +Vst3HostContextProxyImpl::createInstance(Steinberg::TUID /*cid*/, Steinberg::TUID _iid, void** obj) { - // TODO: Implement - std::cerr << "TODO: IHostApplication::createInstance()" << std::endl; - return Steinberg::kNotImplemented; + // Class IDs don't have a meaning here, they just mirrored the interface + // from `IPlugFactory::createInstance()` + constexpr size_t uid_size = sizeof(Steinberg::TUID); + if (!_iid || strnlen(_iid, uid_size) < uid_size) { + return Steinberg::kInvalidArgument; + } + + Steinberg::FUID iid = Steinberg::FUID::fromTUID(_iid); + // If an objects wants to create an `IMessage` object to send it to some + // object it is directly connected to, then we can keep everything local + // on the Wine side. This is mostly an optimization, because it saves a + // lot of unnecessary back and forth communication. + if (are_objects_directly_connected) { + if (iid == Steinberg::Vst::IMessage::iid) { + // TODO: Add logging for this on verbosity level 1 + *obj = new Steinberg::Vst::HostMessage{}; + return Steinberg::kResultTrue; + } else if (iid == Steinberg::Vst::IAttributeList::iid) { + // TODO: Add logging for this on verbosity level 1 + *obj = new Steinberg::Vst::HostAttributeList{}; + return Steinberg::kResultTrue; + } else { + // When the host requests an interface we do not (yet) implement, + // we'll print a recognizable log message + const Steinberg::FUID uid = Steinberg::FUID::fromTUID(_iid); + std::cerr + << "TODO: Implement unknown interface logging on Wine side " + "for Vst3HostContextProxyImpl::createInstance" + << std::endl; + + return Steinberg::kNotImplemented; + } + } else { + // TODO: Implement for objects that are not directly connected + std::cerr + << "TODO: Creating and instances in " + "IHostApplication::createInstance() for indirectly " + "connected objects has not yet been implemented" + << std::endl; + return Steinberg::kNotImplemented; + } } From 50b50418f4fe6ab6dc6fe7427edce8e3a19bd7e4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 13:58:32 +0100 Subject: [PATCH 372/456] Fix messages between directly connecting objects iZotope plugins will already send messages when connect() is called on the first object, so this flag has to be set on both host contexts at the same time. --- src/plugin/bridges/vst3-impls/plugin-factory.cpp | 3 ++- src/wine-host/bridges/vst3-impls/host-context-proxy.cpp | 4 +++- src/wine-host/bridges/vst3.cpp | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.cpp b/src/plugin/bridges/vst3-impls/plugin-factory.cpp index a795a5fe..ac0b4ca9 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-factory.cpp @@ -30,7 +30,7 @@ YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, void** obj) { // Class IDs may be padded with null bytes constexpr size_t uid_size = sizeof(Steinberg::TUID); - if (!cid || !_iid || strnlen(_iid, uid_size) < uid_size) { + if (!cid || !_iid || !obj || strnlen(_iid, uid_size) < uid_size) { return Steinberg::kInvalidArgument; } @@ -62,6 +62,7 @@ YaPluginFactoryImpl::createInstance(Steinberg::FIDString cid, bridge.logger.log_unknown_interface( "In IPluginFactory::createInstance()", uid); + *obj = nullptr; return Steinberg::kNotImplemented; } diff --git a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp index add3ca13..0930c697 100644 --- a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp @@ -62,7 +62,7 @@ Vst3HostContextProxyImpl::createInstance(Steinberg::TUID /*cid*/, // Class IDs don't have a meaning here, they just mirrored the interface // from `IPlugFactory::createInstance()` constexpr size_t uid_size = sizeof(Steinberg::TUID); - if (!_iid || strnlen(_iid, uid_size) < uid_size) { + if (!_iid || !obj || strnlen(_iid, uid_size) < uid_size) { return Steinberg::kInvalidArgument; } @@ -98,6 +98,8 @@ Vst3HostContextProxyImpl::createInstance(Steinberg::TUID /*cid*/, "IHostApplication::createInstance() for indirectly " "connected objects has not yet been implemented" << std::endl; + + *obj = nullptr; return Steinberg::kNotImplemented; } } diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index ae60b580..64a77552 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -181,6 +181,9 @@ void Vst3Bridge::run() { object_instances[request.instance_id] .host_context_proxy->are_objects_directly_connected = true; + object_instances[request.other_instance_id] + .host_context_proxy->are_objects_directly_connected = + true; } return object_instances[request.instance_id] From b0fc8f2c5f8bab7a6a528693cde8da61419ce6fe Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 14:39:25 +0100 Subject: [PATCH 373/456] Remove are_objects_directly_connected check It's not necessary, since all of these objects are simple data objects that will be passed as arguments to other functions. When we have to pass through one of those functions we can just serialize the objects at that point. --- README.md | 1 - .../serialization/vst3/host-context-proxy.h | 9 ---- .../bridges/vst3-impls/host-context-proxy.cpp | 48 +++++++------------ src/wine-host/bridges/vst3.cpp | 15 +----- 4 files changed, 18 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 58df5366..c216d4b9 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ This branch is still very far removed from being in a usable state. Below is an incomplete list of things that still have to be done before this can be used: - Interfaces left to implement: - - `IHostApplication::createComponent()` for indirectly connected objects - `IConnectionPoint::notify()`, and support for indirectly connecting objects through connction proxies - `IEditController2` diff --git a/src/common/serialization/vst3/host-context-proxy.h b/src/common/serialization/vst3/host-context-proxy.h index 0aab8530..e2afe641 100644 --- a/src/common/serialization/vst3/host-context-proxy.h +++ b/src/common/serialization/vst3/host-context-proxy.h @@ -97,15 +97,6 @@ class Vst3HostContextProxy : public YaHostApplication { return arguments.owner_instance_id; } - /** - * Used to shortcut calls to - * `IHostApplication::createInstance(IMessage::iid, IMessage::iid, &obj)` - * when two objects (a processor and a controller instance, for example) are - * directly connected. This way we don't have to proxy the message created - * by the host, which can save a lot of resoruces. - */ - std::atomic_bool are_objects_directly_connected = false; - private: ConstructArgs arguments; }; diff --git a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp index 0930c697..38e9af6d 100644 --- a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp @@ -66,40 +66,26 @@ Vst3HostContextProxyImpl::createInstance(Steinberg::TUID /*cid*/, return Steinberg::kInvalidArgument; } + // These objects don't have to be created by the actual host since they'll + // only be used as an argument to other functions. We can just serialize + // them at that point. Steinberg::FUID iid = Steinberg::FUID::fromTUID(_iid); - // If an objects wants to create an `IMessage` object to send it to some - // object it is directly connected to, then we can keep everything local - // on the Wine side. This is mostly an optimization, because it saves a - // lot of unnecessary back and forth communication. - if (are_objects_directly_connected) { - if (iid == Steinberg::Vst::IMessage::iid) { - // TODO: Add logging for this on verbosity level 1 - *obj = new Steinberg::Vst::HostMessage{}; - return Steinberg::kResultTrue; - } else if (iid == Steinberg::Vst::IAttributeList::iid) { - // TODO: Add logging for this on verbosity level 1 - *obj = new Steinberg::Vst::HostAttributeList{}; - return Steinberg::kResultTrue; - } else { - // When the host requests an interface we do not (yet) implement, - // we'll print a recognizable log message - const Steinberg::FUID uid = Steinberg::FUID::fromTUID(_iid); - std::cerr - << "TODO: Implement unknown interface logging on Wine side " - "for Vst3HostContextProxyImpl::createInstance" - << std::endl; - - return Steinberg::kNotImplemented; - } + if (iid == Steinberg::Vst::IMessage::iid) { + // TODO: Add logging for this on verbosity level 1 + *obj = new Steinberg::Vst::HostMessage{}; + return Steinberg::kResultTrue; + } else if (iid == Steinberg::Vst::IAttributeList::iid) { + // TODO: Add logging for this on verbosity level 1 + *obj = new Steinberg::Vst::HostAttributeList{}; + return Steinberg::kResultTrue; } else { - // TODO: Implement for objects that are not directly connected - std::cerr - << "TODO: Creating and instances in " - "IHostApplication::createInstance() for indirectly " - "connected objects has not yet been implemented" - << std::endl; + // When the host requests an interface we do not (yet) implement, + // we'll print a recognizable log message + const Steinberg::FUID uid = Steinberg::FUID::fromTUID(_iid); + std::cerr << "TODO: Implement unknown interface logging on Wine side " + "for Vst3HostContextProxyImpl::createInstance" + << std::endl; - *obj = nullptr; return Steinberg::kNotImplemented; } } diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 64a77552..756f948e 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -170,22 +170,9 @@ void Vst3Bridge::run() { }, [&](const YaConnectionPoint::Connect& request) -> YaConnectionPoint::Connect::Response { - // We can directly connect the underlying objects. We'll mark - // that we're using a direct connection on our host context - // proxy so that when the plugin wants to create an `IMessage` - // object, we can keep everything local and we Don't have to go - // through the host. + // We can directly connect the underlying objects // TODO: Add support for connecting objects through a proxy // object provided by the host - if (object_instances[request.instance_id].host_context_proxy) { - object_instances[request.instance_id] - .host_context_proxy->are_objects_directly_connected = - true; - object_instances[request.other_instance_id] - .host_context_proxy->are_objects_directly_connected = - true; - } - return object_instances[request.instance_id] .connection_point->connect( object_instances[request.other_instance_id] From f4c871f07ec2d6156ecf5d97bee2eed9877d19c0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Thu, 24 Dec 2020 14:46:02 +0100 Subject: [PATCH 374/456] Add pointer casts to instance creation I copied this from the SDK implementation and they don't do any pointer casts there because it's not strictly necessary, but they're relying on implementation details that may not always hold true. --- src/wine-host/bridges/vst3-impls/host-context-proxy.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp index 38e9af6d..438d54f4 100644 --- a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp @@ -72,11 +72,13 @@ Vst3HostContextProxyImpl::createInstance(Steinberg::TUID /*cid*/, Steinberg::FUID iid = Steinberg::FUID::fromTUID(_iid); if (iid == Steinberg::Vst::IMessage::iid) { // TODO: Add logging for this on verbosity level 1 - *obj = new Steinberg::Vst::HostMessage{}; + *obj = static_cast( + new Steinberg::Vst::HostMessage{}); return Steinberg::kResultTrue; } else if (iid == Steinberg::Vst::IAttributeList::iid) { // TODO: Add logging for this on verbosity level 1 - *obj = new Steinberg::Vst::HostAttributeList{}; + *obj = static_cast( + new Steinberg::Vst::HostAttributeList{}); return Steinberg::kResultTrue; } else { // When the host requests an interface we do not (yet) implement, From b6304c83b5e5de1bfc2b3afdfa9654319367d8b2 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 00:54:08 +0100 Subject: [PATCH 375/456] Implement IAttributeList --- meson.build | 2 + src/common/serialization/vst3/README.md | 1 + .../serialization/vst3/attribute-list.cpp | 115 ++++++++++++++++++ .../serialization/vst3/attribute-list.h | 90 ++++++++++++++ 4 files changed, 208 insertions(+) create mode 100644 src/common/serialization/vst3/attribute-list.cpp create mode 100644 src/common/serialization/vst3/attribute-list.h diff --git a/meson.build b/meson.build index 1c7b9d38..f722f8ee 100644 --- a/meson.build +++ b/meson.build @@ -87,6 +87,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/component-handler/component-handler.cpp', 'src/common/serialization/vst3/host-context/host-application.cpp', + 'src/common/serialization/vst3/attribute-list.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component-handler-proxy.cpp', 'src/common/serialization/vst3/event-list.cpp', @@ -140,6 +141,7 @@ if with_vst3 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/component-handler/component-handler.cpp', 'src/common/serialization/vst3/host-context/host-application.cpp', + 'src/common/serialization/vst3/attribute-list.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component-handler-proxy.cpp', 'src/common/serialization/vst3/event-list.cpp', diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index bc6837d8..f4aabc68 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -36,6 +36,7 @@ implemented for serialization purposes: | yabridge class | Interfaces | Notes | | -------------------- | ------------------- | ---------------------------------------------------------------------- | +| `YaAttributeList` | `IAttributeList` | | | `YaEventList` | `IEventList` | Comes with a lot of serialization wrappers around the related structs. | | `YaParameterChanges` | `IParameterChanges` | | | `YaParamValueQueue` | `IParamValueQueue` | | diff --git a/src/common/serialization/vst3/attribute-list.cpp b/src/common/serialization/vst3/attribute-list.cpp new file mode 100644 index 00000000..33311fe3 --- /dev/null +++ b/src/common/serialization/vst3/attribute-list.cpp @@ -0,0 +1,115 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "attribute-list.h" + +YaAttributeList::YaAttributeList(){FUNKNOWN_CTOR} + +YaAttributeList::~YaAttributeList() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_FUNKNOWN_METHODS(YaAttributeList, + Steinberg::Vst::IAttributeList, + Steinberg::Vst::IAttributeList::iid) +#pragma GCC diagnostic pop + +tresult PLUGIN_API YaAttributeList::setInt(AttrID id, int64 value) { + attrs_int[id] = value; + return Steinberg::kResultOk; +} + +tresult PLUGIN_API YaAttributeList::getInt(AttrID id, int64& value) { + if (const auto it = attrs_int.find(id); it != attrs_int.end()) { + value = it->second; + return Steinberg::kResultOk; + } else { + return Steinberg::kResultFalse; + } +} + +tresult PLUGIN_API YaAttributeList::setFloat(AttrID id, double value) { + attrs_float[id] = value; + return Steinberg::kResultOk; +} + +tresult PLUGIN_API YaAttributeList::getFloat(AttrID id, double& value) { + if (const auto it = attrs_float.find(id); it != attrs_float.end()) { + value = it->second; + return Steinberg::kResultOk; + } else { + return Steinberg::kResultFalse; + } +} + +tresult PLUGIN_API +YaAttributeList::setString(AttrID id, const Steinberg::Vst::TChar* string) { + if (!string) { + return Steinberg::kInvalidArgument; + } + + attrs_string[id] = tchar_pointer_to_u16string(string); + return Steinberg::kResultOk; +} + +tresult PLUGIN_API YaAttributeList::getString(AttrID id, + Steinberg::Vst::TChar* string, + uint32 sizeInBytes) { + if (!string) { + return Steinberg::kInvalidArgument; + } + + if (const auto it = attrs_string.find(id); it != attrs_string.end()) { + // We may only copy `sizeInBytes / 2` UTF-16 characters to `string`, + // We'll also have to make sure it's null terminated, so we'll reserve + // another byte for that. + const size_t copy_characters = std::min( + (static_cast(sizeInBytes) / sizeof(Steinberg::Vst::TChar)) - + 1, + it->second.size()); + std::copy_n(it->second.begin(), copy_characters, string); + string[copy_characters] = 0; + + return Steinberg::kResultOk; + } else { + return Steinberg::kResultFalse; + } +} + +tresult PLUGIN_API YaAttributeList::setBinary(AttrID id, + const void* data, + uint32 sizeInBytes) { + if (!data) { + return Steinberg::kInvalidArgument; + } + + const uint8_t* data_bytes = static_cast(data); + attrs_binary[id].assign(data_bytes, data_bytes + sizeInBytes); + return Steinberg::kResultOk; +} +tresult PLUGIN_API YaAttributeList::getBinary(AttrID id, + const void*& data, + uint32& sizeInBytes) { + if (const auto it = attrs_binary.find(id); it != attrs_binary.end()) { + data = it->second.data(); + sizeInBytes = it->second.size(); + return Steinberg::kResultOk; + } else { + return Steinberg::kResultFalse; + } +} diff --git a/src/common/serialization/vst3/attribute-list.h b/src/common/serialization/vst3/attribute-list.h new file mode 100644 index 00000000..f7cd586c --- /dev/null +++ b/src/common/serialization/vst3/attribute-list.h @@ -0,0 +1,90 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include +#include +#include + +#include "base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IAttributeList` for storing parameters in `YaMessage`. + */ +class YaAttributeList : public Steinberg::Vst::IAttributeList { + public: + /** + * Default constructor with an empty attributeList. The plugin can use this + * to write a attributeList. + */ + YaAttributeList(); + + ~YaAttributeList(); + + DECLARE_FUNKNOWN_METHODS + + virtual tresult PLUGIN_API setInt(AttrID id, int64 value) override; + virtual tresult PLUGIN_API getInt(AttrID id, int64& value) override; + virtual tresult PLUGIN_API setFloat(AttrID id, double value) override; + virtual tresult PLUGIN_API getFloat(AttrID id, double& value) override; + virtual tresult PLUGIN_API + setString(AttrID id, const Steinberg::Vst::TChar* string) override; + virtual tresult PLUGIN_API getString(AttrID id, + Steinberg::Vst::TChar* string, + uint32 sizeInBytes) override; + virtual tresult PLUGIN_API setBinary(AttrID id, + const void* data, + uint32 sizeInBytes) override; + virtual tresult PLUGIN_API getBinary(AttrID id, + const void*& data, + uint32& sizeInBytes) override; + + template + void serialize(S& s) { + s.ext(attrs_int, bitsery::ext::StdMap{1 << 20}, + [](S& s, AttrID& key, int64& value) { + s.text1b(key, 1024); + s.value8b(value); + }); + s.ext(attrs_float, bitsery::ext::StdMap{1 << 20}, + [](S& s, AttrID& key, double& value) { + s.text1b(key, 1024); + s.value8b(value); + }); + s.ext(attrs_string, bitsery::ext::StdMap{1 << 20}, + [](S& s, AttrID& key, double& value) { + s.text1b(key, 1024); + s.text2b(value, 1 << 20); + }); + s.ext(attrs_binary, bitsery::ext::StdMap{1 << 20}, + [](S& s, AttrID& key, std::vector& value) { + s.text1b(key, 1024); + s.container1b(value, 1 << 20); + }); + } + + private: + std::unordered_map attrs_int; + std::unordered_map attrs_float; + std::unordered_map attrs_string; + std::unordered_map> attrs_binary; +}; From 7cfd1982dd759169874435e4c2b6312bc8dccb26 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 01:00:57 +0100 Subject: [PATCH 376/456] Fix typos in comment --- src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp | 4 ++-- src/wine-host/bridges/vst3-impls/host-context-proxy.cpp | 4 ++-- src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp index 8f316833..5a545544 100644 --- a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp @@ -22,8 +22,8 @@ Vst3ComponentHandlerProxyImpl::Vst3ComponentHandlerProxyImpl( Vst3Bridge& bridge, Vst3ComponentHandlerProxy::ConstructArgs&& args) : Vst3ComponentHandlerProxy(std::move(args)), bridge(bridge) { - // The lifecycle is thos object is managed together with that of the plugin - // object instance instance this belongs to + // The lifecycle of this object is managed together with that of the plugin + // object instance this host context got passed to } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp index 438d54f4..c6050b8d 100644 --- a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp @@ -24,8 +24,8 @@ Vst3HostContextProxyImpl::Vst3HostContextProxyImpl( Vst3Bridge& bridge, Vst3HostContextProxy::ConstructArgs&& args) : Vst3HostContextProxy(std::move(args)), bridge(bridge) { - // The lifecycle is thos object is managed together with that of the plugin - // object instance instance this belongs to + // The lifecycle of this object is managed together with that of the plugin + // object instance this host context got passed to } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp index 3ceff0de..6639b661 100644 --- a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp @@ -22,8 +22,8 @@ Vst3PlugFrameProxyImpl::Vst3PlugFrameProxyImpl( Vst3Bridge& bridge, Vst3PlugFrameProxy::ConstructArgs&& args) : Vst3PlugFrameProxy(std::move(args)), bridge(bridge) { - // The lifecycle is thos object is managed together with that of the plugin - // object instance instance this belongs to + // The lifecycle of this object is managed together with that of the plugin + // object instance this host context got passed to } tresult PLUGIN_API From 84b859c499954630130fb897bde6a500b988159f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 00:54:24 +0100 Subject: [PATCH 377/456] Implement IMessage --- meson.build | 2 + src/common/serialization/vst3/README.md | 1 + src/common/serialization/vst3/message.cpp | 50 ++++++++++++++++ src/common/serialization/vst3/message.h | 71 +++++++++++++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 src/common/serialization/vst3/message.cpp create mode 100644 src/common/serialization/vst3/message.h diff --git a/meson.build b/meson.build index f722f8ee..4a061d94 100644 --- a/meson.build +++ b/meson.build @@ -92,6 +92,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/component-handler-proxy.cpp', 'src/common/serialization/vst3/event-list.cpp', 'src/common/serialization/vst3/host-context-proxy.cpp', + 'src/common/serialization/vst3/message.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plug-frame-proxy.cpp', @@ -146,6 +147,7 @@ if with_vst3 'src/common/serialization/vst3/component-handler-proxy.cpp', 'src/common/serialization/vst3/event-list.cpp', 'src/common/serialization/vst3/host-context-proxy.cpp', + 'src/common/serialization/vst3/message.cpp', 'src/common/serialization/vst3/param-value-queue.cpp', 'src/common/serialization/vst3/parameter-changes.cpp', 'src/common/serialization/vst3/plug-frame-proxy.cpp', diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index f4aabc68..7e9ddc79 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -38,6 +38,7 @@ implemented for serialization purposes: | -------------------- | ------------------- | ---------------------------------------------------------------------- | | `YaAttributeList` | `IAttributeList` | | | `YaEventList` | `IEventList` | Comes with a lot of serialization wrappers around the related structs. | +| `YaMessage` | `IMessage` | | | `YaParameterChanges` | `IParameterChanges` | | | `YaParamValueQueue` | `IParamValueQueue` | | | `VectorStream` | `IBStream` | Used for serializing data streams. | diff --git a/src/common/serialization/vst3/message.cpp b/src/common/serialization/vst3/message.cpp new file mode 100644 index 00000000..ca416036 --- /dev/null +++ b/src/common/serialization/vst3/message.cpp @@ -0,0 +1,50 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "message.h" + +YaMessage::YaMessage(){FUNKNOWN_CTOR} + +YaMessage::~YaMessage() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_FUNKNOWN_METHODS(YaMessage, + Steinberg::Vst::IMessage, + Steinberg::Vst::IMessage::iid) +#pragma GCC diagnostic pop + +Steinberg::FIDString PLUGIN_API YaMessage::getMessageID() { + if (message_id) { + return message_id->c_str(); + } else { + return nullptr; + } +} + +void PLUGIN_API YaMessage::setMessageID(Steinberg::FIDString id /*in*/) { + if (id) { + message_id = id; + } else { + message_id.reset(); + } +} + +Steinberg::Vst::IAttributeList* PLUGIN_API YaMessage::getAttributes() { + return &attribute_list; +} diff --git a/src/common/serialization/vst3/message.h b/src/common/serialization/vst3/message.h new file mode 100644 index 00000000..e26c8b23 --- /dev/null +++ b/src/common/serialization/vst3/message.h @@ -0,0 +1,71 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include +#include + +#include "attribute-list.h" +#include "base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IMessage` for serialization purposes. We create instances of + * these in `IHostApplication::createInstance()` so the Windows VST3 plugin can + * send messages between objects. There is one huge caveat here: it is + * impossible to work with arbitrary `IMessage` objects, since there's no way to + * retrieve all of the keys in the attribute list. With this approach we support + * hosts that indirectly connect the processor and the controller through a + * proxy (like Ardour), but we still require a dynamic cast from the `IMessage*` + * passed to `YaConnectionPoint::notify()` to a `YaMessage*` for this to work + * for the above mentioned reason. + */ +class YaMessage : public Steinberg::Vst::IMessage { + public: + /** + * Default constructor with an empty message. The plugin can use this to + * write a message. + */ + YaMessage(); + + ~YaMessage(); + + DECLARE_FUNKNOWN_METHODS + + virtual Steinberg::FIDString PLUGIN_API getMessageID() override; + virtual void PLUGIN_API + setMessageID(Steinberg::FIDString id /*in*/) override; + virtual Steinberg::Vst::IAttributeList* PLUGIN_API getAttributes() override; + + template + void serialize(S& s) { + s.ext(message_id, bitsery::ext::StdOptional{}, + [](S& s, std::string& id) { s.text1b(id, 1024); }); + s.object(attribute_list); + } + + private: + /** + * The implementation that comes with the SDK returns a null pointer when + * the ID has not yet been set, so we'll do the same thing. + */ + std::optional message_id; + + YaAttributeList attribute_list; +}; From 5d2c7e0aeaf3e9bf4fd9b8eac116546eeed092ba Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 00:56:29 +0100 Subject: [PATCH 378/456] Replace SDK IMessage implementation with ours --- .../bridges/vst3-impls/host-context-proxy.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp index c6050b8d..1d1a8551 100644 --- a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp @@ -18,7 +18,8 @@ #include -#include +#include "../../../common/serialization/vst3/attribute-list.h" +#include "../../../common/serialization/vst3/message.h" Vst3HostContextProxyImpl::Vst3HostContextProxyImpl( Vst3Bridge& bridge, @@ -72,13 +73,12 @@ Vst3HostContextProxyImpl::createInstance(Steinberg::TUID /*cid*/, Steinberg::FUID iid = Steinberg::FUID::fromTUID(_iid); if (iid == Steinberg::Vst::IMessage::iid) { // TODO: Add logging for this on verbosity level 1 - *obj = static_cast( - new Steinberg::Vst::HostMessage{}); + *obj = static_cast(new YaMessage{}); return Steinberg::kResultTrue; } else if (iid == Steinberg::Vst::IAttributeList::iid) { // TODO: Add logging for this on verbosity level 1 - *obj = static_cast( - new Steinberg::Vst::HostAttributeList{}); + *obj = + static_cast(new YaAttributeList{}); return Steinberg::kResultTrue; } else { // When the host requests an interface we do not (yet) implement, From 3566aa86a2a8302d53214c901f0c7c03895e1c3a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 12:58:18 +0100 Subject: [PATCH 379/456] Fix IAttributeList serialization --- src/common/serialization/vst3/attribute-list.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/serialization/vst3/attribute-list.h b/src/common/serialization/vst3/attribute-list.h index f7cd586c..12f816de 100644 --- a/src/common/serialization/vst3/attribute-list.h +++ b/src/common/serialization/vst3/attribute-list.h @@ -61,22 +61,22 @@ class YaAttributeList : public Steinberg::Vst::IAttributeList { template void serialize(S& s) { s.ext(attrs_int, bitsery::ext::StdMap{1 << 20}, - [](S& s, AttrID& key, int64& value) { + [](S& s, std::string& key, int64& value) { s.text1b(key, 1024); s.value8b(value); }); s.ext(attrs_float, bitsery::ext::StdMap{1 << 20}, - [](S& s, AttrID& key, double& value) { + [](S& s, std::string& key, double& value) { s.text1b(key, 1024); s.value8b(value); }); s.ext(attrs_string, bitsery::ext::StdMap{1 << 20}, - [](S& s, AttrID& key, double& value) { + [](S& s, std::string& key, std::u16string& value) { s.text1b(key, 1024); s.text2b(value, 1 << 20); }); s.ext(attrs_binary, bitsery::ext::StdMap{1 << 20}, - [](S& s, AttrID& key, std::vector& value) { + [](S& s, std::string& key, std::vector& value) { s.text1b(key, 1024); s.container1b(value, 1 << 20); }); From 65694d261c1d426b5e88430fcabfdca005509f1a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 13:13:56 +0100 Subject: [PATCH 380/456] Implement IConnectionPoint::notify --- src/common/logging/vst3.cpp | 15 +++++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + .../vst3/plugin/connection-point.h | 25 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 19 +++++++++++--- src/wine-host/bridges/vst3.cpp | 5 ++++ 6 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 0a510633..30f76421 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -108,6 +108,21 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaConnectionPoint::Notify& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IConnectionPoint::notify(message = (request.message).getMessageID()) { + message << "with ID = \"" << id << "\""; + } else { + message << "without an ID"; + } + message << ">)"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaEditController::SetComponentState& request) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index e317768c..65c52e31 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -66,6 +66,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const Vst3PluginProxy::GetState&); bool log_request(bool is_host_vst, const YaConnectionPoint::Connect&); bool log_request(bool is_host_vst, const YaConnectionPoint::Disconnect&); + bool log_request(bool is_host_vst, const YaConnectionPoint::Notify&); bool log_request(bool is_host_vst, const YaEditController::SetComponentState&); bool log_request(bool is_host_vst, diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index fe6fe52f..08853061 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -68,6 +68,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(instance_id); + s.object(message); + } + }; + virtual tresult PLUGIN_API notify(Steinberg::Vst::IMessage* message) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 66a5b826..0351b338 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -246,9 +246,22 @@ tresult PLUGIN_API Vst3PluginProxyImpl::disconnect(IConnectionPoint* other) { tresult PLUGIN_API Vst3PluginProxyImpl::notify(Steinberg::Vst::IMessage* message) { - // TODO: Implement - bridge.logger.log("TODO IConnectionPoint::notify()"); - return Steinberg::kNotImplemented; + // Since there is no way to enumerate over all values in an + // `IAttributeList`, we can only support relaying messages that were sent by + // our own objects. This is only needed to support hosts that place a + // connection proxy between two objects instead of connecting them directly. + // If the objects are connected directly we also connected them directly on + // the Wine side, so we don't have to do any additional when those objects + // pass through messages. + if (auto message_impl = dynamic_cast(message)) { + return bridge.send_message(YaConnectionPoint::Notify{ + .instance_id = instance_id(), .message = *message_impl}); + } else { + bridge.logger.log( + "WARNING: Unknown message type passed to " + "'IConnectionPoint::notify()', ignoring"); + return Steinberg::kNotImplemented; + } } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 756f948e..cc86beef 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -187,6 +187,11 @@ void Vst3Bridge::run() { object_instances[request.other_instance_id] .connection_point); }, + [&](YaConnectionPoint::Notify& request) + -> YaConnectionPoint::Notify::Response { + return object_instances[request.instance_id] + .connection_point->notify(&request.message); + }, [&](YaEditController::SetComponentState& request) -> YaEditController::SetComponentState::Response { return object_instances[request.instance_id] From 0fce9c9eed40eb7229d39b10f496e38cf63c9dc8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 13:28:03 +0100 Subject: [PATCH 381/456] Add an IConnectionPoint proxy proxy This is a bit dumb, but this way we can support indirectly connected objects. --- meson.build | 2 + src/common/serialization/vst3/README.md | 25 +++-- .../vst3/connection-point-proxy.cpp | 51 +++++++++ .../vst3/connection-point-proxy.h | 104 ++++++++++++++++++ 4 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 src/common/serialization/vst3/connection-point-proxy.cpp create mode 100644 src/common/serialization/vst3/connection-point-proxy.h diff --git a/meson.build b/meson.build index 4a061d94..86ea1751 100644 --- a/meson.build +++ b/meson.build @@ -90,6 +90,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/attribute-list.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component-handler-proxy.cpp', + 'src/common/serialization/vst3/connection-point-proxy.cpp', 'src/common/serialization/vst3/event-list.cpp', 'src/common/serialization/vst3/host-context-proxy.cpp', 'src/common/serialization/vst3/message.cpp', @@ -145,6 +146,7 @@ if with_vst3 'src/common/serialization/vst3/attribute-list.cpp', 'src/common/serialization/vst3/base.cpp', 'src/common/serialization/vst3/component-handler-proxy.cpp', + 'src/common/serialization/vst3/connection-point-proxy.cpp', 'src/common/serialization/vst3/event-list.cpp', 'src/common/serialization/vst3/host-context-proxy.cpp', 'src/common/serialization/vst3/message.cpp', diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 7e9ddc79..b9b8aeae 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -7,18 +7,19 @@ serialization works. VST3 plugin interfaces are implemented as follows: -| yabridge class | Included in | Interfaces | -| ------------------- | ------------------- | ------------------------------------------------------ | -| `YaPluginFactory` | | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | -| `Vst3PlugViewProxy` | | All of the below: | -| `YaPlugView` | `Vst3PlugViewProxy` | `IPlugView` | -| `Vst3PluginProxy` | | All of the below: | -| `YaAudioProcessor` | `Vst3PluginProxy` | `IAudioProcessor` | -| `YaComponent` | `Vst3PluginProxy` | `IComponent` | -| `YaConnectionPoint` | `Vst3PluginProxy` | `IConnectionPoint` | -| `YaEditController` | `Vst3PluginProxy` | `IEditController` | -| `YaEditController2` | `Vst3PluginProxy` | `IEditController2` | -| `YaPluginBase` | `Vst3PluginProxy` | `IPluginBase` | +| yabridge class | Included in | Interfaces | +| -------------------------- | ------------------- | ------------------------------------------------------ | +| `YaPluginFactory` | | `IPluginFactory`, `IPluginFactory2`, `IPluginFactory3` | +| `Vst3ConnectionPointProxy` | | `IConnectionPoint` through `YaConnectionPoint` | +| `Vst3PlugViewProxy` | | All of the below: | +| `YaPlugView` | `Vst3PlugViewProxy` | `IPlugView` | +| `Vst3PluginProxy` | | All of the below: | +| `YaAudioProcessor` | `Vst3PluginProxy` | `IAudioProcessor` | +| `YaComponent` | `Vst3PluginProxy` | `IComponent` | +| `YaConnectionPoint` | `Vst3PluginProxy` | `IConnectionPoint` | +| `YaEditController` | `Vst3PluginProxy` | `IEditController` | +| `YaEditController2` | `Vst3PluginProxy` | `IEditController2` | +| `YaPluginBase` | `Vst3PluginProxy` | `IPluginBase` | VST3 host interfaces are implemented as follows: diff --git a/src/common/serialization/vst3/connection-point-proxy.cpp b/src/common/serialization/vst3/connection-point-proxy.cpp new file mode 100644 index 00000000..8bf0cf61 --- /dev/null +++ b/src/common/serialization/vst3/connection-point-proxy.cpp @@ -0,0 +1,51 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "connection-point-proxy.h" + +Vst3ConnectionPointProxy::ConstructArgs::ConstructArgs() {} + +Vst3ConnectionPointProxy::ConstructArgs::ConstructArgs( + Steinberg::IPtr object, + size_t owner_instance_id) + : owner_instance_id(owner_instance_id), connection_point_args(object) {} + +Vst3ConnectionPointProxy::Vst3ConnectionPointProxy(const ConstructArgs&& args) + : YaConnectionPoint(std::move(args.connection_point_args)), + arguments(std::move(args)){FUNKNOWN_CTOR} + + Vst3ConnectionPointProxy::~Vst3ConnectionPointProxy() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_REFCOUNT(Vst3ConnectionPointProxy) +#pragma GCC diagnostic pop + +tresult PLUGIN_API +Vst3ConnectionPointProxy::queryInterface(Steinberg::FIDString _iid, + void** obj) { + if (YaConnectionPoint::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::FUnknown::iid, + Steinberg::Vst::IConnectionPoint) + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IConnectionPoint::iid, + Steinberg::Vst::IConnectionPoint) + } + + *obj = nullptr; + return Steinberg::kNoInterface; +} diff --git a/src/common/serialization/vst3/connection-point-proxy.h b/src/common/serialization/vst3/connection-point-proxy.h new file mode 100644 index 00000000..48b255aa --- /dev/null +++ b/src/common/serialization/vst3/connection-point-proxy.h @@ -0,0 +1,104 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "../common.h" +#include "plugin/connection-point.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * This is only needed to...proxy a connection point proxy. Most hosts will + * connect a plugin's processor and controller directly using + * `IConnectionPoint::connect()`. But some hosts, like Ardour, will place a + * proxy object between them that forwards calls to + * `IConnectionPoint::notify()`. When objects are connected directly by the host + * we can also connect them directly in the Wine plugin host, but when the host + * uses proxies we'll also have to go through that proxy. The purpose of this + * class is to provide a proxy for such a connection proxy. So when the plugin + * calls `notify()` on an object of this class, then we will forward that call + * to the `IConnectionPoint` proxy provided by the host, which will then in turn + * call `IConnectionPoint::notify()` on the other object and we'll then forward + * that message again to them Wine plugin host. + */ +class Vst3ConnectionPointProxy : public YaConnectionPoint { + public: + /** + * These are the arguments for constructing a + * `Vst3ConnectionPointProxyImpl`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Read from an existing object. We will try to mimic this object, so + * we'll support any interfaces this object also supports. + * + * This is not necessary in this case since the object has to support + * `IConnectionPoint`, but let's stay consistent with the overall style + * here. + */ + ConstructArgs(Steinberg::IPtr object, + size_t owner_instance_id); + + /** + * The unique instance identifier of the proxy object instance this + * connection proxy has been passed to and thus belongs to. This way we + * can refer to the correct 'actual' `IConnectionPoint` instance when + * the plugin calls `notify()` on this proxy object. + */ + native_size_t owner_instance_id; + + YaConnectionPoint::ConstructArgs connection_point_args; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.object(connection_point_args); + } + }; + + /** + * Instantiate this instance with arguments read from an actual + * `IConnectionPoint` object/proxy. + * + * @note This object will be created as part of handling + * `IConnectionPoint::connect()` if the connection is indirect. + */ + Vst3ConnectionPointProxy(const ConstructArgs&& args); + + /** + * This object will be destroyed again during + * `IConnectionPoint::disconnect()`. + */ + virtual ~Vst3ConnectionPointProxy(); + + DECLARE_FUNKNOWN_METHODS + + /** + * Get the instance ID of the owner of this object. + */ + inline size_t owner_instance_id() const { + return arguments.owner_instance_id; + } + + private: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop From fbad4a65ab7f5ab66d380d1c8701428d9b6a5b92 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 13:46:03 +0100 Subject: [PATCH 382/456] Add an IConnectionPoint proxy implementation We still have to pass this proxy to the plugin. That's next. --- meson.build | 1 + src/common/serialization/vst3.h | 6 ++ src/plugin/bridges/vst3-impls/plugin-proxy.h | 24 ++++--- src/plugin/bridges/vst3.cpp | 6 ++ .../vst3-impls/connection-point-proxy.cpp | 69 +++++++++++++++++++ .../vst3-impls/connection-point-proxy.h | 41 +++++++++++ 6 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp create mode 100644 src/wine-host/bridges/vst3-impls/connection-point-proxy.h diff --git a/meson.build b/meson.build index 86ea1751..b4c42e78 100644 --- a/meson.build +++ b/meson.build @@ -158,6 +158,7 @@ if with_vst3 'src/common/serialization/vst3/plugin-factory.cpp', 'src/common/serialization/vst3/process-data.cpp', 'src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp', + 'src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp', 'src/wine-host/bridges/vst3-impls/host-context-proxy.cpp', 'src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp', 'src/wine-host/bridges/vst3.cpp', diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 08853061..61d93ab3 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -24,6 +24,7 @@ #include "../utils.h" #include "common.h" #include "vst3/component-handler-proxy.h" +#include "vst3/connection-point-proxy.h" #include "vst3/host-context-proxy.h" #include "vst3/plug-frame-proxy.h" #include "vst3/plug-view-proxy.h" @@ -143,6 +144,11 @@ using CallbackRequest = std::variant; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index 621c0d70..d767cd2b 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -124,6 +124,22 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { tresult PLUGIN_API initialize(FUnknown* context) override; tresult PLUGIN_API terminate() override; + /** + * The component handler the host passed to us during + * `IEditController::setComponentHandler()`. When the plugin makes a + * callback on a component handler proxy object, we'll pass the call through + * to this object. + */ + Steinberg::IPtr component_handler; + + /** + * If the host doesn't connect two objects directly in + * `IConnectionPoint::connect` but instead connects them through a proxy, + * we'll store that proxy here. This way we can then route messages sent by + * the plugin through this proxy. So far this is only needed for Ardour. + */ + Steinberg::IPtr connection_point_proxy; + /** * An unmanaged, raw pointer to the `IPlugView` instance returned in our * implementation of `IEditController::createView()`. We need this to handle @@ -136,14 +152,6 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { */ Vst3PlugViewProxyImpl* last_created_plug_view = nullptr; - /** - * The component handler the host passed to us during - * `IEditController::setComponentHandler()`. When the plugin makes a - * callback on a component handler proxy object, we'll pass the call through - * to this object. - */ - Steinberg::IPtr component_handler; - // The following pointers are cast from `host_context` if `setHostContext()` // has been called diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 6712019f..61a4e43e 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -106,6 +106,12 @@ Vst3PluginBridge::Vst3PluginBridge() .get() .component_handler->restartComponent(request.flags); }, + [&](YaConnectionPoint::Notify& request) + -> YaConnectionPoint::Notify::Response { + return plugin_proxies.at(request.instance_id) + .get() + .connection_point_proxy->notify(&request.message); + }, [&](const YaHostApplication::GetName& request) -> YaHostApplication::GetName::Response { tresult result; diff --git a/src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp b/src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp new file mode 100644 index 00000000..24dfe718 --- /dev/null +++ b/src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp @@ -0,0 +1,69 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "connection-point-proxy.h" + +#include + +Vst3ConnectionPointProxyImpl::Vst3ConnectionPointProxyImpl( + Vst3Bridge& bridge, + Vst3ConnectionPointProxy::ConstructArgs&& args) + : Vst3ConnectionPointProxy(std::move(args)), bridge(bridge) {} + +tresult PLUGIN_API +Vst3ConnectionPointProxyImpl::queryInterface(const Steinberg::TUID _iid, + void** obj) { + // TODO: Successful queries should also be logged + const tresult result = Vst3ConnectionPointProxy::queryInterface(_iid, obj); + if (result != Steinberg::kResultOk) { + std::cerr << "TODO: Implement unknown interface logging on Wine side " + "for Vst3ConnectionPointProxyImpl::queryInterface" + << std::endl; + } + + return result; +} + +tresult PLUGIN_API +Vst3ConnectionPointProxyImpl::connect(IConnectionPoint* /*other*/) { + std::cerr << "WARNING: The plugin called IConnectionPoint::connect(), this " + "should not happen" + << std::endl; + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3ConnectionPointProxyImpl::disconnect(IConnectionPoint* /*other*/) { + std::cerr << "WARNING: The plugin called IConnectionPoint::disconnect(), " + "this should not happen" + << std::endl; + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3ConnectionPointProxyImpl::notify(Steinberg::Vst::IMessage* message) { + // As explained in `YaMessage` and `Vst3PluginProxyImpl::notify`, we can + // only support our own `IMessage implementation here` + if (auto message_impl = dynamic_cast(message)) { + return bridge.send_message(YaConnectionPoint::Notify{ + .instance_id = owner_instance_id(), .message = *message_impl}); + } else { + std::cerr << "WARNING: Unknown message type passed to " + "'IConnectionPoint::notify()', ignoring" + << std::endl; + return Steinberg::kNotImplemented; + } +} diff --git a/src/wine-host/bridges/vst3-impls/connection-point-proxy.h b/src/wine-host/bridges/vst3-impls/connection-point-proxy.h new file mode 100644 index 00000000..9cff5c9e --- /dev/null +++ b/src/wine-host/bridges/vst3-impls/connection-point-proxy.h @@ -0,0 +1,41 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include "../vst3.h" + +class Vst3ConnectionPointProxyImpl : public Vst3ConnectionPointProxy { + public: + Vst3ConnectionPointProxyImpl( + Vst3Bridge& bridge, + Vst3ConnectionPointProxy::ConstructArgs&& args); + + /** + * We'll override the query interface to log queries for interfaces we do + * not (yet) support. + */ + tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, + void** obj) override; + + // From `IConnectionPoint` + tresult PLUGIN_API connect(IConnectionPoint* other) override; + tresult PLUGIN_API disconnect(IConnectionPoint* other) override; + tresult PLUGIN_API notify(Steinberg::Vst::IMessage* message) override; + + private: + Vst3Bridge& bridge; +}; From 70cb6dad89193b08d8f88d7d841e03553db2ef82 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 14:20:37 +0100 Subject: [PATCH 383/456] Allow indirect IConnectionPoint connections This is needed to support Ardour. These extra hops and serialization steps will probably hurt performance, but outside of some huge hacks (to connect the components directly anyways) there's not much else we can do. --- README.md | 2 - src/common/logging/vst3.cpp | 23 ++++- .../vst3/connection-point-proxy.cpp | 7 -- .../vst3/connection-point-proxy.h | 38 +------- .../vst3/plugin/connection-point.cpp | 9 ++ .../vst3/plugin/connection-point.h | 86 +++++++++++++++---- .../bridges/vst3-impls/plugin-proxy.cpp | 30 ++++--- src/wine-host/bridges/vst3.cpp | 60 ++++++++++--- src/wine-host/bridges/vst3.h | 15 +++- 9 files changed, 175 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index c216d4b9..f37e039a 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,6 @@ This branch is still very far removed from being in a usable state. Below is an incomplete list of things that still have to be done before this can be used: - Interfaces left to implement: - - `IConnectionPoint::notify()`, and support for indirectly connecting objects - through connction proxies - `IEditController2` - All other mandatory interfaces - All other optional interfaces diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 30f76421..7c68b823 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -94,8 +94,17 @@ bool Vst3Logger::log_request(bool is_host_vst, const YaConnectionPoint::Connect& request) { return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id - << ": IConnectionPoint::connect(other = )"; + << ": IConnectionPoint::connect(other = "; + std::visit( + overload{[&](const native_size_t& other_instance_id) { + message << ""; + }, + [&](const Vst3ConnectionPointProxy::ConstructArgs&) { + message << ""; + }}, + request.other); + message << ")"; }); } @@ -103,8 +112,14 @@ bool Vst3Logger::log_request(bool is_host_vst, const YaConnectionPoint::Disconnect& request) { return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id - << ": IConnectionPoint::disconnect(other = )"; + << ": IConnectionPoint::disconnect(other = "; + if (request.other_instance_id) { + message << ""; + } else { + message << ""; + } + message << ")"; }); } diff --git a/src/common/serialization/vst3/connection-point-proxy.cpp b/src/common/serialization/vst3/connection-point-proxy.cpp index 8bf0cf61..5800c21b 100644 --- a/src/common/serialization/vst3/connection-point-proxy.cpp +++ b/src/common/serialization/vst3/connection-point-proxy.cpp @@ -16,13 +16,6 @@ #include "connection-point-proxy.h" -Vst3ConnectionPointProxy::ConstructArgs::ConstructArgs() {} - -Vst3ConnectionPointProxy::ConstructArgs::ConstructArgs( - Steinberg::IPtr object, - size_t owner_instance_id) - : owner_instance_id(owner_instance_id), connection_point_args(object) {} - Vst3ConnectionPointProxy::Vst3ConnectionPointProxy(const ConstructArgs&& args) : YaConnectionPoint(std::move(args.connection_point_args)), arguments(std::move(args)){FUNKNOWN_CTOR} diff --git a/src/common/serialization/vst3/connection-point-proxy.h b/src/common/serialization/vst3/connection-point-proxy.h index 48b255aa..f6585625 100644 --- a/src/common/serialization/vst3/connection-point-proxy.h +++ b/src/common/serialization/vst3/connection-point-proxy.h @@ -38,40 +38,10 @@ */ class Vst3ConnectionPointProxy : public YaConnectionPoint { public: - /** - * These are the arguments for constructing a - * `Vst3ConnectionPointProxyImpl`. - */ - struct ConstructArgs { - ConstructArgs(); - - /** - * Read from an existing object. We will try to mimic this object, so - * we'll support any interfaces this object also supports. - * - * This is not necessary in this case since the object has to support - * `IConnectionPoint`, but let's stay consistent with the overall style - * here. - */ - ConstructArgs(Steinberg::IPtr object, - size_t owner_instance_id); - - /** - * The unique instance identifier of the proxy object instance this - * connection proxy has been passed to and thus belongs to. This way we - * can refer to the correct 'actual' `IConnectionPoint` instance when - * the plugin calls `notify()` on this proxy object. - */ - native_size_t owner_instance_id; - - YaConnectionPoint::ConstructArgs connection_point_args; - - template - void serialize(S& s) { - s.value8b(owner_instance_id); - s.object(connection_point_args); - } - }; + // We had to define this in `YaConnectionPoint` to work around circular + // includes + using ConstructArgs = + YaConnectionPoint::Vst3ConnectionPointProxyConstructArgs; /** * Instantiate this instance with arguments read from an actual diff --git a/src/common/serialization/vst3/plugin/connection-point.cpp b/src/common/serialization/vst3/plugin/connection-point.cpp index 5114521d..c0c6d443 100644 --- a/src/common/serialization/vst3/plugin/connection-point.cpp +++ b/src/common/serialization/vst3/plugin/connection-point.cpp @@ -23,5 +23,14 @@ YaConnectionPoint::ConstructArgs::ConstructArgs( : supported( Steinberg::FUnknownPtr(object)) {} +YaConnectionPoint::Vst3ConnectionPointProxyConstructArgs:: + Vst3ConnectionPointProxyConstructArgs() {} + +YaConnectionPoint::Vst3ConnectionPointProxyConstructArgs:: + Vst3ConnectionPointProxyConstructArgs( + Steinberg::IPtr object, + size_t owner_instance_id) + : owner_instance_id(owner_instance_id), connection_point_args(object) {} + YaConnectionPoint::YaConnectionPoint(const ConstructArgs&& args) : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin/connection-point.h b/src/common/serialization/vst3/plugin/connection-point.h index 7e21758f..213cdcad 100644 --- a/src/common/serialization/vst3/plugin/connection-point.h +++ b/src/common/serialization/vst3/plugin/connection-point.h @@ -16,7 +16,10 @@ #pragma once +#include + #include +#include #include #include "../../common.h" @@ -32,8 +35,6 @@ * monolithic proxy class we can easily directly connect different objects by * checking if they're a `Vst3PluginProxy` and then fetching that object's * instance ID (if the host doesn't place a proxy object here). - * - * TODO: Make sure we somehow handle proxies created by the host here. */ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint { public: @@ -60,6 +61,45 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint { } }; + protected: + /** + * These are the arguments for constructing a + * `Vst3ConnectionPointProxyImpl`. + * + * It's defined here to work around circular includes. + */ + struct Vst3ConnectionPointProxyConstructArgs { + Vst3ConnectionPointProxyConstructArgs(); + + /** + * Read from an existing object. We will try to mimic this object, so + * we'll support any interfaces this object also supports. + * + * This is not necessary in this case since the object has to support + * `IConnectionPoint`, but let's stay consistent with the overall style + * here. + */ + Vst3ConnectionPointProxyConstructArgs(Steinberg::IPtr object, + size_t owner_instance_id); + + /** + * The unique instance identifier of the proxy object instance this + * connection proxy has been passed to and thus belongs to. This way we + * can refer to the correct 'actual' `IConnectionPoint` instance when + * the plugin calls `notify()` on this proxy object. + */ + native_size_t owner_instance_id; + + ConstructArgs connection_point_args; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.object(connection_point_args); + } + }; + + public: /** * Instantiate this instance with arguments read from another interface * implementation. @@ -69,10 +109,10 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint { inline bool supported() const { return arguments.supported; } /** - * Message to pass through a call to - * `IConnectionPoint::connect(other_instance_id)` to the Wine plugin host. - * At the moment this is only implemented for directly connecting objects - * created by the plugin without any proxies in between them. + * Message to pass through a call to `IConnectionPoint::connect(other)` to + * the Wine plugin host. If the host directly connects two objects, then + * we'll connect them directly as well. Otherwise all messages have to be + * routed through the host. */ struct Connect { using Response = UniversalTResult; @@ -82,24 +122,32 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint { /** * The other object this object should be connected to. When connecting * two `Vst3PluginProxy` objects, we can directly connect the underlying - * objects on the Wine side. + * objects on the Wine side using their instance IDs. Otherwise we'll + * create a proxy object for the connection proxy provided by the host + * that the plugin can use to send messages to. */ - native_size_t other_instance_id; + std::variant + other; template void serialize(S& s) { s.value8b(instance_id); - s.value8b(other_instance_id); + s.ext(other, + bitsery::ext::StdVariant{ + [](S& s, native_size_t& other_instance_id) { + s.value8b(other_instance_id); + }, + [](S& s, Vst3ConnectionPointProxyConstructArgs& args) { + s.object(args); + }}); } }; virtual tresult PLUGIN_API connect(IConnectionPoint* other) override = 0; /** - * Message to pass through a call to - * `IConnectionPoint::disconnect(other_instance_id)` to the Wine plugin - * host. At the moment this is only implemented for directly connecting - * objects created by the plugin without any proxies in between them. + * Message to pass through a call to `IConnectionPoint::disconnect(other)` + * to the Wine plugin host. */ struct Disconnect { using Response = UniversalTResult; @@ -107,15 +155,19 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint { native_size_t instance_id; /** - * The other object backed by a `Vst3PluginProxy` this object was - * connected to and should be disconnected from. When connecting. + * If we connected two objects directly, then this is the instance ID of + * that object. Otherwise we'll just destroy the smart pointer pointing + * to our `IConnectionPoint` proxy object. */ - native_size_t other_instance_id; + std::optional other_instance_id; template void serialize(S& s) { s.value8b(instance_id); - s.value8b(other_instance_id); + s.ext(other_instance_id, bitsery::ext::StdOptional{}, + [](S& s, native_size_t& instance_id) { + s.value8b(instance_id); + }); } }; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 0351b338..de929ccc 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -214,17 +214,19 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getState(Steinberg::IBStream* state) { tresult PLUGIN_API Vst3PluginProxyImpl::connect(IConnectionPoint* other) { // When the host is trying to connect two plugin proxy objects, we can just // identify the other object by its instance IDs and then connect the - // objects in the Wine plugin host directly + // objects in the Wine plugin host directly. Otherwise we'll have to set up + // a proxy for the host's connection proxy so the messages can be routed + // through that. if (auto other_proxy = dynamic_cast(other)) { return bridge.send_message(YaConnectionPoint::Connect{ - .instance_id = instance_id(), - .other_instance_id = other_proxy->instance_id()}); + .instance_id = instance_id(), .other = other_proxy->instance_id()}); } else { - // TODO: Add support for `ConnectionProxy` and similar objects - bridge.logger.log( - "WARNING: The host passed a proxy proxy object to " - "'IConnectionPoint::connect()'. This is currently not supported."); - return Steinberg::kNotImplemented; + connection_point_proxy = other; + + return bridge.send_message(YaConnectionPoint::Connect{ + .instance_id = instance_id(), + .other = + Vst3ConnectionPointProxy::ConstructArgs(other, instance_id())}); } } @@ -235,12 +237,12 @@ tresult PLUGIN_API Vst3PluginProxyImpl::disconnect(IConnectionPoint* other) { .instance_id = instance_id(), .other_instance_id = other_proxy->instance_id()}); } else { - // TODO: Add support for `ConnectionProxy` and similar objects - bridge.logger.log( - "WARNING: The host passed a proxy proxy object to " - "'IConnectionPoint::disconnect()'. This is currently not " - "supported."); - return Steinberg::kNotImplemented; + const tresult result = bridge.send_message( + YaConnectionPoint::Disconnect{.instance_id = instance_id(), + .other_instance_id = std::nullopt}); + connection_point_proxy.reset(); + + return result; } } diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index cc86beef..5f3c58f1 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -21,6 +21,7 @@ #include #include "vst3-impls/component-handler-proxy.h" +#include "vst3-impls/connection-point-proxy.h" #include "vst3-impls/host-context-proxy.h" #include "vst3-impls/plug-frame-proxy.h" @@ -168,24 +169,55 @@ void Vst3Bridge::run() { return Vst3PluginProxy::GetStateResponse{ .result = result, .updated_state = std::move(stream)}; }, - [&](const YaConnectionPoint::Connect& request) + [&](YaConnectionPoint::Connect& request) -> YaConnectionPoint::Connect::Response { - // We can directly connect the underlying objects - // TODO: Add support for connecting objects through a proxy - // object provided by the host - return object_instances[request.instance_id] - .connection_point->connect( - object_instances[request.other_instance_id] - .connection_point); + // If the host directly connected the underlying objects then we + // can directly connect them as well. Otherwise we'll have to go + // through a connection proxy (to proxy the host's connection + // proxy). + return std::visit( + overload{ + [&](const native_size_t& other_instance_id) -> tresult { + return object_instances[request.instance_id] + .connection_point->connect( + object_instances[other_instance_id] + .connection_point); + }, + [&](Vst3ConnectionPointProxy::ConstructArgs& args) + -> tresult { + object_instances[request.instance_id] + .connection_point_proxy = Steinberg::owned( + new Vst3ConnectionPointProxyImpl( + *this, std::move(args))); + + return object_instances[request.instance_id] + .connection_point->connect( + object_instances[request.instance_id] + .connection_point_proxy); + }}, + request.other); }, [&](const YaConnectionPoint::Disconnect& request) -> YaConnectionPoint::Disconnect::Response { - // TODO: Add support for connecting objects through a proxy - // object provided by the host - return object_instances[request.instance_id] - .connection_point->disconnect( - object_instances[request.other_instance_id] - .connection_point); + // If the objects were connected directly we can also disconnect + // them directly. Otherwise we'll disconnect them from our proxy + // object and then destroy that proxy object. + if (request.other_instance_id) { + return object_instances[request.instance_id] + .connection_point->disconnect( + object_instances[*request.other_instance_id] + .connection_point); + } else { + const tresult result = + object_instances[request.instance_id] + .connection_point->disconnect( + object_instances[*request.other_instance_id] + .connection_point_proxy); + object_instances[*request.other_instance_id] + .connection_point_proxy.reset(); + + return result; + } }, [&](YaConnectionPoint::Notify& request) -> YaConnectionPoint::Notify::Response { diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 0f5927dc..de216c19 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -26,9 +26,6 @@ #include "../editor.h" #include "common.h" -// Forward declarations -class Vst3PlugFrameProxyImpl; - /** * A holder for plugin object instance created from the factory. This stores all * relevant interface smart pointers to that object so we can handle control @@ -57,6 +54,18 @@ struct InstanceInterfaces { */ Steinberg::IPtr host_context_proxy; + /** + * If the host connects two objects indirectly using a connection proxy (as + * allowed by the VST3 specification), then we also can't connect the + * objects directly on the Wine side. In that case we'll have to create this + * proxy object, pass it to the plugin, and if the plugin then calls + * `IConnectionPoint::notify()` on it we'll pass that call through to the + * `IConnectionPoint` instance passed to us by the host (which will then in + * turn call `IConnectionPoint::notify()` on our plugin proxy object). + * Proxies for days. + */ + Steinberg::IPtr connection_point_proxy; + /** * After a call to `IEditController::setComponentHandler()`, we'll create a * proxy of that component handler just like we did for the plugin object. From cc2e12c8e42a7a77e8267e816461440436ed4212 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 15:14:30 +0100 Subject: [PATCH 384/456] Allow creating logger instances on the Wine side So we can log filterable messages from the Wine side. Will be used to warn about failed query interface calls. --- src/common/logging/common.cpp | 19 +++++++++++++++++++ src/common/logging/common.h | 7 +++++++ 2 files changed, 26 insertions(+) diff --git a/src/common/logging/common.cpp b/src/common/logging/common.cpp index 55ba54e7..152868f2 100644 --- a/src/common/logging/common.cpp +++ b/src/common/logging/common.cpp @@ -80,6 +80,25 @@ Logger Logger::create_from_environment(std::string prefix) { } } +Logger Logger::create_wine_stderr() { + auto env = boost::this_process::environment(); + std::string verbosity = + env[logging_verbosity_environment_variable].to_string(); + + Verbosity verbosity_level; + try { + verbosity_level = static_cast(std::stoi(verbosity)); + } catch (const std::invalid_argument&) { + verbosity_level = Verbosity::basic; + } + + // We're logging directly to `std::cerr` instead of to `/dev/stderr` because + // we want the STDERR redirection from the group host processes to still + // function here + return Logger(std::shared_ptr(&std::cerr), verbosity_level, + ""); +} + void Logger::log(const std::string& message) { const auto current_time = std::chrono::system_clock::now(); const std::time_t timestamp = diff --git a/src/common/logging/common.h b/src/common/logging/common.h index c26ecc8f..3f10abdc 100644 --- a/src/common/logging/common.h +++ b/src/common/logging/common.h @@ -85,6 +85,13 @@ class Logger { */ static Logger create_from_environment(std::string prefix = ""); + /** + * Create a special logger instance that outputs directly to STDERR without + * any prefixes. This is used to be able to log filterable messages from the + * Wine side of things. + */ + static Logger create_wine_stderr(); + /** * Write a message to the log, prefixing it with a timestamp and this * logger's prefix string. From 8a56b67cb3020a89a3b6db1ebe22157a043efddf Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 15:23:54 +0100 Subject: [PATCH 385/456] Add unknown interface logging on the Wine side --- .../bridges/vst3-impls/component-handler-proxy.cpp | 6 +++--- .../bridges/vst3-impls/connection-point-proxy.cpp | 6 +++--- .../bridges/vst3-impls/host-context-proxy.cpp | 10 ++++------ .../bridges/vst3-impls/plug-frame-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 2 ++ src/wine-host/bridges/vst3.h | 14 ++++++++++++++ 6 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp index 5a545544..ecdb4384 100644 --- a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp @@ -32,9 +32,9 @@ Vst3ComponentHandlerProxyImpl::queryInterface(const Steinberg::TUID _iid, // TODO: Successful queries should also be logged const tresult result = Vst3ComponentHandlerProxy::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { - std::cerr << "TODO: Implement unknown interface logging on Wine side " - "for Vst3ComponentHandlerProxyImpl::queryInterface" - << std::endl; + bridge.logger.log_unknown_interface( + "In IComponentHandler::queryInterface()", + Steinberg::FUID::fromTUID(_iid)); } return result; diff --git a/src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp b/src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp index 24dfe718..55a03c8a 100644 --- a/src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp @@ -29,9 +29,9 @@ Vst3ConnectionPointProxyImpl::queryInterface(const Steinberg::TUID _iid, // TODO: Successful queries should also be logged const tresult result = Vst3ConnectionPointProxy::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { - std::cerr << "TODO: Implement unknown interface logging on Wine side " - "for Vst3ConnectionPointProxyImpl::queryInterface" - << std::endl; + bridge.logger.log_unknown_interface( + "In IConnectionPoint::queryInterface()", + Steinberg::FUID::fromTUID(_iid)); } return result; diff --git a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp index 1d1a8551..ae572e76 100644 --- a/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/host-context-proxy.cpp @@ -37,9 +37,8 @@ Vst3HostContextProxyImpl::queryInterface(const Steinberg::TUID _iid, // TODO: Successful queries should also be logged const tresult result = Vst3HostContextProxy::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { - std::cerr << "TODO: Implement unknown interface logging on Wine side " - "for Vst3HostContextProxyImpl::queryInterface" - << std::endl; + bridge.logger.log_unknown_interface("In FUnknown::queryInterface()", + Steinberg::FUID::fromTUID(_iid)); } return result; @@ -84,9 +83,8 @@ Vst3HostContextProxyImpl::createInstance(Steinberg::TUID /*cid*/, // When the host requests an interface we do not (yet) implement, // we'll print a recognizable log message const Steinberg::FUID uid = Steinberg::FUID::fromTUID(_iid); - std::cerr << "TODO: Implement unknown interface logging on Wine side " - "for Vst3HostContextProxyImpl::createInstance" - << std::endl; + bridge.logger.log_unknown_interface( + "In IHostApplication::createInstance()", uid); return Steinberg::kNotImplemented; } diff --git a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp index 6639b661..74a68021 100644 --- a/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/plug-frame-proxy.cpp @@ -31,9 +31,8 @@ Vst3PlugFrameProxyImpl::queryInterface(const Steinberg::TUID _iid, void** obj) { // TODO: Successful queries should also be logged const tresult result = Vst3PlugFrameProxy::queryInterface(_iid, obj); if (result != Steinberg::kResultOk) { - std::cerr << "TODO: Implement unknown interface logging on Wine side " - "for Vst3PlugFrameProxyImpl::queryInterface" - << std::endl; + bridge.logger.log_unknown_interface("In IPlugFrame::queryInterface()", + Steinberg::FUID::fromTUID(_iid)); } return result; diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 5f3c58f1..e6464be2 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -40,6 +40,8 @@ Vst3Bridge::Vst3Bridge(MainContext& main_context, std::string plugin_dll_path, std::string endpoint_base_dir) : HostBridge(plugin_dll_path), + generic_logger(Logger::create_wine_stderr()), + logger(generic_logger), main_context(main_context), sockets(main_context.context, endpoint_base_dir, false) { std::string error; diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index de216c19..929a55d9 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -263,6 +263,20 @@ class Vst3Bridge : public HostBridge { return do_call_response.get(); } + private: + Logger generic_logger; + + public: + /** + * A logger instance we'll use to log about failed + * `FUnknown::queryInterface` calls, so they can be hidden on verbosity + * level 0. + * + * This only has to be used instead of directly writing to `std::cerr` when + * the message should be hidden on lower verbosity levels. + */ + Vst3Logger logger; + private: /** * Generate a nique instance identifier using an atomic fetch-and-add. This From 7da5ec113c6d669c15fc0c0fd47aa8cacd5a2030 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 16:22:53 +0100 Subject: [PATCH 386/456] Reuse buffers in VST3 audio processing --- src/common/communication/common.h | 3 ++ src/common/communication/vst3.h | 90 ++++++++++++++++++++++++++----- 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index f3f97107..d1a8d873 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -101,6 +101,9 @@ inline void write_object(Socket& socket, const T& object) { * @throw boost::system::system_error If the socket is closed or gets closed * while reading. * + * TODO: Swap these arguments around so they match `write_object`'s argument + * order + * * @relates write_object */ template diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 9fb4b546..233da066 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -84,10 +84,29 @@ class Vst3MessageHandler : public AdHocSocketHandler { * then this indicates that this `Vst3MessageHandler` is handling plugin * -> host callbacks isntead. Optional since it only has to be set on the * plugin's side. + * @param buffer The serialization and receiving buffer to reuse. This is + * optional, but it's useful for minimizing allocations in the audio + * processing loop. * * @relates Vst3MessageHandler::receive_messages */ template + typename T::Response send_message( + const T& object, + std::optional> logging, + std::vector& buffer) { + typename T::Response response_object; + receive_into(object, response_object, logging, buffer); + + return response_object; + } + + /** + * The same as the above, but with a small default buffer. + * + * @overload + */ + template typename T::Response send_message( const T& object, std::optional> logging) { @@ -101,8 +120,6 @@ class Vst3MessageHandler : public AdHocSocketHandler { * `Vst3MessageHandler::send_message()`, but deserializing the response into * an existing object. * - * TODO: We might also need overloads that reuse buffers - * * @param response_object The object to deserialize into. * * @overload Vst3MessageHandler::send_message @@ -111,7 +128,8 @@ class Vst3MessageHandler : public AdHocSocketHandler { typename T::Response& receive_into( const T& object, typename T::Response& response_object, - std::optional> logging) { + std::optional> logging, + std::vector& buffer) { using TResponse = typename T::Response; // Since a lot of messages just return a `tresult`, we can't filter out @@ -129,8 +147,8 @@ class Vst3MessageHandler : public AdHocSocketHandler { // in use it will spawn a new socket for us. this->template send( [&](boost::asio::local::stream_protocol::socket& socket) { - write_object(socket, Request(object)); - read_object(socket, response_object); + write_object(socket, Request(object), buffer); + read_object(socket, buffer, response_object); // FIXME: We have to return something here, and ML was not yet // invented when they came up with C++ so void is not // valid here @@ -145,6 +163,21 @@ class Vst3MessageHandler : public AdHocSocketHandler { return response_object; } + /** + * The same function as above, but with a small default buffer. + * + * @overload + */ + template + typename T::Response& receive_into( + const T& object, + typename T::Response& response_object, + std::optional> logging) { + std::vector buffer(64); + return receive_into(object, response_object, std::move(logging), + buffer); + } + /** * Spawn a new thread to listen for extra connections to `endpoint`, and * then start a blocking loop that handles messages from the primary @@ -165,18 +198,32 @@ class Vst3MessageHandler : public AdHocSocketHandler { * @tparam F A function type in the form of `T::Response(T)` for every `T` * in `Request`. This way we can directly deserialize into a `T::Response` * on the side that called `receive_into(T, T::Response&)`. + * @tparam keep_buffers Whether processing buffers should be kept around and + * reused. This is used to minimize allocations in the audio processing + * loop. This can only be used when `ad_hoc_sockets` is set to false, + * since we can of course only reuse buffers within a single thread. These + * buffers will also never shrink, but that should not be an issue here. * * @relates Vst3MessageHandler::send_event */ - template + template void receive_messages(std::optional> logging, F callback) { + std::vector persistent_buffer{}; + if constexpr (keep_buffers) { + static_assert(!ad_hoc_sockets, + "Buffers can only be reused when ad-hoc socket " + "spawning has been disabled."); + } + // Reading, processing, and writing back the response for the requests // we receive works in the same way regardless of which socket we're // using const auto process_message = [&](boost::asio::local::stream_protocol::socket& socket) { - auto request = read_object(socket); + auto request = keep_buffers ? read_object( + socket, persistent_buffer) + : read_object(socket); // See the comment in `receive_into()` for more information bool should_log_response = false; @@ -201,7 +248,11 @@ class Vst3MessageHandler : public AdHocSocketHandler { logger.log_response(!is_host_vst, response); } - write_object(socket, response); + if constexpr (keep_buffers) { + write_object(socket, response, persistent_buffer); + } else { + write_object(socket, response); + } }, request); }; @@ -299,6 +350,8 @@ class Vst3Sockets : public Sockets { std::to_string(instance_id) + ".sock")) .string(), false); + audio_processor_buffers.try_emplace(instance_id); + audio_processor_sockets.at(instance_id).connect(); } @@ -329,12 +382,17 @@ class Vst3Sockets : public Sockets { std::to_string(instance_id) + ".sock")) .string(), true); + audio_processor_buffers.try_emplace(instance_id); } socket_listening_latch.set_value(); audio_processor_sockets.at(instance_id).connect(); + + // This `true` indicates that we want to reuse our serialization and + // receiving buffers for all calls. This slightly reduces the amount of + // allocations in the audio processing loop. audio_processor_sockets.at(instance_id) - .receive_messages(std::nullopt, cb); + .template receive_messages(std::nullopt, cb); } /** @@ -353,6 +411,7 @@ class Vst3Sockets : public Sockets { if (audio_processor_sockets.contains(instance_id)) { audio_processor_sockets.at(instance_id).close(); audio_processor_sockets.erase(instance_id); + audio_processor_buffers.erase(instance_id); return true; } else { @@ -364,7 +423,8 @@ class Vst3Sockets : public Sockets { * Send a message from the native plugin to the Wine plugin host to handle * an `IAudioProcessor` or `IComponent` call. Since those functions are * called from a hot loop we want every instance to have a dedicated socket - * and thread for handling those. + * and thread for handling those. These calls also always reuse buffers to + * minimize allocations. * * @tparam T Some object in the `AudioProcessorRequest` variant. */ @@ -372,9 +432,9 @@ class Vst3Sockets : public Sockets { typename T::Response send_audio_processor_message( const T& object, std::optional> logging) { - // TODO: These calls should reuse buffers return audio_processor_sockets.at(object.instance_id) - .send_message(object, logging); + .send_message(object, logging, + audio_processor_buffers.at(object.instance_id)); } /** @@ -412,5 +472,11 @@ class Vst3Sockets : public Sockets { */ std::map> audio_processor_sockets; + /** + * Binary buffers used for serializing objects and receiving messages into + * during `send_audio_processor_message()`. This is used to minimize the + * amount of allocations in the audio processing loop. + */ + std::map> audio_processor_buffers; std::mutex audio_processor_sockets_mutex; }; From 49eeee99fa9a4f4728c20e86ac0e84e06a67b655 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 18:11:55 +0100 Subject: [PATCH 387/456] Add an XEmbed compatibility option --- CHANGELOG.md | 9 +++++++-- README.md | 4 ++++ src/common/configuration.cpp | 6 ++++++ src/common/configuration.h | 9 +++++++++ src/plugin/bridges/common.h | 3 +++ 5 files changed, 29 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bd784c7..d8cf67c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,13 @@ Versioning](https://semver.org/spec/v2.0.0.html). TODO: Add the relevant entries here for yabridge's VST3 support -- Added the `with-vst3` option to control whether yabridge should be built with - VST3 support. This is enabled by default. +- Added the `with-vst3` compile time option to control whether yabridge should + be built with VST3 support. This is enabled by default. +- Added an + [option](https://github.com/robbert-vdh/yabridge#compatibility-options) to use + XEmbed instead of yabridge's normal window embedding method. Some plugins have + redrawing issues when using XEmbed and editor resizing won't work, so it's not + recommended to use it as a default. ### Changed diff --git a/README.md b/README.md index f37e039a..7cbf6ed4 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,7 @@ other. See below for an [example](#example) of how these groups can be set up. | --------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `cache_time_info` | `{true,false}` | Compatibility option for plugins that call `audioMasterGetTime()` multiple times during a single processing cycle. With this option subsequent calls during a single audio processing cycle will reuse the value returned by the first call to this function. This is a bug in the plugin, and this option serves as a temporary workaround until the plugin fixes the issue. | | `editor_double_embed` | `{true,false}` | Compatibility option for plugins that rely on the absolute screen coordinates of the window they're embedded in. Since the Wine window gets embedded inside of a window provided by your DAW, these coordinates won't match up and the plugin would end up drawing in the wrong location without this option. Currently the only known plugins that require this option are _PSPaudioware_ plugins with expandable GUIs, such as E27. Defaults to `false`. | +| `editor_xembed` | `{true,false}` | Use Wine's XEmbed implementation instead of yabridge's normal window embedding method. Some plugins will have redrawing issues when using XEmbed and editor resizing won't work properly with it, but it could be useful in certain setups. Defaults to `false`. | These options are workarounds for issues mentioned in the [known issues](#runtime-dependencies-and-known-issues) section. Depending on the hosts @@ -315,6 +316,9 @@ group = "toneboosters" ["PSPaudioware"] editor_double_embed = true +["Analog Lab 3.so"] +editor_xembed = true + ["SWAM Cello 64bit.so"] cache_time_info = true diff --git a/src/common/configuration.cpp b/src/common/configuration.cpp index 8f528a15..79c6e715 100644 --- a/src/common/configuration.cpp +++ b/src/common/configuration.cpp @@ -90,6 +90,12 @@ Configuration::Configuration(const fs::path& config_path, } else { invalid_options.push_back(key); } + } else if (key == "editor_xembed") { + if (const auto parsed_value = value.as_boolean()) { + editor_xembed = parsed_value->get(); + } else { + invalid_options.push_back(key); + } } else if (key == "group") { if (const auto parsed_value = value.as_string()) { group = parsed_value->get(); diff --git a/src/common/configuration.h b/src/common/configuration.h index e499fc73..3b061e3f 100644 --- a/src/common/configuration.h +++ b/src/common/configuration.h @@ -101,6 +101,14 @@ class Configuration { */ bool editor_double_embed = false; + /** + * Use XEmbed instead of yabridge's normal editor embedding method. Wine's + * XEmbed support is not very polished yet and tends to lead to rendering + * issues, so this is disabled by default. Also, editor resizing won't work + * reliably when XEmbed is enabled. + */ + bool editor_xembed = false; + /** * The name of the plugin group that should be used for the plugin this * configuration object was created for. If not set, then the plugin should @@ -135,6 +143,7 @@ class Configuration { void serialize(S& s) { s.value1b(cache_time_info); s.value1b(editor_double_embed); + s.value1b(editor_xembed); s.ext(group, bitsery::ext::StdOptional(), [](S& s, auto& v) { s.text1b(v, 4096); }); s.ext(matched_file, bitsery::ext::StdOptional(), diff --git a/src/plugin/bridges/common.h b/src/plugin/bridges/common.h index 32f5ea2e..cc08ecae 100644 --- a/src/plugin/bridges/common.h +++ b/src/plugin/bridges/common.h @@ -160,6 +160,9 @@ class PluginBridge { if (config.editor_double_embed) { other_options.push_back("editor: double embed"); } + if (config.editor_xembed) { + other_options.push_back("editor: XEmbed"); + } if (!other_options.empty()) { init_msg << join_quoted_strings(other_options) << std::endl; } else { From 80ef1ec394f7d70bcad49dcae98ebbdf2574a4a4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 19:41:48 +0100 Subject: [PATCH 388/456] Add XEmbed support back in Still very much broken, albeit a bit less broken than a year ago. --- src/wine-host/editor.cpp | 191 ++++++++++++++++++++++++++++++--------- src/wine-host/editor.h | 32 +++++++ 2 files changed, 180 insertions(+), 43 deletions(-) diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 0d7dedf6..67c7bd5f 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -30,6 +30,21 @@ constexpr uint16_t event_type_mask = ((1 << 7) - 1); */ constexpr char active_window_property_name[] = "_NET_ACTIVE_WINDOW"; +/** + * Client message name for XEmbed messages. See + * https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html. + */ +constexpr char xembed_message_name[] = "_XEMBED"; + +// Constants from the XEmbed spec +constexpr uint32_t xembed_protocol_version = 0; + +constexpr uint32_t xembed_embedded_notify_msg = 0; +constexpr uint32_t xembed_window_activate_msg = 1; +constexpr uint32_t xembed_focus_in_msg = 4; + +constexpr uint32_t xembed_focus_first = 1; + /** * Find the topmost window (i.e. the window before the root window in the window * tree) starting from a certain window. @@ -83,6 +98,7 @@ Editor::Editor(const Configuration& config, const std::string& window_class_name, const size_t parent_window_handle) : x11_connection(xcb_connect(nullptr, nullptr), xcb_disconnect), + use_xembed(config.editor_xembed), client_area(get_maximum_screen_dimensions(*x11_connection)), window_class(window_class_name), // Create a window without any decoratiosn for easy embedding. The @@ -114,7 +130,7 @@ Editor::Editor(const Configuration& config, // Used for input focus grabbing to only grab focus when the window is // active. - const xcb_intern_atom_cookie_t atom_cookie = xcb_intern_atom( + xcb_intern_atom_cookie_t atom_cookie = xcb_intern_atom( x11_connection.get(), true, strlen(active_window_property_name), active_window_property_name); xcb_intern_atom_reply_t* atom_reply = @@ -135,59 +151,87 @@ Editor::Editor(const Configuration& config, << std::endl; } - // Because we're not using XEmbed, Wine will interpret any local coordinates - // as global coordinates. To work around this we'll tell the Wine window - // it's located at its actual coordinates on screen rather than somewhere - // within. For robustness's sake this should be done both when the actual - // window the Wine window is embedded in (which may not be the parent - // window) is moved or resized, and when the user moves his mouse over the - // window because this is sometimes needed for plugin groups. We also listen - // for EnterNotify and LeaveNotify events on the Wine window so we can grab - // and release input focus as necessary. - const uint32_t topmost_event_mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY; + // When using XEmbed we'll need the atoms for the corresponding properties + atom_cookie = + xcb_intern_atom(x11_connection.get(), true, strlen(xembed_message_name), + xembed_message_name); + atom_reply = + xcb_intern_atom_reply(x11_connection.get(), atom_cookie, &error); + assert(!error); + xcb_xembed_message = atom_reply->atom; + free(atom_reply); + + // When not using XEmbed, Wine will interpret any local coordinates as + // global coordinates. To work around this we'll tell the Wine window it's + // located at its actual coordinates on screen rather than somewhere within. + // For robustness's sake this should be done both when the actual window the + // Wine window is embedded in (which may not be the parent window) is moved + // or resized, and when the user moves his mouse over the window because + // this is sometimes needed for plugin groups. We also listen for + // EnterNotify and LeaveNotify events on the Wine window so we can grab and + // release input focus as necessary. + // If we do enable XEmbed support, we'll also listen for visibility changes + // and trigger the embedding when the window becomes visible + const uint32_t topmost_event_mask = + XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_VISIBILITY_CHANGE; xcb_change_window_attributes(x11_connection.get(), topmost_window, XCB_CW_EVENT_MASK, &topmost_event_mask); xcb_flush(x11_connection.get()); - const uint32_t parent_event_mask = XCB_EVENT_MASK_FOCUS_CHANGE | - XCB_EVENT_MASK_ENTER_WINDOW | - XCB_EVENT_MASK_LEAVE_WINDOW; + const uint32_t parent_event_mask = + XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW | + XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_VISIBILITY_CHANGE; xcb_change_window_attributes(x11_connection.get(), parent_window, XCB_CW_EVENT_MASK, &parent_event_mask); xcb_flush(x11_connection.get()); - // Embed the Win32 window into the window provided by the host. Instead of - // using the XEmbed protocol, we'll register a few events and manage the - // child window ourselves. This is a hack to work around the issue's - // described in `Editor`'s docstring'. - xcb_reparent_window(x11_connection.get(), wine_window, parent_window, 0, 0); - xcb_flush(x11_connection.get()); + if (use_xembed) { + // This call alone doesn't do anything. We need to call this function a + // second time on visibility change, because Wine's XEmbed + // implementation does not work properly (which is why we remvoed XEmbed + // support in the first place). + do_xembed(); + } else { + // Embed the Win32 window into the window provided by the host. Instead + // of using the XEmbed protocol, we'll register a few events and manage + // the child window ourselves. This is a hack to work around the issue's + // described in `Editor`'s docstring'. + xcb_reparent_window(x11_connection.get(), wine_window, parent_window, 0, + 0); + xcb_flush(x11_connection.get()); - ShowWindow(win32_handle.get(), SW_SHOWNORMAL); - if (config.editor_double_embed) { - // FIXME: This emits `-Wignored-attributes` as of Wine 5.22 + // If we're using the double embedding option, then the child window + // should only be created after the parent window is visible + ShowWindow(win32_handle.get(), SW_SHOWNORMAL); + if (config.editor_double_embed) { + // FIXME: This emits `-Wignored-attributes` as of Wine 5.22 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wignored-attributes" - // As explained above, we can't do this directly in the initializer list - win32_child_handle = std::unique_ptr, - decltype(&DestroyWindow)>( - CreateWindowEx( - WS_EX_TOOLWINDOW, reinterpret_cast(window_class.atom), - "yabridge plugin child", WS_CHILD, CW_USEDEFAULT, CW_USEDEFAULT, - client_area.width, client_area.height, win32_handle.get(), - nullptr, GetModuleHandle(nullptr), this), - DestroyWindow); + // As explained above, we can't do this directly in the initializer + // list + win32_child_handle = std::unique_ptr, + decltype(&DestroyWindow)>( + CreateWindowEx(WS_EX_TOOLWINDOW, + reinterpret_cast(window_class.atom), + "yabridge plugin child", WS_CHILD, CW_USEDEFAULT, + CW_USEDEFAULT, client_area.width, + client_area.height, win32_handle.get(), nullptr, + GetModuleHandle(nullptr), this), + DestroyWindow); #pragma GCC diagnostic pop - ShowWindow(win32_child_handle->get(), SW_SHOWNORMAL); - } + ShowWindow(win32_child_handle->get(), SW_SHOWNORMAL); + } - // HACK: I can't seem to figure why the initial reparent would fail on this - // particular i3 config in a way that I'm unable to reproduce, but if - // it doesn't work the first time, just keep trying! - // - // https://github.com/robot-vdh/yabridge/issues/40 - xcb_reparent_window(x11_connection.get(), wine_window, parent_window, 0, 0); - xcb_flush(x11_connection.get()); + // HACK: I can't seem to figure why the initial reparent would fail on + // this particular i3 config in a way that I'm unable to + // reproduce, but if it doesn't work the first time, just keep + // trying! + // + // https://github.com/robot-vdh/yabridge/issues/40 + xcb_reparent_window(x11_connection.get(), wine_window, parent_window, 0, + 0); + xcb_flush(x11_connection.get()); + } } Editor::~Editor() { @@ -236,7 +280,17 @@ void Editor::handle_x11_events() const { // check is sometimes necessary for using multiple editor windows // within a single plugin group. case XCB_CONFIGURE_NOTIFY: - fix_local_coordinates(); + if (!use_xembed) { + fix_local_coordinates(); + } + break; + // Start the XEmbed procedure when the window becomes visible, since + // most hosts will only show the window after the plugin has + // embedded itself into it. + case XCB_VISIBILITY_NOTIFY: + if (use_xembed) { + do_xembed(); + } break; // We want to grab keyboard input focus when the user hovers over // our embedded Wine window AND that window is a child of the @@ -249,7 +303,9 @@ void Editor::handle_x11_events() const { // `EnterNotify'. case XCB_ENTER_NOTIFY: case XCB_FOCUS_IN: - fix_local_coordinates(); + if (!use_xembed) { + fix_local_coordinates(); + } // In case the WM somehow does not support `_NET_ACTIVE_WINDOW`, // a more naive focus grabbing method implemented in the @@ -290,6 +346,10 @@ void Editor::handle_x11_events() const { } void Editor::fix_local_coordinates() const { + if (use_xembed) { + return; + } + // We're purposely not using XEmbed. This has the consequence that wine // still thinks that any X and Y coordinates are relative to the x11 window // root instead of the parent window provided by the DAW, causing all sorts @@ -416,6 +476,51 @@ bool Editor::supports_ewmh_active_window() const { return active_window_property_exists; } +void Editor::send_xembed_message(const xcb_window_t& window, + const uint32_t message, + const uint32_t detail, + const uint32_t data1, + const uint32_t data2) const { + xcb_client_message_event_t event; + event.response_type = XCB_CLIENT_MESSAGE; + event.type = xcb_xembed_message; + event.window = window; + event.format = 32; + event.data.data32[0] = XCB_CURRENT_TIME; + event.data.data32[1] = message; + event.data.data32[2] = detail; + event.data.data32[3] = data1; + event.data.data32[4] = data2; + + xcb_send_event(x11_connection.get(), false, window, XCB_EVENT_MASK_NO_EVENT, + reinterpret_cast(&event)); +} + +void Editor::do_xembed() const { + if (!use_xembed) { + return; + } + + // If we're embedding using XEmbed, then we'll have to go through the whole + // XEmbed dance here. See the spec for more information on how this works: + // https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html#lifecycle + xcb_reparent_window(x11_connection.get(), wine_window, parent_window, 0, 0); + xcb_flush(x11_connection.get()); + + // Let the Wine window know it's being embedded into the parent window + send_xembed_message(wine_window, xembed_embedded_notify_msg, 0, + parent_window, xembed_protocol_version); + send_xembed_message(wine_window, xembed_focus_in_msg, xembed_focus_first, 0, + 0); + send_xembed_message(wine_window, xembed_window_activate_msg, 0, 0, 0); + xcb_flush(x11_connection.get()); + + xcb_map_window(x11_connection.get(), wine_window); + xcb_flush(x11_connection.get()); + + ShowWindow(win32_handle.get(), SW_SHOWNORMAL); +} + LRESULT CALLBACK window_proc(HWND handle, UINT message, WPARAM wParam, diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index ec8fba6a..bccba193 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -85,6 +85,9 @@ class WindowClass { * issues, please let me know and I'll switch to using XEmbed again. * * This workaround was inspired by LinVst. + * + * As of yabridge 3.0 XEMbed is back as an option, but it's disabled by default + * because of the issues mentioned above. */ class Editor { public: @@ -158,12 +161,36 @@ class Editor { */ bool is_wine_window_active() const; + /** + * Send an XEmbed message to a window. This does not include a flush. See + * the spec for more information: + * + * https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html#lifecycle + */ + void send_xembed_message(const xcb_window_t& window, + const uint32_t message, + const uint32_t detail, + const uint32_t data1, + const uint32_t data2) const; + + /** + * Start the XEmbed procedure when `use_xembed` is enabled. This should be + * rerun whenever visibility changes. + */ + void do_xembed() const; + /** * A pointer to the currently active window. Will be a null pointer if no * window is active. */ std::unique_ptr x11_connection; + /** + * Whether to use XEmbed instead of yabridge's normal window embedded. Wine + * with XEmbed tends to cause rendering issues, so it's disabled by default. + */ + const bool use_xembed; + /** * The Wine window's client area, or the maximum size of that window. This * will be set to a size that's large enough to be able to enter full screen @@ -233,4 +260,9 @@ class Editor { * `supports_ewmh_active_window()`. */ mutable std::optional supports_ewmh_active_window_cache; + + /** + * The atom corresponding to `_XEMBED`. + */ + xcb_atom_t xcb_xembed_message; }; From 6b4df4d27474d80021def020a1acf6bdb5830eef Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 21:55:00 +0100 Subject: [PATCH 389/456] Explicitly include This is an indirect dependency in Boost 1.72/1.73, but not in older versions. --- src/common/communication/common.h | 2 ++ src/plugin/bridges/vst2.h | 1 - src/plugin/host-process.h | 3 ++- src/wine-host/bridges/vst2.h | 2 -- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index d1a8d873..38f7dcb6 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -16,6 +16,8 @@ #pragma once +#include + #include #include #include diff --git a/src/plugin/bridges/vst2.h b/src/plugin/bridges/vst2.h index 46415d8e..a363830d 100644 --- a/src/plugin/bridges/vst2.h +++ b/src/plugin/bridges/vst2.h @@ -19,7 +19,6 @@ #include #include -#include #include #include "../../common/communication/vst2.h" diff --git a/src/plugin/host-process.h b/src/plugin/host-process.h index 5c584a61..cc950c81 100644 --- a/src/plugin/host-process.h +++ b/src/plugin/host-process.h @@ -16,6 +16,8 @@ #pragma once +#include + // Boost.Process's auto detection for vfork() support doesn't seem to work #define BOOST_POSIX_HAS_VFORK 1 @@ -23,7 +25,6 @@ #include #include #include -#include #include "../common/communication/common.h" #include "../common/logging/common.h" diff --git a/src/wine-host/bridges/vst2.h b/src/wine-host/bridges/vst2.h index 63848d91..23426289 100644 --- a/src/wine-host/bridges/vst2.h +++ b/src/wine-host/bridges/vst2.h @@ -25,8 +25,6 @@ #include #include -#include - #include "../../common/communication/vst2.h" #include "../../common/configuration.h" #include "../editor.h" From 448158df8f9ab1f50e7d2698b666ade4fc0f0c72 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 22:53:33 +0100 Subject: [PATCH 390/456] Update comments in Editor implementation --- src/wine-host/editor.cpp | 18 ++++++++---------- src/wine-host/editor.h | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 67c7bd5f..b543050c 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -19,8 +19,8 @@ #include /** - * The most significant bit in an event's response type is used to indicate - * whether the event source. + * The most significant bit in an X11 event's response type is used to indicate + * the event source. */ constexpr uint16_t event_type_mask = ((1 << 7) - 1); @@ -186,9 +186,9 @@ Editor::Editor(const Configuration& config, if (use_xembed) { // This call alone doesn't do anything. We need to call this function a - // second time on visibility change, because Wine's XEmbed - // implementation does not work properly (which is why we remvoed XEmbed - // support in the first place). + // second time on visibility change because Wine's XEmbed implementation + // does not work properly (which is why we remvoed XEmbed support in the + // first place). do_xembed(); } else { // Embed the Win32 window into the window provided by the host. Instead @@ -256,7 +256,8 @@ Editor::~Editor() { } HWND Editor::get_win32_handle() const { - if (win32_child_handle) { + // FIXME: The double embed and XEmbed options don't work together right now + if (win32_child_handle && !use_xembed) { return win32_child_handle->get(); } else { return win32_handle.get(); @@ -264,9 +265,6 @@ HWND Editor::get_win32_handle() const { } void Editor::handle_x11_events() const { - // TODO: Initiating drag-and-drop in Serum _sometimes_ causes the GUI to - // update while dragging while other times it does not. From all the - // plugins I've tested this only happens in Serum though. xcb_generic_event_t* generic_event; while ((generic_event = xcb_poll_for_event(x11_connection.get())) != nullptr) { @@ -350,7 +348,7 @@ void Editor::fix_local_coordinates() const { return; } - // We're purposely not using XEmbed. This has the consequence that wine + // We're purposely not using XEmbed here. This has the consequence that wine // still thinks that any X and Y coordinates are relative to the x11 window // root instead of the parent window provided by the DAW, causing all sorts // of GUI interactions to break. To alleviate this we'll just lie to Wine diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index bccba193..0d428f8c 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -86,7 +86,7 @@ class WindowClass { * * This workaround was inspired by LinVst. * - * As of yabridge 3.0 XEMbed is back as an option, but it's disabled by default + * As of yabridge 3.0 XEmbed is back as an option, but it's disabled by default * because of the issues mentioned above. */ class Editor { From 91a96249fc8d98403899c4ce98826edd75dab623 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 25 Dec 2020 23:59:26 +0100 Subject: [PATCH 391/456] Implement IEditController2::setKnobMode --- src/common/logging/vst3.cpp | 9 +++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + .../vst3/plugin/edit-controller-2.h | 18 ++++++++++++++++++ src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 6 ++++++ src/wine-host/bridges/vst3.h | 1 + 7 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 7c68b823..257bff76 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -253,6 +253,15 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaEditController2::SetKnobMode& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IEditController2::setKnobMode(mode = " << request.mode + << ")"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaPlugView::IsPlatformTypeSupported& request) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 65c52e31..552b095a 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -88,6 +88,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaEditController::SetComponentHandler&); bool log_request(bool is_host_vst, const YaEditController::CreateView&); + bool log_request(bool is_host_vst, const YaEditController2::SetKnobMode&); bool log_request(bool is_host_vst, const YaPlugView::IsPlatformTypeSupported&); bool log_request(bool is_host_vst, const YaPlugView::Attached&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 61d93ab3..cff9d4dd 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -81,6 +81,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(mode); + } + }; + virtual tresult PLUGIN_API setKnobMode(Steinberg::Vst::KnobMode mode) override = 0; virtual tresult PLUGIN_API openHelp(TBool onlyCheck) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index de929ccc..e2abaa51 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -394,9 +394,8 @@ Vst3PluginProxyImpl::createView(Steinberg::FIDString name) { tresult PLUGIN_API Vst3PluginProxyImpl::setKnobMode(Steinberg::Vst::KnobMode mode) { - // TODO: Implement - bridge.logger.log("TODO: IEditController2::setKnobMode()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaEditController2::SetKnobMode{ + .instance_id = instance_id(), .mode = mode}); } tresult PLUGIN_API Vst3PluginProxyImpl::openHelp(TBool onlyCheck) { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index e6464be2..0e4eb535 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -34,6 +34,7 @@ InstanceInterfaces::InstanceInterfaces( component(object), connection_point(object), edit_controller(object), + edit_controller_2(object), plugin_base(object) {} Vst3Bridge::Vst3Bridge(MainContext& main_context, @@ -335,6 +336,11 @@ void Vst3Bridge::run() { request.instance_id) : std::nullopt)}; }, + [&](const YaEditController2::SetKnobMode& request) + -> YaEditController2::SetKnobMode::Response { + return object_instances[request.instance_id] + .edit_controller_2->setKnobMode(request.mode); + }, [&](const YaPlugView::IsPlatformTypeSupported& request) -> YaPlugView::IsPlatformTypeSupported::Response { // The host will of course want to pass an X11 window ID for the diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index 929a55d9..fe31615d 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -113,6 +113,7 @@ struct InstanceInterfaces { Steinberg::FUnknownPtr component; Steinberg::FUnknownPtr connection_point; Steinberg::FUnknownPtr edit_controller; + Steinberg::FUnknownPtr edit_controller_2; Steinberg::FUnknownPtr plugin_base; }; From 3c5700163e1f5a790e94ddb25206b46f72bf86ba Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 00:06:32 +0100 Subject: [PATCH 392/456] Implement IEditController2::openHelp --- src/common/logging/vst3.cpp | 9 +++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + .../vst3/plugin/edit-controller-2.h | 19 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 257bff76..17240ff4 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -262,6 +262,15 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaEditController2::OpenHelp& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IEditController2::openHelp(onlyCheck = " + << (request.only_check ? "true" : "false") << ")"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaPlugView::IsPlatformTypeSupported& request) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 552b095a..385e5135 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -89,6 +89,7 @@ class Vst3Logger { const YaEditController::SetComponentHandler&); bool log_request(bool is_host_vst, const YaEditController::CreateView&); bool log_request(bool is_host_vst, const YaEditController2::SetKnobMode&); + bool log_request(bool is_host_vst, const YaEditController2::OpenHelp&); bool log_request(bool is_host_vst, const YaPlugView::IsPlatformTypeSupported&); bool log_request(bool is_host_vst, const YaPlugView::Attached&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index cff9d4dd..8f72ca2d 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -82,6 +82,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(instance_id); + s.value1b(only_check); + } + }; + virtual tresult PLUGIN_API openHelp(TBool onlyCheck) override = 0; virtual tresult PLUGIN_API openAboutBox(TBool onlyCheck) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index e2abaa51..a2b773c3 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -399,9 +399,8 @@ Vst3PluginProxyImpl::setKnobMode(Steinberg::Vst::KnobMode mode) { } tresult PLUGIN_API Vst3PluginProxyImpl::openHelp(TBool onlyCheck) { - // TODO: Implement - bridge.logger.log("TODO: IEditController2::openHelp()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaEditController2::OpenHelp{ + .instance_id = instance_id(), .only_check = onlyCheck}); } tresult PLUGIN_API Vst3PluginProxyImpl::openAboutBox(TBool onlyCheck) { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 0e4eb535..8dae153e 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -341,6 +341,11 @@ void Vst3Bridge::run() { return object_instances[request.instance_id] .edit_controller_2->setKnobMode(request.mode); }, + [&](const YaEditController2::OpenHelp& request) + -> YaEditController2::OpenHelp::Response { + return object_instances[request.instance_id] + .edit_controller_2->openHelp(request.only_check); + }, [&](const YaPlugView::IsPlatformTypeSupported& request) -> YaPlugView::IsPlatformTypeSupported::Response { // The host will of course want to pass an X11 window ID for the From 86aaf2fa3a3d39f17058ed68f05ba9f74f6f87b7 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 00:10:39 +0100 Subject: [PATCH 393/456] Implement IEditController2::openAboutBox With this IEditController2 has been fully implemented. --- README.md | 5 ++--- src/common/logging/vst3.cpp | 9 +++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 1 + .../vst3/plugin/edit-controller-2.h | 19 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 7 files changed, 39 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7cbf6ed4..e1d45aa4 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,8 @@ This branch is still very far removed from being in a usable state. Below is an incomplete list of things that still have to be done before this can be used: - Interfaces left to implement: - - `IEditController2` - - All other mandatory interfaces - - All other optional interfaces + - All other mandatory and optional VST 3.0 interfaces + - All interfaces introduced after that - Fully implemented: see [this document](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/src/common/serialization/vst3/README.md) - Update yabridgectl to handle buth VST2 and VST3 plugins. diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 17240ff4..83dd5c88 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -271,6 +271,15 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaEditController2::OpenAboutBox& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IEditController2::openAboutBox(onlyCheck = " + << (request.only_check ? "true" : "false") << ")"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaPlugView::IsPlatformTypeSupported& request) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 385e5135..9077f66a 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -90,6 +90,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaEditController::CreateView&); bool log_request(bool is_host_vst, const YaEditController2::SetKnobMode&); bool log_request(bool is_host_vst, const YaEditController2::OpenHelp&); + bool log_request(bool is_host_vst, const YaEditController2::OpenAboutBox&); bool log_request(bool is_host_vst, const YaPlugView::IsPlatformTypeSupported&); bool log_request(bool is_host_vst, const YaPlugView::Attached&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 8f72ca2d..87d4551a 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -83,6 +83,7 @@ using ControlRequest = std::variant + void serialize(S& s) { + s.value8b(instance_id); + s.value1b(only_check); + } + }; + virtual tresult PLUGIN_API openAboutBox(TBool onlyCheck) override = 0; protected: diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index a2b773c3..de7c1deb 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -404,9 +404,8 @@ tresult PLUGIN_API Vst3PluginProxyImpl::openHelp(TBool onlyCheck) { } tresult PLUGIN_API Vst3PluginProxyImpl::openAboutBox(TBool onlyCheck) { - // TODO: Implement - bridge.logger.log("TODO: IEditController2::openAboutBox()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaEditController2::OpenAboutBox{ + .instance_id = instance_id(), .only_check = onlyCheck}); } tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* context) { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 8dae153e..5f86f1c1 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -346,6 +346,11 @@ void Vst3Bridge::run() { return object_instances[request.instance_id] .edit_controller_2->openHelp(request.only_check); }, + [&](const YaEditController2::OpenAboutBox& request) + -> YaEditController2::OpenAboutBox::Response { + return object_instances[request.instance_id] + .edit_controller_2->openAboutBox(request.only_check); + }, [&](const YaPlugView::IsPlatformTypeSupported& request) -> YaPlugView::IsPlatformTypeSupported::Response { // The host will of course want to pass an X11 window ID for the From 57e23ee39256b9639c4271a9ce4bd62a2880ba1e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 01:42:55 +0100 Subject: [PATCH 394/456] Rewrite X11 event mask A literal like this is much more understandable. --- src/wine-host/editor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index b543050c..5de2f913 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -22,7 +22,7 @@ * The most significant bit in an X11 event's response type is used to indicate * the event source. */ -constexpr uint16_t event_type_mask = ((1 << 7) - 1); +constexpr uint16_t event_type_mask = 0b0111'1111; /** * The name of the X11 property on the root window used to denote the active From f4ad43638f041f114606bb8ee8c437018fc76fb1 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 12:55:57 +0100 Subject: [PATCH 395/456] [yabridgectl] Update readme for VST3 --- tools/yabridgectl/README.md | 63 ++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/tools/yabridgectl/README.md b/tools/yabridgectl/README.md index 0366b119..85b21562 100644 --- a/tools/yabridgectl/README.md +++ b/tools/yabridgectl/README.md @@ -13,11 +13,12 @@ from anywhere. All of the information below can also be found through ### Yabridge path -Yabridgectl will need to know where it can find the `libyabridge-vst*.so` files. -By default it will search for it in both `~/.local/share/yabridge` (the -recommended installation directory when using the prebuilt binaries) and in -`/usr/lib`. You can use the command below to override this behaviour and to use -a custom installation directory instead. +Yabridgectl will need to know where it can find `libyabridge-vst2.so` and +`libyabridge-vst3.so`. By default it will search for it in both +`~/.local/share/yabridge` (the recommended installation directory when using the +prebuilt binaries), in `/usr/lib` and in `/usr/local/lib`. You can use the +command below to override this behaviour and to use a custom installation +directory instead. ```shell yabridgectl set --path= @@ -26,11 +27,11 @@ yabridgectl set --path= ### Installation methods Yabridge can be set up using either copies or symlinks. By default, yabridgectl -will use the copy-based installation method since this will work with any VST -host. If you are using a DAW that supports individually sandboxed plugins such -as Bitwig Studio, then you can choose between using copies and symlinks using -the command below. Make sure to rerun `yabridgectl sync` after changing this -setting. +will use the copy-based installation method since this will work with any host, +and there's usually no reason to use symlinks anymore. If you are using a DAW +that supports individually sandboxed plugins such as Bitwig Studio, then you can +choose between using copies and symlinks using the command below. Make sure to +rerun `yabridgectl sync` after changing this setting. ```shell yabridgectl set --method= @@ -38,15 +39,18 @@ yabridgectl set --method= ### Managing directories -Yabridgectl can manage multiple Windows VST plugin install locations for you. To -add, remove and list directories, you can use the commands below. The status -command will show you yabridgectl's current settings and the installation status -for all of your plugins. +Yabridgectl can manage multiple Windows plugin install locations for you. +Whenever you run `yabridgectl sync` it will search these directories for VST2 +plugins and VST3 modules. To add, remove and list directories, you can use the +commands below. The status command will show you yabridgectl's current settings +and the installation status for all of your plugins. ```shell # Add a directory containing plugins -# Use the command from the next line to add the most common VST2 plugin directory +# Use the command from the next line to add the most common VST2 plugin directory: # yabridgectl add "$HOME/.wine/drive_c/Program Files/Steinberg/VstPlugins" +# VST3 plugins are located here: +# yabridgectl add "$HOME/.wine/drive_c/Program Files/Common Files/VST3" yabridgectl add # Remove a plugin location, this will ask you if you want to remove any leftover files from yabridge yabridgectl rm @@ -59,12 +63,13 @@ yabridgectl status ### Installing and updating Lastly you can tell yabridgectl to set up or update yabridge for all of your -plugins at once using the commands below. Yabridgectl will warn you if it finds -unrelated `.so` files that may have been left after uninstalling a plugin. You -can rerun the sync command with the `--prune` option to delete those files. If -you are using the default copy-based installation method, it will also verify -that your search `PATH` has been set up correctly so you can get up and running -faster. +VST2 and VST3 plugins at the same time using the commands below. Yabridgectl +will warn you if it finds unrelated `.so` files that may have been left after +uninstalling a plugin, or if it finds any unknown VST3 plugins in +`~/.vst3/yabridge`. You can rerun the sync command with the `--prune` option to +delete those files. If you are using the default copy-based installation method, +it will also verify that your search `PATH` has been set up correctly so you can +get up and running faster. ```shell # Set up or update yabridge for all plugins found under the plugin locations @@ -77,15 +82,15 @@ yabridgectl sync --force ## Alternatives -TODO: This now only mentions how to do this for VST2 plugins. We should probably -just drop this section altogether since using yabridgectl is so much easier, and -if someone really wants to do this by hand then they'll be able to whip up their -own script based on the instructions in the main readme. - If you want to script your own installation behaviour and don't feel like using -yabridgectl, then you could use one of the below bash snippets instead. This -approach is slightly less robust and does not perform any problem detection or -status reporting, but it will get you started. +yabridgectl, then you could use one of the below bash snippets instead to set up +yabridge for VST2 plugins. This approach is slightly less robust and does not +perform any problem detection or status reporting, but it will get you started. +Doing the same thing for VST3 plugins is much more complicated and it involves +[merged +bundle](https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles) +with the Windows VST3 module symlinked in, so it's recommended to have +yabridgectl do that for you. ```shell # For use with symlinks From 38f34f91f2befd2617499b1d8354125188318d4a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 13:31:29 +0100 Subject: [PATCH 396/456] Update the readme for VST3 --- CHANGELOG.md | 3 + README.md | 136 ++++++++++++++++++++++++++----------------- docs/architecture.md | 2 +- 3 files changed, 88 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8cf67c5..263f395f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +TODO: Remove the mentions of VST3 support not yet being part of the released +version from the readme + ### Added TODO: Add the relevant entries here for yabridge's VST3 support diff --git a/README.md b/README.md index e1d45aa4..8a71946e 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,14 @@ [![Discord](https://img.shields.io/discord/786993304197267527.svg?label=Discord&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/pyNeweqadf) Yet Another way to use Windows VST plugins on Linux. Yabridge seamlessly -supports running both 64-bit Windows VST2 plugins as well as 32-bit Windows VST2 -plugins in a 64-bit Linux VST host, with optional support for inter-plugin -communication through [plugin groups](#plugin-groups). Its modern concurrent -architecture and focus on transparency allows yabridge to be both fast and -highly compatible, while also staying easy to debug and maintain. +supports running both 32-bit and 64-bit Windows VST2 and Windows VST3 plugins in +a 64-bit Linux VST host, with optional support for inter-plugin communication +through [plugin groups](#plugin-groups). Its modern concurrent architecture and +focus on transparency allows yabridge to be both fast and highly compatible, +while also staying easy to debug and maintain. + +_VST3 support is scheduled for yabridge 3.0. At the moment it's only available +on the master branch._ ## TODOs @@ -20,9 +23,6 @@ incomplete list of things that still have to be done before this can be used: - All interfaces introduced after that - Fully implemented: see [this document](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/src/common/serialization/vst3/README.md) -- Update yabridgectl to handle buth VST2 and VST3 plugins. -- Update all documentation to refer to VST2 and VST3 support separately, and - figure out how to do this in the least confusing way possible. - Mention that this update will break all existing symlinks and that the old `libyabridge.so` file should be removed when upgrading. - Pay close attention when updating the plugin groups section of the readme, @@ -31,9 +31,6 @@ incomplete list of things that still have to be done before this can be used: - Update all the AUR packages for the `libyabridge-vst{2,3}.so` changes. - Test the binaries built on GitHub on plain Ubuntu 18.04, are we missing any static linking? -- When this is in a usable enough state to be merged into master, make sure to - put a notice up at the top of the readme saying that `libyabridge.so` is now - called `libyabridge-vst2.so` for the master branch version. ![yabridge screenshot](https://raw.githubusercontent.com/robbert-vdh/yabridge/master/screenshot.png) @@ -63,7 +60,7 @@ incomplete list of things that still have to be done before this can be used: ## Tested with -Yabridge has been tested under the following VST hosts using Wine Staging 5.9: +Yabridge has been tested under the following hosts using Wine Staging 6.0: - Bitwig Studio 3.3 - Carla 2.2 @@ -95,10 +92,8 @@ Linux Mint and Pop!\_OS should install Wine Staging from the [WineHQ repositories](https://wiki.winehq.org/Download) as the versions of Wine provided by those distro's repositories will be too old to be used with yabridge. -Most VST plugins first need to be installed in your Wine environment before -they can be converted by yabridge for use in linux. For a general overview -on how to use Wine to install Windows applications, check out Wine's -[user guide](https://wiki.winehq.org/Wine_User%27s_Guide#Using_Wine). +For a general overview on how to use Wine to install Windows applications, check +out Wine's [user guide](https://wiki.winehq.org/Wine_User%27s_Guide#Using_Wine). ### Automatic setup (recommended) @@ -124,27 +119,34 @@ yabridge from source or if you installed the files to some other location, then you can use `yabridgectl set --path=` to tell yabridgectl where it can find the files. -Next, you'll want to tell yabridgectl where it can find your plugins. For this -you can use yabridgectl's `add`, `rm` and `list` commands. For instance, to add -the most common VST2 plugin directory, use `yabridgectl add "$HOME/.wine/drive_c/Program Files/Steinberg/VstPlugins"`. You can use +Next, you'll want to tell yabridgectl where it can find your VST2 and VST3 +plugins. **Note that VST3 support is not yet available on yabridge 2.x.** For +this you can use yabridgectl's `add`, `rm` and `list` commands. You can also use `yabridgectl status` to get an overview of the current settings and the -installation status of all of your plugins. +installation status of all of your plugins. To add the most common VST2 plugin +directory, use +`yabridgectl add "$HOME/.wine/drive_c/Program Files/Steinberg/VstPlugins"`. VST3 +plugins under Windows are always installed to the same directory, and you can +use `yabridgectl add "$HOME/.wine/drive_c/Program Files/Common Files/VST3"` to +add that one. Finally, you can run `yabridgectl sync` to finish setting up yabridge for all of -your plugins. Simply tell your VST host to search for plugins in the directories -you've just added using `yabridgectl add` and you'll be good to go. _Don't -forget to rerun `yabridgectl sync` whenever you update yabridge if you are using -the default copy-based installation method._ +your plugins. For VST2 plugins this will create `.so` files alongside the +Windows VST2 plugins, so if you tell your Linux VST host to search for VST2 +plugins there you'll be good to go. VST3 plugins are always set up in +`~/.vst3/yabridge` as per the VST3 specification, and your VST3 host will pick +those up automatically. _Don't forget to rerun `yabridgectl sync` whenever you +update yabridge if you are using the default copy-based installation method._ ### Manual setup Setting up yabridge through yabridgectl is the recommended installation method as it makes updating easier and yabridgectl will check for some common mistakes -during the installation process. To set up yabridge without using yabridgectl, -first download and extract yabridge's files like in the section above. The rest -of this section assumes that you have extracted the files to `~/.local/share` -(such that `~/.local/share/yabridge/libyabridge-vst2.so` exists), and that you -want to set up yabridge for the VST2 plugin called +during the installation process. To manually set up yabridge for VST2 plugins, first +download and extract yabridge's files like in the section above. The rest of +this section assumes that you have extracted the files to `~/.local/share` (such +that `~/.local/share/yabridge/libyabridge-vst2.so` exists), and that you want to +set up yabridge for the VST2 plugin called `~/.wine/drive_c/Program Files/Steinberg/VstPlugins/plugin.dll`. Depending on whether you want to use copy or symlink-based installation method, @@ -159,17 +161,19 @@ cp ~/.local/share/yabridge/libyabridge-vst2.so "$HOME/.wine/drive_c/Program File ln -sf ~/.local/share/yabridge/libyabridge-vst2.so "$HOME/.wine/drive_c/Program Files/Steinberg/VstPlugins/plugin.so" ``` -The symlink-based installation method will not work with any host that does not -individually sandbox its plugins. If you are using the copy-based installation -method, then don't forget to overwrite all copies of `libyabridge-vst2.so` you -created this way whenever you update yabridge. +Doing the same thing for VST3 plugins involves creating a [merged VST3 +bundle](https://steinbergmedia.github.io/vst3_doc/vstinterfaces/vst3loc.html#mergedbundles) +by hand with the Windows VST3 plugin symlinked in. Doing this without +yabridgectl is not supported since it's a very error prone process. ### DAW setup -Finally, open your DAW's VST location configuration and tell it to look for -plugins under `~/.wine/drive_c/Program Files/Steinberg/VstPlugins`, or whichever -directories you've added in yabridgectl. That way it will automatically pick up -all of your Windows VST2 plugins. +After first setting up yabridge for VST2 plugins, open your DAW's plugin location +configuration and tell it to search for VST2 plugins under +`~/.wine/drive_c/Program Files/Steinberg/VstPlugins`, or whichever directories +you've added in yabridgectl. That way it will automatically pick up all of your +Windows VST2 plugins. For VST3 plugins no additional DAW configuration is +needed, as those plugins will be set up under `~/.vst3/yabridge`. ### Bitbridge @@ -180,6 +184,10 @@ yabridge is also able to load 32-bit VST plugins. The installation procedure for automatically detect whether a plugin is 32-bit or 64-bit on startup and it will handle it accordingly. +_Because of the way VST3 bundles work, it's at the moment not possible to choose +between the 32-bit and 64-bit versions of a VST3 plugin if you have both +installed. We'll add a `yabridge.toml` option for this later._ + ### Wine prefixes It is also possible to use yabridge with multiple Wine prefixes. Yabridge will @@ -191,8 +199,8 @@ the Wine prefix for all instances of yabridge. This section is only relevant if you're using the _copy-based_ installation method and your yabridge files are located somewhere other than in -`~/.local/share/yabridge`. If you're using one of the AUR packages then you can -also skip this section. +`~/.local/share/yabridge`. You can likely skip this section. If you're using one +of the AUR packages then you also don't have to worry about any of this. Yabridge needs to know where it can find `yabridge-host.exe`. By default yabridge will search your through search path as well as in @@ -275,6 +283,12 @@ process. Of course, plugin groups with the same name but in different Wine prefixes and with different architectures will be run independently of each other. See below for an [example](#example) of how these groups can be set up. +_Note that because of the way VST3 works, multiple instances of a single VST3 +plugin will always be hosted in a single process regardless of whether you have +enabled plugin groups or not. The only reason to use plugin groups with VST3 +plugins is to get slightly lower loading times the first time you load a new +plugin._ + #### Compatibility options | Option | Values | Description | @@ -289,18 +303,12 @@ and plugins you use you might want to enable some of them. #### Example -All of the paths used here are relative to the `yabridge.toml` file. +All of the paths used here are relative to the `yabridge.toml` file. A +configuration file for VST2 plugins might look a little something like this: ```toml # ~/.wine/drive_c/Program Files/Steinberg/VstPlugins/yabridge.toml -# This would cause all plugins to be hosted within a single process. Doing so -# greatly reduces the loading time of individual plugins, with the caveat being -# that plugins are no longer sandboxed from eachother. -# -# ["*"] -# group = "all" - ["FabFilter Pro-Q 3.so"] group = "fabfilter" @@ -333,6 +341,28 @@ group = "This will be ignored!" # Of course, you can also add multiple plugins to the same group by hand ["iZotope7/Insight 2.so"] group = "izotope" + +# This would cause all plugins to be hosted within a single process. Doing so +# greatly reduces the loading time of individual plugins, with the caveat being +# that plugins are no longer sandboxed from eachother. +# +# ["*"] +# group = "all" +``` + +For VST3 plugins you should just match the directory instead of the `.so` file +deep within in, like this: + +```toml +# ~/.vst3/yabridge/yabridge.toml + +["FabFilter*.vst3"] +group = "fabfilter" + +["Misstortion2.vst3"] +# This option is not needed and also not recommended, but an example config file +# without any options looks weird +editor_xembed = true ``` ## Troubleshooting common issues @@ -415,7 +445,7 @@ these negative side effects: - First of all, you'll want to make sure that you can run programs with realtime priorities. Note that on Arch and Manjaro this does not necessarily require a - realtime kernel as they include the `PREMPT` patch set in their regular + realtime kernel as they include the `PREEMPT` patch set in their regular kernels. You can verify that this is workign correctly by running `chrt -f 10 whoami`, which should print your username. @@ -447,15 +477,16 @@ these negative side effects: other distros, then please let me know! - [Plugin groups](#plugin-groups) can also greatly improve performance when - using many instances of the same plugin. Some plugins, like the BBC Spitfire + using many instances of the same VST2 plugin. _VST3 plugins have similar + functionality built in by design_. Some plugins, like the BBC Spitfire plugins, can share a lot of resources between different instances of the plugin. Hosting all instances of the same plugin in a single process can in those cases greatly reduce overall CPU usage and get rid of latency spikes. ## Runtime dependencies and known issues -Any VST2 plugin should function out of the box, although some plugins will need -some additional dependencies for their GUIs to work correctly. Notable examples +Any plugin should function out of the box, although some plugins will need some +additional dependencies for their GUIs to work correctly. Notable examples include: - **Serum** requires you to disable `d2d1.dll` in `winecfg` and to install @@ -546,6 +577,7 @@ The following dependencies are included in the repository as a Meson wrap: - [tomlplusplus](https://github.com/marzer/tomlplusplus) - Version 3.7.1 of the [VST3 SDK](https://github.com/robbert-vdh/vst3sdk) with some [patches](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/tools/patch-vst3-sdk.sh) + to allow Winelib compilation The project can then be compiled as follows: @@ -572,7 +604,7 @@ After you've finished building you can follow the instructions under the It is also possible to compile a host application for yabridge that's compatible with 32-bit plugins such as old SynthEdit plugins. This will allow yabridge to -act as a bitbirdge, allowing you to run old 32-bit only Windows VST2 plugins in +act as a bitbridge, allowing you to run old 32-bit only Windows VST2 plugins in a modern 64-bit Linux VST host. For this you'll need to have installed the 32 bit versions of the Boost and XCB libraries. This can then be set up as follows: diff --git a/docs/architecture.md b/docs/architecture.md index 93a80873..ebfe6c29 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -5,7 +5,7 @@ TODO: This document has not yet been updated since adding VST3 support The project consists of two components: a Linux native VST plugin (`libyabridge.so`) and a VST host that runs under Wine (`yabridge-host.exe`/`yabridge-host.exe.so`, and -`yabridge-host-32.exe`/`yabridge-host-32.exe.so` if the bitbirdge is enabled). +`yabridge-host-32.exe`/`yabridge-host-32.exe.so` if the bitbridge is enabled). I'll refer to the copy of or the symlink to `libyabridge.so` as _the plugin_, the native Linux VST host that's hosting the plugin as _the native VST host_, the Wine VST host application that's hosting a Windows `.dll` file as _the Wine From d32e566446f1e228f2505bdfcd4c15de9734197b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 13:44:23 +0100 Subject: [PATCH 397/456] Update the changelog for VST3 support --- CHANGELOG.md | 16 ++++++++++++---- README.md | 5 ----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 263f395f..07ad7728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,16 @@ version from the readme ### Added -TODO: Add the relevant entries here for yabridge's VST3 support - +- Yabridge 3.0 introduces the first ever Wine VST3 bridge, allowing you to use + Windows VST3 plugins in Linux VST3 hosts with full VST 3.7.1 compatibility. + Simply tell yabridgectl to look for plugins in + `$HOME/.wine/drive_c/Program Files/Common Files/VST3`, run `yabridgectl sync`, + and your VST3 compatible DAW will pick up the new plugins in + `~/.vst3/yabridge` automatically. Even though this feature has been tested + extensively with a variety of VST3 plugins and hosts, there's still a large + part of the VST 3.7.1 specification that none of the hosts or plugins we can + across actually used, so please let me know if you run into any weird + behaviour! - Added the `with-vst3` compile time option to control whether yabridge should be built with VST3 support. This is enabled by default. - Added an @@ -26,8 +34,8 @@ TODO: Add the relevant entries here for yabridge's VST3 support ### Changed - `libyabridge.so` is now called `libyabridge-vst2.so`. If you're using - yabridgectl then nothing changes here. To avoid any confusion in the future - it's adviced to remove the old `libyabridge.so` file when upgrading. + yabridgectl then nothing changes here. **To avoid any confusion in the future, + please remove the old `libyabridge.so` file before upgrading.** - VST2 editor idle events are now handled slightly differently. This should result in even more responsive GUIs and I have not come across any plugins where this caused issues, but please let me know if it does break anything for diff --git a/README.md b/README.md index 8a71946e..4dca59f5 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,6 @@ incomplete list of things that still have to be done before this can be used: - All interfaces introduced after that - Fully implemented: see [this document](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/src/common/serialization/vst3/README.md) -- Mention that this update will break all existing symlinks and that the old - `libyabridge.so` file should be removed when upgrading. -- Pay close attention when updating the plugin groups section of the readme, - since VST3 plugins by design cannot be hosted completely individually (as in, - each plugin is basically in its own group). - Update all the AUR packages for the `libyabridge-vst{2,3}.so` changes. - Test the binaries built on GitHub on plain Ubuntu 18.04, are we missing any static linking? From b9b6129933df23b8a38e627689cb805f2593dd48 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 13:54:26 +0100 Subject: [PATCH 398/456] Rewrite the readme introduction --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4dca59f5..bc44f820 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,10 @@ [![Discord](https://img.shields.io/discord/786993304197267527.svg?label=Discord&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/pyNeweqadf) Yet Another way to use Windows VST plugins on Linux. Yabridge seamlessly -supports running both 32-bit and 64-bit Windows VST2 and Windows VST3 plugins in -a 64-bit Linux VST host, with optional support for inter-plugin communication -through [plugin groups](#plugin-groups). Its modern concurrent architecture and +supports using both 32-bit and 64-bit Windows VST2 and VST3 plugins in a 64-bit +Linux VST host as if they were native VST2 and VST3 plugins, with optional +support for [plugin groups](#plugin-groups) to enable inter-plugin communication +for VST2 plugins and quick startup times. Its modern concurrent architecture and focus on transparency allows yabridge to be both fast and highly compatible, while also staying easy to debug and maintain. From 5e26d30752367623acd64ffc3caa030f718de997 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 14:12:40 +0100 Subject: [PATCH 399/456] Add a wrapper for IUnitHandler --- CHANGELOG.md | 4 +- README.md | 8 +- meson.build | 2 + src/common/serialization/vst3/README.md | 1 + .../vst3/component-handler/unit-handler.cpp | 26 +++++++ .../vst3/component-handler/unit-handler.h | 74 +++++++++++++++++++ 6 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 src/common/serialization/vst3/component-handler/unit-handler.cpp create mode 100644 src/common/serialization/vst3/component-handler/unit-handler.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 07ad7728..613f38d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,8 @@ Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] -TODO: Remove the mentions of VST3 support not yet being part of the released -version from the readme +TODO: Remove the mentions of VST3 support not yet being part of the released version from the readme +TODO: Add an updates screenshot with some fancy VST3-only plugins to the readme ### Added diff --git a/README.md b/README.md index bc44f820..5387c579 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,11 @@ on the master branch._ This branch is still very far removed from being in a usable state. Below is an incomplete list of things that still have to be done before this can be used: -- Interfaces left to implement: - - All other mandatory and optional VST 3.0 interfaces - - All interfaces introduced after that -- Fully implemented: see [this +- Support all VST 3.7.1 interfaces. We're currently support all mandatory VST + 3.0.0 interfaces with one or two missing options interfaces. +- See [this document](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/src/common/serialization/vst3/README.md) + for all fully implemetned interfaces. - Update all the AUR packages for the `libyabridge-vst{2,3}.so` changes. - Test the binaries built on GitHub on plain Ubuntu 18.04, are we missing any static linking? diff --git a/meson.build b/meson.build index b4c42e78..532eb9a3 100644 --- a/meson.build +++ b/meson.build @@ -86,6 +86,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/component-handler/component-handler.cpp', + 'src/common/serialization/vst3/component-handler/unit-handler.cpp', 'src/common/serialization/vst3/host-context/host-application.cpp', 'src/common/serialization/vst3/attribute-list.cpp', 'src/common/serialization/vst3/base.cpp', @@ -142,6 +143,7 @@ if with_vst3 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/component-handler/component-handler.cpp', + 'src/common/serialization/vst3/component-handler/unit-handler.cpp', 'src/common/serialization/vst3/host-context/host-application.cpp', 'src/common/serialization/vst3/attribute-list.cpp', 'src/common/serialization/vst3/base.cpp', diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index b9b8aeae..7fb81ac4 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -29,6 +29,7 @@ VST3 host interfaces are implemented as follows: | `YaHostApplication` | `Vst3HostContextProxy` | `IHostApplication` | | `Vst3ComponentHandlerProxy` | | All of the below: | | `YaComponentHandler` | `Vst3ComponentHandlerProxy` | `IComponentHandler` | +| `YaUnitHandler` | `Vst3ComponentHandlerProxy` | `IUnitHandler` | | `Vst3PlugFrameProxy` | | All of the below: | | `YaPlugFrame` | `Vst3PlugFrameProxy` | `IPlugFrame` | diff --git a/src/common/serialization/vst3/component-handler/unit-handler.cpp b/src/common/serialization/vst3/component-handler/unit-handler.cpp new file mode 100644 index 00000000..cd04034e --- /dev/null +++ b/src/common/serialization/vst3/component-handler/unit-handler.cpp @@ -0,0 +1,26 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "unit-handler.h" + +YaUnitHandler::ConstructArgs::ConstructArgs() {} + +YaUnitHandler::ConstructArgs::ConstructArgs( + Steinberg::IPtr object) + : supported(Steinberg::FUnknownPtr(object)) {} + +YaUnitHandler::YaUnitHandler(const ConstructArgs&& args) + : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/component-handler/unit-handler.h b/src/common/serialization/vst3/component-handler/unit-handler.h new file mode 100644 index 00000000..b861eb70 --- /dev/null +++ b/src/common/serialization/vst3/component-handler/unit-handler.h @@ -0,0 +1,74 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "../../common.h" +#include "../base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IUnitHandler` for serialization purposes. This is instantiated + * as part of `Vst3UnitHandlerProxy`. + */ +class YaUnitHandler : public Steinberg::Vst::IUnitHandler { + public: + /** + * These are the arguments for creating a `YaUnitHandler`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Check whether an existing implementation implements `IUnitHandler` + * and read arguments from it. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + template + void serialize(S& s) { + s.value1b(supported); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaUnitHandler(const ConstructArgs&& args); + + inline bool supported() const { return arguments.supported; } + + virtual tresult PLUGIN_API + notifyUnitSelection(Steinberg::Vst::UnitID unitId) override = 0; + virtual tresult PLUGIN_API + notifyProgramListChange(Steinberg::Vst::ProgramListID listId, + int32 programIndex) override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop From 934aea386052f8fdb83ef9a35f093a25a763346c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 14:17:10 +0100 Subject: [PATCH 400/456] Add IUnitHandler stubs to component handler proxy --- .../vst3/component-handler-proxy.cpp | 5 ++++- .../serialization/vst3/component-handler-proxy.h | 6 +++++- .../vst3-impls/component-handler-proxy.cpp | 15 +++++++++++++++ .../bridges/vst3-impls/component-handler-proxy.h | 7 +++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/common/serialization/vst3/component-handler-proxy.cpp b/src/common/serialization/vst3/component-handler-proxy.cpp index 3961b91b..c134a39c 100644 --- a/src/common/serialization/vst3/component-handler-proxy.cpp +++ b/src/common/serialization/vst3/component-handler-proxy.cpp @@ -21,10 +21,13 @@ Vst3ComponentHandlerProxy::ConstructArgs::ConstructArgs() {} Vst3ComponentHandlerProxy::ConstructArgs::ConstructArgs( Steinberg::IPtr object, size_t owner_instance_id) - : owner_instance_id(owner_instance_id), component_handler_args(object) {} + : owner_instance_id(owner_instance_id), + component_handler_args(object), + unit_handler_args(object) {} Vst3ComponentHandlerProxy::Vst3ComponentHandlerProxy(const ConstructArgs&& args) : YaComponentHandler(std::move(args.component_handler_args)), + YaUnitHandler(std::move(args.unit_handler_args)), arguments(std::move(args)){FUNKNOWN_CTOR} Vst3ComponentHandlerProxy::~Vst3ComponentHandlerProxy() { diff --git a/src/common/serialization/vst3/component-handler-proxy.h b/src/common/serialization/vst3/component-handler-proxy.h index eced256b..0c82cea4 100644 --- a/src/common/serialization/vst3/component-handler-proxy.h +++ b/src/common/serialization/vst3/component-handler-proxy.h @@ -18,6 +18,7 @@ #include "../common.h" #include "component-handler/component-handler.h" +#include "component-handler/unit-handler.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -30,7 +31,8 @@ * by the plugin we are proxying for the `IComponentHandler*` argument passed to * plugin by the host. */ -class Vst3ComponentHandlerProxy : public YaComponentHandler { +class Vst3ComponentHandlerProxy : public YaComponentHandler, + public YaUnitHandler { public: /** * These are the arguments for constructing a @@ -55,11 +57,13 @@ class Vst3ComponentHandlerProxy : public YaComponentHandler { native_size_t owner_instance_id; YaComponentHandler::ConstructArgs component_handler_args; + YaUnitHandler::ConstructArgs unit_handler_args; template void serialize(S& s) { s.value8b(owner_instance_id); s.object(component_handler_args); + s.object(unit_handler_args); } }; diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp index ecdb4384..690501e1 100644 --- a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp @@ -66,3 +66,18 @@ Vst3ComponentHandlerProxyImpl::restartComponent(int32 flags) { return bridge.send_message(YaComponentHandler::RestartComponent{ .owner_instance_id = owner_instance_id(), .flags = flags}); } + +tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::notifyUnitSelection( + Steinberg::Vst::UnitID unitId) { + // TODO: Implement + std::cerr << "TODO: IUnitHandler::notifyUnitSelection" << std::endl; + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::notifyProgramListChange( + Steinberg::Vst::ProgramListID listId, + int32 programIndex) { + // TODO: Implement + std::cerr << "TODO: IUnitHandler::notifyProgramListChange" << std::endl; + return Steinberg::kNotImplemented; +} diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.h b/src/wine-host/bridges/vst3-impls/component-handler-proxy.h index a4433891..fcf0edf9 100644 --- a/src/wine-host/bridges/vst3-impls/component-handler-proxy.h +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.h @@ -39,6 +39,13 @@ class Vst3ComponentHandlerProxyImpl : public Vst3ComponentHandlerProxy { tresult PLUGIN_API endEdit(Steinberg::Vst::ParamID id) override; tresult PLUGIN_API restartComponent(int32 flags) override; + // From `IUnitHandler` + tresult PLUGIN_API + notifyUnitSelection(Steinberg::Vst::UnitID unitId) override; + tresult PLUGIN_API + notifyProgramListChange(Steinberg::Vst::ProgramListID listId, + int32 programIndex) override; + private: Vst3Bridge& bridge; }; From bf40e107800b54eeaa5943d5589c484f48b7c877 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 14:26:00 +0100 Subject: [PATCH 401/456] Implement IUnitHandler::notifyUnitSelection --- src/common/logging/vst3.cpp | 10 ++++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 3 ++- .../vst3/component-handler/unit-handler.h | 19 +++++++++++++++++++ .../bridges/vst3-impls/plugin-factory.h | 4 ++-- .../bridges/vst3-impls/plugin-proxy.cpp | 4 ++++ src/plugin/bridges/vst3-impls/plugin-proxy.h | 9 +++++++-- src/plugin/bridges/vst3.cpp | 6 ++++++ .../vst3-impls/component-handler-proxy.cpp | 5 ++--- 9 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 83dd5c88..d7a8f10d 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -700,6 +700,16 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request( + bool is_host_vst, + const YaUnitHandler::NotifyUnitSelection& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id + << ": IUnitHandler::notifyUnitSelection(unitId = " + << request.unit_id << ")"; + }); +} + void Vst3Logger::log_response(bool is_host_vst, const Ack&) { log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 9077f66a..72696842 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -139,6 +139,8 @@ class Vst3Logger { const YaComponentHandler::RestartComponent&); bool log_request(bool is_host_vst, const YaHostApplication::GetName&); bool log_request(bool is_host_vst, const YaPlugFrame::ResizeView&); + bool log_request(bool is_host_vst, + const YaUnitHandler::NotifyUnitSelection&); void log_response(bool is_host_vst, const Ack&); void log_response( diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 87d4551a..5fea6574 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -153,7 +153,8 @@ using CallbackRequest = std::variant; + YaPlugFrame::ResizeView, + YaUnitHandler::NotifyUnitSelection>; template void serialize(S& s, CallbackRequest& payload) { diff --git a/src/common/serialization/vst3/component-handler/unit-handler.h b/src/common/serialization/vst3/component-handler/unit-handler.h index b861eb70..0f322a40 100644 --- a/src/common/serialization/vst3/component-handler/unit-handler.h +++ b/src/common/serialization/vst3/component-handler/unit-handler.h @@ -61,6 +61,25 @@ class YaUnitHandler : public Steinberg::Vst::IUnitHandler { inline bool supported() const { return arguments.supported; } + /** + * Message to pass through a call to + * `IUnitHandler::notifyUnitSelection(unit_id)` to the unit handler provided + * by the host. + */ + struct NotifyUnitSelection { + using Response = UniversalTResult; + + native_size_t owner_instance_id; + + Steinberg::Vst::UnitID unit_id; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.value4b(unit_id); + } + }; + virtual tresult PLUGIN_API notifyUnitSelection(Steinberg::Vst::UnitID unitId) override = 0; virtual tresult PLUGIN_API diff --git a/src/plugin/bridges/vst3-impls/plugin-factory.h b/src/plugin/bridges/vst3-impls/plugin-factory.h index 3e2d93a6..209ce765 100644 --- a/src/plugin/bridges/vst3-impls/plugin-factory.h +++ b/src/plugin/bridges/vst3-impls/plugin-factory.h @@ -28,8 +28,8 @@ class YaPluginFactoryImpl : public YaPluginFactory { void** obj) override; tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override; - // The following pointers are cast from `host_context` if `setHostContext()` - // has been called + // The following pointers are cast from `host_context` if + // `IPluginFactory3::setHostContext()` has been called Steinberg::FUnknownPtr host_application; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index de7c1deb..bf94bbd7 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -357,6 +357,10 @@ tresult PLUGIN_API Vst3PluginProxyImpl::setComponentHandler( // this component handler component_handler = handler; + // Automatically converted smart pointers for when the plugin performs a + // callback later + unit_handler = component_handler; + return bridge.send_message(YaEditController::SetComponentHandler{ .instance_id = instance_id(), .component_handler_proxy_args = diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index d767cd2b..c9ca866f 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -152,11 +152,16 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { */ Vst3PlugViewProxyImpl* last_created_plug_view = nullptr; - // The following pointers are cast from `host_context` if `setHostContext()` - // has been called + // The following pointers are cast from `host_context` if + // `IPluginBase::initialize()` has been called Steinberg::FUnknownPtr host_application; + // The following pointers are cast from `component_handler` if + // `IEditController::setComponentHandler()` has been called + + Steinberg::FUnknownPtr unit_handler; + private: Vst3PluginBridge& bridge; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index 61a4e43e..c0480f2b 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -145,6 +145,12 @@ Vst3PluginBridge::Vst3PluginBridge() return plug_view->plug_frame->resizeView(plug_view, &request.new_size); }, + [&](const YaUnitHandler::NotifyUnitSelection& request) + -> YaUnitHandler::NotifyUnitSelection::Response { + return plugin_proxies.at(request.owner_instance_id) + .get() + .unit_handler->notifyUnitSelection(request.unit_id); + }, }); }); } diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp index 690501e1..f16ae185 100644 --- a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp @@ -69,9 +69,8 @@ Vst3ComponentHandlerProxyImpl::restartComponent(int32 flags) { tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::notifyUnitSelection( Steinberg::Vst::UnitID unitId) { - // TODO: Implement - std::cerr << "TODO: IUnitHandler::notifyUnitSelection" << std::endl; - return Steinberg::kNotImplemented; + return bridge.send_message(YaUnitHandler::NotifyUnitSelection{ + .owner_instance_id = owner_instance_id(), .unit_id = unitId}); } tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::notifyProgramListChange( From 47177ed8892555b8a15aeb272a6aff9593149648 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 14:30:28 +0100 Subject: [PATCH 402/456] Implement IUnitHandler::notifyProgramListChange With this IUnitHandler has been fully implemented. --- src/common/logging/vst3.cpp | 11 ++++++++++ src/common/logging/vst3.h | 2 ++ src/common/serialization/vst3.h | 3 ++- .../vst3/component-handler/unit-handler.h | 22 +++++++++++++++++++ src/plugin/bridges/vst3.cpp | 7 ++++++ .../vst3-impls/component-handler-proxy.cpp | 7 +++--- 6 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index d7a8f10d..ec1d4544 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -710,6 +710,17 @@ bool Vst3Logger::log_request( }); } +bool Vst3Logger::log_request( + bool is_host_vst, + const YaUnitHandler::NotifyProgramListChange& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.owner_instance_id + << ": IUnitHandler::notifyProgramListChange(listId = " + << request.list_id + << ", programIndex = " << request.program_index << ")"; + }); +} + void Vst3Logger::log_response(bool is_host_vst, const Ack&) { log_response_base(is_host_vst, [&](auto& message) { message << "ACK"; }); } diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 72696842..26abcf35 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -141,6 +141,8 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPlugFrame::ResizeView&); bool log_request(bool is_host_vst, const YaUnitHandler::NotifyUnitSelection&); + bool log_request(bool is_host_vst, + const YaUnitHandler::NotifyProgramListChange&); void log_response(bool is_host_vst, const Ack&); void log_response( diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 5fea6574..86e6d6a1 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -154,7 +154,8 @@ using CallbackRequest = std::variant; + YaUnitHandler::NotifyUnitSelection, + YaUnitHandler::NotifyProgramListChange>; template void serialize(S& s, CallbackRequest& payload) { diff --git a/src/common/serialization/vst3/component-handler/unit-handler.h b/src/common/serialization/vst3/component-handler/unit-handler.h index 0f322a40..21e82cfe 100644 --- a/src/common/serialization/vst3/component-handler/unit-handler.h +++ b/src/common/serialization/vst3/component-handler/unit-handler.h @@ -82,6 +82,28 @@ class YaUnitHandler : public Steinberg::Vst::IUnitHandler { virtual tresult PLUGIN_API notifyUnitSelection(Steinberg::Vst::UnitID unitId) override = 0; + + /** + * Message to pass through a call to + * `IUnitHandler::notifyProgramListChange(list_id, program_index)` to the + * unit handler provided by the host. + */ + struct NotifyProgramListChange { + using Response = UniversalTResult; + + native_size_t owner_instance_id; + + Steinberg::Vst::ProgramListID list_id; + int32 program_index; + + template + void serialize(S& s) { + s.value8b(owner_instance_id); + s.value4b(list_id); + s.value4b(program_index); + } + }; + virtual tresult PLUGIN_API notifyProgramListChange(Steinberg::Vst::ProgramListID listId, int32 programIndex) override = 0; diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index c0480f2b..fcb9eee0 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -151,6 +151,13 @@ Vst3PluginBridge::Vst3PluginBridge() .get() .unit_handler->notifyUnitSelection(request.unit_id); }, + [&](const YaUnitHandler::NotifyProgramListChange& request) + -> YaUnitHandler::NotifyProgramListChange::Response { + return plugin_proxies.at(request.owner_instance_id) + .get() + .unit_handler->notifyProgramListChange( + request.list_id, request.program_index); + }, }); }); } diff --git a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp index f16ae185..ece96996 100644 --- a/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/component-handler-proxy.cpp @@ -76,7 +76,8 @@ tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::notifyUnitSelection( tresult PLUGIN_API Vst3ComponentHandlerProxyImpl::notifyProgramListChange( Steinberg::Vst::ProgramListID listId, int32 programIndex) { - // TODO: Implement - std::cerr << "TODO: IUnitHandler::notifyProgramListChange" << std::endl; - return Steinberg::kNotImplemented; + return bridge.send_message(YaUnitHandler::NotifyProgramListChange{ + .owner_instance_id = owner_instance_id(), + .list_id = listId, + .program_index = programIndex}); } From db2cc5800aad78d7b92a025a0ccb70974105cac3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 16:30:25 +0100 Subject: [PATCH 403/456] Mostly fix editor GUIs drifting in negative coords I think some rounding in Wine is causing this issue, but then again we're not supposed to send these ConfigureNotify events to the window directly anyways. --- CHANGELOG.md | 5 +++++ src/wine-host/editor.cpp | 36 ++++++++++++++++++++++++++++++++---- src/wine-host/editor.h | 11 +++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 613f38d3..1b1c1dd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,11 @@ TODO: Add an updates screenshot with some fancy VST3-only plugins to the readme and 5.8 required a change, but that change now breaks builds using Wine 6.0 and up, so this change has been reverted. +### Fixed + +- Mostly fixed editor GUIs drifting off to the top or the left when dragging the + window off screen in those directions. + ### yabridgectl - Updated for the changes in yabridge 3.0. Yabridgectl now allows you to set up diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 5de2f913..5ba57a78 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -376,16 +376,44 @@ void Editor::fix_local_coordinates() const { translated_event.response_type = XCB_CONFIGURE_NOTIFY; translated_event.event = wine_window; translated_event.window = wine_window; - // This should be set to the same sizes the window was created on. Since - // we're not using `SetWindowPos` to resize the Window, Wine can get a bit - // confused when we suddenly report a different client area size. Without - // this certain plugins (such as those by Valhalla DSP) would break. + // This should be set to the same sizes the window was created on. Wine can + // get a bit confused when we suddenly report a different client area size. + // Without this certain plugins (such as those by Valhalla DSP) would break. translated_event.width = client_area.width; translated_event.height = client_area.height; translated_event.x = translated_coordinates->dst_x; translated_event.y = translated_coordinates->dst_y; free(translated_coordinates); + // When the window gets dragged off to the left or top corner of the screen + // a lot of plugin's GUIs start to drift off in the wrong direction when we + // send these negative coordinates in a ConfigureNotify event. To somewhat + // work around this, we'll move the Wine window to offset these changes. + // Since this could in theory cause other weird behaviour, we'll only undo + // these changes (by moving the window back to `(0, 0)`) once. + int correction_x = 0; + int correction_y = 0; + if (translated_event.x < 0) { + correction_x = -translated_event.x; + } + if (translated_event.y < 0) { + correction_y = -translated_event.y; + } + + if (correction_x != 0 || correction_y != 0) { + SetWindowPos( + get_win32_handle(), nullptr, correction_x, correction_y, 0, 0, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER); + has_negative_coordinate_correction = true; + } else if (has_negative_coordinate_correction.compare_exchange_strong( + const_cast(static_cast(true)), false)) { + // Undo this only once, since who knows what side effects this might + // cause + SetWindowPos( + get_win32_handle(), nullptr, 0, 0, 0, 0, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER); + } + xcb_send_event( x11_connection.get(), false, wine_window, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index 0d428f8c..56e6e47f 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -250,6 +250,17 @@ class Editor { */ const xcb_window_t topmost_window; + /** + * In `fix_local_coordinates()` we send a ConfigureNotify event to the Wine + * window with its screen coordinates. Because you're not supposed to do + * that directly, we're getting some strange behaviour on some plugins when + * the window gets dragged off screen to the left or the top. We perform a + * small workaround to mostly correct this issue. This flag indicates + * whether we have done this in the last call to `fix_local_coordinates()`, + * since we only only need to undo the changes made there once. + */ + mutable std::atomic_bool has_negative_coordinate_correction = false; + /** * The atom corresponding to `_NET_ACTIVE_WINDOW`. */ From bce3afa5e48a2e5faa916a5ee40cead7c5881cba Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 18:13:40 +0100 Subject: [PATCH 404/456] Revert "Mostly fix editor GUIs drifting in negative coords" I've also tried a lot of other things, but none of the solutions I've tried work 100% of the time. It sounds like a better idea to have something that doesn't work consistently than to have something that inconsistently sort of works. Setting the size in `WM_WINDOWPOSCHANGING` to (0, 0) fixes the drifting, but the mouse coordinates are still wrong and `SetWindowPos()` breaks the reparenting. This reverts commit db2cc5800aad78d7b92a025a0ccb70974105cac3. --- CHANGELOG.md | 5 ----- src/wine-host/editor.cpp | 36 ++++-------------------------------- src/wine-host/editor.h | 11 ----------- 3 files changed, 4 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b1c1dd3..613f38d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,11 +45,6 @@ TODO: Add an updates screenshot with some fancy VST3-only plugins to the readme and 5.8 required a change, but that change now breaks builds using Wine 6.0 and up, so this change has been reverted. -### Fixed - -- Mostly fixed editor GUIs drifting off to the top or the left when dragging the - window off screen in those directions. - ### yabridgectl - Updated for the changes in yabridge 3.0. Yabridgectl now allows you to set up diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 5ba57a78..5de2f913 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -376,44 +376,16 @@ void Editor::fix_local_coordinates() const { translated_event.response_type = XCB_CONFIGURE_NOTIFY; translated_event.event = wine_window; translated_event.window = wine_window; - // This should be set to the same sizes the window was created on. Wine can - // get a bit confused when we suddenly report a different client area size. - // Without this certain plugins (such as those by Valhalla DSP) would break. + // This should be set to the same sizes the window was created on. Since + // we're not using `SetWindowPos` to resize the Window, Wine can get a bit + // confused when we suddenly report a different client area size. Without + // this certain plugins (such as those by Valhalla DSP) would break. translated_event.width = client_area.width; translated_event.height = client_area.height; translated_event.x = translated_coordinates->dst_x; translated_event.y = translated_coordinates->dst_y; free(translated_coordinates); - // When the window gets dragged off to the left or top corner of the screen - // a lot of plugin's GUIs start to drift off in the wrong direction when we - // send these negative coordinates in a ConfigureNotify event. To somewhat - // work around this, we'll move the Wine window to offset these changes. - // Since this could in theory cause other weird behaviour, we'll only undo - // these changes (by moving the window back to `(0, 0)`) once. - int correction_x = 0; - int correction_y = 0; - if (translated_event.x < 0) { - correction_x = -translated_event.x; - } - if (translated_event.y < 0) { - correction_y = -translated_event.y; - } - - if (correction_x != 0 || correction_y != 0) { - SetWindowPos( - get_win32_handle(), nullptr, correction_x, correction_y, 0, 0, - SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER); - has_negative_coordinate_correction = true; - } else if (has_negative_coordinate_correction.compare_exchange_strong( - const_cast(static_cast(true)), false)) { - // Undo this only once, since who knows what side effects this might - // cause - SetWindowPos( - get_win32_handle(), nullptr, 0, 0, 0, 0, - SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER); - } - xcb_send_event( x11_connection.get(), false, wine_window, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index 56e6e47f..0d428f8c 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -250,17 +250,6 @@ class Editor { */ const xcb_window_t topmost_window; - /** - * In `fix_local_coordinates()` we send a ConfigureNotify event to the Wine - * window with its screen coordinates. Because you're not supposed to do - * that directly, we're getting some strange behaviour on some plugins when - * the window gets dragged off screen to the left or the top. We perform a - * small workaround to mostly correct this issue. This flag indicates - * whether we have done this in the last call to `fix_local_coordinates()`, - * since we only only need to undo the changes made there once. - */ - mutable std::atomic_bool has_negative_coordinate_correction = false; - /** * The atom corresponding to `_NET_ACTIVE_WINDOW`. */ From f940d3371b3f7bdc6a5d9f03848ccf1ff3a8de3d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 21:13:23 +0100 Subject: [PATCH 405/456] Allow casting YaComponentInfo to IUnitHandler Quite an important detail. --- src/common/serialization/vst3/component-handler-proxy.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common/serialization/vst3/component-handler-proxy.cpp b/src/common/serialization/vst3/component-handler-proxy.cpp index c134a39c..45753acb 100644 --- a/src/common/serialization/vst3/component-handler-proxy.cpp +++ b/src/common/serialization/vst3/component-handler-proxy.cpp @@ -48,6 +48,10 @@ Vst3ComponentHandlerProxy::queryInterface(Steinberg::FIDString _iid, QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IComponentHandler::iid, Steinberg::Vst::IComponentHandler) } + if (YaUnitHandler::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IUnitHandler::iid, + Steinberg::Vst::IUnitHandler) + } *obj = nullptr; return Steinberg::kNoInterface; From aed369c4be6d7b3d69e16783e3d1ed000d00b188 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 21:17:59 +0100 Subject: [PATCH 406/456] Add stubs for IUnitInfo This is a big boy. --- meson.build | 2 + src/common/serialization/vst3/README.md | 1 + .../serialization/vst3/plugin-proxy.cpp | 8 +- src/common/serialization/vst3/plugin-proxy.h | 6 +- .../serialization/vst3/plugin/unit-info.cpp | 26 +++++ .../serialization/vst3/plugin/unit-info.h | 108 ++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 98 ++++++++++++++++ src/plugin/bridges/vst3-impls/plugin-proxy.h | 38 ++++++ 8 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 src/common/serialization/vst3/plugin/unit-info.cpp create mode 100644 src/common/serialization/vst3/plugin/unit-info.h diff --git a/meson.build b/meson.build index 532eb9a3..cb033ff4 100644 --- a/meson.build +++ b/meson.build @@ -85,6 +85,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/plugin/edit-controller.cpp', 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', + 'src/common/serialization/vst3/plugin/unit-info.cpp', 'src/common/serialization/vst3/component-handler/component-handler.cpp', 'src/common/serialization/vst3/component-handler/unit-handler.cpp', 'src/common/serialization/vst3/host-context/host-application.cpp', @@ -142,6 +143,7 @@ if with_vst3 'src/common/serialization/vst3/plugin/edit-controller.cpp', 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', + 'src/common/serialization/vst3/plugin/unit-info.cpp', 'src/common/serialization/vst3/component-handler/component-handler.cpp', 'src/common/serialization/vst3/component-handler/unit-handler.cpp', 'src/common/serialization/vst3/host-context/host-application.cpp', diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 7fb81ac4..e1e1c1d8 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -20,6 +20,7 @@ VST3 plugin interfaces are implemented as follows: | `YaEditController` | `Vst3PluginProxy` | `IEditController` | | `YaEditController2` | `Vst3PluginProxy` | `IEditController2` | | `YaPluginBase` | `Vst3PluginProxy` | `IPluginBase` | +| `YaUnitInfo` | `Vst3PluginProxy` | `IUnitInfo` | VST3 host interfaces are implemented as follows: diff --git a/src/common/serialization/vst3/plugin-proxy.cpp b/src/common/serialization/vst3/plugin-proxy.cpp index 5f4b07a1..417b55ce 100644 --- a/src/common/serialization/vst3/plugin-proxy.cpp +++ b/src/common/serialization/vst3/plugin-proxy.cpp @@ -27,7 +27,8 @@ Vst3PluginProxy::ConstructArgs::ConstructArgs( connection_point_args(object), edit_controller_args(object), edit_controller_2_args(object), - plugin_base_args(object) {} + plugin_base_args(object), + unit_info_args(object) {} Vst3PluginProxy::Vst3PluginProxy(const ConstructArgs&& args) : YaAudioProcessor(std::move(args.audio_processor_args)), @@ -36,6 +37,7 @@ Vst3PluginProxy::Vst3PluginProxy(const ConstructArgs&& args) YaEditController(std::move(args.edit_controller_args)), YaEditController2(std::move(args.edit_controller_2_args)), YaPluginBase(std::move(args.plugin_base_args)), + YaUnitInfo(std::move(args.unit_info_args)), arguments(std::move(args)){FUNKNOWN_CTOR} Vst3PluginProxy::~Vst3PluginProxy() { @@ -87,6 +89,10 @@ tresult PLUGIN_API Vst3PluginProxy::queryInterface(Steinberg::FIDString _iid, QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IEditController2::iid, Steinberg::Vst::IEditController2) } + if (YaUnitInfo::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IUnitInfo::iid, + Steinberg::Vst::IUnitInfo) + } *obj = nullptr; return Steinberg::kNoInterface; diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index 9e6d177e..892f2239 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -25,6 +25,7 @@ #include "plugin/edit-controller-2.h" #include "plugin/edit-controller.h" #include "plugin/plugin-base.h" +#include "plugin/unit-info.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" @@ -57,7 +58,8 @@ class Vst3PluginProxy : public YaAudioProcessor, public YaConnectionPoint, public YaEditController, public YaEditController2, - public YaPluginBase { + public YaPluginBase, + public YaUnitInfo { public: /** * These are the arguments for constructing a `Vst3PluginProxyImpl`. @@ -82,6 +84,7 @@ class Vst3PluginProxy : public YaAudioProcessor, YaEditController::ConstructArgs edit_controller_args; YaEditController2::ConstructArgs edit_controller_2_args; YaPluginBase::ConstructArgs plugin_base_args; + YaUnitInfo::ConstructArgs unit_info_args; template void serialize(S& s) { @@ -92,6 +95,7 @@ class Vst3PluginProxy : public YaAudioProcessor, s.object(edit_controller_args); s.object(edit_controller_2_args); s.object(plugin_base_args); + s.object(unit_info_args); } }; diff --git a/src/common/serialization/vst3/plugin/unit-info.cpp b/src/common/serialization/vst3/plugin/unit-info.cpp new file mode 100644 index 00000000..8f8cf098 --- /dev/null +++ b/src/common/serialization/vst3/plugin/unit-info.cpp @@ -0,0 +1,26 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "unit-info.h" + +YaUnitInfo::ConstructArgs::ConstructArgs() {} + +YaUnitInfo::ConstructArgs::ConstructArgs( + Steinberg::IPtr object) + : supported(Steinberg::FUnknownPtr(object)) {} + +YaUnitInfo::YaUnitInfo(const ConstructArgs&& args) + : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h new file mode 100644 index 00000000..d421530d --- /dev/null +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -0,0 +1,108 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "../../common.h" +#include "../base.h" +#include "../host-context-proxy.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IUnitInfo` for serialization purposes. This is instantiated as + * part of `Vst3PluginProxy`. + */ +class YaUnitInfo : public Steinberg::Vst::IUnitInfo { + public: + /** + * These are the arguments for creating a `YaUnitInfo`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Check whether an existing implementation implements `IUnitInfo` and + * read arguments from it. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + template + void serialize(S& s) { + s.value1b(supported); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaUnitInfo(const ConstructArgs&& args); + + inline bool supported() const { return arguments.supported; } + + virtual int32 PLUGIN_API getUnitCount() override = 0; + virtual tresult PLUGIN_API + getUnitInfo(int32 unitIndex, + Steinberg::Vst::UnitInfo& info /*out*/) override = 0; + virtual int32 PLUGIN_API getProgramListCount() override = 0; + virtual tresult PLUGIN_API getProgramListInfo( + int32 listIndex, + Steinberg::Vst::ProgramListInfo& info /*out*/) override = 0; + virtual tresult PLUGIN_API + getProgramName(Steinberg::Vst::ProgramListID listId, + int32 programIndex, + Steinberg::Vst::String128 name /*out*/) override = 0; + virtual tresult PLUGIN_API getProgramInfo( + Steinberg::Vst::ProgramListID listId, + int32 programIndex, + Steinberg::Vst::CString attributeId /*in*/, + Steinberg::Vst::String128 attributeValue /*out*/) override = 0; + virtual tresult PLUGIN_API + hasProgramPitchNames(Steinberg::Vst::ProgramListID listId, + int32 programIndex) override = 0; + virtual tresult PLUGIN_API + getProgramPitchName(Steinberg::Vst::ProgramListID listId, + int32 programIndex, + int16 midiPitch, + Steinberg::Vst::String128 name /*out*/) override = 0; + virtual Steinberg::Vst::UnitID PLUGIN_API getSelectedUnit() override = 0; + virtual tresult PLUGIN_API + selectUnit(Steinberg::Vst::UnitID unitId) override = 0; + virtual tresult PLUGIN_API + getUnitByBus(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 busIndex, + int32 channel, + Steinberg::Vst::UnitID& unitId /*out*/) override = 0; + virtual tresult PLUGIN_API + setUnitProgramData(int32 listOrUnitId, + int32 programIndex, + Steinberg::IBStream* data) override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index bf94bbd7..79e39057 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -439,3 +439,101 @@ tresult PLUGIN_API Vst3PluginProxyImpl::terminate() { return bridge.send_message( YaPluginBase::Terminate{.instance_id = instance_id()}); } + +int32 PLUGIN_API Vst3PluginProxyImpl::getUnitCount() { + // TODO: Implement + bridge.logger.log("TODO: IUnitInfo::getUnitCount()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::getUnitInfo(int32 unitIndex, + Steinberg::Vst::UnitInfo& info /*out*/) { + // TODO: Implement + bridge.logger.log("TODO: IUnitInfo::getUnitInfo()"); + return Steinberg::kNotImplemented; +} + +int32 PLUGIN_API Vst3PluginProxyImpl::getProgramListCount() { + // TODO: Implement + bridge.logger.log("TODO: IUnitInfo::getProgramListCount()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PluginProxyImpl::getProgramListInfo( + int32 listIndex, + Steinberg::Vst::ProgramListInfo& info /*out*/) { + // TODO: Implement + bridge.logger.log("TODO: IUnitInfo::getProgramListInfo()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::getProgramName(Steinberg::Vst::ProgramListID listId, + int32 programIndex, + Steinberg::Vst::String128 name /*out*/) { + // TODO: Implement + bridge.logger.log("TODO: IUnitInfo::getProgramName()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PluginProxyImpl::getProgramInfo( + Steinberg::Vst::ProgramListID listId, + int32 programIndex, + Steinberg::Vst::CString attributeId /*in*/, + Steinberg::Vst::String128 attributeValue /*out*/) { + // TODO: Implement + bridge.logger.log("TODO: IUnitInfo::getProgramInfo()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::hasProgramPitchNames(Steinberg::Vst::ProgramListID listId, + int32 programIndex) { + // TODO: Implement + bridge.logger.log("TODO: IUnitInfo::hasProgramPitchNames()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API Vst3PluginProxyImpl::getProgramPitchName( + Steinberg::Vst::ProgramListID listId, + int32 programIndex, + int16 midiPitch, + Steinberg::Vst::String128 name /*out*/) { + // TODO: Implement + bridge.logger.log("TODO: IUnitInfo::getProgramPitchName()"); + return Steinberg::kNotImplemented; +} + +Steinberg::Vst::UnitID PLUGIN_API Vst3PluginProxyImpl::getSelectedUnit() { + // TODO: Implement + bridge.logger.log("TODO: IUnitInfo::getSelectedUnit()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::selectUnit(Steinberg::Vst::UnitID unitId) { + // TODO: Implement + bridge.logger.log("TODO: IUnitInfo::selectUnit()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::getUnitByBus(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 busIndex, + int32 channel, + Steinberg::Vst::UnitID& unitId /*out*/) { + // TODO: Implement + bridge.logger.log("TODO: IUnitInfo::getUnitByBus()"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::setUnitProgramData(int32 listOrUnitId, + int32 programIndex, + Steinberg::IBStream* data) { + // TODO: Implement + bridge.logger.log("TODO: IUnitInfo::setUnitProgramData()"); + return Steinberg::kNotImplemented; +} diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index c9ca866f..24a7ae84 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -124,6 +124,44 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { tresult PLUGIN_API initialize(FUnknown* context) override; tresult PLUGIN_API terminate() override; + // From `IUnitInfo` + int32 PLUGIN_API getUnitCount() override; + tresult PLUGIN_API + getUnitInfo(int32 unitIndex, + Steinberg::Vst::UnitInfo& info /*out*/) override; + int32 PLUGIN_API getProgramListCount() override; + tresult PLUGIN_API + getProgramListInfo(int32 listIndex, + Steinberg::Vst::ProgramListInfo& info /*out*/) override; + tresult PLUGIN_API + getProgramName(Steinberg::Vst::ProgramListID listId, + int32 programIndex, + Steinberg::Vst::String128 name /*out*/) override; + tresult PLUGIN_API + getProgramInfo(Steinberg::Vst::ProgramListID listId, + int32 programIndex, + Steinberg::Vst::CString attributeId /*in*/, + Steinberg::Vst::String128 attributeValue /*out*/) override; + tresult PLUGIN_API + hasProgramPitchNames(Steinberg::Vst::ProgramListID listId, + int32 programIndex) override; + tresult PLUGIN_API + getProgramPitchName(Steinberg::Vst::ProgramListID listId, + int32 programIndex, + int16 midiPitch, + Steinberg::Vst::String128 name /*out*/) override; + Steinberg::Vst::UnitID PLUGIN_API getSelectedUnit() override; + tresult PLUGIN_API selectUnit(Steinberg::Vst::UnitID unitId) override; + tresult PLUGIN_API + getUnitByBus(Steinberg::Vst::MediaType type, + Steinberg::Vst::BusDirection dir, + int32 busIndex, + int32 channel, + Steinberg::Vst::UnitID& unitId /*out*/) override; + tresult PLUGIN_API setUnitProgramData(int32 listOrUnitId, + int32 programIndex, + Steinberg::IBStream* data) override; + /** * The component handler the host passed to us during * `IEditController::setComponentHandler()`. When the plugin makes a From f3e706a39a1c9b992dbb13fb1201767cfd85e025 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 21:33:19 +0100 Subject: [PATCH 407/456] Implement IUnitInfo::getUnitCount --- src/common/logging/vst3.cpp | 7 +++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 3 ++- src/common/serialization/vst3/plugin/unit-info.h | 15 +++++++++++++++ src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 11 +++++++++-- src/wine-host/bridges/vst3.h | 1 + 7 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index ec1d4544..26c51aa2 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -430,6 +430,13 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitInfo::GetUnitCount& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id << ": IUnitInfo::getUnitCount()"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 26abcf35..9e9c261d 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -108,6 +108,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); bool log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); + bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitCount&); bool log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 86e6d6a1..270a0bfc 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -99,7 +99,8 @@ using ControlRequest = std::variant; + YaPluginFactory::SetHostContext, + YaUnitInfo::GetUnitCount>; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h index d421530d..561ff788 100644 --- a/src/common/serialization/vst3/plugin/unit-info.h +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -62,6 +62,21 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { inline bool supported() const { return arguments.supported; } + /** + * Message to pass through a call to `IUnitInfo::getUnitCount()` to the Wine + * plugin host. + */ + struct GetUnitCount { + using Response = UniversalTResult; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + virtual int32 PLUGIN_API getUnitCount() override = 0; virtual tresult PLUGIN_API getUnitInfo(int32 unitIndex, diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 79e39057..44b1aa06 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -441,9 +441,8 @@ tresult PLUGIN_API Vst3PluginProxyImpl::terminate() { } int32 PLUGIN_API Vst3PluginProxyImpl::getUnitCount() { - // TODO: Implement - bridge.logger.log("TODO: IUnitInfo::getUnitCount()"); - return Steinberg::kNotImplemented; + return bridge.send_message( + YaUnitInfo::GetUnitCount{.instance_id = instance_id()}); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 5f86f1c1..4bbd69e7 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -35,7 +35,8 @@ InstanceInterfaces::InstanceInterfaces( connection_point(object), edit_controller(object), edit_controller_2(object), - plugin_base(object) {} + plugin_base(object), + unit_info(object) {} Vst3Bridge::Vst3Bridge(MainContext& main_context, std::string plugin_dll_path, @@ -564,7 +565,13 @@ void Vst3Bridge::run() { assert(factory_3); return factory_3->setHostContext(plugin_factory_host_context); - }}); + }, + [&](const YaUnitInfo::GetUnitCount& request) + -> YaUnitInfo::GetUnitCount::Response { + return object_instances[request.instance_id] + .unit_info->getUnitCount(); + }, + }); } void Vst3Bridge::handle_x11_events() { diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index fe31615d..f159090b 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -115,6 +115,7 @@ struct InstanceInterfaces { Steinberg::FUnknownPtr edit_controller; Steinberg::FUnknownPtr edit_controller_2; Steinberg::FUnknownPtr plugin_base; + Steinberg::FUnknownPtr unit_info; }; /** From 5e832a26896e582481052df46d88cbd655a608a8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 22:03:26 +0100 Subject: [PATCH 408/456] Implement IUnitInfo::getUnitInfo --- src/common/logging/vst3.cpp | 31 +++++++++++-- src/common/logging/vst3.h | 2 + src/common/serialization/vst3.h | 3 +- .../serialization/vst3/plugin/component.h | 3 ++ .../serialization/vst3/plugin/unit-info.h | 46 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 10 ++-- src/wine-host/bridges/vst3.cpp | 10 ++++ 7 files changed, 96 insertions(+), 9 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 26c51aa2..583b0f59 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -437,6 +437,15 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitInfo::GetUnitInfo& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IUnitInfo::getUnitInfo(unitIndex = " << request.unit_index + << ", &info)"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { @@ -828,6 +837,23 @@ void Vst3Logger::log_response(bool is_host_vst, }); } +void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { + log_response_base(is_host_vst, + [&](auto& message) { message << ""; }); +} + +void Vst3Logger::log_response(bool is_host_vst, + const YaUnitInfo::GetUnitInfoResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", "; + } + }); +} + void Vst3Logger::log_response( bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse& response) { @@ -919,11 +945,6 @@ void Vst3Logger::log_response( }); } -void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { - log_response_base(is_host_vst, - [&](auto& message) { message << ""; }); -} - void Vst3Logger::log_response( bool is_host_vst, const YaHostApplication::GetNameResponse& response) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 9e9c261d..67d0a7c0 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -109,6 +109,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); bool log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitCount&); + bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitInfo&); bool log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); @@ -162,6 +163,7 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaPlugView::GetSizeResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); + void log_response(bool is_host_vst, const YaUnitInfo::GetUnitInfoResponse&); void log_response(bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 270a0bfc..f8406b39 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -100,7 +100,8 @@ using ControlRequest = std::variant; + YaUnitInfo::GetUnitCount, + YaUnitInfo::GetUnitInfo>; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/plugin/component.h b/src/common/serialization/vst3/plugin/component.h index 9449e6fb..7ca7f0db 100644 --- a/src/common/serialization/vst3/plugin/component.h +++ b/src/common/serialization/vst3/plugin/component.h @@ -32,6 +32,9 @@ * part of `Vst3PluginProxy`. Event though `IComponent` inherits from * `IPlguinBase`, we'll implement that separately in `YaPluginBase` because * `IEditController` also inherits from `IPluginBase`. + * + * TODO: Remove the original fields for out parameters in the structs. They're + * really supposed to be empty. */ class YaComponent : public Steinberg::Vst::IComponent { public: diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h index 561ff788..78f0af98 100644 --- a/src/common/serialization/vst3/plugin/unit-info.h +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -78,6 +78,40 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { }; virtual int32 PLUGIN_API getUnitCount() override = 0; + + /** + * The response code and returned unit information for a call to + * `IUnitInfo::getUnitInfo(unit_index)`. + */ + struct GetUnitInfoResponse { + UniversalTResult result; + Steinberg::Vst::UnitInfo info; + + template + void serialize(S& s) { + s.object(result); + s.object(info); + } + }; + + /** + * Message to pass through a call to `IUnitInfo::getUnitInfo(unit_index)` to + * the Wine plugin host. + */ + struct GetUnitInfo { + using Response = GetUnitInfoResponse; + + native_size_t instance_id; + + int32 unit_index; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(unit_index); + } + }; + virtual tresult PLUGIN_API getUnitInfo(int32 unitIndex, Steinberg::Vst::UnitInfo& info /*out*/) override = 0; @@ -121,3 +155,15 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { }; #pragma GCC diagnostic pop + +namespace Steinberg { +namespace Vst { +template +void serialize(S& s, UnitInfo& info) { + s.value4b(info.id); + s.value4b(info.parentUnitId); + s.text2b(info.name); + s.value4b(info.programListId); +} +} // namespace Vst +} // namespace Steinberg diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 44b1aa06..71fdd72a 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -448,9 +448,13 @@ int32 PLUGIN_API Vst3PluginProxyImpl::getUnitCount() { tresult PLUGIN_API Vst3PluginProxyImpl::getUnitInfo(int32 unitIndex, Steinberg::Vst::UnitInfo& info /*out*/) { - // TODO: Implement - bridge.logger.log("TODO: IUnitInfo::getUnitInfo()"); - return Steinberg::kNotImplemented; + const GetUnitInfoResponse response = + bridge.send_message(YaUnitInfo::GetUnitInfo{ + .instance_id = instance_id(), .unit_index = unitIndex}); + + info = response.info; + + return response.result; } int32 PLUGIN_API Vst3PluginProxyImpl::getProgramListCount() { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 4bbd69e7..1bd0058c 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -571,6 +571,16 @@ void Vst3Bridge::run() { return object_instances[request.instance_id] .unit_info->getUnitCount(); }, + [&](const YaUnitInfo::GetUnitInfo& request) + -> YaUnitInfo::GetUnitInfo::Response { + Steinberg::Vst::UnitInfo info; + const tresult result = + object_instances[request.instance_id] + .unit_info->getUnitInfo(request.unit_index, info); + + return YaUnitInfo::GetUnitInfoResponse{.result = result, + .info = info}; + }, }); } From 999cf45d6ab35f1a7654dfa5613bf0708e549908 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 22:07:12 +0100 Subject: [PATCH 409/456] Fix the return type for IUnitInfo::getUnitCount --- src/common/serialization/vst3/plugin/unit-info.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h index 78f0af98..c7ae905e 100644 --- a/src/common/serialization/vst3/plugin/unit-info.h +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -67,7 +67,7 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { * plugin host. */ struct GetUnitCount { - using Response = UniversalTResult; + using Response = PrimitiveWrapper; native_size_t instance_id; From 92a7cb755a91cb43a04b754e085bc483f447a5f0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 22:10:46 +0100 Subject: [PATCH 410/456] Implement IUnitInfo::getProgramListCount --- src/common/logging/vst3.cpp | 7 +++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 3 ++- src/common/serialization/vst3/plugin/unit-info.h | 16 ++++++++++++++++ src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 583b0f59..8922cde7 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -446,6 +446,13 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitInfo::GetProgramListCount& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id << ": IUnitInfo::getProgramListCount()"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 67d0a7c0..9ca88ec8 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -110,6 +110,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitCount&); bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitInfo&); + bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramListCount&); bool log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index f8406b39..db1b1a60 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -101,7 +101,8 @@ using ControlRequest = std::variant; + YaUnitInfo::GetUnitInfo, + YaUnitInfo::GetProgramListCount>; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h index c7ae905e..e4498710 100644 --- a/src/common/serialization/vst3/plugin/unit-info.h +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -115,6 +115,22 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { virtual tresult PLUGIN_API getUnitInfo(int32 unitIndex, Steinberg::Vst::UnitInfo& info /*out*/) override = 0; + + /** + * Message to pass through a call to `IUnitInfo::getProgramListCount()` to + * the Wine plugin host. + */ + struct GetProgramListCount { + using Response = PrimitiveWrapper; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + virtual int32 PLUGIN_API getProgramListCount() override = 0; virtual tresult PLUGIN_API getProgramListInfo( int32 listIndex, diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 71fdd72a..629bd400 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -458,9 +458,8 @@ Vst3PluginProxyImpl::getUnitInfo(int32 unitIndex, } int32 PLUGIN_API Vst3PluginProxyImpl::getProgramListCount() { - // TODO: Implement - bridge.logger.log("TODO: IUnitInfo::getProgramListCount()"); - return Steinberg::kNotImplemented; + return bridge.send_message( + YaUnitInfo::GetProgramListCount{.instance_id = instance_id()}); } tresult PLUGIN_API Vst3PluginProxyImpl::getProgramListInfo( diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 1bd0058c..bbf1f1f4 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -581,6 +581,11 @@ void Vst3Bridge::run() { return YaUnitInfo::GetUnitInfoResponse{.result = result, .info = info}; }, + [&](const YaUnitInfo::GetProgramListCount& request) + -> YaUnitInfo::GetProgramListCount::Response { + return object_instances[request.instance_id] + .unit_info->getProgramListCount(); + }, }); } From 60f6b30b845d3fa368868e6df3a22042a58b8a3d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 22:17:21 +0100 Subject: [PATCH 411/456] Implement IUnitInfo::getProgramListInfo --- src/common/logging/vst3.cpp | 22 ++++++++++ src/common/logging/vst3.h | 3 ++ src/common/serialization/vst3.h | 3 +- .../serialization/vst3/plugin/unit-info.h | 41 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 10 +++-- src/wine-host/bridges/vst3.cpp | 10 +++++ 6 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 8922cde7..8ca1ef0d 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -453,6 +453,15 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitInfo::GetProgramListInfo& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IUnitInfo::getProgramListInfo(listIndex = " + << request.list_index << ", &info)"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { @@ -861,6 +870,19 @@ void Vst3Logger::log_response(bool is_host_vst, }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaUnitInfo::GetProgramListInfoResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", "; + } + }); +} + void Vst3Logger::log_response( bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse& response) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 9ca88ec8..697975cf 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -111,6 +111,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitCount&); bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitInfo&); bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramListCount&); + bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramListInfo&); bool log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); @@ -165,6 +166,8 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); void log_response(bool is_host_vst, const YaUnitInfo::GetUnitInfoResponse&); + void log_response(bool is_host_vst, + const YaUnitInfo::GetProgramListInfoResponse&); void log_response(bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index db1b1a60..279f2e90 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -102,7 +102,8 @@ using ControlRequest = std::variant; + YaUnitInfo::GetProgramListCount, + YaUnitInfo::GetProgramListInfo>; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h index e4498710..21846b83 100644 --- a/src/common/serialization/vst3/plugin/unit-info.h +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -132,6 +132,40 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { }; virtual int32 PLUGIN_API getProgramListCount() override = 0; + + /** + * The response code and returned unit information for a call to + * `IUnitInfo::getProgramListInfo(list_index)`. + */ + struct GetProgramListInfoResponse { + UniversalTResult result; + Steinberg::Vst::ProgramListInfo info; + + template + void serialize(S& s) { + s.object(result); + s.object(info); + } + }; + + /** + * Message to pass through a call to + * `IUnitInfo::getProgramListInfo(list_index)` to the Wine plugin host. + */ + struct GetProgramListInfo { + using Response = GetProgramListInfoResponse; + + native_size_t instance_id; + + int32 list_index; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(list_index); + } + }; + virtual tresult PLUGIN_API getProgramListInfo( int32 listIndex, Steinberg::Vst::ProgramListInfo& info /*out*/) override = 0; @@ -181,5 +215,12 @@ void serialize(S& s, UnitInfo& info) { s.text2b(info.name); s.value4b(info.programListId); } + +template +void serialize(S& s, ProgramListInfo& info) { + s.value4b(info.id); + s.text2b(info.name); + s.value4b(info.programCount); +} } // namespace Vst } // namespace Steinberg diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 629bd400..7c182ad5 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -465,9 +465,13 @@ int32 PLUGIN_API Vst3PluginProxyImpl::getProgramListCount() { tresult PLUGIN_API Vst3PluginProxyImpl::getProgramListInfo( int32 listIndex, Steinberg::Vst::ProgramListInfo& info /*out*/) { - // TODO: Implement - bridge.logger.log("TODO: IUnitInfo::getProgramListInfo()"); - return Steinberg::kNotImplemented; + const GetProgramListInfoResponse response = + bridge.send_message(YaUnitInfo::GetProgramListInfo{ + .instance_id = instance_id(), .list_index = listIndex}); + + info = response.info; + + return response.result; } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index bbf1f1f4..66ee170b 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -586,6 +586,16 @@ void Vst3Bridge::run() { return object_instances[request.instance_id] .unit_info->getProgramListCount(); }, + [&](const YaUnitInfo::GetProgramListInfo& request) + -> YaUnitInfo::GetProgramListInfo::Response { + Steinberg::Vst::ProgramListInfo info; + const tresult result = object_instances[request.instance_id] + .unit_info->getProgramListInfo( + request.list_index, info); + + return YaUnitInfo::GetProgramListInfoResponse{.result = result, + .info = info}; + }, }); } From 204765ec0ce6f68ff7c3695f731c974622d5087d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 22:37:45 +0100 Subject: [PATCH 412/456] Implement IUnitInfo::getProgramName --- src/common/logging/vst3.cpp | 21 +++++++++ src/common/logging/vst3.h | 3 ++ src/common/serialization/vst3.h | 3 +- .../serialization/vst3/plugin/unit-info.h | 47 +++++++++++++++++-- .../bridges/vst3-impls/plugin-proxy.cpp | 12 +++-- src/wine-host/bridges/vst3.cpp | 11 +++++ 6 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 8ca1ef0d..76b6e327 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -462,6 +462,15 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitInfo::GetProgramName& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IUnitInfo::getProgramName(listId = " << request.list_id + << ", programIndex = " << request.program_index << ", &name)"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { @@ -883,6 +892,18 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaUnitInfo::GetProgramNameResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", \"" << VST3::StringConvert::convert(response.name) + << "\""; + } + }); +} + void Vst3Logger::log_response( bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse& response) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 697975cf..cf60d46c 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -112,6 +112,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitInfo&); bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramListCount&); bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramListInfo&); + bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramName&); bool log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); @@ -168,6 +169,8 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaUnitInfo::GetUnitInfoResponse&); void log_response(bool is_host_vst, const YaUnitInfo::GetProgramListInfoResponse&); + void log_response(bool is_host_vst, + const YaUnitInfo::GetProgramNameResponse&); void log_response(bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 279f2e90..233e4534 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -103,7 +103,8 @@ using ControlRequest = std::variant; + YaUnitInfo::GetProgramListInfo, + YaUnitInfo::GetProgramName>; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h index 21846b83..6ff6f454 100644 --- a/src/common/serialization/vst3/plugin/unit-info.h +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -81,7 +81,7 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { /** * The response code and returned unit information for a call to - * `IUnitInfo::getUnitInfo(unit_index)`. + * `IUnitInfo::getUnitInfo(unit_index, &info)`. */ struct GetUnitInfoResponse { UniversalTResult result; @@ -95,8 +95,8 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { }; /** - * Message to pass through a call to `IUnitInfo::getUnitInfo(unit_index)` to - * the Wine plugin host. + * Message to pass through a call to `IUnitInfo::getUnitInfo(unit_index, + * &info)` to the Wine plugin host. */ struct GetUnitInfo { using Response = GetUnitInfoResponse; @@ -135,7 +135,7 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { /** * The response code and returned unit information for a call to - * `IUnitInfo::getProgramListInfo(list_index)`. + * `IUnitInfo::getProgramListInfo(list_index, &info)`. */ struct GetProgramListInfoResponse { UniversalTResult result; @@ -150,7 +150,8 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { /** * Message to pass through a call to - * `IUnitInfo::getProgramListInfo(list_index)` to the Wine plugin host. + * `IUnitInfo::getProgramListInfo(list_index, &info)` to the Wine plugin + * host. */ struct GetProgramListInfo { using Response = GetProgramListInfoResponse; @@ -169,6 +170,42 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { virtual tresult PLUGIN_API getProgramListInfo( int32 listIndex, Steinberg::Vst::ProgramListInfo& info /*out*/) override = 0; + + /** + * The response code and returned name for a call to + * `IUnitInfo::getProgramName(list_id, program_index, &name)`. + */ + struct GetProgramNameResponse { + UniversalTResult result; + std::u16string name; + + template + void serialize(S& s) { + s.object(result); + s.text2b(name, std::extent_v); + } + }; + + /** + * Message to pass through a call to `IUnitInfo::getProgramName(list_id, + * program_index, &name)` to the Wine plugin host. + */ + struct GetProgramName { + using Response = GetProgramNameResponse; + + native_size_t instance_id; + + Steinberg::Vst::ProgramListID list_id; + int32 program_index; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(list_id); + s.value4b(program_index); + } + }; + virtual tresult PLUGIN_API getProgramName(Steinberg::Vst::ProgramListID listId, int32 programIndex, diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 7c182ad5..01ce3d98 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -478,9 +478,15 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getProgramName(Steinberg::Vst::ProgramListID listId, int32 programIndex, Steinberg::Vst::String128 name /*out*/) { - // TODO: Implement - bridge.logger.log("TODO: IUnitInfo::getProgramName()"); - return Steinberg::kNotImplemented; + const GetProgramNameResponse response = bridge.send_message( + YaUnitInfo::GetProgramName{.instance_id = instance_id(), + .list_id = listId, + .program_index = programIndex}); + + std::copy(response.name.begin(), response.name.end(), name); + name[response.name.size()] = 0; + + return response.result; } tresult PLUGIN_API Vst3PluginProxyImpl::getProgramInfo( diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 66ee170b..cc561337 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -596,6 +596,17 @@ void Vst3Bridge::run() { return YaUnitInfo::GetProgramListInfoResponse{.result = result, .info = info}; }, + [&](const YaUnitInfo::GetProgramName& request) + -> YaUnitInfo::GetProgramName::Response { + Steinberg::Vst::String128 name{0}; + const tresult result = + object_instances[request.instance_id] + .unit_info->getProgramName(request.list_id, + request.program_index, name); + + return YaUnitInfo::GetProgramNameResponse{ + .result = result, .name = tchar_pointer_to_u16string(name)}; + }, }); } From f96e6b5a1e71d2be672b0b78870e1535e11a18bb Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 22:59:21 +0100 Subject: [PATCH 413/456] Implement IUnitInfo::getProgramInfo --- src/common/logging/vst3.cpp | 24 ++++++++++++ src/common/logging/vst3.h | 3 ++ src/common/serialization/vst3.h | 3 +- .../serialization/vst3/plugin/unit-info.h | 39 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 16 ++++++-- src/wine-host/bridges/vst3.cpp | 14 +++++++ 6 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 76b6e327..00ad16e2 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -471,6 +471,17 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitInfo::GetProgramInfo& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IUnitInfo::getProgramInfo(listId = " << request.list_id + << ", programIndex = " << request.program_index + << ", attributeId = " << request.attribute_id + << ", &attributeValue)"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { @@ -904,6 +915,19 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaUnitInfo::GetProgramInfoResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", \"" + << VST3::StringConvert::convert(response.attribute_value) + << "\""; + } + }); +} + void Vst3Logger::log_response( bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse& response) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index cf60d46c..c80c3f8a 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -113,6 +113,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramListCount&); bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramListInfo&); bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramName&); + bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramInfo&); bool log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); @@ -171,6 +172,8 @@ class Vst3Logger { const YaUnitInfo::GetProgramListInfoResponse&); void log_response(bool is_host_vst, const YaUnitInfo::GetProgramNameResponse&); + void log_response(bool is_host_vst, + const YaUnitInfo::GetProgramInfoResponse&); void log_response(bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 233e4534..f27b5299 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -104,7 +104,8 @@ using ControlRequest = std::variant; + YaUnitInfo::GetProgramName, + YaUnitInfo::GetProgramInfo>; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h index 6ff6f454..57265d22 100644 --- a/src/common/serialization/vst3/plugin/unit-info.h +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -210,6 +210,45 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { getProgramName(Steinberg::Vst::ProgramListID listId, int32 programIndex, Steinberg::Vst::String128 name /*out*/) override = 0; + + /** + * The response code and returned name for a call to + * `IUnitInfo::getPrograminfo(list_id, program_index, attribute_name, + * &attribute_value)`. + */ + struct GetProgramInfoResponse { + UniversalTResult result; + std::u16string attribute_value; + + template + void serialize(S& s) { + s.object(result); + s.text2b(attribute_value, std::extent_v); + } + }; + + /** + * Message to pass through a call to `IUnitInfo::getProgramInfo(list_id, + * program_index, attribute_id, &attribute_value)` to the Wine plugin host. + */ + struct GetProgramInfo { + using Response = GetProgramInfoResponse; + + native_size_t instance_id; + + Steinberg::Vst::ProgramListID list_id; + int32 program_index; + std::string attribute_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(list_id); + s.value4b(program_index); + s.text1b(attribute_id, 256); + } + }; + virtual tresult PLUGIN_API getProgramInfo( Steinberg::Vst::ProgramListID listId, int32 programIndex, diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 01ce3d98..7319faf2 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -494,9 +494,19 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getProgramInfo( int32 programIndex, Steinberg::Vst::CString attributeId /*in*/, Steinberg::Vst::String128 attributeValue /*out*/) { - // TODO: Implement - bridge.logger.log("TODO: IUnitInfo::getProgramInfo()"); - return Steinberg::kNotImplemented; + assert(attributeId); + + const GetProgramInfoResponse response = bridge.send_message( + YaUnitInfo::GetProgramInfo{.instance_id = instance_id(), + .list_id = listId, + .program_index = programIndex, + .attribute_id = attributeId}); + + std::copy(response.attribute_value.begin(), response.attribute_value.end(), + attributeValue); + attributeValue[response.attribute_value.size()] = 0; + + return response.result; } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index cc561337..b84ce2e7 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -607,6 +607,20 @@ void Vst3Bridge::run() { return YaUnitInfo::GetProgramNameResponse{ .result = result, .name = tchar_pointer_to_u16string(name)}; }, + [&](const YaUnitInfo::GetProgramInfo& request) + -> YaUnitInfo::GetProgramInfo::Response { + Steinberg::Vst::String128 attribute_value{0}; + const tresult result = + object_instances[request.instance_id] + .unit_info->getProgramInfo( + request.list_id, request.program_index, + request.attribute_id.c_str(), attribute_value); + + return YaUnitInfo::GetProgramInfoResponse{ + .result = result, + .attribute_value = + tchar_pointer_to_u16string(attribute_value)}; + }, }); } From e414c58a7ada5f8f2ec6447ae799069b9b4be648 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 23:04:02 +0100 Subject: [PATCH 414/456] Implement IUnitInfo::hasProgramPitchNames --- src/common/logging/vst3.cpp | 10 +++++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 3 ++- .../serialization/vst3/plugin/unit-info.h | 22 +++++++++++++++++++ .../bridges/vst3-impls/plugin-proxy.cpp | 7 +++--- src/wine-host/bridges/vst3.cpp | 6 +++++ 6 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 00ad16e2..ffa03067 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -482,6 +482,16 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitInfo::HasProgramPitchNames& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IUnitInfo::hasProgramPitchNames(listId = " + << request.list_id + << ", programIndex = " << request.program_index << ")"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index c80c3f8a..ff0c9017 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -114,6 +114,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramListInfo&); bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramName&); bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramInfo&); + bool log_request(bool is_host_vst, const YaUnitInfo::HasProgramPitchNames&); bool log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index f27b5299..aa2da1ad 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -105,7 +105,8 @@ using ControlRequest = std::variant; + YaUnitInfo::GetProgramInfo, + YaUnitInfo::HasProgramPitchNames>; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h index 57265d22..9fb5dedb 100644 --- a/src/common/serialization/vst3/plugin/unit-info.h +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -254,6 +254,28 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { int32 programIndex, Steinberg::Vst::CString attributeId /*in*/, Steinberg::Vst::String128 attributeValue /*out*/) override = 0; + + /** + * Message to pass through a call to + * `IUnitInfo::hasProgramPitchNames(list_id, program_index)` to the Wine + * plugin host. + */ + struct HasProgramPitchNames { + using Response = UniversalTResult; + + native_size_t instance_id; + + Steinberg::Vst::ProgramListID list_id; + int32 program_index; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(list_id); + s.value4b(program_index); + } + }; + virtual tresult PLUGIN_API hasProgramPitchNames(Steinberg::Vst::ProgramListID listId, int32 programIndex) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 7319faf2..32d05195 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -512,9 +512,10 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getProgramInfo( tresult PLUGIN_API Vst3PluginProxyImpl::hasProgramPitchNames(Steinberg::Vst::ProgramListID listId, int32 programIndex) { - // TODO: Implement - bridge.logger.log("TODO: IUnitInfo::hasProgramPitchNames()"); - return Steinberg::kNotImplemented; + return bridge.send_message( + YaUnitInfo::HasProgramPitchNames{.instance_id = instance_id(), + .list_id = listId, + .program_index = programIndex}); } tresult PLUGIN_API Vst3PluginProxyImpl::getProgramPitchName( diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index b84ce2e7..86026b2e 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -621,6 +621,12 @@ void Vst3Bridge::run() { .attribute_value = tchar_pointer_to_u16string(attribute_value)}; }, + [&](const YaUnitInfo::HasProgramPitchNames& request) + -> YaUnitInfo::HasProgramPitchNames::Response { + return object_instances[request.instance_id] + .unit_info->hasProgramPitchNames(request.list_id, + request.program_index); + }, }); } From d34b399ba0b05768669b66540c30f088fd906786 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 26 Dec 2020 23:20:13 +0100 Subject: [PATCH 415/456] Implement IUnitInfo::getProgramPitchName --- src/common/logging/vst3.cpp | 23 ++++++++++ src/common/logging/vst3.h | 3 ++ src/common/serialization/vst3.h | 3 +- .../serialization/vst3/plugin/unit-info.h | 42 ++++++++++++++++++- .../bridges/vst3-impls/plugin-proxy.cpp | 13 ++++-- src/wine-host/bridges/vst3.cpp | 12 ++++++ 6 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index ffa03067..2fe0bb51 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -492,6 +492,17 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitInfo::GetProgramPitchName& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IUnitInfo::getProgramPitchName(listId = " + << request.list_id + << ", programIndex = " << request.program_index + << ", midiPitch = " << request.midi_pitch << ", &name)"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { @@ -938,6 +949,18 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaUnitInfo::GetProgramPitchNameResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", \"" << VST3::StringConvert::convert(response.name) + << "\""; + } + }); +} + void Vst3Logger::log_response( bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse& response) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index ff0c9017..173b0b63 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -115,6 +115,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramName&); bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramInfo&); bool log_request(bool is_host_vst, const YaUnitInfo::HasProgramPitchNames&); + bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramPitchName&); bool log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); @@ -175,6 +176,8 @@ class Vst3Logger { const YaUnitInfo::GetProgramNameResponse&); void log_response(bool is_host_vst, const YaUnitInfo::GetProgramInfoResponse&); + void log_response(bool is_host_vst, + const YaUnitInfo::GetProgramPitchNameResponse&); void log_response(bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index aa2da1ad..d40757f3 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -106,7 +106,8 @@ using ControlRequest = std::variant; + YaUnitInfo::HasProgramPitchNames, + YaUnitInfo::GetProgramPitchName>; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h index 9fb5dedb..48402fdd 100644 --- a/src/common/serialization/vst3/plugin/unit-info.h +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -212,7 +212,7 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { Steinberg::Vst::String128 name /*out*/) override = 0; /** - * The response code and returned name for a call to + * The response code and returned value for a call to * `IUnitInfo::getPrograminfo(list_id, program_index, attribute_name, * &attribute_value)`. */ @@ -279,6 +279,46 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { virtual tresult PLUGIN_API hasProgramPitchNames(Steinberg::Vst::ProgramListID listId, int32 programIndex) override = 0; + + /** + * The response code and returned name for a call to + * `IUnitInfo::getProgramPitchName(list_id, program_index, midi_pitch, + * &name)`. + */ + struct GetProgramPitchNameResponse { + UniversalTResult result; + std::u16string name; + + template + void serialize(S& s) { + s.object(result); + s.text2b(name, std::extent_v); + } + }; + + /** + * Message to pass through a call to + * `IUnitInfo::getProgramPitchName(list_id, program_index, midi_pitch, + * &name)` to the Wine plugin host. + */ + struct GetProgramPitchName { + using Response = GetProgramPitchNameResponse; + + native_size_t instance_id; + + Steinberg::Vst::ProgramListID list_id; + int32 program_index; + int16 midi_pitch; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(list_id); + s.value4b(program_index); + s.value2b(midi_pitch); + } + }; + virtual tresult PLUGIN_API getProgramPitchName(Steinberg::Vst::ProgramListID listId, int32 programIndex, diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 32d05195..db48af26 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -523,9 +523,16 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getProgramPitchName( int32 programIndex, int16 midiPitch, Steinberg::Vst::String128 name /*out*/) { - // TODO: Implement - bridge.logger.log("TODO: IUnitInfo::getProgramPitchName()"); - return Steinberg::kNotImplemented; + const GetProgramPitchNameResponse response = bridge.send_message( + YaUnitInfo::GetProgramPitchName{.instance_id = instance_id(), + .list_id = listId, + .program_index = programIndex, + .midi_pitch = midiPitch}); + + std::copy(response.name.begin(), response.name.end(), name); + name[response.name.size()] = 0; + + return response.result; } Steinberg::Vst::UnitID PLUGIN_API Vst3PluginProxyImpl::getSelectedUnit() { diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 86026b2e..bfd15565 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -627,6 +627,18 @@ void Vst3Bridge::run() { .unit_info->hasProgramPitchNames(request.list_id, request.program_index); }, + [&](const YaUnitInfo::GetProgramPitchName& request) + -> YaUnitInfo::GetProgramPitchName::Response { + Steinberg::Vst::String128 name{0}; + const tresult result = + object_instances[request.instance_id] + .unit_info->getProgramPitchName( + request.list_id, request.program_index, + request.midi_pitch, name); + + return YaUnitInfo::GetProgramPitchNameResponse{ + .result = result, .name = tchar_pointer_to_u16string(name)}; + }, }); } From 14c47c1c0977c2f839f1aa680a4b7f135bed1cff Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 12:50:13 +0100 Subject: [PATCH 416/456] Change plugin factory logging format --- src/common/logging/vst3.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 2fe0bb51..248dbf07 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -426,7 +426,7 @@ bool Vst3Logger::log_request(bool is_host_vst, bool Vst3Logger::log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&) { return log_request_base(is_host_vst, [&](auto& message) { - message << "IPluginFactory3::setHostContext()"; + message << "IPluginFactory3::setHostContext(context = )"; }); } @@ -889,8 +889,8 @@ void Vst3Logger::log_response(bool is_host_vst, void Vst3Logger::log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs& args) { log_response_base(is_host_vst, [&](auto& message) { - message << " with " << args.num_classes - << " registered classes"; + message << ""; }); } From 095716d2488c4ef48ee38ba3620b7d462c58dc20 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 12:55:54 +0100 Subject: [PATCH 417/456] Fix retrieving old-style class infos Most plugins implement IPluginFactory3 so this slipped through. Melodyne 5 apparently does not. --- src/common/serialization/vst3/plugin-factory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/serialization/vst3/plugin-factory.cpp b/src/common/serialization/vst3/plugin-factory.cpp index 96d91a4e..3990e3a9 100644 --- a/src/common/serialization/vst3/plugin-factory.cpp +++ b/src/common/serialization/vst3/plugin-factory.cpp @@ -120,7 +120,7 @@ int32 PLUGIN_API YaPluginFactory::countClasses() { tresult PLUGIN_API YaPluginFactory::getClassInfo(Steinberg::int32 index, Steinberg::PClassInfo* info) { - if (index >= static_cast(arguments.class_infos_unicode.size())) { + if (index >= static_cast(arguments.class_infos_1.size())) { return Steinberg::kInvalidArgument; } @@ -134,7 +134,7 @@ tresult PLUGIN_API YaPluginFactory::getClassInfo(Steinberg::int32 index, tresult PLUGIN_API YaPluginFactory::getClassInfo2(int32 index, Steinberg::PClassInfo2* info) { - if (index >= static_cast(arguments.class_infos_1.size())) { + if (index >= static_cast(arguments.class_infos_2.size())) { return Steinberg::kInvalidArgument; } From 07d57771c113cd65c28ad116c6c56a20183e59f7 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 13:03:49 +0100 Subject: [PATCH 418/456] Fix IPluginFactory implementation level detection These were not getting initialized, and could thus evaluate to true if you're unlucky. --- src/common/serialization/vst3/plugin-factory.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/serialization/vst3/plugin-factory.h b/src/common/serialization/vst3/plugin-factory.h index fdc572c7..13292762 100644 --- a/src/common/serialization/vst3/plugin-factory.h +++ b/src/common/serialization/vst3/plugin-factory.h @@ -49,12 +49,12 @@ class YaPluginFactory : public Steinberg::IPluginFactory3 { /** * Whether `factory` supported `IPluginFactory2`. */ - bool supports_plugin_factory_2; + bool supports_plugin_factory_2 = false; /** * Whether `factory` supported `IPluginFactory3`. */ - bool supports_plugin_factory_3; + bool supports_plugin_factory_3 = false; /** * For `IPluginFactory::getFactoryInfo`. From 86c8b284a248fb23367ea41eea8043020cd0e0a4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 13:31:16 +0100 Subject: [PATCH 419/456] Mention the XEmbed Wine patch in the readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5387c579..6bab9cd9 100644 --- a/README.md +++ b/README.md @@ -291,7 +291,7 @@ plugin._ | --------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `cache_time_info` | `{true,false}` | Compatibility option for plugins that call `audioMasterGetTime()` multiple times during a single processing cycle. With this option subsequent calls during a single audio processing cycle will reuse the value returned by the first call to this function. This is a bug in the plugin, and this option serves as a temporary workaround until the plugin fixes the issue. | | `editor_double_embed` | `{true,false}` | Compatibility option for plugins that rely on the absolute screen coordinates of the window they're embedded in. Since the Wine window gets embedded inside of a window provided by your DAW, these coordinates won't match up and the plugin would end up drawing in the wrong location without this option. Currently the only known plugins that require this option are _PSPaudioware_ plugins with expandable GUIs, such as E27. Defaults to `false`. | -| `editor_xembed` | `{true,false}` | Use Wine's XEmbed implementation instead of yabridge's normal window embedding method. Some plugins will have redrawing issues when using XEmbed and editor resizing won't work properly with it, but it could be useful in certain setups. Defaults to `false`. | +| `editor_xembed` | `{true,false}` | Use Wine's XEmbed implementation instead of yabridge's normal window embedding method. Some plugins will have redrawing issues when using XEmbed and editor resizing won't work properly with it, but it could be useful in certain setups. You may need to use [this Wine patch](https://github.com/psycha0s/airwave/blob/master/fix-xembed-wine-windows.patch) if you're getting blank editor windows. Defaults to `false`. | These options are workarounds for issues mentioned in the [known issues](#runtime-dependencies-and-known-issues) section. Depending on the hosts From 35c71383337e71653caf482b357db0f2433f6547 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 14:24:50 +0100 Subject: [PATCH 420/456] Add a background brush to the window So the background always gets cleared out when the window resizes. --- CHANGELOG.md | 5 +++++ meson.build | 3 +++ src/wine-host/editor.cpp | 1 + 3 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 613f38d3..5c910094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,11 @@ TODO: Add an updates screenshot with some fancy VST3-only plugins to the readme and 5.8 required a change, but that change now breaks builds using Wine 6.0 and up, so this change has been reverted. +### Fixed + +- Added a background to the editor window to get rid of artifacts that could + occur when the plugin or the host don't resize the window correctly. + ### yabridgectl - Updated for the changes in yabridge 3.0. Yabridgectl now allows you to set up diff --git a/meson.build b/meson.build index cb033ff4..3165a5e4 100644 --- a/meson.build +++ b/meson.build @@ -188,6 +188,7 @@ bitsery_dep = subproject('bitsery', version : '5.2.0').get_variable('bitsery_dep function2_dep = subproject('function2', version : '4.1.0').get_variable('function2_dep') threads_dep = dependency('threads') tomlplusplus_dep = subproject('tomlplusplus', version : '2.1.0').get_variable('tomlplusplus_dep') +wine_gdi32_dep = declare_dependency(link_args : '-lgdi32') # The built in threads dependency does not know how to handle winegcc wine_threads_dep = declare_dependency(link_args : '-lpthread') xcb_dep = dependency('xcb') @@ -377,6 +378,7 @@ host_64bit_deps = [ bitsery_dep, function2_dep, tomlplusplus_dep, + wine_gdi32_dep, wine_threads_dep, xcb_dep, ] @@ -423,6 +425,7 @@ if with_bitbridge bitsery_dep, function2_dep, tomlplusplus_dep, + wine_gdi32_dep, wine_threads_dep, xcb_32bit_dep, ] diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index 5de2f913..ceb441f5 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -666,6 +666,7 @@ ATOM register_window_class(std::string window_class_name) { window_class.lpfnWndProc = window_proc; window_class.hInstance = GetModuleHandle(nullptr); window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.hbrBackground = CreateSolidBrush(RGB(32, 32, 32)); window_class.lpszClassName = window_class_name.c_str(); return RegisterClassEx(&window_class); From fbef37b924394e4d1f079bf6802c75179ccfc12f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 15:08:01 +0100 Subject: [PATCH 421/456] Disable blitting on window position changes This slightly reduces flickering because redraws are now a bit faster. --- CHANGELOG.md | 3 +++ src/wine-host/editor.cpp | 17 +++++++++++++++-- src/wine-host/editor.h | 12 ++++++------ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c910094..452db89a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,9 @@ TODO: Add an updates screenshot with some fancy VST3-only plugins to the readme - `libyabridge.so` is now called `libyabridge-vst2.so`. If you're using yabridgectl then nothing changes here. **To avoid any confusion in the future, please remove the old `libyabridge.so` file before upgrading.** +- Slightly increased resposniveness when resizing plugin GUIs by preventing + unnecessary blitting. This also reduces flickering with plugins that don't do + double buffering. - VST2 editor idle events are now handled slightly differently. This should result in even more responsive GUIs and I have not come across any plugins where this caused issues, but please let me know if it does break anything for diff --git a/src/wine-host/editor.cpp b/src/wine-host/editor.cpp index ceb441f5..af22697a 100644 --- a/src/wine-host/editor.cpp +++ b/src/wine-host/editor.cpp @@ -97,8 +97,8 @@ WindowClass::~WindowClass() { Editor::Editor(const Configuration& config, const std::string& window_class_name, const size_t parent_window_handle) - : x11_connection(xcb_connect(nullptr, nullptr), xcb_disconnect), - use_xembed(config.editor_xembed), + : use_xembed(config.editor_xembed), + x11_connection(xcb_connect(nullptr, nullptr), xcb_disconnect), client_area(get_maximum_screen_dimensions(*x11_connection)), window_class(window_class_name), // Create a window without any decoratiosn for easy embedding. The @@ -540,6 +540,19 @@ LRESULT CALLBACK window_proc(HWND handle, SetWindowLongPtr(handle, GWLP_USERDATA, reinterpret_cast(editor)); } break; + // Setting `SWP_NOCOPYBITS` somewhat reduces flickering on + // `fix_local_coordinates()` calls with plugins that don't do double + // buffering since it speeds up the redrawing process. + case WM_WINDOWPOSCHANGING: { + auto editor = reinterpret_cast( + GetWindowLongPtr(handle, GWLP_USERDATA)); + if (!editor || editor->use_xembed) { + break; + } + + WINDOWPOS* info = reinterpret_cast(lParam); + info->flags |= SWP_NOCOPYBITS | SWP_DEFERERASE; + } break; // In case the WM does not support the EWMH active window property, // we'll fall back to grabbing focus when the user clicks on the window // by listening to the generated `WM_PARENTNOTIFY` messages. Otherwise diff --git a/src/wine-host/editor.h b/src/wine-host/editor.h index 0d428f8c..0ffce84e 100644 --- a/src/wine-host/editor.h +++ b/src/wine-host/editor.h @@ -151,6 +151,12 @@ class Editor { */ void set_input_focus(bool grab) const; + /** + * Whether to use XEmbed instead of yabridge's normal window embedded. Wine + * with XEmbed tends to cause rendering issues, so it's disabled by default. + */ + const bool use_xembed; + private: /** * Returns `true` if the currently active window (as per @@ -185,12 +191,6 @@ class Editor { */ std::unique_ptr x11_connection; - /** - * Whether to use XEmbed instead of yabridge's normal window embedded. Wine - * with XEmbed tends to cause rendering issues, so it's disabled by default. - */ - const bool use_xembed; - /** * The Wine window's client area, or the maximum size of that window. This * will be set to a size that's large enough to be able to enter full screen From 70c57925935dba20881d97b5086dd693bab00d7e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 16:48:47 +0100 Subject: [PATCH 422/456] Implement IUnitInfo::getSelectedUnit --- src/common/logging/vst3.cpp | 7 +++++++ src/common/logging/vst3.h | 1 + src/common/serialization/vst3.h | 3 ++- src/common/serialization/vst3/plugin/unit-info.h | 16 ++++++++++++++++ src/plugin/bridges/vst3-impls/plugin-proxy.cpp | 5 ++--- src/wine-host/bridges/vst3.cpp | 5 +++++ 6 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 248dbf07..61dcfdcb 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -503,6 +503,13 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitInfo::GetSelectedUnit& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id << ": IUnitInfo::getSelectedUnit()"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 173b0b63..3556df14 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -116,6 +116,7 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramInfo&); bool log_request(bool is_host_vst, const YaUnitInfo::HasProgramPitchNames&); bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramPitchName&); + bool log_request(bool is_host_vst, const YaUnitInfo::GetSelectedUnit&); bool log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index d40757f3..b8c88840 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -107,7 +107,8 @@ using ControlRequest = std::variant; + YaUnitInfo::GetProgramPitchName, + YaUnitInfo::GetSelectedUnit>; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h index 48402fdd..a206537d 100644 --- a/src/common/serialization/vst3/plugin/unit-info.h +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -324,6 +324,22 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { int32 programIndex, int16 midiPitch, Steinberg::Vst::String128 name /*out*/) override = 0; + + /** + * Message to pass through a call to `IUnitInfo::getSelectedUnit()` to the + * Wine plugin host. + */ + struct GetSelectedUnit { + using Response = PrimitiveWrapper; + + native_size_t instance_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + } + }; + virtual Steinberg::Vst::UnitID PLUGIN_API getSelectedUnit() override = 0; virtual tresult PLUGIN_API selectUnit(Steinberg::Vst::UnitID unitId) override = 0; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index db48af26..7916333a 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -536,9 +536,8 @@ tresult PLUGIN_API Vst3PluginProxyImpl::getProgramPitchName( } Steinberg::Vst::UnitID PLUGIN_API Vst3PluginProxyImpl::getSelectedUnit() { - // TODO: Implement - bridge.logger.log("TODO: IUnitInfo::getSelectedUnit()"); - return Steinberg::kNotImplemented; + return bridge.send_message( + YaUnitInfo::GetSelectedUnit{.instance_id = instance_id()}); } tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index bfd15565..17a2a650 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -639,6 +639,11 @@ void Vst3Bridge::run() { return YaUnitInfo::GetProgramPitchNameResponse{ .result = result, .name = tchar_pointer_to_u16string(name)}; }, + [&](const YaUnitInfo::GetSelectedUnit& request) + -> YaUnitInfo::GetSelectedUnit::Response { + return object_instances[request.instance_id] + .unit_info->getSelectedUnit(); + }, }); } From 1229518fb53defcb9896bf90ddb86d08767e17fe Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 17:20:53 +0100 Subject: [PATCH 423/456] Add request and response structs for IUnitInfo --- .../serialization/vst3/plugin/unit-info.h | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h index a206537d..f0d04095 100644 --- a/src/common/serialization/vst3/plugin/unit-info.h +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -341,14 +341,97 @@ class YaUnitInfo : public Steinberg::Vst::IUnitInfo { }; virtual Steinberg::Vst::UnitID PLUGIN_API getSelectedUnit() override = 0; + + /** + * Message to pass through a call to `IUnitInfo::selectUnit(unit_id)` to the + * Wine plugin host. + */ + struct SelectUnit { + using Response = UniversalTResult; + + native_size_t instance_id; + + Steinberg::Vst::UnitID unit_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(unit_id); + } + }; + virtual tresult PLUGIN_API selectUnit(Steinberg::Vst::UnitID unitId) override = 0; + + /** + * The response code and returned unit ID for a call to + * `IUnitInfo::getUnitByBus(type, dir, bus_index, channel, &unit_id)`. + */ + struct GetUnitByBusResponse { + UniversalTResult result; + Steinberg::Vst::UnitID unit_id; + + template + void serialize(S& s) { + s.object(result); + s.value4b(unit_id); + } + }; + + /** + * Message to pass through a call to `IUnitInfo::getUnitByBus(type, dir, + * bus_index, channel, &unit_id)` to the Wine plugin host. + */ + struct GetUnitByBus { + using Response = GetUnitByBusResponse; + + native_size_t instance_id; + + Steinberg::Vst::MediaType type; + Steinberg::Vst::BusDirection dir; + int32 bus_index; + int32 channel; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(type); + s.value4b(dir); + s.value4b(bus_index); + s.value4b(channel); + } + }; + virtual tresult PLUGIN_API getUnitByBus(Steinberg::Vst::MediaType type, Steinberg::Vst::BusDirection dir, int32 busIndex, int32 channel, Steinberg::Vst::UnitID& unitId /*out*/) override = 0; + + /* + * Message to pass through a call to + * `IUnitInfo::setUnitProgramData(list_or_unit_id, program_index, data)` to + * the Wine plugin host. + */ + struct SetUnitProgramData { + using Response = UniversalTResult; + + native_size_t instance_id; + + int32 list_or_unit_id; + int32 program_index; + VectorStream data; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(list_or_unit_id); + s.value4b(program_index); + s.object(data); + } + }; + virtual tresult PLUGIN_API setUnitProgramData(int32 listOrUnitId, int32 programIndex, From 163c936286719a0ab602c4f99b9311080d24eeb0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 17:55:22 +0100 Subject: [PATCH 424/456] Add logging for the new IUnitInfo structs --- src/common/logging/vst3.cpp | 43 +++++++++++++++++++++++++++++++++++++ src/common/logging/vst3.h | 5 +++++ 2 files changed, 48 insertions(+) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 61dcfdcb..116b2f0b 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -510,6 +510,38 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitInfo::SelectUnit& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IUnitInfo::selectUnit(unitId = " << request.unit_id + << ")"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitInfo::GetUnitByBus& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IUnitInfo::getUnitByBus(type = " << request.type + << ", dir = " << request.dir + << ", busIndex = " << request.bus_index + << ", channel = " << request.channel << ", &unitId)"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitInfo::SetUnitProgramData& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << request.instance_id + << ": IUnitInfo::setUnitProgramData(listOrUnitId = " + << request.list_or_unit_id + << ", programIndex = " << request.program_index + << ", data = )"; + }); +} + bool Vst3Logger::log_request( bool is_host_vst, const YaAudioProcessor::SetBusArrangements& request) { @@ -968,6 +1000,17 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaUnitInfo::GetUnitByBusResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", unit #" << response.unit_id; + } + }); +} + void Vst3Logger::log_response( bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse& response) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 3556df14..2e9a0b1f 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -117,6 +117,9 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaUnitInfo::HasProgramPitchNames&); bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramPitchName&); bool log_request(bool is_host_vst, const YaUnitInfo::GetSelectedUnit&); + bool log_request(bool is_host_vst, const YaUnitInfo::SelectUnit&); + bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitByBus&); + bool log_request(bool is_host_vst, const YaUnitInfo::SetUnitProgramData&); bool log_request(bool is_host_vst, const YaAudioProcessor::SetBusArrangements&); @@ -179,6 +182,8 @@ class Vst3Logger { const YaUnitInfo::GetProgramInfoResponse&); void log_response(bool is_host_vst, const YaUnitInfo::GetProgramPitchNameResponse&); + void log_response(bool is_host_vst, + const YaUnitInfo::GetUnitByBusResponse&); void log_response(bool is_host_vst, const YaAudioProcessor::GetBusArrangementResponse&); From 2823a74783ebd22a346772edc03282c71ff32d18 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 18:07:32 +0100 Subject: [PATCH 425/456] Implement all IUnitInfo functions With this IUnitInfo has been fully implemented. --- src/common/serialization/vst3.h | 5 +++- .../bridges/vst3-impls/plugin-proxy.cpp | 26 ++++++++++++------- src/wine-host/bridges/vst3.cpp | 24 +++++++++++++++++ 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index b8c88840..5452a09e 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -108,7 +108,10 @@ using ControlRequest = std::variant; + YaUnitInfo::GetSelectedUnit, + YaUnitInfo::SelectUnit, + YaUnitInfo::GetUnitByBus, + YaUnitInfo::SetUnitProgramData>; template void serialize(S& s, ControlRequest& payload) { diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 7916333a..39221270 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -542,9 +542,8 @@ Steinberg::Vst::UnitID PLUGIN_API Vst3PluginProxyImpl::getSelectedUnit() { tresult PLUGIN_API Vst3PluginProxyImpl::selectUnit(Steinberg::Vst::UnitID unitId) { - // TODO: Implement - bridge.logger.log("TODO: IUnitInfo::selectUnit()"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaUnitInfo::SelectUnit{ + .instance_id = instance_id(), .unit_id = unitId}); } tresult PLUGIN_API @@ -553,16 +552,25 @@ Vst3PluginProxyImpl::getUnitByBus(Steinberg::Vst::MediaType type, int32 busIndex, int32 channel, Steinberg::Vst::UnitID& unitId /*out*/) { - // TODO: Implement - bridge.logger.log("TODO: IUnitInfo::getUnitByBus()"); - return Steinberg::kNotImplemented; + const GetUnitByBusResponse response = bridge.send_message( + YaUnitInfo::GetUnitByBus{.instance_id = instance_id(), + .type = type, + .dir = dir, + .bus_index = busIndex, + .channel = channel}); + + unitId = response.unit_id; + + return response.result; } tresult PLUGIN_API Vst3PluginProxyImpl::setUnitProgramData(int32 listOrUnitId, int32 programIndex, Steinberg::IBStream* data) { - // TODO: Implement - bridge.logger.log("TODO: IUnitInfo::setUnitProgramData()"); - return Steinberg::kNotImplemented; + return bridge.send_message( + YaUnitInfo::SetUnitProgramData{.instance_id = instance_id(), + .list_or_unit_id = listOrUnitId, + .program_index = programIndex, + .data = data}); } diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 17a2a650..35852cea 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -644,6 +644,30 @@ void Vst3Bridge::run() { return object_instances[request.instance_id] .unit_info->getSelectedUnit(); }, + [&](const YaUnitInfo::SelectUnit& request) + -> YaUnitInfo::SelectUnit::Response { + return object_instances[request.instance_id] + .unit_info->selectUnit(request.unit_id); + }, + [&](const YaUnitInfo::GetUnitByBus& request) + -> YaUnitInfo::GetUnitByBus::Response { + Steinberg::Vst::UnitID unit_id; + const tresult result = + object_instances[request.instance_id] + .unit_info->getUnitByBus(request.type, request.dir, + request.bus_index, + request.channel, unit_id); + + return YaUnitInfo::GetUnitByBusResponse{.result = result, + .unit_id = unit_id}; + }, + [&](YaUnitInfo::SetUnitProgramData& request) + -> YaUnitInfo::SetUnitProgramData::Response { + return object_instances[request.instance_id] + .unit_info->setUnitProgramData(request.list_or_unit_id, + request.program_index, + &request.data); + }, }); } From 5773c471f9ff9c8235c0c29a4179d3db8e8909a8 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 18:24:41 +0100 Subject: [PATCH 426/456] [yabridgectl] Don't purge VST3 yabridge.toml We only want to consider directories here. --- tools/yabridgectl/src/actions.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tools/yabridgectl/src/actions.rs b/tools/yabridgectl/src/actions.rs index 59bc2e20..3e3a1db5 100644 --- a/tools/yabridgectl/src/actions.rs +++ b/tools/yabridgectl/src/actions.rs @@ -322,12 +322,15 @@ pub fn do_sync(config: &mut Config, options: &SyncOptions) -> Result<()> { dirs.filter_map(|entry| entry.ok()) .map(|entry| entry.path()) .filter_map(|path| { - // Add all files and directories in `~/.vst3/yabridge` to `orphan_files` if they - // are not a VST3 module we just created - if !yabridge_vst3_bundles.contains_key(&path) { - utils::get_file_type(path) - } else { - None + // Add all directories in `~/.vst3/yabridge` to `orphan_files` if they are not a + // VST3 module we just created. We'll ignore symlinks and regular files since + // those are always user created. + match ( + yabridge_vst3_bundles.contains_key(&path), + utils::get_file_type(path), + ) { + (false, result @ Some(NativeFile::Directory(_))) => result, + _ => None, } }), ); From d6a33b422d29132b3f8e2ac93f1eea1570cf1e18 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 18:30:01 +0100 Subject: [PATCH 427/456] Fix Vst3Bridge stderr logger This was trying to free `std::cerr`, which of course won't work. --- src/common/logging/common.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/logging/common.cpp b/src/common/logging/common.cpp index 152868f2..c9877479 100644 --- a/src/common/logging/common.cpp +++ b/src/common/logging/common.cpp @@ -95,8 +95,8 @@ Logger Logger::create_wine_stderr() { // We're logging directly to `std::cerr` instead of to `/dev/stderr` because // we want the STDERR redirection from the group host processes to still // function here - return Logger(std::shared_ptr(&std::cerr), verbosity_level, - ""); + return Logger(std::shared_ptr(&std::cerr, [](auto*) {}), + verbosity_level, ""); } void Logger::log(const std::string& message) { From 9c3b3a0ca9c19a941f7337c6ca39e368ede5417a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 18:37:07 +0100 Subject: [PATCH 428/456] Fix typo in plugin factory logging --- src/common/logging/vst3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 116b2f0b..8ec0c08f 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -47,7 +47,7 @@ bool Vst3Logger::log_request(bool is_host_vst, bool Vst3Logger::log_request(bool is_host_vst, const Vst3PluginProxy::Construct& request) { return log_request_base(is_host_vst, [&](auto& message) { - message << "IPluginFactory::createComponent(cid = " + message << "IPluginFactory::createInstance(cid = " << format_uid(Steinberg::FUID::fromTUID(request.cid.data())) << ", _iid = "; switch (request.requested_interface) { From 047c7691e299667390e35685fa6c2b68c9a0a8df Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 23:24:42 +0100 Subject: [PATCH 429/456] Flip the logging prefixes This makes it easier to visually distinguish the direction of the request. --- src/common/logging/vst3.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 2e9a0b1f..7f95fe2d 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -224,7 +224,7 @@ class Vst3Logger { if (is_host_vst) { message << "[host -> vst] >> "; } else { - message << "[host <- vst] >> "; + message << "[vst -> host] >> "; } callback(message); @@ -253,7 +253,7 @@ class Vst3Logger { void log_response_base(bool is_host_vst, F callback) { std::ostringstream message; if (is_host_vst) { - message << "[host -> vst] "; + message << "[vst <- host] "; } else { message << "[host <- vst] "; } From 9cacf03765b8e4000d9fdfc36096d8f066fa0fbb Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 27 Dec 2020 23:34:40 +0100 Subject: [PATCH 430/456] Add missing lambda return type annotations --- src/wine-host/bridges/vst3.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 35852cea..fe2f427f 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -275,21 +275,25 @@ void Vst3Bridge::run() { return YaEditController::GetParamValueByStringResponse{ .result = result, .value_normalized = value_normalized}; }, - [&](const YaEditController::NormalizedParamToPlain& request) { + [&](const YaEditController::NormalizedParamToPlain& request) + -> YaEditController::NormalizedParamToPlain::Response { return object_instances[request.instance_id] .edit_controller->normalizedParamToPlain( request.id, request.value_normalized); }, - [&](const YaEditController::PlainParamToNormalized& request) { + [&](const YaEditController::PlainParamToNormalized& request) + -> YaEditController::PlainParamToNormalized::Response { return object_instances[request.instance_id] .edit_controller->plainParamToNormalized( request.id, request.plain_value); }, - [&](const YaEditController::GetParamNormalized& request) { + [&](const YaEditController::GetParamNormalized& request) + -> YaEditController::GetParamNormalized::Response { return object_instances[request.instance_id] .edit_controller->getParamNormalized(request.id); }, - [&](const YaEditController::SetParamNormalized& request) { + [&](const YaEditController::SetParamNormalized& request) + -> YaEditController::SetParamNormalized::Response { return object_instances[request.instance_id] .edit_controller->setParamNormalized(request.id, request.value); From dd74e548546df2609c7c89d915235ed2f29d9761 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 28 Dec 2020 00:38:35 +0100 Subject: [PATCH 431/456] Add FIXME for a crazy design issue in VocalSynth 2 They're exchanging pointers between the processor and the controller using messages. And not only that, they're storing the message objects instead of storing the pointers. --- src/wine-host/bridges/vst3.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index fe2f427f..a308c4b9 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -225,6 +225,17 @@ void Vst3Bridge::run() { }, [&](YaConnectionPoint::Notify& request) -> YaConnectionPoint::Notify::Response { + // FIXME: This needs to be redesigned. We should only send a + // pointer to the `IMessage` instead of copying the + // actual message. What ends up happening is that iZotope + // VocalSynth 2 exchanges pointers to the processor and + // the controller. Then at some point during an + // `IAudioProcessor::process()` call after a parameter + // changes, it will try to access the pointers stored in + // that message. So to be able to support that, the + // message object we pass to notify here still has to be + // alive at that point. This goes 100% against the design + // of VST3, but there's nothing we can do about it. return object_instances[request.instance_id] .connection_point->notify(&request.message); }, From 4226ab6e430da810ac7d57ecf91f9ea25ca86613 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Mon, 28 Dec 2020 13:19:34 +0100 Subject: [PATCH 432/456] Pass pointers to `IMessage` objects around Instead of serializing the actual `YaMessage`, for the reasons mentioned in the comments. This was needed to stop iZotope VocalSynth 2 in Ardour from segfaulting when editing parameters, because that plugin is apparently being very naughty. --- src/common/logging/vst3.cpp | 10 +- src/common/serialization/vst3/README.md | 1 + src/common/serialization/vst3/message.cpp | 46 ++++++++ src/common/serialization/vst3/message.h | 105 +++++++++++++++--- .../vst3/plugin/connection-point.h | 13 ++- .../bridges/vst3-impls/plugin-proxy.cpp | 18 +-- src/plugin/bridges/vst3.cpp | 2 +- .../vst3-impls/connection-point-proxy.cpp | 13 +-- src/wine-host/bridges/vst3.cpp | 26 ++--- 9 files changed, 181 insertions(+), 53 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 8ec0c08f..a6dd672f 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -126,13 +126,15 @@ bool Vst3Logger::log_request(bool is_host_vst, bool Vst3Logger::log_request(bool is_host_vst, const YaConnectionPoint::Notify& request) { return log_request_base(is_host_vst, [&](auto& message) { + // We can safely print the pointer as long we don't dereference it message << request.instance_id - << ": IConnectionPoint::notify(message = (request.message).getMessageID()) { - message << "with ID = \"" << id << "\""; + const_cast(request.message_ptr).getMessageID()) { + message << " with ID = \"" << id << "\""; } else { - message << "without an ID"; + message << " without an ID"; } message << ">)"; }); diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index e1e1c1d8..8bc48c81 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -42,6 +42,7 @@ implemented for serialization purposes: | `YaAttributeList` | `IAttributeList` | | | `YaEventList` | `IEventList` | Comes with a lot of serialization wrappers around the related structs. | | `YaMessage` | `IMessage` | | +| `YaMessagePtr` | `IMessage` | Should be used in inter process communication to exchange messages | | `YaParameterChanges` | `IParameterChanges` | | | `YaParamValueQueue` | `IParamValueQueue` | | | `VectorStream` | `IBStream` | Used for serializing data streams. | diff --git a/src/common/serialization/vst3/message.cpp b/src/common/serialization/vst3/message.cpp index ca416036..ac285a2a 100644 --- a/src/common/serialization/vst3/message.cpp +++ b/src/common/serialization/vst3/message.cpp @@ -16,6 +16,52 @@ #include "message.h" +YaMessagePtr::YaMessagePtr(){FUNKNOWN_CTOR} + +YaMessagePtr::YaMessagePtr(IMessage& message) + : message_id(message.getMessageID() + ? std::make_optional(message.getMessageID()) + : std::nullopt), + original_message_ptr(static_cast( + reinterpret_cast(&message))){FUNKNOWN_CTOR} + + YaMessagePtr::~YaMessagePtr() { + FUNKNOWN_DTOR +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +IMPLEMENT_FUNKNOWN_METHODS(YaMessagePtr, + Steinberg::Vst::IMessage, + Steinberg::Vst::IMessage::iid) +#pragma GCC diagnostic pop + +Steinberg::Vst::IMessage* YaMessagePtr::get_original() const { + // See the docstrings on `YaMessage` and `YaMessagePtr` + return reinterpret_cast( + static_cast(original_message_ptr)); +} + +Steinberg::FIDString PLUGIN_API YaMessagePtr::getMessageID() { + if (message_id) { + return message_id->c_str(); + } else { + return nullptr; + } +} + +void PLUGIN_API YaMessagePtr::setMessageID(Steinberg::FIDString id /*in*/) { + if (id) { + message_id = id; + } else { + message_id.reset(); + } +} + +Steinberg::Vst::IAttributeList* PLUGIN_API YaMessagePtr::getAttributes() { + return &attribute_list; +} + YaMessage::YaMessage(){FUNKNOWN_CTOR} YaMessage::~YaMessage() { diff --git a/src/common/serialization/vst3/message.h b/src/common/serialization/vst3/message.h index e26c8b23..a4e1aa6c 100644 --- a/src/common/serialization/vst3/message.h +++ b/src/common/serialization/vst3/message.h @@ -19,6 +19,7 @@ #include #include +#include "../common.h" #include "attribute-list.h" #include "base.h" @@ -26,15 +27,94 @@ #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" /** - * Wraps around `IMessage` for serialization purposes. We create instances of - * these in `IHostApplication::createInstance()` so the Windows VST3 plugin can - * send messages between objects. There is one huge caveat here: it is - * impossible to work with arbitrary `IMessage` objects, since there's no way to - * retrieve all of the keys in the attribute list. With this approach we support - * hosts that indirectly connect the processor and the controller through a - * proxy (like Ardour), but we still require a dynamic cast from the `IMessage*` - * passed to `YaConnectionPoint::notify()` to a `YaMessage*` for this to work - * for the above mentioned reason. + * A serialization wrapper around `IMessage`. As explained in `YaMessage`, we + * can't exchange the regular `YaMessage` object when dealing with + * `IConnectionPoint` connection proxies. Instead, we'll use this wrapper that + * only stores the ID (for logging purposes) and a pointer to the original + * object. That way we can pass the original message created by the plugin to + * the receiver without having to know what object the host's connection proxy + * is actually connecting us to. + * + * @note THis object should _not_ be passed to the plugin directly. The only + * purpose of this object is to be able to pass the original `IMessage*` + * object passed the connection proxy to the receiver, by wrapping a pointer + * to it in this object. `YaMessagePtr::get_original()` can be used to + * retrieve the original object. + */ +class YaMessagePtr : public Steinberg::Vst::IMessage { + public: + YaMessagePtr(); + + /** + * Create a proxy for this message. We'll store the message's ID for logging + * purposes as well as a pointer to it so we can retrieve the object after a + * round trip from the Wine plugin host, to the native plugin, to the host, + * back to the native plugin, and then finally back to the Wine plugin host + * again. + */ + explicit YaMessagePtr(IMessage& message); + + ~YaMessagePtr(); + + DECLARE_FUNKNOWN_METHODS + + /** + * Get back a pointer to the original `IMessage` object passed to the + * constructor. This should be used on the Wine plugin host side when + * handling `IConnectionPoint::notify`. + */ + Steinberg::Vst::IMessage* get_original() const; + + virtual Steinberg::FIDString PLUGIN_API getMessageID() override; + virtual void PLUGIN_API + setMessageID(Steinberg::FIDString id /*in*/) override; + virtual Steinberg::Vst::IAttributeList* PLUGIN_API getAttributes() override; + + template + void serialize(S& s) { + s.ext(message_id, bitsery::ext::StdOptional{}, + [](S& s, std::string& id) { s.text1b(id, 1024); }); + s.value8b(original_message_ptr); + } + + private: + /** + * The implementation that comes with the SDK returns a null pointer when + * the ID has not yet been set, so we'll do the same thing. + */ + std::optional message_id; + + /** + * The pointer to the message passed during the constructor, as a 64-bit + * unsigned integer. This way we can retrieve the original object after a + * round trip. + */ + native_size_t original_message_ptr = 0; + + /** + * An empty attribute list, in case the host checks this for some reason. + */ + YaAttributeList attribute_list; +}; + +/** + * A `IMessage` implementation the plugin can use to exchange messages with. We + * create instances of these in `IHostApplication::createInstance()` so the + * Windows VST3 plugin can send messages between objects. A plugin's controller + * or processor will fill the message with data and then try to send it to the + * connected object using `IConnectionPoint::notify()`. For directly connected + * objects this works exactly like you'd expect. When the host places a proxy + * between the two, it becomes a bit more interesting, and we'll have to proxy + * that proxy. In that case we won't send the actual `YaMessage` object from the + * Wine plugin host to the native plugin, and then back to the Wine plugin host. + * Instead, we'll send a thin wrapper that only stores a name and a pointer to + * the actual object. This is needed in case the plugin tries to store the + * `IMessage` object, thinking it's backed by a smart pointer. This means that + * the message we pass while handling `IConnectionPoint::notify` should live as + * long as the original message object, thus we'll use a pointer to get back the + * original message object. + * + * @relates YaMessagePtr */ class YaMessage : public Steinberg::Vst::IMessage { public: @@ -53,13 +133,6 @@ class YaMessage : public Steinberg::Vst::IMessage { setMessageID(Steinberg::FIDString id /*in*/) override; virtual Steinberg::Vst::IAttributeList* PLUGIN_API getAttributes() override; - template - void serialize(S& s) { - s.ext(message_id, bitsery::ext::StdOptional{}, - [](S& s, std::string& id) { s.text1b(id, 1024); }); - s.object(attribute_list); - } - private: /** * The implementation that comes with the SDK returns a null pointer when diff --git a/src/common/serialization/vst3/plugin/connection-point.h b/src/common/serialization/vst3/plugin/connection-point.h index 213cdcad..4dab808d 100644 --- a/src/common/serialization/vst3/plugin/connection-point.h +++ b/src/common/serialization/vst3/plugin/connection-point.h @@ -178,21 +178,24 @@ class YaConnectionPoint : public Steinberg::Vst::IConnectionPoint { * the Wine plugin host. Since `IAttributeList` does not have any way to * iterate over all values, we only support messages sent by plugins using * our own implementation of the interface, since there's no way to - * serialize them otherwise. This `IConnectionPoint::notify()` - * implementation is also only used with hosts that do not connect objects - * directly and use connection proxies instead. + * serialize them otherwise. Additionally, plugins may store the `IMessage` + * pointer for later usage, so we have to pass through a pointer to the + * original message so it has the same lifetime as the original message. + * This `IConnectionPoint::notify()` implementation is also only used with + * hosts that do not connect objects directly and use connection proxies + * instead. */ struct Notify { using Response = UniversalTResult; native_size_t instance_id; - YaMessage message; + YaMessagePtr message_ptr; template void serialize(S& s) { s.value8b(instance_id); - s.object(message); + s.object(message_ptr); } }; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 39221270..615710cb 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -250,14 +250,18 @@ tresult PLUGIN_API Vst3PluginProxyImpl::notify(Steinberg::Vst::IMessage* message) { // Since there is no way to enumerate over all values in an // `IAttributeList`, we can only support relaying messages that were sent by - // our own objects. This is only needed to support hosts that place a - // connection proxy between two objects instead of connecting them directly. - // If the objects are connected directly we also connected them directly on - // the Wine side, so we don't have to do any additional when those objects - // pass through messages. - if (auto message_impl = dynamic_cast(message)) { + // our own objects. Additionally, the `IMessage*` we end up passing to the + // plugin needs to have the same lifetime as the original object, because + // some plugins are being a bit naughty. That's why we pass around a pointer + // to the original message object. + // All of this is only needed to support hosts that place a connection proxy + // between two objects instead of connecting them directly. If the objects + // are connected directly we also connected them directly on the Wine side, + // so we don't have to do any additional when those objects pass through + // messages. + if (auto message_ptr = dynamic_cast(message)) { return bridge.send_message(YaConnectionPoint::Notify{ - .instance_id = instance_id(), .message = *message_impl}); + .instance_id = instance_id(), .message_ptr = *message_ptr}); } else { bridge.logger.log( "WARNING: Unknown message type passed to " diff --git a/src/plugin/bridges/vst3.cpp b/src/plugin/bridges/vst3.cpp index fcb9eee0..b59564ef 100644 --- a/src/plugin/bridges/vst3.cpp +++ b/src/plugin/bridges/vst3.cpp @@ -110,7 +110,7 @@ Vst3PluginBridge::Vst3PluginBridge() -> YaConnectionPoint::Notify::Response { return plugin_proxies.at(request.instance_id) .get() - .connection_point_proxy->notify(&request.message); + .connection_point_proxy->notify(&request.message_ptr); }, [&](const YaHostApplication::GetName& request) -> YaHostApplication::GetName::Response { diff --git a/src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp b/src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp index 55a03c8a..4949a1f8 100644 --- a/src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp +++ b/src/wine-host/bridges/vst3-impls/connection-point-proxy.cpp @@ -55,15 +55,14 @@ Vst3ConnectionPointProxyImpl::disconnect(IConnectionPoint* /*other*/) { tresult PLUGIN_API Vst3ConnectionPointProxyImpl::notify(Steinberg::Vst::IMessage* message) { - // As explained in `YaMessage` and `Vst3PluginProxyImpl::notify`, we can - // only support our own `IMessage implementation here` - if (auto message_impl = dynamic_cast(message)) { - return bridge.send_message(YaConnectionPoint::Notify{ - .instance_id = owner_instance_id(), .message = *message_impl}); + if (message) { + return bridge.send_message( + YaConnectionPoint::Notify{.instance_id = owner_instance_id(), + .message_ptr = YaMessagePtr(*message)}); } else { - std::cerr << "WARNING: Unknown message type passed to " + std::cerr << "WARNING: Null pointer passed to " "'IConnectionPoint::notify()', ignoring" << std::endl; - return Steinberg::kNotImplemented; + return Steinberg::kInvalidArgument; } } diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index a308c4b9..7679ef20 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -223,21 +223,21 @@ void Vst3Bridge::run() { return result; } }, - [&](YaConnectionPoint::Notify& request) + [&](const YaConnectionPoint::Notify& request) -> YaConnectionPoint::Notify::Response { - // FIXME: This needs to be redesigned. We should only send a - // pointer to the `IMessage` instead of copying the - // actual message. What ends up happening is that iZotope - // VocalSynth 2 exchanges pointers to the processor and - // the controller. Then at some point during an - // `IAudioProcessor::process()` call after a parameter - // changes, it will try to access the pointers stored in - // that message. So to be able to support that, the - // message object we pass to notify here still has to be - // alive at that point. This goes 100% against the design - // of VST3, but there's nothing we can do about it. + // NOTE: We're using a few tricks here to pass through a pointer + // to the _original_ `IMessage` object passed to a + // connection proxy. This is needed because some plugins + // like iZotope VocalSynth 2 use these messages to + // exchange pointers between their objects so they can + // break out of VST3's separation, but they might also + // store the message object and not the actual pointers. + // We should thus be passing a (raw) pointer to the + // original object so we can pretend none of this wrapping + // and serializing has ever happened. return object_instances[request.instance_id] - .connection_point->notify(&request.message); + .connection_point->notify( + request.message_ptr.get_original()); }, [&](YaEditController::SetComponentState& request) -> YaEditController::SetComponentState::Response { From 33806139e9067c7e114ef2bb0d8453fd5365327e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 00:25:44 +0100 Subject: [PATCH 433/456] Revert "Allow disabling ad-hoc socket spawning" It turns out we can't safely disable this, because in some situations we still have these mutually recursive function calls. We could optimize this a bit to have those calls be handled by the general sockets, but this is much more manageable. This reverts commit 415c1b56831fd5300fdf532cc2c2a02df56f3e26. --- src/common/communication/common.h | 144 ++++++++++++------------------ src/common/communication/vst2.h | 4 +- src/common/communication/vst3.h | 19 ++-- 3 files changed, 66 insertions(+), 101 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 38f7dcb6..9829c691 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -452,13 +452,8 @@ class SocketHandler { * * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. - * @tparam ad_hoc_sockets Whether new sockets should be created on demand to be - * able to handle multiple function calls at the same time. If this is set to - * false, then simultaneous `send_message()` calls will have to wait for the - * earlier call to finish. This also means that the listening side does not - * have to spawn a thread to constantly listen for new connections. */ -template +template class AdHocSocketHandler { protected: /** @@ -542,15 +537,11 @@ class AdHocSocketHandler { */ template T send(F callback) { - // When the ad-hoc socket spawning bheaviour is disabled we always want - // to acquire the lock, waiting if necessary. // XXX: Maybe at some point we should benchmark how often this // ad hoc socket spawning mechanism gets used. If some hosts // for instance consistently and repeatedly trigger this then // we might be able to do some optimizations there. - std::unique_lock lock = - ad_hoc_sockets ? std::unique_lock(write_mutex, std::try_to_lock) - : std::unique_lock(write_mutex); + std::unique_lock lock(write_mutex, std::try_to_lock); if (lock.owns_lock()) { // This was used to always block when sending the first message, // because the other side may not be listening for additional @@ -618,90 +609,71 @@ class AdHocSocketHandler { void receive_multi(std::optional> logger, F primary_callback, G secondary_callback) { - if constexpr (ad_hoc_sockets) { - // As described above we'll handle incoming requests for `socket` on - // this thread. We'll also listen for incoming connections on - // `endpoint` on another thread. For any incoming connection we'll - // spawn a new thread to handle the request. When `socket` closes - // and this loop breaks, the listener and any still active threads - // will be cleaned up before this function exits. - boost::asio::io_context secondary_context{}; + // As described above we'll handle incoming requests for `socket` on + // this thread. We'll also listen for incoming connections on `endpoint` + // on another thread. For any incoming connection we'll spawn a new + // thread to handle the request. When `socket` closes and this loop + // breaks, the listener and any still active threads will be cleaned up + // before this function exits. + boost::asio::io_context secondary_context{}; - // The previous acceptor has already been shut down by - // `AdHocSocketHandler::connect()` - acceptor.emplace(secondary_context, endpoint); + // The previous acceptor has already been shut down by + // `AdHocSocketHandler::connect()` + acceptor.emplace(secondary_context, endpoint); - // This works the exact same was as `active_plugins` and - // `next_plugin_id` in `GroupBridge` - std::map active_secondary_requests{}; - std::atomic_size_t next_request_id{}; - std::mutex active_secondary_requests_mutex{}; - accept_requests( - *acceptor, logger, - [&](boost::asio::local::stream_protocol::socket - secondary_socket) { - const size_t request_id = next_request_id.fetch_add(1); + // This works the exact same was as `active_plugins` and + // `next_plugin_id` in `GroupBridge` + std::map active_secondary_requests{}; + std::atomic_size_t next_request_id{}; + std::mutex active_secondary_requests_mutex{}; + accept_requests( + *acceptor, logger, + [&](boost::asio::local::stream_protocol::socket secondary_socket) { + const size_t request_id = next_request_id.fetch_add(1); - // We have to make sure to keep moving these sockets into - // the threads that will handle them - std::lock_guard lock(active_secondary_requests_mutex); - active_secondary_requests[request_id] = Thread( - [&, - request_id](boost::asio::local::stream_protocol::socket - secondary_socket) { - secondary_callback(secondary_socket); + // We have to make sure to keep moving these sockets into the + // threads that will handle them + std::lock_guard lock(active_secondary_requests_mutex); + active_secondary_requests[request_id] = Thread( + [&, request_id](boost::asio::local::stream_protocol::socket + secondary_socket) { + secondary_callback(secondary_socket); - // When we have processed this request, we'll join - // the thread again with the thread that's handling - // `secondary_context` - boost::asio::post( - secondary_context, [&, request_id]() { - std::lock_guard lock( - active_secondary_requests_mutex); + // When we have processed this request, we'll join the + // thread again with the thread that's handling + // `secondary_context` + boost::asio::post(secondary_context, [&, request_id]() { + std::lock_guard lock( + active_secondary_requests_mutex); - // The join is implicit because we're using - // `std::jthread`/`Win32Thread` - active_secondary_requests.erase(request_id); - }); - }, - std::move(secondary_socket)); - }); + // The join is implicit because we're using + // `std::jthread`/`Win32Thread` + active_secondary_requests.erase(request_id); + }); + }, + std::move(secondary_socket)); + }); - Thread secondary_requests_handler( - [&]() { secondary_context.run(); }); + Thread secondary_requests_handler([&]() { secondary_context.run(); }); - // Now we'll handle reads on the primary socket in a loop until the - // socket shuts down - while (true) { - try { - primary_callback(socket); - } catch (const boost::system::system_error&) { - // This happens when the sockets got closed because the - // plugin is being shut down - break; - } - } - - // After the primary socket gets terminated (during shutdown) we'll - // make sure all outstanding jobs have been processed and then drop - // all work from the IO context - std::lock_guard lock(active_secondary_requests_mutex); - secondary_context.stop(); - acceptor.reset(); - } else { - // If ad-hoc sockets are disabled, then we only care about the - // primary socket loop (e.g. when handing calls to - // `IAudioProcessor`, where we want the exact same behaviour as when - // handling normal VST3 messages but we don't want to spawn - // additional threads for perofrmance considerations) - while (true) { - try { - primary_callback(socket); - } catch (const boost::system::system_error&) { - break; - } + // Now we'll handle reads on the primary socket in a loop until the + // socket shuts down + while (true) { + try { + primary_callback(socket); + } catch (const boost::system::system_error&) { + // This happens when the sockets got closed because the plugin + // is being shut down + break; } } + + // After the primary socket gets terminated (during shutdown) we'll make + // sure all outstanding jobs have been processed and then drop all work + // from the IO context + std::lock_guard lock(active_secondary_requests_mutex); + secondary_context.stop(); + acceptor.reset(); } /** diff --git a/src/common/communication/vst2.h b/src/common/communication/vst2.h index 60f97de9..60bb1e95 100644 --- a/src/common/communication/vst2.h +++ b/src/common/communication/vst2.h @@ -106,7 +106,7 @@ class DefaultDataConverter { * should be `std::jthread` and on the Wine side this should be `Win32Thread`. */ template -class EventHandler : public AdHocSocketHandler { +class EventHandler : public AdHocSocketHandler { public: /** * Sets up a single main socket for this type of events. The sockets won't @@ -125,7 +125,7 @@ class EventHandler : public AdHocSocketHandler { EventHandler(boost::asio::io_context& io_context, boost::asio::local::stream_protocol::endpoint endpoint, bool listen) - : AdHocSocketHandler(io_context, endpoint, listen) {} + : AdHocSocketHandler(io_context, endpoint, listen) {} /** * Serialize and send an event over a socket. This is used for both the host diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index 233da066..a80d090d 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -39,14 +39,9 @@ * @tparam Thread The thread implementation to use. On the Linux side this * should be `std::jthread` and on the Wine side this should be `Win32Thread`. * @tparam Request Either `ControlRequest` or `CallbackRequest`. - * @tparam ad_hoc_sockets Whether new sockets should be created on demand to be - * able to handle multiple function calls at the same time. If this is set to - * false, then simultaneous `send_message()` calls will have to wait for the - * earlier call to finish. This also means that the listening side does not - * have to spawn a thread to constantly listen for new connections. */ -template -class Vst3MessageHandler : public AdHocSocketHandler { +template +class Vst3MessageHandler : public AdHocSocketHandler { public: /** * Sets up a single main socket for this type of events. The sockets won't @@ -65,9 +60,7 @@ class Vst3MessageHandler : public AdHocSocketHandler { Vst3MessageHandler(boost::asio::io_context& io_context, boost::asio::local::stream_protocol::endpoint endpoint, bool listen) - : AdHocSocketHandler(io_context, - endpoint, - listen) {} + : AdHocSocketHandler(io_context, endpoint, listen) {} /** * Serialize and send an event over a socket and return the appropriate @@ -447,14 +440,14 @@ class Vst3Sockets : public Sockets { * This will be listened on by the Wine plugin host when it calls * `receive_multi()`. */ - Vst3MessageHandler host_vst_control; + Vst3MessageHandler host_vst_control; /** * For sending callbacks from the plugin back to the host. After we have a * better idea of what our communication model looks like we'll probably * want to provide an abstraction similar to `EventHandler`. */ - Vst3MessageHandler vst_host_callback; + Vst3MessageHandler vst_host_callback; private: boost::asio::io_context& io_context; @@ -470,7 +463,7 @@ class Vst3Sockets : public Sockets { * would have one dedicated thread for handling function calls to these * interfaces, and then another dedicated thread just idling around. */ - std::map> + std::map> audio_processor_sockets; /** * Binary buffers used for serializing objects and receiving messages into From 0a05558a8ca32b9ebd508be355b7718641fc5d34 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 00:39:23 +0100 Subject: [PATCH 434/456] Make the persistent buffer thread local Now that we allow ad hoc socket spawning for the audio sockets. --- src/common/communication/common.h | 14 ++++++-------- src/common/communication/vst3.h | 32 ++++++++++++++----------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/common/communication/common.h b/src/common/communication/common.h index 9829c691..ef9a34be 100644 --- a/src/common/communication/common.h +++ b/src/common/communication/common.h @@ -491,14 +491,12 @@ class AdHocSocketHandler { if (acceptor) { acceptor->accept(socket); - if constexpr (ad_hoc_sockets) { - // As mentioned in `acceptor's` docstring, this acceptor will be - // recreated in `receive_multi()` on another context, and - // potentially on the other side of the connection in the case - // where we're handling `vst_host_callback` VST2 events - acceptor.reset(); - boost::filesystem::remove(endpoint.path()); - } + // As mentioned in `acceptor's` docstring, this acceptor will be + // recreated in `receive_multi()` on another context, and + // potentially on the other side of the connection in the case + // where we're handling `vst_host_callback` VST2 events + acceptor.reset(); + boost::filesystem::remove(endpoint.path()); } else { socket.connect(endpoint); } diff --git a/src/common/communication/vst3.h b/src/common/communication/vst3.h index a80d090d..e306c333 100644 --- a/src/common/communication/vst3.h +++ b/src/common/communication/vst3.h @@ -189,34 +189,30 @@ class Vst3MessageHandler : public AdHocSocketHandler { * request. See the definition of `F` for more information. * * @tparam F A function type in the form of `T::Response(T)` for every `T` - * in `Request`. This way we can directly deserialize into a `T::Response` - * on the side that called `receive_into(T, T::Response&)`. - * @tparam keep_buffers Whether processing buffers should be kept around and - * reused. This is used to minimize allocations in the audio processing - * loop. This can only be used when `ad_hoc_sockets` is set to false, - * since we can of course only reuse buffers within a single thread. These - * buffers will also never shrink, but that should not be an issue here. + * in `Request`. This way we can directly deserialize into a `T::Response` + * on the side that called `receive_into(T, T::Response&)`. + * @tparam persistent_buffers Whether processing buffers should be kept + * around and reused. This is used to minimize allocations in the audio + * processing loop. These buffers will also never shrink, but that should + * not be an issue with the `IAudioProcessor` and `IComponent` functions. + * Saving and loading state is handled on the main sockets. * * @relates Vst3MessageHandler::send_event */ - template + template void receive_messages(std::optional> logging, F callback) { - std::vector persistent_buffer{}; - if constexpr (keep_buffers) { - static_assert(!ad_hoc_sockets, - "Buffers can only be reused when ad-hoc socket " - "spawning has been disabled."); - } + thread_local std::vector persistent_buffer{}; // Reading, processing, and writing back the response for the requests // we receive works in the same way regardless of which socket we're // using const auto process_message = [&](boost::asio::local::stream_protocol::socket& socket) { - auto request = keep_buffers ? read_object( - socket, persistent_buffer) - : read_object(socket); + auto request = + persistent_buffers + ? read_object(socket, persistent_buffer) + : read_object(socket); // See the comment in `receive_into()` for more information bool should_log_response = false; @@ -241,7 +237,7 @@ class Vst3MessageHandler : public AdHocSocketHandler { logger.log_response(!is_host_vst, response); } - if constexpr (keep_buffers) { + if constexpr (persistent_buffers) { write_object(socket, response, persistent_buffer); } else { write_object(socket, response); From 49745d23f1e92ffd42db8d7de2a15815a4e4be9f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 00:50:27 +0100 Subject: [PATCH 435/456] Fix IEditController::createView log message --- src/common/logging/vst3.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index a6dd672f..bcdd4930 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -250,8 +250,8 @@ bool Vst3Logger::log_request(bool is_host_vst, const YaEditController::CreateView& request) { return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id - << ": IEditController::createView(name = " << request.name - << ")"; + << ": IEditController::createView(name = \"" << request.name + << "\")"; }); } From c42c05a7957c928b3cc663a4ef06b58110e9bd8d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 01:26:25 +0100 Subject: [PATCH 436/456] Make BusInfo logging more verbose --- src/common/logging/vst3.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index bcdd4930..63962128 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -1083,7 +1083,10 @@ void Vst3Logger::log_response(bool is_host_vst, log_response_base(is_host_vst, [&](auto& message) { message << response.result.string(); if (response.result == Steinberg::kResultOk) { - message << ", "; + message << ", "; } }); } From 1e1eaee69c0fdc44d72db9f556efb9154c29df76 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 13:13:15 +0100 Subject: [PATCH 437/456] Log the entire speaker arrangement bitsets This makes it very clear why lots of plugins don't work in Ardour right now. --- src/common/logging/vst3.cpp | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 63962128..dfea34a9 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -16,6 +16,8 @@ #include "vst3.h" +#include + #include #include "src/common/serialization/vst3.h" @@ -550,10 +552,31 @@ bool Vst3Logger::log_request( return log_request_base(is_host_vst, [&](auto& message) { message << request.instance_id << ": IAudioProcessor::setBusArrangements(inputs = " - "[SpeakerArrangement; " - << request.inputs.size() << "], numIns = " << request.num_ins - << ", outputs = [SpeakerArrangement; " << request.outputs.size() - << "], numOuts = " << request.num_outs << ")"; + "["; + + for (bool first = true; const auto& arrangement : request.inputs) { + if (!first) { + message << ", "; + } + message << "SpeakerArrangement: 0b" + << std::bitset( + arrangement); + first = false; + } + + message << "], numIns = " << request.num_ins << ", outputs = ["; + + for (bool first = true; const auto& arrangement : request.outputs) { + if (!first) { + message << ", "; + } + message << "SpeakerArrangement: 0b" + << std::bitset( + arrangement); + first = false; + } + + message << "], numOuts = " << request.num_outs << ")"; }); } @@ -1019,7 +1042,10 @@ void Vst3Logger::log_response( log_response_base(is_host_vst, [&](auto& message) { message << response.result.string(); if (response.result == Steinberg::kResultOk) { - message << ", "; + message << ", ( + response.updated_arr) + << ">"; } }); } From 902b05ddc0e1268cb51896ff087fc62d10fa48f6 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 14:42:36 +0100 Subject: [PATCH 438/456] Update todos in the readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6bab9cd9..368729a2 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,11 @@ on the master branch._ ## TODOs -This branch is still very far removed from being in a usable state. Below is an -incomplete list of things that still have to be done before this can be used: +There's still lots of work to be done here. Below is an incomplete list of +things that still have to be done before this is in a usable state: -- Support all VST 3.7.1 interfaces. We're currently support all mandatory VST - 3.0.0 interfaces with one or two missing options interfaces. +- Support the complete 3.7.1 specification. We currently support all mandatory + VST 3.0.0 interfaces with one or two missing options interfaces. - See [this document](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/src/common/serialization/vst3/README.md) for all fully implemetned interfaces. From 7946a1d87439c485c633ebeda367be67bee8d644 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 14:57:45 +0100 Subject: [PATCH 439/456] Set the global bridge instance to null on deinit Hosts like Qtractor will deinitialize the module only to then immediately initialize it again, so without this we would get an assertion failure in `InitModule()`. --- src/plugin/vst3-plugin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugin/vst3-plugin.cpp b/src/plugin/vst3-plugin.cpp index 3e27c884..c0955ef8 100644 --- a/src/plugin/vst3-plugin.cpp +++ b/src/plugin/vst3-plugin.cpp @@ -54,6 +54,8 @@ bool DeinitModule() { assert(bridge != nullptr); delete bridge; + bridge = nullptr; + return true; } From 5a3b6319bdb849488b0f45890d7ce65f14c08cfe Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 15:15:56 +0100 Subject: [PATCH 440/456] Add VST3 status to supported DAWs list --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 368729a2..e243a07a 100644 --- a/README.md +++ b/README.md @@ -58,13 +58,15 @@ things that still have to be done before this is in a usable state: Yabridge has been tested under the following hosts using Wine Staging 6.0: -- Bitwig Studio 3.3 -- Carla 2.2 -- Ardour 6.5 -- Mixbus 6.0.702 -- Qtractor 0.9.18 -- REAPER 6.18 -- Renoise 3.2.4 +| Host | VST2 | VST3 | +| ----------------- | ------------------ | -------------------------------------------------------------------------------- | +| Bitwig Studio 3.3 | :heavy_check_mark: | :heavy_check_mark: | +| Carla 2.2 | :heavy_check_mark: | :heavy_check_mark: | +| REAPER 6.19 | :heavy_check_mark: | :heavy_check_mark: | +| Ardour 6.5 | :heavy_check_mark: | :warning: Several plugins segfault because Ardour skips part of the setup proces | +| Mixbus 6.0.702 | :heavy_check_mark: | :warning: Same situation as with Ardour | +| Qtractor 0.9.19 | :heavy_check_mark: | :x: See [rncbc/qtractor#291](https://github.com/rncbc/qtractor/issues/291) | +| Renoise 3.2.4 | :heavy_check_mark: | Does not support VST3 | Please let me know if there are any issues with other VST hosts. From b34762db2912d6d2ef906d68db59797c7e229a7c Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 15:23:22 +0100 Subject: [PATCH 441/456] Mention that XEmbed is only available on master --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e243a07a..74a0f544 100644 --- a/README.md +++ b/README.md @@ -289,11 +289,11 @@ plugin._ #### Compatibility options -| Option | Values | Description | -| --------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `cache_time_info` | `{true,false}` | Compatibility option for plugins that call `audioMasterGetTime()` multiple times during a single processing cycle. With this option subsequent calls during a single audio processing cycle will reuse the value returned by the first call to this function. This is a bug in the plugin, and this option serves as a temporary workaround until the plugin fixes the issue. | -| `editor_double_embed` | `{true,false}` | Compatibility option for plugins that rely on the absolute screen coordinates of the window they're embedded in. Since the Wine window gets embedded inside of a window provided by your DAW, these coordinates won't match up and the plugin would end up drawing in the wrong location without this option. Currently the only known plugins that require this option are _PSPaudioware_ plugins with expandable GUIs, such as E27. Defaults to `false`. | -| `editor_xembed` | `{true,false}` | Use Wine's XEmbed implementation instead of yabridge's normal window embedding method. Some plugins will have redrawing issues when using XEmbed and editor resizing won't work properly with it, but it could be useful in certain setups. You may need to use [this Wine patch](https://github.com/psycha0s/airwave/blob/master/fix-xembed-wine-windows.patch) if you're getting blank editor windows. Defaults to `false`. | +| Option | Values | Description | +| --------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `cache_time_info` | `{true,false}` | Compatibility option for plugins that call `audioMasterGetTime()` multiple times during a single processing cycle. With this option subsequent calls during a single audio processing cycle will reuse the value returned by the first call to this function. This is a bug in the plugin, and this option serves as a temporary workaround until the plugin fixes the issue. | +| `editor_double_embed` | `{true,false}` | Compatibility option for plugins that rely on the absolute screen coordinates of the window they're embedded in. Since the Wine window gets embedded inside of a window provided by your DAW, these coordinates won't match up and the plugin would end up drawing in the wrong location without this option. Currently the only known plugins that require this option are _PSPaudioware_ plugins with expandable GUIs, such as E27. Defaults to `false`. | +| `editor_xembed` | `{true,false}` | Use Wine's XEmbed implementation instead of yabridge's normal window embedding method. Some plugins will have redrawing issues when using XEmbed and editor resizing won't work properly with it, but it could be useful in certain setups. You may need to use [this Wine patch](https://github.com/psycha0s/airwave/blob/master/fix-xembed-wine-windows.patch) if you're getting blank editor windows. Defaults to `false`. _This option is only availble on the master branch._ | These options are workarounds for issues mentioned in the [known issues](#runtime-dependencies-and-known-issues) section. Depending on the hosts From c1118af21cbbf89b352a7cc2777688e3dfd3c2b7 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 15:34:15 +0100 Subject: [PATCH 442/456] Update the readmes for merging into master --- README.md | 20 +++----------------- src/common/serialization/vst3/README.md | 8 ++++---- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 74a0f544..8fb374e8 100644 --- a/README.md +++ b/README.md @@ -11,22 +11,8 @@ for VST2 plugins and quick startup times. Its modern concurrent architecture and focus on transparency allows yabridge to be both fast and highly compatible, while also staying easy to debug and maintain. -_VST3 support is scheduled for yabridge 3.0. At the moment it's only available -on the master branch._ - -## TODOs - -There's still lots of work to be done here. Below is an incomplete list of -things that still have to be done before this is in a usable state: - -- Support the complete 3.7.1 specification. We currently support all mandatory - VST 3.0.0 interfaces with one or two missing options interfaces. -- See [this - document](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/src/common/serialization/vst3/README.md) - for all fully implemetned interfaces. -- Update all the AUR packages for the `libyabridge-vst{2,3}.so` changes. -- Test the binaries built on GitHub on plain Ubuntu 18.04, are we missing any - static linking? +_VST3 support is scheduled for yabridge 3.0. At the moment it's only available on the master branch._ +_See [this document](https://github.com/robbert-vdh/yabridge/blob/master/src/common/serialization/vst3/README.md) for all currently implemented interfaces._ ![yabridge screenshot](https://raw.githubusercontent.com/robbert-vdh/yabridge/master/screenshot.png) @@ -574,7 +560,7 @@ The following dependencies are included in the repository as a Meson wrap: - [function2](https://github.com/Naios/function2) - [tomlplusplus](https://github.com/marzer/tomlplusplus) - Version 3.7.1 of the [VST3 SDK](https://github.com/robbert-vdh/vst3sdk) with - some [patches](https://github.com/robbert-vdh/yabridge/tree/feature/vst3/tools/patch-vst3-sdk.sh) + some [patches](https://github.com/robbert-vdh/yabridge/blob/master/tools/patch-vst3-sdk.sh) to allow Winelib compilation The project can then be compiled as follows: diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 8bc48c81..16cc4050 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -1,9 +1,9 @@ # VST3 interfaces -TODO: After merging into master, update this link to just point to GitHub - -See [docs/vst3.md](../../../../docs/vst3.md) for more information on how the -serialization works. +We currently support all VST 3.0.0 interfaces with the exceptions of +`IProgramListData` and `IUnitData`. See +[docs/vst3.md](https://github.com/robbert-vdh/yabridge/blob/master/docs/vst3.md) +for more information on how the serialization works. VST3 plugin interfaces are implemented as follows: From c2f1722f144bb7aca226804cfef1baca195f32b5 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 15:47:25 +0100 Subject: [PATCH 443/456] Add a proxy class for IProgramListData --- meson.build | 2 + src/common/serialization/vst3/README.md | 5 +- .../vst3/plugin/program-list-data.cpp | 27 +++++++ .../vst3/plugin/program-list-data.h | 79 +++++++++++++++++++ .../serialization/vst3/plugin/unit-info.h | 1 - 5 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 src/common/serialization/vst3/plugin/program-list-data.cpp create mode 100644 src/common/serialization/vst3/plugin/program-list-data.h diff --git a/meson.build b/meson.build index 75130443..e0ebb398 100644 --- a/meson.build +++ b/meson.build @@ -99,6 +99,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/plugin/edit-controller.cpp', 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', + 'src/common/serialization/vst3/plugin/program-list-data.cpp', 'src/common/serialization/vst3/plugin/unit-info.cpp', 'src/common/serialization/vst3/component-handler/component-handler.cpp', 'src/common/serialization/vst3/component-handler/unit-handler.cpp', @@ -157,6 +158,7 @@ if with_vst3 'src/common/serialization/vst3/plugin/edit-controller.cpp', 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', + 'src/common/serialization/vst3/plugin/program-list-data.cpp', 'src/common/serialization/vst3/plugin/unit-info.cpp', 'src/common/serialization/vst3/component-handler/component-handler.cpp', 'src/common/serialization/vst3/component-handler/unit-handler.cpp', diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 16cc4050..097fdad6 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -1,7 +1,7 @@ # VST3 interfaces -We currently support all VST 3.0.0 interfaces with the exceptions of -`IProgramListData` and `IUnitData`. See +We currently support all VST 3.0.0 interfaces with the exception of `IUnitData`. +See [docs/vst3.md](https://github.com/robbert-vdh/yabridge/blob/master/docs/vst3.md) for more information on how the serialization works. @@ -20,6 +20,7 @@ VST3 plugin interfaces are implemented as follows: | `YaEditController` | `Vst3PluginProxy` | `IEditController` | | `YaEditController2` | `Vst3PluginProxy` | `IEditController2` | | `YaPluginBase` | `Vst3PluginProxy` | `IPluginBase` | +| `YaProgramListData` | `Vst3PluginProxy` | `IProgramListData` | | `YaUnitInfo` | `Vst3PluginProxy` | `IUnitInfo` | VST3 host interfaces are implemented as follows: diff --git a/src/common/serialization/vst3/plugin/program-list-data.cpp b/src/common/serialization/vst3/plugin/program-list-data.cpp new file mode 100644 index 00000000..3e76e812 --- /dev/null +++ b/src/common/serialization/vst3/plugin/program-list-data.cpp @@ -0,0 +1,27 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "program-list-data.h" + +YaProgramListData::ConstructArgs::ConstructArgs() {} + +YaProgramListData::ConstructArgs::ConstructArgs( + Steinberg::IPtr object) + : supported( + Steinberg::FUnknownPtr(object)) {} + +YaProgramListData::YaProgramListData(const ConstructArgs&& args) + : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin/program-list-data.h b/src/common/serialization/vst3/plugin/program-list-data.h new file mode 100644 index 00000000..75ed089b --- /dev/null +++ b/src/common/serialization/vst3/plugin/program-list-data.h @@ -0,0 +1,79 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "../../common.h" +#include "../base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IProgramListData` for serialization purposes. This is + * instantiated as part of `Vst3PluginProxy`. + */ +class YaProgramListData : public Steinberg::Vst::IProgramListData { + public: + /** + * These are the arguments for creating a `YaProgramListData`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Check whether an existing implementation implements + * `IProgramListData` and read arguments from it. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + template + void serialize(S& s) { + s.value1b(supported); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaProgramListData(const ConstructArgs&& args); + + inline bool supported() const { return arguments.supported; } + + virtual tresult PLUGIN_API + programDataSupported(Steinberg::Vst::ProgramListID listId) override = 0; + virtual tresult PLUGIN_API + getProgramData(Steinberg::Vst::ProgramListID listId, + int32 programIndex, + Steinberg::IBStream* data) override = 0; + virtual tresult PLUGIN_API + setProgramData(Steinberg::Vst::ProgramListID listId, + int32 programIndex, + Steinberg::IBStream* data) override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop diff --git a/src/common/serialization/vst3/plugin/unit-info.h b/src/common/serialization/vst3/plugin/unit-info.h index f0d04095..1708336a 100644 --- a/src/common/serialization/vst3/plugin/unit-info.h +++ b/src/common/serialization/vst3/plugin/unit-info.h @@ -20,7 +20,6 @@ #include "../../common.h" #include "../base.h" -#include "../host-context-proxy.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" From 22269570d0c0192e363bd198b614ed94b8242095 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 15:53:01 +0100 Subject: [PATCH 444/456] Add stubs for IProgramListData --- .../serialization/vst3/plugin-proxy.cpp | 6 +++++ src/common/serialization/vst3/plugin-proxy.h | 4 +++ .../bridges/vst3-impls/plugin-proxy.cpp | 25 +++++++++++++++++++ src/plugin/bridges/vst3-impls/plugin-proxy.h | 10 ++++++++ 4 files changed, 45 insertions(+) diff --git a/src/common/serialization/vst3/plugin-proxy.cpp b/src/common/serialization/vst3/plugin-proxy.cpp index 417b55ce..438fa0af 100644 --- a/src/common/serialization/vst3/plugin-proxy.cpp +++ b/src/common/serialization/vst3/plugin-proxy.cpp @@ -28,6 +28,7 @@ Vst3PluginProxy::ConstructArgs::ConstructArgs( edit_controller_args(object), edit_controller_2_args(object), plugin_base_args(object), + program_list_data_args(object), unit_info_args(object) {} Vst3PluginProxy::Vst3PluginProxy(const ConstructArgs&& args) @@ -37,6 +38,7 @@ Vst3PluginProxy::Vst3PluginProxy(const ConstructArgs&& args) YaEditController(std::move(args.edit_controller_args)), YaEditController2(std::move(args.edit_controller_2_args)), YaPluginBase(std::move(args.plugin_base_args)), + YaProgramListData(std::move(args.program_list_data_args)), YaUnitInfo(std::move(args.unit_info_args)), arguments(std::move(args)){FUNKNOWN_CTOR} @@ -89,6 +91,10 @@ tresult PLUGIN_API Vst3PluginProxy::queryInterface(Steinberg::FIDString _iid, QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IEditController2::iid, Steinberg::Vst::IEditController2) } + if (YaProgramListData::supported()) { + QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IProgramListData::iid, + Steinberg::Vst::IProgramListData) + } if (YaUnitInfo::supported()) { QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IUnitInfo::iid, Steinberg::Vst::IUnitInfo) diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index 892f2239..e988445d 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -25,6 +25,7 @@ #include "plugin/edit-controller-2.h" #include "plugin/edit-controller.h" #include "plugin/plugin-base.h" +#include "plugin/program-list-data.h" #include "plugin/unit-info.h" #pragma GCC diagnostic push @@ -59,6 +60,7 @@ class Vst3PluginProxy : public YaAudioProcessor, public YaEditController, public YaEditController2, public YaPluginBase, + public YaProgramListData, public YaUnitInfo { public: /** @@ -84,6 +86,7 @@ class Vst3PluginProxy : public YaAudioProcessor, YaEditController::ConstructArgs edit_controller_args; YaEditController2::ConstructArgs edit_controller_2_args; YaPluginBase::ConstructArgs plugin_base_args; + YaProgramListData::ConstructArgs program_list_data_args; YaUnitInfo::ConstructArgs unit_info_args; template @@ -95,6 +98,7 @@ class Vst3PluginProxy : public YaAudioProcessor, s.object(edit_controller_args); s.object(edit_controller_2_args); s.object(plugin_base_args); + s.object(program_list_data_args); s.object(unit_info_args); } }; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 615710cb..a9a32cb6 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -444,6 +444,31 @@ tresult PLUGIN_API Vst3PluginProxyImpl::terminate() { YaPluginBase::Terminate{.instance_id = instance_id()}); } +tresult PLUGIN_API Vst3PluginProxyImpl::programDataSupported( + Steinberg::Vst::ProgramListID listId) { + // TODO: Implement + bridge.logger.log("TODO: IProgramListData::programDataSupported"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::getProgramData(Steinberg::Vst::ProgramListID listId, + int32 programIndex, + Steinberg::IBStream* data) { + // TODO: Implement + bridge.logger.log("TODO: IProgramListData::getProgramData"); + return Steinberg::kNotImplemented; +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::setProgramData(Steinberg::Vst::ProgramListID listId, + int32 programIndex, + Steinberg::IBStream* data) { + // TODO: Implement + bridge.logger.log("TODO: IProgramListData::setProgramData"); + return Steinberg::kNotImplemented; +} + int32 PLUGIN_API Vst3PluginProxyImpl::getUnitCount() { return bridge.send_message( YaUnitInfo::GetUnitCount{.instance_id = instance_id()}); diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index 24a7ae84..dcd35d34 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -124,6 +124,16 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { tresult PLUGIN_API initialize(FUnknown* context) override; tresult PLUGIN_API terminate() override; + // From `IProgramListData` + tresult PLUGIN_API + programDataSupported(Steinberg::Vst::ProgramListID listId) override; + tresult PLUGIN_API getProgramData(Steinberg::Vst::ProgramListID listId, + int32 programIndex, + Steinberg::IBStream* data) override; + tresult PLUGIN_API setProgramData(Steinberg::Vst::ProgramListID listId, + int32 programIndex, + Steinberg::IBStream* data) override; + // From `IUnitInfo` int32 PLUGIN_API getUnitCount() override; tresult PLUGIN_API From ccba1ce36cd636f9086ada17b2cbb3c80fe3f933 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 16:11:25 +0100 Subject: [PATCH 445/456] Add IProgramListData request and response objects --- .../vst3/plugin/program-list-data.h | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/common/serialization/vst3/plugin/program-list-data.h b/src/common/serialization/vst3/plugin/program-list-data.h index 75ed089b..434441be 100644 --- a/src/common/serialization/vst3/plugin/program-list-data.h +++ b/src/common/serialization/vst3/plugin/program-list-data.h @@ -61,12 +61,92 @@ class YaProgramListData : public Steinberg::Vst::IProgramListData { inline bool supported() const { return arguments.supported; } + /** + * Message to pass through a call to + * `IProgramListData::programDataSupported(list_id)` to the Wine plugin + * host. + */ + struct ProgramDataSupported { + using Response = UniversalTResult; + + native_size_t instance_id; + + Steinberg::Vst::ProgramListID list_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(list_id); + } + }; + virtual tresult PLUGIN_API programDataSupported(Steinberg::Vst::ProgramListID listId) override = 0; + + /** + * The response code and written state for a call to + * `IProgramListData::getProgramData(list_id, program_index, &data)`. + */ + struct GetProgramDataResponse { + UniversalTResult result; + VectorStream data; + + template + void serialize(S& s) { + s.object(result); + s.object(data); + } + }; + + /** + * Message to pass through a call to + * `IProgramListData::getProgramData(list_id, program_index, &data)` to the + * Wine plugin host. + */ + struct GetProgramData { + using Response = GetProgramDataResponse; + + native_size_t instance_id; + + Steinberg::Vst::ProgramListID list_id; + int32 program_index; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(list_id); + s.value4b(program_index); + } + }; + virtual tresult PLUGIN_API getProgramData(Steinberg::Vst::ProgramListID listId, int32 programIndex, Steinberg::IBStream* data) override = 0; + + /** + * Message to pass through a call to + * `IProgramListData::SetProgramData(list_id, program_index, data)` to the + * Wine plugin host. + */ + struct SetProgramData { + using Response = UniversalTResult; + + native_size_t instance_id; + + Steinberg::Vst::ProgramListID list_id; + int32 program_index; + VectorStream data; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(list_id); + s.value4b(program_index); + s.object(data); + } + }; + virtual tresult PLUGIN_API setProgramData(Steinberg::Vst::ProgramListID listId, int32 programIndex, From 1c7a5e08a06a4f47c85eb0e58dec19e321897fee Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 17:32:33 +0100 Subject: [PATCH 446/456] Check for null pointers in input parameter changes This is not allowed to be a null pointer, but the SDK's plugin validator will pass a null pointer anyways. --- src/common/serialization/vst3/process-data.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/common/serialization/vst3/process-data.cpp b/src/common/serialization/vst3/process-data.cpp index 1e805495..769470ff 100644 --- a/src/common/serialization/vst3/process-data.cpp +++ b/src/common/serialization/vst3/process-data.cpp @@ -143,7 +143,12 @@ YaProcessData::YaProcessData(const Steinberg::Vst::ProcessData& process_data) symbolic_sample_size(process_data.symbolicSampleSize), num_samples(process_data.numSamples), outputs_num_channels(process_data.numOutputs), - input_parameter_changes(*process_data.inputParameterChanges), + // Even though `ProcessData::inputParamterChanges` is mandatory, the VST3 + // validator will pass a null pointer here + input_parameter_changes( + process_data.inputParameterChanges + ? YaParameterChanges(*process_data.inputParameterChanges) + : YaParameterChanges()), output_parameter_changes_supported(process_data.outputParameterChanges), input_events(process_data.inputEvents ? std::make_optional( *process_data.inputEvents) From 3704ca8cb07712ac674e39cab58c51821a3b0cd4 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 18:37:28 +0100 Subject: [PATCH 447/456] Add logging for the YaProgramListData structs --- src/common/logging/vst3.cpp | 39 +++++++++++++++++++++++++++++++++++++ src/common/logging/vst3.h | 8 ++++++++ 2 files changed, 47 insertions(+) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index dfea34a9..435ab1f7 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -434,6 +434,33 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaProgramListData::ProgramDataSupported&) { + return log_request_base(is_host_vst, [&](auto& message) { + message << "IProgramListData::programDataSupported()"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaProgramListData::GetProgramData& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << "IProgramListData::getProgramData(listId = " + << request.list_id + << ", programIndex = " << request.program_index << ", &data)"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaProgramListData::SetProgramData& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << "IProgramListData::setProgramData(listId = " + << request.list_id + << ", programIndex = " << request.program_index + << ", data = )"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaUnitInfo::GetUnitCount& request) { return log_request_base(is_host_vst, [&](auto& message) { @@ -963,6 +990,18 @@ void Vst3Logger::log_response(bool is_host_vst, const Configuration&) { [&](auto& message) { message << ""; }); } +void Vst3Logger::log_response( + bool is_host_vst, + const YaProgramListData::GetProgramDataResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", "; + } + }); +} + void Vst3Logger::log_response(bool is_host_vst, const YaUnitInfo::GetUnitInfoResponse& response) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index 7f95fe2d..bf4604a5 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -108,6 +108,12 @@ class Vst3Logger { bool log_request(bool is_host_vst, const YaPluginBase::Terminate&); bool log_request(bool is_host_vst, const YaPluginFactory::Construct&); bool log_request(bool is_host_vst, const YaPluginFactory::SetHostContext&); + bool log_request(bool is_host_vst, + const YaProgramListData::ProgramDataSupported&); + bool log_request(bool is_host_vst, + const YaProgramListData::GetProgramData&); + bool log_request(bool is_host_vst, + const YaProgramListData::SetProgramData&); bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitCount&); bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitInfo&); bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramListCount&); @@ -173,6 +179,8 @@ class Vst3Logger { void log_response(bool is_host_vst, const YaPlugView::GetSizeResponse&); void log_response(bool is_host_vst, const YaPluginFactory::ConstructArgs&); void log_response(bool is_host_vst, const Configuration&); + void log_response(bool is_host_vst, + const YaProgramListData::GetProgramDataResponse&); void log_response(bool is_host_vst, const YaUnitInfo::GetUnitInfoResponse&); void log_response(bool is_host_vst, const YaUnitInfo::GetProgramListInfoResponse&); From 38211d0fb374c49fcd6af94937b3147dd30c3708 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 18:50:17 +0100 Subject: [PATCH 448/456] Implement IProgramListData on the Wine side --- src/common/serialization/vst3.h | 3 +++ src/wine-host/bridges/vst3.cpp | 23 +++++++++++++++++++++++ src/wine-host/bridges/vst3.h | 1 + 3 files changed, 27 insertions(+) diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 5452a09e..9ae4dec4 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -100,6 +100,9 @@ using ControlRequest = std::variant YaProgramListData::ProgramDataSupported::Response { + return object_instances[request.instance_id] + .program_list_data->programDataSupported(request.list_id); + }, + [&](const YaProgramListData::GetProgramData& request) + -> YaProgramListData::GetProgramData::Response { + VectorStream data{}; + const tresult result = + object_instances[request.instance_id] + .program_list_data->getProgramData( + request.list_id, request.program_index, &data); + + return YaProgramListData::GetProgramDataResponse{ + .result = result, .data = std::move(data)}; + }, + [&](YaProgramListData::SetProgramData& request) + -> YaProgramListData::SetProgramData::Response { + return object_instances[request.instance_id] + .program_list_data->setProgramData( + request.list_id, request.program_index, &request.data); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index f159090b..f674b199 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -115,6 +115,7 @@ struct InstanceInterfaces { Steinberg::FUnknownPtr edit_controller; Steinberg::FUnknownPtr edit_controller_2; Steinberg::FUnknownPtr plugin_base; + Steinberg::FUnknownPtr program_list_data; Steinberg::FUnknownPtr unit_info; }; From 96fe8b16a5ae66bafb0d6221a0b56da877a8a376 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 18:54:22 +0100 Subject: [PATCH 449/456] Add more explicit moves in VST3 message handling The compiler might be smart enough to do this for us, but doing it manually doesn't hurt. --- src/wine-host/bridges/vst3.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 96513f07..2b59b904 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -258,7 +258,7 @@ void Vst3Bridge::run() { request.info); return YaEditController::GetParameterInfoResponse{ - .result = result, .updated_info = request.info}; + .result = result, .updated_info = std::move(request.info)}; }, [&](const YaEditController::GetParamStringByValue& request) -> YaEditController::GetParamStringByValue::Response { @@ -478,7 +478,7 @@ void Vst3Bridge::run() { .plug_view->getSize(&request.size); return YaPlugView::GetSizeResponse{ - .result = result, .updated_size = request.size}; + .result = result, .updated_size = std::move(request.size)}; }, [&](YaPlugView::OnSize& request) -> YaPlugView::OnSize::Response { // HACK: This function has to be run from the UI thread since @@ -617,7 +617,7 @@ void Vst3Bridge::run() { .unit_info->getUnitInfo(request.unit_index, info); return YaUnitInfo::GetUnitInfoResponse{.result = result, - .info = info}; + .info = std::move(info)}; }, [&](const YaUnitInfo::GetProgramListCount& request) -> YaUnitInfo::GetProgramListCount::Response { @@ -631,8 +631,8 @@ void Vst3Bridge::run() { .unit_info->getProgramListInfo( request.list_index, info); - return YaUnitInfo::GetProgramListInfoResponse{.result = result, - .info = info}; + return YaUnitInfo::GetProgramListInfoResponse{ + .result = result, .info = std::move(info)}; }, [&](const YaUnitInfo::GetProgramName& request) -> YaUnitInfo::GetProgramName::Response { From bddcc40e2484387f23e27c10d0d6e1e832d2ecd3 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 19:11:12 +0100 Subject: [PATCH 450/456] Implement IProgramListData on the plugin side With this the interface is fully implemented. --- .../bridges/vst3-impls/plugin-proxy.cpp | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index a9a32cb6..9d21e1cf 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -446,27 +446,33 @@ tresult PLUGIN_API Vst3PluginProxyImpl::terminate() { tresult PLUGIN_API Vst3PluginProxyImpl::programDataSupported( Steinberg::Vst::ProgramListID listId) { - // TODO: Implement - bridge.logger.log("TODO: IProgramListData::programDataSupported"); - return Steinberg::kNotImplemented; + return bridge.send_message(YaProgramListData::ProgramDataSupported{ + .instance_id = instance_id(), .list_id = listId}); } tresult PLUGIN_API Vst3PluginProxyImpl::getProgramData(Steinberg::Vst::ProgramListID listId, int32 programIndex, Steinberg::IBStream* data) { - // TODO: Implement - bridge.logger.log("TODO: IProgramListData::getProgramData"); - return Steinberg::kNotImplemented; + const GetProgramDataResponse response = bridge.send_message( + YaProgramListData::GetProgramData{.instance_id = instance_id(), + .list_id = listId, + .program_index = programIndex}); + + assert(response.data.write_back(data) == Steinberg::kResultOk); + + return response.result; } tresult PLUGIN_API Vst3PluginProxyImpl::setProgramData(Steinberg::Vst::ProgramListID listId, int32 programIndex, Steinberg::IBStream* data) { - // TODO: Implement - bridge.logger.log("TODO: IProgramListData::setProgramData"); - return Steinberg::kNotImplemented; + return bridge.send_message( + YaProgramListData::SetProgramData{.instance_id = instance_id(), + .list_id = listId, + .program_index = programIndex, + .data = data}); } int32 PLUGIN_API Vst3PluginProxyImpl::getUnitCount() { From 104cdef919462c3787179f1f4bc7d5b23a12dc05 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 21:47:01 +0100 Subject: [PATCH 451/456] Add a proxy class for IUnitData --- meson.build | 2 + src/common/serialization/vst3/README.md | 4 +- .../serialization/vst3/plugin/unit-data.cpp | 27 +++++++ .../serialization/vst3/plugin/unit-data.h | 77 +++++++++++++++++++ 4 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/common/serialization/vst3/plugin/unit-data.cpp create mode 100644 src/common/serialization/vst3/plugin/unit-data.h diff --git a/meson.build b/meson.build index e0ebb398..4253245a 100644 --- a/meson.build +++ b/meson.build @@ -100,6 +100,7 @@ vst3_plugin_sources = [ 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/plugin/program-list-data.cpp', + 'src/common/serialization/vst3/plugin/unit-data.cpp', 'src/common/serialization/vst3/plugin/unit-info.cpp', 'src/common/serialization/vst3/component-handler/component-handler.cpp', 'src/common/serialization/vst3/component-handler/unit-handler.cpp', @@ -159,6 +160,7 @@ if with_vst3 'src/common/serialization/vst3/plugin/edit-controller-2.cpp', 'src/common/serialization/vst3/plugin/plugin-base.cpp', 'src/common/serialization/vst3/plugin/program-list-data.cpp', + 'src/common/serialization/vst3/plugin/unit-data.cpp', 'src/common/serialization/vst3/plugin/unit-info.cpp', 'src/common/serialization/vst3/component-handler/component-handler.cpp', 'src/common/serialization/vst3/component-handler/unit-handler.cpp', diff --git a/src/common/serialization/vst3/README.md b/src/common/serialization/vst3/README.md index 097fdad6..32056749 100644 --- a/src/common/serialization/vst3/README.md +++ b/src/common/serialization/vst3/README.md @@ -1,7 +1,6 @@ # VST3 interfaces -We currently support all VST 3.0.0 interfaces with the exception of `IUnitData`. -See +We currently support all VST 3.0.0 interfaces. See [docs/vst3.md](https://github.com/robbert-vdh/yabridge/blob/master/docs/vst3.md) for more information on how the serialization works. @@ -21,6 +20,7 @@ VST3 plugin interfaces are implemented as follows: | `YaEditController2` | `Vst3PluginProxy` | `IEditController2` | | `YaPluginBase` | `Vst3PluginProxy` | `IPluginBase` | | `YaProgramListData` | `Vst3PluginProxy` | `IProgramListData` | +| `YaUnitData` | `Vst3PluginProxy` | `IUnitData` | | `YaUnitInfo` | `Vst3PluginProxy` | `IUnitInfo` | VST3 host interfaces are implemented as follows: diff --git a/src/common/serialization/vst3/plugin/unit-data.cpp b/src/common/serialization/vst3/plugin/unit-data.cpp new file mode 100644 index 00000000..b03cd05a --- /dev/null +++ b/src/common/serialization/vst3/plugin/unit-data.cpp @@ -0,0 +1,27 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "unit-data.h" + +YaUnitData::ConstructArgs::ConstructArgs() {} + +YaUnitData::ConstructArgs::ConstructArgs( + Steinberg::IPtr object) + : supported( + Steinberg::FUnknownPtr(object)) {} + +YaUnitData::YaUnitData(const ConstructArgs&& args) + : arguments(std::move(args)) {} diff --git a/src/common/serialization/vst3/plugin/unit-data.h b/src/common/serialization/vst3/plugin/unit-data.h new file mode 100644 index 00000000..a021ab0f --- /dev/null +++ b/src/common/serialization/vst3/plugin/unit-data.h @@ -0,0 +1,77 @@ +// yabridge: a Wine VST bridge +// Copyright (C) 2020 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include "../../common.h" +#include "../base.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + +/** + * Wraps around `IUnitData` for serialization purposes. This is instantiated as + * part of `Vst3PluginProxy`. + */ +class YaUnitData : public Steinberg::Vst::IUnitData { + public: + /** + * These are the arguments for creating a `YaUnitData`. + */ + struct ConstructArgs { + ConstructArgs(); + + /** + * Check whether an existing implementation implements `IUnitData` and + * read arguments from it. + */ + ConstructArgs(Steinberg::IPtr object); + + /** + * Whether the object supported this interface. + */ + bool supported; + + template + void serialize(S& s) { + s.value1b(supported); + } + }; + + /** + * Instantiate this instance with arguments read from another interface + * implementation. + */ + YaUnitData(const ConstructArgs&& args); + + inline bool supported() const { return arguments.supported; } + + virtual tresult PLUGIN_API + unitDataSupported(Steinberg::Vst::UnitID unitId) override = 0; + virtual tresult PLUGIN_API + getUnitData(Steinberg::Vst::UnitID unitId, + Steinberg::IBStream* data) override = 0; + virtual tresult PLUGIN_API + setUnitData(Steinberg::Vst::UnitID unitId, + Steinberg::IBStream* data) override = 0; + + protected: + ConstructArgs arguments; +}; + +#pragma GCC diagnostic pop From 53c5e46b4cfc9716a8710db42232f95ff3267250 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 21:48:38 +0100 Subject: [PATCH 452/456] Add the request and response structs for IUnitData --- .../serialization/vst3/plugin/unit-data.h | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/common/serialization/vst3/plugin/unit-data.h b/src/common/serialization/vst3/plugin/unit-data.h index a021ab0f..bd200e2b 100644 --- a/src/common/serialization/vst3/plugin/unit-data.h +++ b/src/common/serialization/vst3/plugin/unit-data.h @@ -61,11 +61,84 @@ class YaUnitData : public Steinberg::Vst::IUnitData { inline bool supported() const { return arguments.supported; } + /** + * Message to pass through a call to `IUnitData::unitDataSupported(unit_id)` + * to the Wine plugin host. + */ + struct UnitDataSupported { + using Response = UniversalTResult; + + native_size_t instance_id; + + Steinberg::Vst::UnitID unit_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(unit_id); + } + }; + virtual tresult PLUGIN_API unitDataSupported(Steinberg::Vst::UnitID unitId) override = 0; + + /** + * The response code and written state for a call to + * `IUnitData::getUnitData(unit_id, &data)`. + */ + struct GetUnitDataResponse { + UniversalTResult result; + VectorStream data; + + template + void serialize(S& s) { + s.object(result); + s.object(data); + } + }; + + /** + * Message to pass through a call to `IUnitData::getUnitData(unit_id, + * &data)` to the Wine plugin host. + */ + struct GetUnitData { + using Response = GetUnitDataResponse; + + native_size_t instance_id; + + Steinberg::Vst::UnitID unit_id; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(unit_id); + } + }; + virtual tresult PLUGIN_API getUnitData(Steinberg::Vst::UnitID unitId, Steinberg::IBStream* data) override = 0; + + /** + * Message to pass through a call to `IUnitData::SetUnitData(unit_id, data)` + * to the Wine plugin host. + */ + struct SetUnitData { + using Response = UniversalTResult; + + native_size_t instance_id; + + Steinberg::Vst::UnitID unit_id; + VectorStream data; + + template + void serialize(S& s) { + s.value8b(instance_id); + s.value4b(unit_id); + s.object(data); + } + }; + virtual tresult PLUGIN_API setUnitData(Steinberg::Vst::UnitID unitId, Steinberg::IBStream* data) override = 0; From f85912fd255cb0e2f60b43060f17c3ac91c193b6 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 21:51:44 +0100 Subject: [PATCH 453/456] Add logging for IUnitData structs --- src/common/logging/vst3.cpp | 35 ++++++++++++++++++++ src/common/logging/vst3.h | 4 +++ src/common/serialization/vst3/plugin-proxy.h | 1 + 3 files changed, 40 insertions(+) diff --git a/src/common/logging/vst3.cpp b/src/common/logging/vst3.cpp index 435ab1f7..70388205 100644 --- a/src/common/logging/vst3.cpp +++ b/src/common/logging/vst3.cpp @@ -461,6 +461,30 @@ bool Vst3Logger::log_request(bool is_host_vst, }); } +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitData::UnitDataSupported&) { + return log_request_base(is_host_vst, [&](auto& message) { + message << "IUnitData::unitDataSupported()"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitData::GetUnitData& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << "IUnitData::getUnitData(listId = " << request.unit_id + << ", &data)"; + }); +} + +bool Vst3Logger::log_request(bool is_host_vst, + const YaUnitData::SetUnitData& request) { + return log_request_base(is_host_vst, [&](auto& message) { + message << "IUnitData::setUnitData(listId = " << request.unit_id + << ", data = )"; + }); +} + bool Vst3Logger::log_request(bool is_host_vst, const YaUnitInfo::GetUnitCount& request) { return log_request_base(is_host_vst, [&](auto& message) { @@ -1002,6 +1026,17 @@ void Vst3Logger::log_response( }); } +void Vst3Logger::log_response(bool is_host_vst, + const YaUnitData::GetUnitDataResponse& response) { + log_response_base(is_host_vst, [&](auto& message) { + message << response.result.string(); + if (response.result == Steinberg::kResultOk) { + message << ", "; + } + }); +} + void Vst3Logger::log_response(bool is_host_vst, const YaUnitInfo::GetUnitInfoResponse& response) { log_response_base(is_host_vst, [&](auto& message) { diff --git a/src/common/logging/vst3.h b/src/common/logging/vst3.h index bf4604a5..7cf87565 100644 --- a/src/common/logging/vst3.h +++ b/src/common/logging/vst3.h @@ -114,6 +114,9 @@ class Vst3Logger { const YaProgramListData::GetProgramData&); bool log_request(bool is_host_vst, const YaProgramListData::SetProgramData&); + bool log_request(bool is_host_vst, const YaUnitData::UnitDataSupported&); + bool log_request(bool is_host_vst, const YaUnitData::GetUnitData&); + bool log_request(bool is_host_vst, const YaUnitData::SetUnitData&); bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitCount&); bool log_request(bool is_host_vst, const YaUnitInfo::GetUnitInfo&); bool log_request(bool is_host_vst, const YaUnitInfo::GetProgramListCount&); @@ -181,6 +184,7 @@ class Vst3Logger { void log_response(bool is_host_vst, const Configuration&); void log_response(bool is_host_vst, const YaProgramListData::GetProgramDataResponse&); + void log_response(bool is_host_vst, const YaUnitData::GetUnitDataResponse&); void log_response(bool is_host_vst, const YaUnitInfo::GetUnitInfoResponse&); void log_response(bool is_host_vst, const YaUnitInfo::GetProgramListInfoResponse&); diff --git a/src/common/serialization/vst3/plugin-proxy.h b/src/common/serialization/vst3/plugin-proxy.h index e988445d..05b3a489 100644 --- a/src/common/serialization/vst3/plugin-proxy.h +++ b/src/common/serialization/vst3/plugin-proxy.h @@ -26,6 +26,7 @@ #include "plugin/edit-controller.h" #include "plugin/plugin-base.h" #include "plugin/program-list-data.h" +#include "plugin/unit-data.h" #include "plugin/unit-info.h" #pragma GCC diagnostic push From 3553b080fe8c72f47f7bb4d9b8953964fe61422a Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 22:00:22 +0100 Subject: [PATCH 454/456] Implement IUnitData With this all VST 3.0.0 interfaces are finally supported. --- src/common/serialization/vst3.h | 3 +++ .../serialization/vst3/plugin-proxy.cpp | 6 +++++ src/common/serialization/vst3/plugin-proxy.h | 3 +++ .../bridges/vst3-impls/plugin-proxy.cpp | 25 +++++++++++++++++++ src/plugin/bridges/vst3-impls/plugin-proxy.h | 8 ++++++ src/wine-host/bridges/vst3.cpp | 21 ++++++++++++++++ src/wine-host/bridges/vst3.h | 1 + 7 files changed, 67 insertions(+) diff --git a/src/common/serialization/vst3.h b/src/common/serialization/vst3.h index 9ae4dec4..5d925107 100644 --- a/src/common/serialization/vst3.h +++ b/src/common/serialization/vst3.h @@ -103,6 +103,9 @@ using ControlRequest = std::variant @@ -100,6 +102,7 @@ class Vst3PluginProxy : public YaAudioProcessor, s.object(edit_controller_2_args); s.object(plugin_base_args); s.object(program_list_data_args); + s.object(unit_data_args); s.object(unit_info_args); } }; diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp index 9d21e1cf..e5aca967 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.cpp +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.cpp @@ -475,6 +475,31 @@ Vst3PluginProxyImpl::setProgramData(Steinberg::Vst::ProgramListID listId, .data = data}); } +tresult PLUGIN_API +Vst3PluginProxyImpl::unitDataSupported(Steinberg::Vst::UnitID unitId) { + return bridge.send_message(YaUnitData::UnitDataSupported{ + .instance_id = instance_id(), .unit_id = unitId}); +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::getUnitData(Steinberg::Vst::UnitID unitId, + Steinberg::IBStream* data) { + const GetUnitDataResponse response = + bridge.send_message(YaUnitData::GetUnitData{ + .instance_id = instance_id(), .unit_id = unitId}); + + assert(response.data.write_back(data) == Steinberg::kResultOk); + + return response.result; +} + +tresult PLUGIN_API +Vst3PluginProxyImpl::setUnitData(Steinberg::Vst::UnitID unitId, + Steinberg::IBStream* data) { + return bridge.send_message(YaUnitData::SetUnitData{ + .instance_id = instance_id(), .unit_id = unitId, .data = data}); +} + int32 PLUGIN_API Vst3PluginProxyImpl::getUnitCount() { return bridge.send_message( YaUnitInfo::GetUnitCount{.instance_id = instance_id()}); diff --git a/src/plugin/bridges/vst3-impls/plugin-proxy.h b/src/plugin/bridges/vst3-impls/plugin-proxy.h index dcd35d34..4a04db6c 100644 --- a/src/plugin/bridges/vst3-impls/plugin-proxy.h +++ b/src/plugin/bridges/vst3-impls/plugin-proxy.h @@ -134,6 +134,14 @@ class Vst3PluginProxyImpl : public Vst3PluginProxy { int32 programIndex, Steinberg::IBStream* data) override; + // From `IUnitData` + tresult PLUGIN_API + unitDataSupported(Steinberg::Vst::UnitID unitId) override; + tresult PLUGIN_API getUnitData(Steinberg::Vst::UnitID unitId, + Steinberg::IBStream* data) override; + tresult PLUGIN_API setUnitData(Steinberg::Vst::UnitID unitId, + Steinberg::IBStream* data) override; + // From `IUnitInfo` int32 PLUGIN_API getUnitCount() override; tresult PLUGIN_API diff --git a/src/wine-host/bridges/vst3.cpp b/src/wine-host/bridges/vst3.cpp index 2b59b904..8e89907d 100644 --- a/src/wine-host/bridges/vst3.cpp +++ b/src/wine-host/bridges/vst3.cpp @@ -36,6 +36,7 @@ InstanceInterfaces::InstanceInterfaces( edit_controller(object), edit_controller_2(object), plugin_base(object), + unit_data(object), program_list_data(object), unit_info(object) {} @@ -587,6 +588,26 @@ void Vst3Bridge::run() { .program_list_data->setProgramData( request.list_id, request.program_index, &request.data); }, + [&](const YaUnitData::UnitDataSupported& request) + -> YaUnitData::UnitDataSupported::Response { + return object_instances[request.instance_id] + .unit_data->unitDataSupported(request.unit_id); + }, + [&](const YaUnitData::GetUnitData& request) + -> YaUnitData::GetUnitData::Response { + VectorStream data{}; + const tresult result = + object_instances[request.instance_id] + .unit_data->getUnitData(request.unit_id, &data); + + return YaUnitData::GetUnitDataResponse{.result = result, + .data = std::move(data)}; + }, + [&](YaUnitData::SetUnitData& request) + -> YaUnitData::SetUnitData::Response { + return object_instances[request.instance_id] + .unit_data->setUnitData(request.unit_id, &request.data); + }, [&](const YaPluginFactory::Construct&) -> YaPluginFactory::Construct::Response { return YaPluginFactory::ConstructArgs( diff --git a/src/wine-host/bridges/vst3.h b/src/wine-host/bridges/vst3.h index f674b199..20ab77a0 100644 --- a/src/wine-host/bridges/vst3.h +++ b/src/wine-host/bridges/vst3.h @@ -115,6 +115,7 @@ struct InstanceInterfaces { Steinberg::FUnknownPtr edit_controller; Steinberg::FUnknownPtr edit_controller_2; Steinberg::FUnknownPtr plugin_base; + Steinberg::FUnknownPtr unit_data; Steinberg::FUnknownPtr program_list_data; Steinberg::FUnknownPtr unit_info; }; From fa4bd4bb24338bd22b7784bfff253d8cd01eed1e Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 22:02:29 +0100 Subject: [PATCH 455/456] Update the VST3 notice in the readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8fb374e8..ae1ef0ec 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ for VST2 plugins and quick startup times. Its modern concurrent architecture and focus on transparency allows yabridge to be both fast and highly compatible, while also staying easy to debug and maintain. -_VST3 support is scheduled for yabridge 3.0. At the moment it's only available on the master branch._ +_VST3 support is currently experimental and only available on the master branch. Yabridge 3.0 will ship with full VST3 support._ _See [this document](https://github.com/robbert-vdh/yabridge/blob/master/src/common/serialization/vst3/README.md) for all currently implemented interfaces._ ![yabridge screenshot](https://raw.githubusercontent.com/robbert-vdh/yabridge/master/screenshot.png) From 2b0fb8f954c1ff4b8d148f4e7555b3fe3f1edd4f Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 29 Dec 2020 22:09:09 +0100 Subject: [PATCH 456/456] Change wording in changelog --- CHANGELOG.md | 28 ++++++++++++++-------------- README.md | 10 +++++----- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a1dfa1c..66d4564f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,23 +13,23 @@ TODO: Add an updates screenshot with some fancy VST3-only plugins to the readme ### Added -- Yabridge 3.0 introduces the first ever Wine VST3 bridge, allowing you to use - Windows VST3 plugins in Linux VST3 hosts with full VST 3.7.1 compatibility. - Simply tell yabridgectl to look for plugins in +- Yabridge 3.0 introduces the first ever true Wine VST3 bridge, allowing you to + use Windows VST3 plugins in Linux VST3 hosts with full VST 3.7.1 + compatibility. Simply tell yabridgectl to look for plugins in `$HOME/.wine/drive_c/Program Files/Common Files/VST3`, run `yabridgectl sync`, and your VST3 compatible DAW will pick up the new plugins in `~/.vst3/yabridge` automatically. Even though this feature has been tested extensively with a variety of VST3 plugins and hosts, there's still a large - part of the VST 3.7.1 specification that none of the hosts or plugins we can + part of the VST 3.7.1 specification that none of the hosts or plugins we came across actually used, so please let me know if you run into any weird behaviour! - Added the `with-vst3` compile time option to control whether yabridge should be built with VST3 support. This is enabled by default. - Added an [option](https://github.com/robbert-vdh/yabridge#compatibility-options) to use - XEmbed instead of yabridge's normal window embedding method. Some plugins have - redrawing issues when using XEmbed and editor resizing won't work, so it's not - recommended to use it as a default. + Wine's XEmbed implementation instead of yabridge's normal window embedding + method. Some plugins have will have redrawing issues when using XEmbed or the + editor might not show up at all, so your mileage may very much vary. ### Changed @@ -41,19 +41,19 @@ TODO: Add an updates screenshot with some fancy VST3-only plugins to the readme double buffering. - VST2 editor idle events are now handled slightly differently. This should result in even more responsive GUIs and I have not come across any plugins - where this caused issues, but please let me know if it does break anything for - you. + where this change introduced issues, but please let me know if it does break + anything for you. - Changed part of the build process considering [this Wine bug](https://bugs.winehq.org/show_bug.cgi?id=49138). Building with Wine 5.7 and 5.8 required a change, but that change now breaks builds using Wine 6.0 - and up. We now detect which version of Wine is used to build with, and we then - apply the change conditionally to be able to support building with both older - and newer versions of Wine. + and up. The build process now detect which version of Wine is used to build + with, and it then applies the change conditionally to be able to support + building with both older and newer versions of Wine. ### Fixed -- Added a background to the editor window to get rid of artifacts that could - occur when the plugin or the host don't resize the window correctly. +- Added a background to the editor window to get rid of artifacts that would + occur if the plugin or the host didn't resize the window correctly. ### yabridgectl diff --git a/README.md b/README.md index ae1ef0ec..e13c339c 100644 --- a/README.md +++ b/README.md @@ -275,11 +275,11 @@ plugin._ #### Compatibility options -| Option | Values | Description | -| --------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `cache_time_info` | `{true,false}` | Compatibility option for plugins that call `audioMasterGetTime()` multiple times during a single processing cycle. With this option subsequent calls during a single audio processing cycle will reuse the value returned by the first call to this function. This is a bug in the plugin, and this option serves as a temporary workaround until the plugin fixes the issue. | -| `editor_double_embed` | `{true,false}` | Compatibility option for plugins that rely on the absolute screen coordinates of the window they're embedded in. Since the Wine window gets embedded inside of a window provided by your DAW, these coordinates won't match up and the plugin would end up drawing in the wrong location without this option. Currently the only known plugins that require this option are _PSPaudioware_ plugins with expandable GUIs, such as E27. Defaults to `false`. | -| `editor_xembed` | `{true,false}` | Use Wine's XEmbed implementation instead of yabridge's normal window embedding method. Some plugins will have redrawing issues when using XEmbed and editor resizing won't work properly with it, but it could be useful in certain setups. You may need to use [this Wine patch](https://github.com/psycha0s/airwave/blob/master/fix-xembed-wine-windows.patch) if you're getting blank editor windows. Defaults to `false`. _This option is only availble on the master branch._ | +| Option | Values | Description | +| --------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `cache_time_info` | `{true,false}` | Compatibility option for plugins that call `audioMasterGetTime()` multiple times during a single processing cycle. With this option subsequent calls during a single audio processing cycle will reuse the value returned by the first call to this function. This is a bug in the plugin, and this option serves as a temporary workaround until the plugin fixes the issue. | +| `editor_double_embed` | `{true,false}` | Compatibility option for plugins that rely on the absolute screen coordinates of the window they're embedded in. Since the Wine window gets embedded inside of a window provided by your DAW, these coordinates won't match up and the plugin would end up drawing in the wrong location without this option. Currently the only known plugins that require this option are _PSPaudioware_ plugins with expandable GUIs, such as E27. Defaults to `false`. | +| `editor_xembed` | `{true,false}` | Use Wine's XEmbed implementation instead of yabridge's normal window embedding method. Some plugins will have redrawing issues when using XEmbed and editor resizing won't always work properly with it, but it could be useful in certain setups. You may need to use [this Wine patch](https://github.com/psycha0s/airwave/blob/master/fix-xembed-wine-windows.patch) if you're getting blank editor windows. Defaults to `false`. _This option is only availble on the master branch._ | These options are workarounds for issues mentioned in the [known issues](#runtime-dependencies-and-known-issues) section. Depending on the hosts