mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 03:50:11 +02:00
Merge branch 'feature/vst3'
This commit is contained in:
@@ -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-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.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
|
||||
|
||||
+49
-3
@@ -8,14 +8,60 @@ 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: Add an updates screenshot with some fancy VST3-only plugins to the readme
|
||||
|
||||
### Added
|
||||
|
||||
- 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 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
|
||||
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
|
||||
|
||||
- `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 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 would
|
||||
occur if the plugin or the host didn't resize the window correctly.
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
@@ -4,16 +4,15 @@
|
||||
[](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 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.
|
||||
|
||||
VST3 support for yabridge is still very far removed from being in a usable
|
||||
state, but you can track the progress in the
|
||||
[feature/vst3](https://github.com/robbert-vdh/yabridge/tree/feature/vst3)
|
||||
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._
|
||||
|
||||

|
||||
|
||||
@@ -36,21 +35,24 @@ branch.
|
||||
- [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)
|
||||
|
||||
## 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
|
||||
- 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.
|
||||
|
||||
@@ -74,10 +76,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)
|
||||
|
||||
@@ -103,51 +103,61 @@ yabridge from source or if you installed the files to some other location, then
|
||||
you can use `yabridgectl set --path=<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.so` exists), and that you want
|
||||
to set up yabridge for the VST2 plugin called `~/.wine/drive_c/Program Files/Steinberg/VstPlugins/plugin.dll`.
|
||||
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,
|
||||
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
|
||||
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
|
||||
|
||||
@@ -158,6 +168,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
|
||||
@@ -169,8 +183,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
|
||||
@@ -253,12 +267,19 @@ 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 |
|
||||
| --------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `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`. |
|
||||
| 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
|
||||
@@ -266,18 +287,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"
|
||||
|
||||
@@ -292,6 +307,9 @@ group = "toneboosters"
|
||||
["PSPaudioware"]
|
||||
editor_double_embed = true
|
||||
|
||||
["Analog Lab 3.so"]
|
||||
editor_xembed = true
|
||||
|
||||
["SWAM Cello 64bit.so"]
|
||||
cache_time_info = true
|
||||
|
||||
@@ -307,6 +325,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
|
||||
@@ -389,7 +429,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.
|
||||
|
||||
@@ -421,15 +461,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
|
||||
@@ -515,9 +556,12 @@ 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)
|
||||
- Version 3.7.1 of the [VST3 SDK](https://github.com/robbert-vdh/vst3sdk) with
|
||||
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:
|
||||
|
||||
@@ -544,7 +588,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:
|
||||
|
||||
@@ -612,7 +656,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
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# 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
|
||||
`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
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
# VST3 serialization
|
||||
|
||||
TODO: Flesh this out further, update the instantiation part, make the proxying part clearer
|
||||
|
||||
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
|
||||
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<I>`) 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
|
||||
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 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 basic model works as follows:
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
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` (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<T>` 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<size_t, PluginObject>` 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<IPluginFactory>` 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 interface versions that were supported by the original
|
||||
`IPtr<IPluginFactory>` we are proxying.
|
||||
|
||||
## Interface Instantiation
|
||||
|
||||
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`
|
||||
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::Construct{cid}` to the Wine plugin host process.
|
||||
4. The Wine plugin host will then call
|
||||
`module->getFactory().createInstance<IFoo>(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 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<IFoo>` to an `std::map<size_t, IPtr<IFoo>>` 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.
|
||||
|
||||
## 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
|
||||
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. In `IPtr<T>`'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.
|
||||
+364
-102
@@ -2,7 +2,7 @@ project(
|
||||
'yabridge',
|
||||
'cpp',
|
||||
version : '2.2.1',
|
||||
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
|
||||
@@ -31,12 +31,13 @@ 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')
|
||||
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'
|
||||
@@ -48,6 +49,10 @@ if with_winedbg
|
||||
compiler_options += '-DWITH_WINEDBG'
|
||||
endif
|
||||
|
||||
if with_vst3
|
||||
compiler_options += '-DWITH_VST3'
|
||||
endif
|
||||
|
||||
# Wine versions after Wine 5.6 and before 6.0 require a `__cdecl` calling
|
||||
# convention to be specified on the `main()` functions or else `argc` and `argv`
|
||||
# will point to the wrong memory. Similarly, with other versions of Wine this
|
||||
@@ -66,63 +71,76 @@ endif
|
||||
# and the name of the host binary
|
||||
subdir('src/common/config')
|
||||
|
||||
# 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)
|
||||
boost_filesystem_dep = dependency(
|
||||
'boost',
|
||||
version : '>=1.66',
|
||||
modules : ['filesystem'],
|
||||
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')
|
||||
threads_dep = dependency('threads')
|
||||
tomlplusplus_dep = subproject('tomlplusplus', version : '2.1.0').get_variable('tomlplusplus_dep')
|
||||
# The built in threads dependency does not know how to handle winegcc
|
||||
wine_threads_dep = declare_dependency(link_args : '-lpthread')
|
||||
xcb_dep = dependency('xcb')
|
||||
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,
|
||||
]
|
||||
|
||||
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.
|
||||
|
||||
shared_library(
|
||||
'yabridge',
|
||||
[
|
||||
'src/common/configuration.cpp',
|
||||
'src/common/logging.cpp',
|
||||
'src/common/serialization.cpp',
|
||||
'src/common/communication.cpp',
|
||||
'src/common/utils.cpp',
|
||||
'src/plugin/host-process.cpp',
|
||||
'src/plugin/plugin.cpp',
|
||||
'src/plugin/plugin-bridge.cpp',
|
||||
'src/plugin/utils.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']
|
||||
)
|
||||
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/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',
|
||||
'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-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',
|
||||
'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/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',
|
||||
'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',
|
||||
'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/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',
|
||||
'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.cpp',
|
||||
'src/common/serialization.cpp',
|
||||
'src/common/communication.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',
|
||||
@@ -130,47 +148,266 @@ host_sources = [
|
||||
version_header,
|
||||
]
|
||||
|
||||
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',
|
||||
'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-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',
|
||||
'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/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',
|
||||
'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',
|
||||
'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',
|
||||
]
|
||||
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,
|
||||
native : false,
|
||||
# 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)
|
||||
boost_filesystem_dep = dependency(
|
||||
'boost',
|
||||
version : '>=1.66',
|
||||
modules : ['filesystem'],
|
||||
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')
|
||||
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')
|
||||
|
||||
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')
|
||||
|
||||
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
|
||||
# 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')
|
||||
|
||||
# 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'),
|
||||
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'),
|
||||
cpp_args : vst3_compiler_options,
|
||||
include_directories : vst3_include_dir,
|
||||
override_options : ['warning_level=0'],
|
||||
native : true,
|
||||
)
|
||||
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,
|
||||
compile_args : vst3_compiler_options,
|
||||
)
|
||||
|
||||
# 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 = [
|
||||
# Some stuff from `windows.h` results in conflicting definitions
|
||||
'-DNOMINMAX',
|
||||
'-DWINE_NOWINSOCK',
|
||||
]
|
||||
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_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_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_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_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
|
||||
# `windows.h`
|
||||
compile_args : vst3_compiler_options + vst3_wine_compiler_options,
|
||||
)
|
||||
|
||||
# And another time for the 32-bit version
|
||||
if with_bitbridge
|
||||
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_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_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_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_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
|
||||
# `windows.h`
|
||||
compile_args : vst3_compiler_options + vst3_wine_compiler_options,
|
||||
)
|
||||
endif
|
||||
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,
|
||||
function2_dep,
|
||||
threads_dep,
|
||||
tomlplusplus_dep,
|
||||
wine_threads_dep,
|
||||
xcb_dep
|
||||
],
|
||||
cpp_args : compiler_options + ['-m64'],
|
||||
link_args : ['-m64']
|
||||
cpp_args : compiler_options,
|
||||
link_args : ['-ldl']
|
||||
)
|
||||
|
||||
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']
|
||||
)
|
||||
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(
|
||||
'yabridge-vst3',
|
||||
vst3_plugin_sources,
|
||||
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'],
|
||||
)
|
||||
endif
|
||||
|
||||
host_64bit_deps = [
|
||||
boost_dep,
|
||||
boost_filesystem_dep,
|
||||
bitsery_dep,
|
||||
function2_dep,
|
||||
tomlplusplus_dep,
|
||||
wine_gdi32_dep,
|
||||
wine_threads_dep,
|
||||
xcb_dep,
|
||||
]
|
||||
if with_vst3
|
||||
host_64bit_deps += [
|
||||
vst3_sdk_hosting_wine_64bit_dep,
|
||||
wine_ole32_dep,
|
||||
wine_shell32_dep,
|
||||
wine_uuid_dep,
|
||||
]
|
||||
endif
|
||||
|
||||
if with_bitbridge
|
||||
message('Bitbridge enabled, configuring a 32-bit host application')
|
||||
@@ -181,7 +418,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_32bit_dep = winegcc.find_library(
|
||||
'boost_filesystem',
|
||||
static : with_static_boost,
|
||||
dirs : [
|
||||
@@ -198,24 +435,57 @@ if with_bitbridge
|
||||
'/usr/lib',
|
||||
]
|
||||
)
|
||||
xcb_dep = winegcc.find_library('xcb')
|
||||
xcb_32bit_dep = winegcc.find_library('xcb')
|
||||
|
||||
host_32bit_deps = [
|
||||
boost_dep,
|
||||
boost_filesystem_32bit_dep,
|
||||
bitsery_dep,
|
||||
function2_dep,
|
||||
tomlplusplus_dep,
|
||||
wine_gdi32_dep,
|
||||
wine_threads_dep,
|
||||
xcb_32bit_dep,
|
||||
]
|
||||
if with_vst3
|
||||
host_32bit_deps += [
|
||||
vst3_sdk_hosting_wine_32bit_dep,
|
||||
wine_ole32_dep,
|
||||
wine_shell32_dep,
|
||||
wine_uuid_dep,
|
||||
]
|
||||
endif
|
||||
endif
|
||||
|
||||
executable(
|
||||
individual_host_name_64bit,
|
||||
individual_host_sources,
|
||||
native : false,
|
||||
include_directories : include_dir,
|
||||
dependencies : host_64bit_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_64bit_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_32bit_deps,
|
||||
cpp_args : compiler_options + ['-m32'],
|
||||
link_args : ['-m32']
|
||||
link_args : ['-m32'],
|
||||
)
|
||||
|
||||
executable(
|
||||
@@ -223,16 +493,8 @@ 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_32bit_deps,
|
||||
cpp_args : compiler_options + ['-m32'],
|
||||
link_args : ['-m32']
|
||||
link_args : ['-m32'],
|
||||
)
|
||||
endif
|
||||
|
||||
@@ -12,6 +12,20 @@ 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',
|
||||
value : true,
|
||||
description : 'Whether to build the VST3 version of yabridge. This requires CMake to be installed.'
|
||||
)
|
||||
|
||||
option(
|
||||
'with-winedbg',
|
||||
type : 'boolean',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginterfaces/base/funknown.h>
|
||||
|
||||
#include <bitsery/details/serialization_common.h>
|
||||
#include <bitsery/traits/core/traits.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
/**
|
||||
* An adapter for serializing and deserializing `Steinberg::FUID`s.
|
||||
*/
|
||||
class FUID {
|
||||
public:
|
||||
template <typename Ser, typename Fnc>
|
||||
void serialize(Ser& ser, const Steinberg::FUID& uid, Fnc&&) const {
|
||||
Steinberg::FUID::String uid_str;
|
||||
uid.toString(uid_str);
|
||||
|
||||
ser.text1b(uid_str);
|
||||
}
|
||||
|
||||
template <typename Des, typename Fnc>
|
||||
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<ext::FUID, Steinberg::FUID> {
|
||||
using TValue = void;
|
||||
static constexpr bool SupportValueOverload = false;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
} // namespace traits
|
||||
} // namespace bitsery
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,769 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
|
||||
#ifdef __WINE__
|
||||
#include "../wine-host/boost-fix.h"
|
||||
#endif
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/local/stream_protocol.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "../logging/common.h"
|
||||
|
||||
template <typename B>
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<B>;
|
||||
|
||||
template <typename B>
|
||||
using InputAdapter = bitsery::InputBufferAdapter<B>;
|
||||
|
||||
/**
|
||||
* 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 <typename T, typename Socket>
|
||||
inline void write_object(Socket& socket,
|
||||
const T& object,
|
||||
std::vector<uint8_t>& buffer) {
|
||||
const size_t size =
|
||||
bitsery::quickSerialization<OutputAdapter<std::vector<uint8_t>>>(
|
||||
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<uint64_t, 1>{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 <typename T, typename Socket>
|
||||
inline void write_object(Socket& socket, const T& object) {
|
||||
std::vector<uint8_t> 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.
|
||||
* @param object The object to serialize into. There are also overrides that
|
||||
* create a new default initialized `T`
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* TODO: Swap these arguments around so they match `write_object`'s argument
|
||||
* order
|
||||
*
|
||||
* @relates write_object
|
||||
*/
|
||||
template <typename T, typename Socket>
|
||||
inline T& read_object(Socket& socket, std::vector<uint8_t>& buffer, T& object) {
|
||||
// See the note above on the use of `uint64_t` instead of `size_t`
|
||||
std::array<uint64_t, 1> message_length;
|
||||
boost::asio::read(socket, boost::asio::buffer(message_length),
|
||||
boost::asio::transfer_exactly(sizeof(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
|
||||
boost::asio::read(socket, boost::asio::buffer(buffer),
|
||||
boost::asio::transfer_exactly(size));
|
||||
|
||||
auto [_, success] =
|
||||
bitsery::quickDeserialization<InputAdapter<std::vector<uint8_t>>>(
|
||||
{buffer.begin(), size}, object);
|
||||
|
||||
if (BOOST_UNLIKELY(!success)) {
|
||||
throw std::runtime_error("Deserialization failure in call: " +
|
||||
std::string(__PRETTY_FUNCTION__));
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* `read_object()` into a new default initialized object with an existing
|
||||
* buffer.
|
||||
*
|
||||
* @overload
|
||||
*/
|
||||
template <typename T, typename Socket>
|
||||
inline T read_object(Socket& socket, std::vector<uint8_t>& buffer) {
|
||||
T object;
|
||||
read_object<T>(socket, buffer, object);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* `read_object()` into an existing object a small default
|
||||
* buffer for convenience.
|
||||
*
|
||||
* @overload
|
||||
*/
|
||||
template <typename T, typename Socket>
|
||||
inline T& read_object(Socket& socket, T& object) {
|
||||
std::vector<uint8_t> buffer(64);
|
||||
return read_object<T>(socket, buffer, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* `read_object()` into a new default initialized object with a small default
|
||||
* buffer for convenience.
|
||||
*
|
||||
* @overload
|
||||
*/
|
||||
template <typename T, typename Socket>
|
||||
inline T read_object(Socket& socket) {
|
||||
T object;
|
||||
std::vector<uint8_t> buffer(64);
|
||||
read_object<T>(socket, buffer, object);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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/<uid>/yabridge-<plugin_name>-<random_id>/`.
|
||||
*
|
||||
* 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/<uid>/yabridge-<plugin_name>-<random_id>/`.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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. The caller is responsible for preventing
|
||||
* this.
|
||||
*
|
||||
* @see write_object
|
||||
* @see SocketHandler::receive_single
|
||||
* @see SocketHandler::receive_multi
|
||||
*/
|
||||
template <typename T>
|
||||
inline void send(const T& object, std::vector<uint8_t>& buffer) {
|
||||
write_object(socket, object, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* `SocketHandler::send()` with a small default buffer for convenience.
|
||||
*
|
||||
* @overload
|
||||
*/
|
||||
template <typename T>
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
* @see SocketHandler::receive_multi
|
||||
*/
|
||||
template <typename T>
|
||||
inline T receive_single(std::vector<uint8_t>& buffer) {
|
||||
return read_object<T>(socket, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* `SocketHandler::receive_single()` with a small default buffer for
|
||||
* convenience.
|
||||
*
|
||||
* @overload
|
||||
*/
|
||||
template <typename T>
|
||||
inline T receive_single() {
|
||||
return read_object<T>(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<uint8_t>&)`
|
||||
* 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 <typename T, typename F>
|
||||
void receive_multi(F callback) {
|
||||
std::vector<uint8_t> buffer{};
|
||||
while (true) {
|
||||
try {
|
||||
auto object = receive_single<T>(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<boost::asio::local::stream_protocol::acceptor> 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 <typename Thread>
|
||||
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 T The return value of F.
|
||||
* @tparam F A function in the form of
|
||||
* `T(boost::asio::local::stream_protocol::socket&)`.
|
||||
*/
|
||||
template <typename T, typename F>
|
||||
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()) {
|
||||
// This was used to always block when sending the first message,
|
||||
// because the other side may not be listening for additional
|
||||
// connections yet
|
||||
auto result = callback(socket);
|
||||
sent_first_event = true;
|
||||
|
||||
return result;
|
||||
} else {
|
||||
try {
|
||||
boost::asio::local::stream_protocol::socket secondary_socket(
|
||||
io_context);
|
||||
secondary_socket.connect(endpoint);
|
||||
|
||||
return callback(secondary_socket);
|
||||
} catch (const boost::system::system_error& e) {
|
||||
// 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.
|
||||
// Note that this should **only** be done before the call to
|
||||
// `connect()`. If we get here at any other point then it
|
||||
// means that the plugin side is no longer listening on the
|
||||
// sockets, and we should thus just exit.
|
||||
if (!sent_first_event) {
|
||||
std::lock_guard lock(write_mutex);
|
||||
|
||||
auto result = callback(socket);
|
||||
sent_first_event = true;
|
||||
|
||||
return result;
|
||||
} else {
|
||||
// Rethrow the exception if the sockets we're not
|
||||
// handling the specific case described above
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
* @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.
|
||||
*
|
||||
* @tparam F A function type in the form of
|
||||
* `void(boost::asio::local::stream_protocol::socket&)`.
|
||||
* @tparam G The same as `F`.
|
||||
*/
|
||||
template <typename F, typename G>
|
||||
void receive_multi(std::optional<std::reference_wrapper<Logger>> 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{};
|
||||
|
||||
// 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<size_t, Thread> 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);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* The same as the above, but with a single callback for incoming
|
||||
* connections on the primary socket and on secondary sockets.
|
||||
*
|
||||
* @overload
|
||||
*/
|
||||
template <typename F>
|
||||
void receive_multi(std::optional<std::reference_wrapper<Logger>> logger,
|
||||
F callback) {
|
||||
receive_multi(logger, callback, callback);
|
||||
}
|
||||
|
||||
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 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
|
||||
* `void(boost::asio::local::stream_protocol::socket)` to handle a new
|
||||
* incoming connection.
|
||||
*/
|
||||
template <typename F>
|
||||
void accept_requests(
|
||||
boost::asio::local::stream_protocol::acceptor& acceptor,
|
||||
std::optional<std::reference_wrapper<Logger>> logger,
|
||||
F callback) {
|
||||
acceptor.async_accept(
|
||||
[&, logger, 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 (logger) {
|
||||
logger->get().log(
|
||||
"Failure while accepting connections: " +
|
||||
error.message());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
callback(std::move(secondary_socket));
|
||||
|
||||
accept_requests(acceptor, logger, 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<boost::asio::local::stream_protocol::acceptor> 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;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the remove has processed an event we sent from
|
||||
* this side. When a Windows VST2 plugin performs a host callback in its
|
||||
* constructor, before the native plugin has had time to connect to the
|
||||
* sockets, we want it to always wait for the sockets to come online, but
|
||||
* this fallback behaviour should only happen during initialization.
|
||||
*/
|
||||
std::atomic_bool sent_first_event = false;
|
||||
};
|
||||
@@ -14,19 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "communication.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
/**
|
||||
* Used for generating random identifiers.
|
||||
*/
|
||||
constexpr char alphanumeric_characters[] =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
#include "vst2.h"
|
||||
|
||||
EventPayload DefaultDataConverter::read(const int /*opcode*/,
|
||||
const int /*index*/,
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "../logging/vst2.h"
|
||||
#include "../serialization/vst2.h"
|
||||
#include "common.h"
|
||||
|
||||
/**
|
||||
* Encodes the base behavior for reading from and writing to the `data` argument
|
||||
* for event dispatch functions. This provides base functionality for these
|
||||
* kinds of events. The `dispatch()` function will require some more specific
|
||||
* structs.
|
||||
*/
|
||||
class DefaultDataConverter {
|
||||
public:
|
||||
virtual ~DefaultDataConverter(){};
|
||||
|
||||
/**
|
||||
* Read data from the `data` void pointer into a an `EventPayload` value
|
||||
* that can be serialized and conveys the meaning of the event.
|
||||
*/
|
||||
virtual EventPayload read(const int opcode,
|
||||
const int index,
|
||||
const intptr_t value,
|
||||
const void* data) const;
|
||||
|
||||
/**
|
||||
* Read data from the `value` pointer into a an `EventPayload` value that
|
||||
* can be serialized and conveys the meaning of the event. This is only used
|
||||
* for the `effSetSpeakerArrangement` and `effGetSpeakerArrangement` events.
|
||||
*/
|
||||
virtual std::optional<EventPayload> read_value(const int opcode,
|
||||
const intptr_t value) const;
|
||||
|
||||
/**
|
||||
* Write the reponse back to the `data` pointer.
|
||||
*/
|
||||
virtual void write(const int opcode,
|
||||
void* data,
|
||||
const EventResult& response) const;
|
||||
|
||||
/**
|
||||
* Write the reponse back to the `value` pointer. This is only used during
|
||||
* the `effGetSpeakerArrangement` event.
|
||||
*/
|
||||
virtual void write_value(const int opcode,
|
||||
intptr_t value,
|
||||
const EventResult& response) const;
|
||||
|
||||
/**
|
||||
* This function can override a callback's return value based on the opcode.
|
||||
* This is used in one place to return a pointer to a `VstTime` object
|
||||
* that's contantly being updated.
|
||||
*
|
||||
* @param opcode The opcode for the current event.
|
||||
* @param original The original return value as returned by the callback
|
||||
* function.
|
||||
*/
|
||||
virtual intptr_t return_value(const int opcode,
|
||||
const intptr_t original) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* `audioMaster()` calls are different. Not only do they have they come with
|
||||
* complex payload values, they can also be called simultaneously from multiple
|
||||
* threads, and `audioMaster()` and `dispatch()` calls can even be mutually
|
||||
* recursive. Luckily this does not happen very often, but it does mean that our
|
||||
* simple 'one-socket-per-function' model doesn't work anymore. Because setting
|
||||
* up new sockets is quite expensive and this is seldom needed, this works
|
||||
* slightly differently:
|
||||
*
|
||||
* - We'll keep a single long lived socket connection. This works the exact same
|
||||
* 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.
|
||||
*
|
||||
* The `EventHandler::send_event()` method is used to send events. If the socket
|
||||
* is currently being written to, we'll first create a new socket connection as
|
||||
* described above. Similarly, the `EventHandler::receive_events()` method first
|
||||
* sets up asynchronous listeners for the socket endpoint, and then block and
|
||||
* handle events until the main socket is closed.
|
||||
*
|
||||
* @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 <typename Thread>
|
||||
class EventHandler : public AdHocSocketHandler<Thread> {
|
||||
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
|
||||
*/
|
||||
EventHandler(boost::asio::io_context& io_context,
|
||||
boost::asio::local::stream_protocol::endpoint endpoint,
|
||||
bool listen)
|
||||
: AdHocSocketHandler<Thread>(io_context, endpoint, listen) {}
|
||||
|
||||
/**
|
||||
* 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 data_converter Some struct that knows how to read data from and
|
||||
* write data back to the `data` void pointer. For host callbacks this
|
||||
* parameter contains either a string or a null pointer while `dispatch()`
|
||||
* calls might contain opcode specific structs. See the documentation for
|
||||
* `EventPayload` for more information. The `DefaultDataConverter` defined
|
||||
* above handles the basic behavior that's sufficient for host callbacks.
|
||||
* @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.
|
||||
*
|
||||
* @relates EventHandler::receive_events
|
||||
* @relates passthrough_event
|
||||
*/
|
||||
template <typename D>
|
||||
intptr_t send_event(D& data_converter,
|
||||
std::optional<std::pair<Vst2Logger&, bool>> logging,
|
||||
int opcode,
|
||||
int index,
|
||||
intptr_t value,
|
||||
void* data,
|
||||
float option) {
|
||||
// Encode the right payload types for this event. Check the
|
||||
// documentation for `EventPayload` for more information. These types
|
||||
// are converted to C-style data structures in `passthrough_event()` so
|
||||
// they can be passed to a plugin or callback function.
|
||||
const EventPayload payload =
|
||||
data_converter.read(opcode, index, value, data);
|
||||
const std::optional<EventPayload> value_payload =
|
||||
data_converter.read_value(opcode, value);
|
||||
|
||||
if (logging) {
|
||||
auto [logger, is_dispatch] = *logging;
|
||||
logger.log_event(is_dispatch, opcode, index, value, payload, option,
|
||||
value_payload);
|
||||
}
|
||||
|
||||
const Event event{.opcode = opcode,
|
||||
.index = index,
|
||||
.value = value,
|
||||
.option = option,
|
||||
.payload = payload,
|
||||
.value_payload = 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.
|
||||
EventResult response = this->template send<EventResult>(
|
||||
[&](boost::asio::local::stream_protocol::socket& socket) {
|
||||
write_object(socket, event);
|
||||
return read_object<EventResult>(socket);
|
||||
});
|
||||
|
||||
if (logging) {
|
||||
auto [logger, is_dispatch] = *logging;
|
||||
logger.log_event_response(is_dispatch, opcode,
|
||||
response.return_value, response.payload,
|
||||
response.value_payload);
|
||||
}
|
||||
|
||||
data_converter.write(opcode, data, response);
|
||||
data_converter.write_value(opcode, value, response);
|
||||
|
||||
return data_converter.return_value(opcode, response.return_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn a new thread to listen for extra connections to `endpoint`, and
|
||||
* 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
|
||||
* `EventPayload` into the format used by VST2, calls either `dispatch()` or
|
||||
* `audioMaster()` depending on the context, and then serializes the result
|
||||
* back into an `EventResultPayload`.
|
||||
*
|
||||
* @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 The function used to generate a response out of an event.
|
||||
* See the definition of `F` for more information.
|
||||
*
|
||||
* @tparam F A function type in the form of `EventResponse(Event, bool)`.
|
||||
* The boolean flag is `true` when this event was received on the main
|
||||
* socket, and `false` otherwise.
|
||||
*
|
||||
* @relates EventHandler::send_event
|
||||
* @relates passthrough_event
|
||||
*/
|
||||
template <typename F>
|
||||
void receive_events(std::optional<std::pair<Vst2Logger&, bool>> 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
|
||||
const auto process_event =
|
||||
[&](boost::asio::local::stream_protocol::socket& socket,
|
||||
bool on_main_thread) {
|
||||
auto event = read_object<Event>(socket);
|
||||
if (logging) {
|
||||
auto [logger, is_dispatch] = *logging;
|
||||
logger.log_event(is_dispatch, event.opcode, event.index,
|
||||
event.value, event.payload, event.option,
|
||||
event.value_payload);
|
||||
}
|
||||
|
||||
EventResult response = callback(event, on_main_thread);
|
||||
if (logging) {
|
||||
auto [logger, is_dispatch] = *logging;
|
||||
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.logger))
|
||||
: std::nullopt,
|
||||
[&](boost::asio::local::stream_protocol::socket& socket) {
|
||||
process_event(socket, true);
|
||||
},
|
||||
[&](boost::asio::local::stream_protocol::socket& socket) {
|
||||
process_event(socket, false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages all the sockets used for communicating between the plugin and the
|
||||
* 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
|
||||
* 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 <typename Thread>
|
||||
class Vst2Sockets : 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 Vst2Sockets::connect
|
||||
*/
|
||||
Vst2Sockets(boost::asio::io_context& io_context,
|
||||
const boost::filesystem::path& endpoint_base_dir,
|
||||
bool listen)
|
||||
: Sockets(endpoint_base_dir),
|
||||
host_vst_dispatch(io_context,
|
||||
(base_dir / "host_vst_dispatch.sock").string(),
|
||||
listen),
|
||||
vst_host_callback(io_context,
|
||||
(base_dir / "vst_host_callback.sock").string(),
|
||||
listen),
|
||||
host_vst_parameters(io_context,
|
||||
(base_dir / "host_vst_parameters.sock").string(),
|
||||
listen),
|
||||
host_vst_process_replacing(
|
||||
io_context,
|
||||
(base_dir / "host_vst_process_replacing.sock").string(),
|
||||
listen),
|
||||
host_vst_control(io_context,
|
||||
(base_dir / "host_vst_control.sock").string(),
|
||||
listen) {}
|
||||
|
||||
~Vst2Sockets() { close(); }
|
||||
|
||||
void connect() override {
|
||||
host_vst_dispatch.connect();
|
||||
vst_host_callback.connect();
|
||||
host_vst_parameters.connect();
|
||||
host_vst_process_replacing.connect();
|
||||
host_vst_control.connect();
|
||||
}
|
||||
|
||||
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 `<from>_<to>_<event>`. For
|
||||
// instance the socket named `host_vst_dispatch` forwards
|
||||
// `AEffect.dispatch()` calls from the native VST host to the Windows VST
|
||||
// plugin (through the Wine VST host).
|
||||
|
||||
/**
|
||||
* The socket that forwards all `dispatcher()` calls from the VST host to
|
||||
* the plugin.
|
||||
*/
|
||||
EventHandler<Thread> host_vst_dispatch;
|
||||
/**
|
||||
* The socket that forwards all `audioMaster()` calls from the Windows VST
|
||||
* plugin to the host.
|
||||
*/
|
||||
EventHandler<Thread> vst_host_callback;
|
||||
/**
|
||||
* Used for both `getParameter` and `setParameter` since they mostly
|
||||
* overlap.
|
||||
*/
|
||||
SocketHandler host_vst_parameters;
|
||||
/**
|
||||
* Used for processing audio usign the `process()`, `processReplacing()` and
|
||||
* `processDoubleReplacing()` functions.
|
||||
*/
|
||||
SocketHandler host_vst_process_replacing;
|
||||
/**
|
||||
* A control socket that sends data that is not suitable for the other
|
||||
* sockets. At the moment this is only used to, on startup, send the Windows
|
||||
* VST plugin's `AEffect` object to the native VST plugin, and to then send
|
||||
* the configuration (from `config`) back to the Wine host.
|
||||
*/
|
||||
SocketHandler host_vst_control;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unmarshall an `EventPayload` back to the representation used by VST2, pass
|
||||
* that value to a callback function (either `AEffect::dispatcher()` for host ->
|
||||
* plugin events or `audioMaster()` for plugin -> host events), and then
|
||||
* serialize the results back into an `EventResult`.
|
||||
*
|
||||
* This is the receiving analogue of the `*DataCovnerter` objects.
|
||||
*
|
||||
* @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 instance.
|
||||
* @param callback The function to call with the arguments received from the
|
||||
* socket, either `AEffect::dispatcher()` or `audioMasterCallback()`.
|
||||
*
|
||||
* @tparam F A function with the same signature as `AEffect::dispatcher` or
|
||||
* `audioMasterCallback`.
|
||||
*
|
||||
* @return The result of the operation. If necessary the `DataConverter` will
|
||||
* unmarshall the payload again and write it back.
|
||||
*
|
||||
* @relates EventHandler::receive_events
|
||||
*/
|
||||
template <typename F>
|
||||
EventResult passthrough_event(AEffect* plugin, F callback, Event& event) {
|
||||
// This buffer is used to write strings and small objects to. We'll
|
||||
// initialize the beginning with null values to both prevent it from being
|
||||
// read as some arbitrary C-style string, and to make sure that
|
||||
// `*static_cast<void**>(string_buffer.data)` will be a null pointer if the
|
||||
// plugin is supposed to write a pointer there but doesn't (such as with
|
||||
// `effEditGetRect`/`WantsVstRect`).
|
||||
std::array<char, max_string_length> string_buffer;
|
||||
std::fill(string_buffer.begin(), string_buffer.begin() + sizeof(size_t), 0);
|
||||
|
||||
auto read_payload_fn = overload{
|
||||
[&](const std::nullptr_t&) -> void* { return nullptr; },
|
||||
[&](const std::string& s) -> void* {
|
||||
return const_cast<char*>(s.c_str());
|
||||
},
|
||||
[&](const ChunkData& chunk) -> void* {
|
||||
return const_cast<uint8_t*>(chunk.buffer.data());
|
||||
},
|
||||
[&](native_size_t& window_handle) -> void* {
|
||||
// This is the X11 window handle that the editor should reparent
|
||||
// itself to. We have a special wrapper around the dispatch function
|
||||
// that intercepts `effEditOpen` events and creates a Win32 window
|
||||
// and then finally embeds the X11 window Wine created into this
|
||||
// wnidow handle. Make sure to convert the window ID first to
|
||||
// `size_t` in case this is the 32-bit host.
|
||||
return reinterpret_cast<void*>(static_cast<size_t>(window_handle));
|
||||
},
|
||||
[&](const AEffect&) -> void* { return nullptr; },
|
||||
[&](DynamicVstEvents& events) -> void* {
|
||||
return &events.as_c_events();
|
||||
},
|
||||
[&](DynamicSpeakerArrangement& speaker_arrangement) -> void* {
|
||||
return &speaker_arrangement.as_c_speaker_arrangement();
|
||||
},
|
||||
[&](WantsAEffectUpdate&) -> void* {
|
||||
// The host will never actually ask for an updated `AEffect` object
|
||||
// since that should not be a thing. This is purely a meant as a
|
||||
// workaround for plugins that initialize their `AEffect` object
|
||||
// after the plugin has already finished initializing.
|
||||
return nullptr;
|
||||
},
|
||||
[&](WantsChunkBuffer&) -> void* { return string_buffer.data(); },
|
||||
[&](VstIOProperties& props) -> void* { return &props; },
|
||||
[&](VstMidiKeyName& key_name) -> void* { return &key_name; },
|
||||
[&](VstParameterProperties& props) -> void* { return &props; },
|
||||
[&](WantsVstRect&) -> void* { return string_buffer.data(); },
|
||||
[&](const WantsVstTimeInfo&) -> void* { return nullptr; },
|
||||
[&](WantsString&) -> void* { return string_buffer.data(); }};
|
||||
|
||||
// Almost all events pass data through the `data` argument. There are two
|
||||
// events, `effSetParameter` and `effGetParameter` that also pass data
|
||||
// through the value argument.
|
||||
void* data = std::visit(read_payload_fn, event.payload);
|
||||
intptr_t value = event.value;
|
||||
if (event.value_payload) {
|
||||
value = reinterpret_cast<intptr_t>(
|
||||
std::visit(read_payload_fn, *event.value_payload));
|
||||
}
|
||||
|
||||
const intptr_t return_value =
|
||||
callback(plugin, event.opcode, event.index, value, data, event.option);
|
||||
|
||||
// Only write back data when needed, this depends on the event payload type
|
||||
auto write_payload_fn = overload{
|
||||
[&](auto) -> EventResultPayload { return nullptr; },
|
||||
[&](const AEffect& updated_plugin) -> EventResultPayload {
|
||||
// This is a bit of a special case! Instead of writing some return
|
||||
// value, we will update values on the native VST plugin's `AEffect`
|
||||
// object. This is triggered by the `audioMasterIOChanged` callback
|
||||
// from the hosted VST plugin.
|
||||
update_aeffect(*plugin, updated_plugin);
|
||||
|
||||
return nullptr;
|
||||
},
|
||||
[&](DynamicSpeakerArrangement& speaker_arrangement)
|
||||
-> EventResultPayload { return speaker_arrangement; },
|
||||
[&](WantsAEffectUpdate&) -> EventResultPayload { return *plugin; },
|
||||
[&](WantsChunkBuffer&) -> EventResultPayload {
|
||||
// In this case the plugin will have written its data stored in an
|
||||
// array to which a pointer is stored in `data`, with the return
|
||||
// value from the event determines how much data the plugin has
|
||||
// written
|
||||
const uint8_t* chunk_data = *static_cast<uint8_t**>(data);
|
||||
return ChunkData{
|
||||
std::vector<uint8_t>(chunk_data, chunk_data + return_value)};
|
||||
},
|
||||
[&](WantsVstRect&) -> EventResultPayload {
|
||||
// The plugin should have written a pointer to a VstRect struct into
|
||||
// the data pointer. I haven't seen this fail yet, but since some
|
||||
// hosts will call `effEditGetRect()` before `effEditOpen()` I can
|
||||
// assume there are plugins that don't handle this correctly.
|
||||
VstRect* editor_rect = *static_cast<VstRect**>(data);
|
||||
if (!editor_rect) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return *editor_rect;
|
||||
},
|
||||
[&](WantsVstTimeInfo&) -> EventResultPayload {
|
||||
// Not sure why the VST API has twenty different ways of
|
||||
// returning structs, but in this case the value returned from
|
||||
// the callback function is actually a pointer to a
|
||||
// `VstTimeInfo` struct! It can also be a null pointer if the
|
||||
// host doesn't support this.
|
||||
const auto time_info =
|
||||
reinterpret_cast<const VstTimeInfo*>(return_value);
|
||||
if (!time_info) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return *time_info;
|
||||
}
|
||||
},
|
||||
[&](WantsString&) -> EventResultPayload {
|
||||
return std::string(static_cast<char*>(data));
|
||||
},
|
||||
[&](VstIOProperties& props) -> EventResultPayload { return props; },
|
||||
[&](VstMidiKeyName& key_name) -> EventResultPayload {
|
||||
return key_name;
|
||||
},
|
||||
[&](VstParameterProperties& props) -> EventResultPayload {
|
||||
return props;
|
||||
}};
|
||||
|
||||
// As mentioned about, the `effSetSpeakerArrangement` and
|
||||
// `effGetSpeakerArrangement` events are the only two events that use the
|
||||
// value argument as a pointer to write data to. Additionally, the
|
||||
// `effGetSpeakerArrangement` expects the plugin to write its own data to
|
||||
// this value. Hence why we need to encode the response here separately.
|
||||
const EventResultPayload response_data =
|
||||
std::visit(write_payload_fn, event.payload);
|
||||
std::optional<EventResultPayload> value_response_data = std::nullopt;
|
||||
if (event.value_payload) {
|
||||
value_response_data =
|
||||
std::visit(write_payload_fn, *event.value_payload);
|
||||
}
|
||||
|
||||
EventResult response{.return_value = return_value,
|
||||
.payload = response_data,
|
||||
.value_payload = value_response_data};
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <future>
|
||||
#include <variant>
|
||||
|
||||
#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`.
|
||||
*/
|
||||
template <typename Thread, typename Request>
|
||||
class Vst3MessageHandler : public AdHocSocketHandler<Thread> {
|
||||
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<Thread>(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 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
|
||||
* -> 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>
|
||||
typename T::Response send_message(
|
||||
const T& object,
|
||||
std::optional<std::pair<Vst3Logger&, bool>> logging,
|
||||
std::vector<uint8_t>& 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>
|
||||
typename T::Response send_message(
|
||||
const T& object,
|
||||
std::optional<std::pair<Vst3Logger&, bool>> logging) {
|
||||
typename T::Response response_object;
|
||||
receive_into(object, response_object, logging);
|
||||
|
||||
return response_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* `Vst3MessageHandler::send_message()`, but deserializing the response into
|
||||
* an existing object.
|
||||
*
|
||||
* @param response_object The object to deserialize into.
|
||||
*
|
||||
* @overload Vst3MessageHandler::send_message
|
||||
*/
|
||||
template <typename T>
|
||||
typename T::Response& receive_into(
|
||||
const T& object,
|
||||
typename T::Response& response_object,
|
||||
std::optional<std::pair<Vst3Logger&, bool>> logging,
|
||||
std::vector<uint8_t>& buffer) {
|
||||
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;
|
||||
should_log_response = logger.log_request(is_host_vst, object);
|
||||
}
|
||||
|
||||
// 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.
|
||||
this->template send<std::monostate>(
|
||||
[&](boost::asio::local::stream_protocol::socket& socket) {
|
||||
write_object(socket, Request(object), buffer);
|
||||
read_object<TResponse>(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
|
||||
return std::monostate{};
|
||||
});
|
||||
|
||||
if (should_log_response) {
|
||||
auto [logger, is_host_vst] = *logging;
|
||||
logger.log_response(!is_host_vst, response_object);
|
||||
}
|
||||
|
||||
return response_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* The same function as above, but with a small default buffer.
|
||||
*
|
||||
* @overload
|
||||
*/
|
||||
template <typename T>
|
||||
typename T::Response& receive_into(
|
||||
const T& object,
|
||||
typename T::Response& response_object,
|
||||
std::optional<std::pair<Vst3Logger&, bool>> logging) {
|
||||
std::vector<uint8_t> 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
|
||||
* `socket`.
|
||||
*
|
||||
* The specified function receives a `Request` variant object containing an
|
||||
* 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,
|
||||
* 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 `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 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 <bool persistent_buffers = false, typename F>
|
||||
void receive_messages(std::optional<std::pair<Vst3Logger&, bool>> logging,
|
||||
F callback) {
|
||||
thread_local std::vector<uint8_t> 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 =
|
||||
persistent_buffers
|
||||
? read_object<Request>(socket, persistent_buffer)
|
||||
: read_object<Request>(socket);
|
||||
|
||||
// See the comment in `receive_into()` for more information
|
||||
bool should_log_response = false;
|
||||
if (logging) {
|
||||
should_log_response = std::visit(
|
||||
[&](const auto& object) {
|
||||
auto [logger, is_host_vst] = *logging;
|
||||
return logger.log_request(is_host_vst, object);
|
||||
},
|
||||
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.
|
||||
std::visit(
|
||||
[&]<typename T>(T object) {
|
||||
typename T::Response response = callback(object);
|
||||
|
||||
if (should_log_response) {
|
||||
auto [logger, is_host_vst] = *logging;
|
||||
logger.log_response(!is_host_vst, response);
|
||||
}
|
||||
|
||||
if constexpr (persistent_buffers) {
|
||||
write_object(socket, response, persistent_buffer);
|
||||
} else {
|
||||
write_object(socket, response);
|
||||
}
|
||||
},
|
||||
request);
|
||||
};
|
||||
|
||||
this->receive_multi(logging
|
||||
? std::optional(std::ref(logging->first.logger))
|
||||
: std::nullopt,
|
||||
process_message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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`.
|
||||
*/
|
||||
template <typename Thread>
|
||||
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),
|
||||
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),
|
||||
io_context(io_context) {}
|
||||
|
||||
~Vst3Sockets() { close(); }
|
||||
|
||||
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();
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_buffers.try_emplace(instance_id);
|
||||
|
||||
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 <typename F>
|
||||
void add_audio_processor_and_listen(
|
||||
size_t instance_id,
|
||||
std::promise<void>& 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);
|
||||
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)
|
||||
.template receive_messages<true>(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);
|
||||
audio_processor_buffers.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. These calls also always reuse buffers to
|
||||
* minimize allocations.
|
||||
*
|
||||
* @tparam T Some object in the `AudioProcessorRequest` variant.
|
||||
*/
|
||||
template <typename T>
|
||||
typename T::Response send_audio_processor_message(
|
||||
const T& object,
|
||||
std::optional<std::pair<Vst3Logger&, bool>> logging) {
|
||||
return audio_processor_sockets.at(object.instance_id)
|
||||
.send_message(object, logging,
|
||||
audio_processor_buffers.at(object.instance_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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`. 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()`.
|
||||
*/
|
||||
Vst3MessageHandler<Thread, ControlRequest> 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<Thread, CallbackRequest> 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<size_t, Vst3MessageHandler<Thread, AudioProcessorRequest>>
|
||||
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<size_t, std::vector<uint8_t>> audio_processor_buffers;
|
||||
std::mutex audio_processor_sockets_mutex;
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
@@ -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,10 +143,11 @@ 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(),
|
||||
[](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); });
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#ifdef __WINE__
|
||||
#include "../wine-host/boost-fix.h"
|
||||
#endif
|
||||
|
||||
#include <vestige/aeffectx.h>
|
||||
|
||||
#include <boost/process/environment.hpp>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
/**
|
||||
* The environment variable indicating whether to log to a file. Will log to
|
||||
* STDERR if not specified.
|
||||
*/
|
||||
constexpr char logging_file_environment_variable[] = "YABRIDGE_DEBUG_FILE";
|
||||
|
||||
/**
|
||||
* The verbosity of the logging, defaults to `Logger::Verbosity::basic`.
|
||||
*
|
||||
* @see Logger::Verbosity
|
||||
*/
|
||||
constexpr char logging_verbosity_environment_variable[] =
|
||||
"YABRIDGE_DEBUG_LEVEL";
|
||||
|
||||
Logger::Logger(std::shared_ptr<std::ostream> stream,
|
||||
Verbosity verbosity_level,
|
||||
std::string prefix)
|
||||
: verbosity(verbosity_level), stream(stream), prefix(prefix) {}
|
||||
|
||||
Logger Logger::create_from_environment(std::string prefix) {
|
||||
auto env = boost::this_process::environment();
|
||||
std::string file_path = env[logging_file_environment_variable].to_string();
|
||||
std::string verbosity =
|
||||
env[logging_verbosity_environment_variable].to_string();
|
||||
|
||||
// Default to `Verbosity::basic` if the environment variable has not
|
||||
// been set or if it is not an integer.
|
||||
Verbosity verbosity_level;
|
||||
try {
|
||||
verbosity_level = static_cast<Verbosity>(std::stoi(verbosity));
|
||||
} catch (const std::invalid_argument&) {
|
||||
verbosity_level = Verbosity::basic;
|
||||
}
|
||||
|
||||
// If `file` points to a valid location then use create/truncate the
|
||||
// file and write all of the logs there, otherwise use STDERR
|
||||
auto log_file = std::make_shared<std::ofstream>(
|
||||
file_path, std::fstream::out | std::fstream::app);
|
||||
if (log_file->is_open()) {
|
||||
return Logger(log_file, verbosity_level, prefix);
|
||||
} else {
|
||||
// For STDERR we sadly can't just use `std::cerr`. In the group process
|
||||
// we need to capture all output generated by the process itself, and
|
||||
// the only way to do this is by reopening the STDERR and STDOUT streams
|
||||
// to a pipe. Luckily `/dev/stderr` stays unaffected, so we can still
|
||||
// write there without causing infinite loops.
|
||||
return Logger(std::make_shared<std::ofstream>("/dev/stderr"),
|
||||
verbosity_level, 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<Verbosity>(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::ostream>(&std::cerr, [](auto*) {}),
|
||||
verbosity_level, "");
|
||||
}
|
||||
|
||||
void Logger::log(const std::string& message) {
|
||||
const auto current_time = std::chrono::system_clock::now();
|
||||
const std::time_t timestamp =
|
||||
std::chrono::system_clock::to_time_t(current_time);
|
||||
|
||||
// How did C++ manage to get time formatting libraries without a way to
|
||||
// actually get a timestamp in a threadsafe way? `localtime_r` in C++ is not
|
||||
// portable but luckily we only have to support GCC anyway.
|
||||
std::tm tm;
|
||||
localtime_r(×tamp, &tm);
|
||||
|
||||
std::ostringstream formatted_message;
|
||||
formatted_message << std::put_time(&tm, "%T") << " ";
|
||||
formatted_message << prefix;
|
||||
formatted_message << message;
|
||||
// Flushing a stringstream doesn't do anything, but we need to put a
|
||||
// linefeed in this string stream rather writing it sprightly to the output
|
||||
// stream to prevent two messages from being put on the same row
|
||||
formatted_message << std::endl;
|
||||
|
||||
*stream << formatted_message.str() << std::flush;
|
||||
}
|
||||
|
||||
void Logger::log_trace(const std::string& message) {
|
||||
if (verbosity >= Verbosity::all_events) {
|
||||
log(message);
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
|
||||
#include "serialization.h"
|
||||
|
||||
/**
|
||||
* Super basic logging facility meant for debugging malfunctioning VST
|
||||
* plugins. This is also used to redirect the output of the Wine process
|
||||
@@ -86,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.
|
||||
@@ -94,29 +100,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<EventPayload>& value_payload);
|
||||
void log_event_response(
|
||||
bool is_dispatch,
|
||||
int opcode,
|
||||
intptr_t return_value,
|
||||
const EventResultPayload& payload,
|
||||
const std::optional<EventResultPayload>& 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
|
||||
@@ -127,38 +110,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<std::ostream> 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<std::string> opcode_to_string(bool is_dispatch, int opcode);
|
||||
@@ -14,316 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "logging.h"
|
||||
#include "vst2.h"
|
||||
|
||||
#ifdef __WINE__
|
||||
#include "../wine-host/boost-fix.h"
|
||||
#endif
|
||||
#include <sstream>
|
||||
|
||||
#include <vestige/aeffectx.h>
|
||||
|
||||
#include <boost/process/environment.hpp>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include "vst24.h"
|
||||
|
||||
/**
|
||||
* The environment variable indicating whether to log to a file. Will log to
|
||||
* STDERR if not specified.
|
||||
*/
|
||||
constexpr char logging_file_environment_variable[] = "YABRIDGE_DEBUG_FILE";
|
||||
|
||||
/**
|
||||
* The verbosity of the logging, defaults to `Logger::Verbosity::basic`.
|
||||
*
|
||||
* @see Logger::Verbosity
|
||||
*/
|
||||
constexpr char logging_verbosity_environment_variable[] =
|
||||
"YABRIDGE_DEBUG_LEVEL";
|
||||
|
||||
Logger::Logger(std::shared_ptr<std::ostream> stream,
|
||||
Verbosity verbosity_level,
|
||||
std::string prefix)
|
||||
: stream(stream), verbosity(verbosity_level), prefix(prefix) {}
|
||||
|
||||
Logger Logger::create_from_environment(std::string prefix) {
|
||||
auto env = boost::this_process::environment();
|
||||
std::string file_path = env[logging_file_environment_variable].to_string();
|
||||
std::string verbosity =
|
||||
env[logging_verbosity_environment_variable].to_string();
|
||||
|
||||
// Default to `Verbosity::basic` if the environment variable has not
|
||||
// been set or if it is not an integer.
|
||||
Verbosity verbosity_level;
|
||||
try {
|
||||
verbosity_level = static_cast<Verbosity>(std::stoi(verbosity));
|
||||
} catch (const std::invalid_argument&) {
|
||||
verbosity_level = Verbosity::basic;
|
||||
}
|
||||
|
||||
// If `file` points to a valid location then use create/truncate the
|
||||
// file and write all of the logs there, otherwise use STDERR
|
||||
auto log_file = std::make_shared<std::ofstream>(
|
||||
file_path, std::fstream::out | std::fstream::app);
|
||||
if (log_file->is_open()) {
|
||||
return Logger(log_file, verbosity_level, prefix);
|
||||
} else {
|
||||
// For STDERR we sadly can't just use `std::cerr`. In the group process
|
||||
// we need to capture all output generated by the process itself, and
|
||||
// the only way to do this is by reopening the STDERR and STDOUT streams
|
||||
// to a pipe. Luckily `/dev/stderr` stays unaffected, so we can still
|
||||
// write there without causing infinite loops.
|
||||
return Logger(std::make_shared<std::ofstream>("/dev/stderr"),
|
||||
verbosity_level, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::log(const std::string& message) {
|
||||
const auto current_time = std::chrono::system_clock::now();
|
||||
const std::time_t timestamp =
|
||||
std::chrono::system_clock::to_time_t(current_time);
|
||||
|
||||
// How did C++ manage to get time formatting libraries without a way to
|
||||
// actually get a timestamp in a threadsafe way? `localtime_r` in C++ is not
|
||||
// portable but luckily we only have to support GCC anyway.
|
||||
std::tm tm;
|
||||
localtime_r(×tamp, &tm);
|
||||
|
||||
std::ostringstream formatted_message;
|
||||
formatted_message << std::put_time(&tm, "%T") << " ";
|
||||
formatted_message << prefix;
|
||||
formatted_message << message;
|
||||
// Flushing a stringstream doesn't do anything, but we need to put a
|
||||
// linefeed in this string stream rather writing it sprightly to the output
|
||||
// stream to prevent two messages from being put on the same row
|
||||
formatted_message << std::endl;
|
||||
|
||||
*stream << formatted_message.str() << std::flush;
|
||||
}
|
||||
|
||||
void Logger::log_trace(const std::string& message) {
|
||||
if (verbosity >= Verbosity::all_events) {
|
||||
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<EventPayload>& 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 << "<opcode = " << opcode << ">";
|
||||
}
|
||||
|
||||
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 << "<nullptr>"; },
|
||||
[&](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 << "<window " << window_id << ">";
|
||||
},
|
||||
[&](const AEffect&) { message << "<nullptr>"; },
|
||||
[&](const DynamicVstEvents& events) {
|
||||
message << "<" << events.events.size() << " midi_events>";
|
||||
},
|
||||
[&](const DynamicSpeakerArrangement& speaker_arrangement) {
|
||||
message << "<" << speaker_arrangement.speakers.size()
|
||||
<< " output_speakers>";
|
||||
},
|
||||
[&](const VstIOProperties&) { message << "<io_properties>"; },
|
||||
[&](const VstMidiKeyName&) { message << "<key_name>"; },
|
||||
[&](const VstParameterProperties&) {
|
||||
message << "<writable_buffer>";
|
||||
},
|
||||
[&](const WantsAEffectUpdate&) { message << "<nullptr>"; },
|
||||
[&](const WantsChunkBuffer&) {
|
||||
message << "<writable_buffer>";
|
||||
},
|
||||
[&](const WantsVstRect&) { message << "<writable_buffer>"; },
|
||||
[&](const WantsVstTimeInfo&) { message << "<nullptr>"; },
|
||||
[&](const WantsString&) { message << "<writable_string>"; }},
|
||||
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<EventResultPayload>& 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 << ", <AEffect_object>"; },
|
||||
[&](const DynamicSpeakerArrangement& speaker_arrangement) {
|
||||
message << ", <" << speaker_arrangement.speakers.size()
|
||||
<< " output_speakers>";
|
||||
},
|
||||
[&](const VstIOProperties&) { message << ", <io_properties>"; },
|
||||
[&](const VstMidiKeyName&) { message << ", <key_name>"; },
|
||||
[&](const VstParameterProperties& props) {
|
||||
message << ", <parameter_properties for '" << props.label
|
||||
<< "'>";
|
||||
},
|
||||
[&](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;
|
||||
}
|
||||
Vst2Logger::Vst2Logger(Logger& generic_logger) : logger(generic_logger) {}
|
||||
|
||||
std::optional<std::string> opcode_to_string(bool is_dispatch, int opcode) {
|
||||
if (is_dispatch) {
|
||||
@@ -621,3 +316,217 @@ std::optional<std::string> opcode_to_string(bool is_dispatch, int opcode) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Vst2Logger::log_get_parameter(int index) {
|
||||
if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) {
|
||||
std::ostringstream message;
|
||||
message << ">> getParameter() " << index;
|
||||
|
||||
log(message.str());
|
||||
}
|
||||
}
|
||||
|
||||
void Vst2Logger::log_get_parameter_response(float value) {
|
||||
if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) {
|
||||
std::ostringstream message;
|
||||
message << " getParameter() :: " << value;
|
||||
|
||||
log(message.str());
|
||||
}
|
||||
}
|
||||
|
||||
void Vst2Logger::log_set_parameter(int index, float value) {
|
||||
if (BOOST_UNLIKELY(logger.verbosity >= Logger::Verbosity::most_events)) {
|
||||
std::ostringstream message;
|
||||
message << ">> setParameter() " << index << " = " << value;
|
||||
|
||||
log(message.str());
|
||||
}
|
||||
}
|
||||
|
||||
void Vst2Logger::log_set_parameter_response() {
|
||||
if (BOOST_UNLIKELY(logger.verbosity >= Logger::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<EventPayload>& value_payload) {
|
||||
if (BOOST_UNLIKELY(logger.verbosity >= Logger::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 << "<opcode = " << opcode << ">";
|
||||
}
|
||||
|
||||
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 << "<nullptr>"; },
|
||||
[&](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 << "<window " << window_id << ">";
|
||||
},
|
||||
[&](const AEffect&) { message << "<nullptr>"; },
|
||||
[&](const DynamicVstEvents& events) {
|
||||
message << "<" << events.events.size() << " midi_events>";
|
||||
},
|
||||
[&](const DynamicSpeakerArrangement& speaker_arrangement) {
|
||||
message << "<" << speaker_arrangement.speakers.size()
|
||||
<< " output_speakers>";
|
||||
},
|
||||
[&](const VstIOProperties&) { message << "<io_properties>"; },
|
||||
[&](const VstMidiKeyName&) { message << "<key_name>"; },
|
||||
[&](const VstParameterProperties&) {
|
||||
message << "<writable_buffer>";
|
||||
},
|
||||
[&](const WantsAEffectUpdate&) { message << "<nullptr>"; },
|
||||
[&](const WantsChunkBuffer&) {
|
||||
message << "<writable_buffer>";
|
||||
},
|
||||
[&](const WantsVstRect&) { message << "<writable_buffer>"; },
|
||||
[&](const WantsVstTimeInfo&) { message << "<nullptr>"; },
|
||||
[&](const WantsString&) { message << "<writable_string>"; }},
|
||||
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<EventResultPayload>& value_payload) {
|
||||
if (BOOST_UNLIKELY(logger.verbosity >= Logger::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 << ", <AEffect_object>"; },
|
||||
[&](const DynamicSpeakerArrangement& speaker_arrangement) {
|
||||
message << ", <" << speaker_arrangement.speakers.size()
|
||||
<< " output_speakers>";
|
||||
},
|
||||
[&](const VstIOProperties&) { message << ", <io_properties>"; },
|
||||
[&](const VstMidiKeyName&) { message << ", <key_name>"; },
|
||||
[&](const VstParameterProperties& props) {
|
||||
message << ", <parameter_properties for '" << props.label
|
||||
<< "'>";
|
||||
},
|
||||
[&](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 (logger.verbosity >= Logger::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;
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#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<std::string> opcode_to_string(bool is_dispatch, int opcode);
|
||||
|
||||
/**
|
||||
* 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:
|
||||
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_trace(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<EventPayload>& value_payload);
|
||||
void log_event_response(
|
||||
bool is_dispatch,
|
||||
int opcode,
|
||||
intptr_t return_value,
|
||||
const EventResultPayload& payload,
|
||||
const std::optional<EventResultPayload>& value_payload);
|
||||
|
||||
/**
|
||||
* The underlying logger instance we're wrapping.
|
||||
*/
|
||||
Logger& logger;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Determine whether an event should be filtered based on the current
|
||||
* verbosity level.
|
||||
*/
|
||||
bool should_filter_event(bool is_dispatch, int opcode) const;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,276 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "../serialization/vst3.h"
|
||||
#include "common.h"
|
||||
|
||||
/**
|
||||
* 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:
|
||||
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_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<Steinberg::FUID>& 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
|
||||
// (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.
|
||||
|
||||
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&);
|
||||
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,
|
||||
const YaEditController::GetParameterCount&);
|
||||
bool log_request(bool is_host_vst,
|
||||
const YaEditController::GetParameterInfo&);
|
||||
bool log_request(bool is_host_vst,
|
||||
const YaEditController::GetParamStringByValue&);
|
||||
bool log_request(bool is_host_vst,
|
||||
const YaEditController::GetParamValueByString&);
|
||||
bool log_request(bool is_host_vst,
|
||||
const YaEditController::NormalizedParamToPlain&);
|
||||
bool log_request(bool is_host_vst,
|
||||
const YaEditController::PlainParamToNormalized&);
|
||||
bool log_request(bool is_host_vst,
|
||||
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 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&);
|
||||
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 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 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&);
|
||||
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 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&);
|
||||
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 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&);
|
||||
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&);
|
||||
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&);
|
||||
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(
|
||||
bool is_host_vst,
|
||||
const std::variant<Vst3PluginProxy::ConstructArgs, UniversalTResult>&);
|
||||
void log_response(bool is_host_vst,
|
||||
const Vst3PluginProxy::GetStateResponse&);
|
||||
void log_response(bool is_host_vst,
|
||||
const YaEditController::GetParameterInfoResponse&);
|
||||
void log_response(bool is_host_vst,
|
||||
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 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 YaUnitData::GetUnitDataResponse&);
|
||||
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 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&);
|
||||
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&);
|
||||
|
||||
template <typename T>
|
||||
void log_response(bool is_host_vst, const PrimitiveWrapper<T>& value) {
|
||||
// For logging all primitive return values other than `tresult`
|
||||
log_response_base(is_host_vst,
|
||||
[&](auto& message) { message << value; });
|
||||
}
|
||||
|
||||
Logger& logger;
|
||||
|
||||
private:
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Returns `true` if the log message was displayed, and the response should
|
||||
* thus also be logged.
|
||||
*/
|
||||
template <std::invocable<std::ostringstream&> F>
|
||||
bool 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] >> ";
|
||||
} else {
|
||||
message << "[vst -> host] >> ";
|
||||
}
|
||||
|
||||
callback(message);
|
||||
log(message.str());
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <std::invocable<std::ostringstream&> F>
|
||||
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 <std::invocable<std::ostringstream&> F>
|
||||
void log_response_base(bool is_host_vst, F callback) {
|
||||
std::ostringstream message;
|
||||
if (is_host_vst) {
|
||||
message << "[vst <- host] ";
|
||||
} else {
|
||||
message << "[host <- vst] ";
|
||||
}
|
||||
|
||||
callback(message);
|
||||
log(message.str());
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
#include "plugins.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
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
|
||||
// end of the MS-DOS stub, at offset 0x3c
|
||||
uint32_t pe_signature_offset;
|
||||
file.seekg(0x3c);
|
||||
file.read(reinterpret_cast<char*>(&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<char*>(&pe_signature), sizeof(pe_signature));
|
||||
file.read(reinterpret_cast<char*>(&machine_type), sizeof(machine_type));
|
||||
|
||||
constexpr char expected_pe_signature[4] = {'P', 'E', '\0', '\0'};
|
||||
if (pe_signature !=
|
||||
*reinterpret_cast<const uint32_t*>(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 LibArchitecture::dll_32;
|
||||
break;
|
||||
case 0x8664: // IMAGE_FILE_MACHINE_AMD64
|
||||
case 0x0000: // IMAGE_FILE_MACHINE_UNKNOWN
|
||||
return LibArchitecture::dll_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());
|
||||
}
|
||||
|
||||
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) {
|
||||
// We'll capitalize the acronyms because this is also our human readable
|
||||
// format
|
||||
if (plugin_type == PluginType::vst2) {
|
||||
return "VST2";
|
||||
} else if (plugin_type == PluginType::vst3) {
|
||||
return "VST3";
|
||||
} else {
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __WINE__
|
||||
#include "../wine-host/boost-fix.h"
|
||||
#endif
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
// Utilities and tags for plugin types and architectures
|
||||
|
||||
/**
|
||||
* A tag to differentiate between 32 and 64-bit `.dll` files, used to determine
|
||||
* which host application to use.
|
||||
*/
|
||||
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 `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
|
||||
* gracefully show an error message without resorting to exceptions.
|
||||
*/
|
||||
enum class PluginType { vst2, vst3, unknown };
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, PluginType& plugin_type) {
|
||||
s.value4b(plugin_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
*/
|
||||
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);
|
||||
@@ -0,0 +1,85 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitsery/traits/string.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#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
|
||||
// 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<size_t, uint64_t>);
|
||||
static_assert(std::is_same_v<intptr_t, int64_t>);
|
||||
#endif
|
||||
using native_size_t = uint64_t;
|
||||
using native_intptr_t = int64_t;
|
||||
|
||||
/**
|
||||
* 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 HostRequest {
|
||||
PluginType plugin_type;
|
||||
std::string plugin_path;
|
||||
std::string endpoint_base_dir;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.object(plugin_type);
|
||||
s.text1b(plugin_path, 4096);
|
||||
s.text1b(endpoint_base_dir, 4096);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<HostRequest> {
|
||||
std::size_t operator()(HostRequest const& params) const noexcept {
|
||||
std::hash<string> hasher{};
|
||||
|
||||
return hasher(params.plugin_path) ^
|
||||
(hasher(params.endpoint_base_dir) << 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 HostResponse {
|
||||
pid_t pid;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value4b(pid);
|
||||
}
|
||||
};
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "serialization.h"
|
||||
#include "vst2.h"
|
||||
|
||||
DynamicVstEvents::DynamicVstEvents(const VstEvents& c_events)
|
||||
: events(c_events.numEvents) {
|
||||
@@ -16,18 +16,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/ext/pointer.h>
|
||||
#include <variant>
|
||||
|
||||
#include <bitsery/ext/std_optional.h>
|
||||
#include <bitsery/ext/std_variant.h>
|
||||
#include <bitsery/traits/array.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
#include <vestige/aeffectx.h>
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "vst24.h"
|
||||
#include "../utils.h"
|
||||
#include "../vst24.h"
|
||||
#include "common.h"
|
||||
|
||||
// These constants are limits used by bitsery
|
||||
|
||||
@@ -54,38 +53,12 @@ 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;
|
||||
|
||||
// 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<size_t, uint64_t>);
|
||||
static_assert(std::is_same_v<intptr_t, int64_t>);
|
||||
#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 <class... Ts>
|
||||
struct overload : Ts... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
template <class... Ts>
|
||||
overload(Ts...) -> overload<Ts...>;
|
||||
|
||||
/**
|
||||
* Update an `AEffect` object, copying values from `updated_plugin` to `plugin`.
|
||||
* This will copy all flags and regular values, leaving all pointers in `plugin`
|
||||
@@ -458,7 +431,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.
|
||||
@@ -601,44 +574,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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.text1b(plugin_path, 4096);
|
||||
s.text1b(endpoint_base_dir, 4096);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<GroupRequest> {
|
||||
std::size_t operator()(GroupRequest const& params) const noexcept {
|
||||
std::hash<string> 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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value4b(pid);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,183 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include <bitsery/ext/std_variant.h>
|
||||
|
||||
#include "../configuration.h"
|
||||
#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"
|
||||
#include "vst3/plugin-factory.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
|
||||
// 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 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
|
||||
|
||||
// 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
|
||||
* the configuration.
|
||||
*/
|
||||
struct WantsConfiguration {
|
||||
using Response = Configuration;
|
||||
|
||||
template <typename S>
|
||||
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
|
||||
* request of type `ControlRequest(T)` should send back a `T::Response`.
|
||||
*/
|
||||
using ControlRequest = std::variant<Vst3PlugViewProxy::Destruct,
|
||||
Vst3PluginProxy::Construct,
|
||||
Vst3PluginProxy::Destruct,
|
||||
Vst3PluginProxy::SetState,
|
||||
Vst3PluginProxy::GetState,
|
||||
YaConnectionPoint::Connect,
|
||||
YaConnectionPoint::Disconnect,
|
||||
YaConnectionPoint::Notify,
|
||||
YaEditController::SetComponentState,
|
||||
YaEditController::GetParameterCount,
|
||||
YaEditController::GetParameterInfo,
|
||||
YaEditController::GetParamStringByValue,
|
||||
YaEditController::GetParamValueByString,
|
||||
YaEditController::NormalizedParamToPlain,
|
||||
YaEditController::PlainParamToNormalized,
|
||||
YaEditController::GetParamNormalized,
|
||||
YaEditController::SetParamNormalized,
|
||||
YaEditController::SetComponentHandler,
|
||||
YaEditController::CreateView,
|
||||
YaEditController2::SetKnobMode,
|
||||
YaEditController2::OpenHelp,
|
||||
YaEditController2::OpenAboutBox,
|
||||
YaPlugView::IsPlatformTypeSupported,
|
||||
YaPlugView::Attached,
|
||||
YaPlugView::Removed,
|
||||
YaPlugView::OnWheel,
|
||||
YaPlugView::OnKeyDown,
|
||||
YaPlugView::OnKeyUp,
|
||||
YaPlugView::GetSize,
|
||||
YaPlugView::OnSize,
|
||||
YaPlugView::OnFocus,
|
||||
YaPlugView::SetFrame,
|
||||
YaPlugView::CanResize,
|
||||
YaPlugView::CheckSizeConstraint,
|
||||
YaPluginBase::Initialize,
|
||||
YaPluginBase::Terminate,
|
||||
YaPluginFactory::Construct,
|
||||
YaPluginFactory::SetHostContext,
|
||||
YaProgramListData::ProgramDataSupported,
|
||||
YaProgramListData::GetProgramData,
|
||||
YaProgramListData::SetProgramData,
|
||||
YaUnitData::UnitDataSupported,
|
||||
YaUnitData::GetUnitData,
|
||||
YaUnitData::SetUnitData,
|
||||
YaUnitInfo::GetUnitCount,
|
||||
YaUnitInfo::GetUnitInfo,
|
||||
YaUnitInfo::GetProgramListCount,
|
||||
YaUnitInfo::GetProgramListInfo,
|
||||
YaUnitInfo::GetProgramName,
|
||||
YaUnitInfo::GetProgramInfo,
|
||||
YaUnitInfo::HasProgramPitchNames,
|
||||
YaUnitInfo::GetProgramPitchName,
|
||||
YaUnitInfo::GetSelectedUnit,
|
||||
YaUnitInfo::SelectUnit,
|
||||
YaUnitInfo::GetUnitByBus,
|
||||
YaUnitInfo::SetUnitProgramData>;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, ControlRequest& payload) {
|
||||
// All of the objects in `ControlRequest` should have their own
|
||||
// serialization function.
|
||||
s.ext(payload, bitsery::ext::StdVariant{});
|
||||
}
|
||||
|
||||
/**
|
||||
* A subset of all functions a host can call on a plugin. These functions are
|
||||
* called from a hot loop every processing cycle, so we want a dedicated socket
|
||||
* for these for every plugin instance.
|
||||
*/
|
||||
using AudioProcessorRequest =
|
||||
std::variant<YaAudioProcessor::SetBusArrangements,
|
||||
YaAudioProcessor::GetBusArrangement,
|
||||
YaAudioProcessor::CanProcessSampleSize,
|
||||
YaAudioProcessor::GetLatencySamples,
|
||||
YaAudioProcessor::SetupProcessing,
|
||||
YaAudioProcessor::SetProcessing,
|
||||
YaAudioProcessor::Process,
|
||||
YaAudioProcessor::GetTailSamples,
|
||||
YaComponent::GetControllerClassId,
|
||||
YaComponent::SetIoMode,
|
||||
YaComponent::GetBusCount,
|
||||
YaComponent::GetBusInfo,
|
||||
YaComponent::GetRoutingInfo,
|
||||
YaComponent::ActivateBus,
|
||||
YaComponent::SetActive>;
|
||||
|
||||
template <typename S>
|
||||
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
|
||||
* `CallbackRequest(T)` should send back a `T::Response`.
|
||||
*/
|
||||
using CallbackRequest = std::variant<WantsConfiguration,
|
||||
YaComponentHandler::BeginEdit,
|
||||
YaComponentHandler::PerformEdit,
|
||||
YaComponentHandler::EndEdit,
|
||||
YaComponentHandler::RestartComponent,
|
||||
// Used when the host uses proxy objects,
|
||||
// and we have to route
|
||||
// `IConnectionPoint::notify` calls through
|
||||
// there
|
||||
YaConnectionPoint::Notify,
|
||||
YaHostApplication::GetName,
|
||||
YaPlugFrame::ResizeView,
|
||||
YaUnitHandler::NotifyUnitSelection,
|
||||
YaUnitHandler::NotifyProgramListChange>;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, CallbackRequest& payload) {
|
||||
// All of the objects in `CallbackRequest` should have their own
|
||||
// serialization function.
|
||||
s.ext(payload, bitsery::ext::StdVariant{});
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
# VST3 interfaces
|
||||
|
||||
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.
|
||||
|
||||
VST3 plugin interfaces are implemented as follows:
|
||||
|
||||
| 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` |
|
||||
| `YaProgramListData` | `Vst3PluginProxy` | `IProgramListData` |
|
||||
| `YaUnitData` | `Vst3PluginProxy` | `IUnitData` |
|
||||
| `YaUnitInfo` | `Vst3PluginProxy` | `IUnitInfo` |
|
||||
|
||||
VST3 host interfaces are implemented as follows:
|
||||
|
||||
| yabridge class | Included in | Interfaces |
|
||||
| --------------------------- | --------------------------- | ------------------- |
|
||||
| `Vst3HostContextProxy` | | All of the below: |
|
||||
| `YaHostApplication` | `Vst3HostContextProxy` | `IHostApplication` |
|
||||
| `Vst3ComponentHandlerProxy` | | All of the below: |
|
||||
| `YaComponentHandler` | `Vst3ComponentHandlerProxy` | `IComponentHandler` |
|
||||
| `YaUnitHandler` | `Vst3ComponentHandlerProxy` | `IUnitHandler` |
|
||||
| `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:
|
||||
|
||||
| yabridge class | Interfaces | Notes |
|
||||
| -------------------- | ------------------- | ---------------------------------------------------------------------- |
|
||||
| `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. |
|
||||
|
||||
And finally `YaProcessData` uses the above along with `YaAudioBusBuffers` to
|
||||
wrap around `ProcessData`.
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#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<size_t>(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<const uint8_t*>(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;
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <bitsery/ext/std_map.h>
|
||||
#include <bitsery/ext/std_optional.h>
|
||||
#include <pluginterfaces/vst/ivstmessage.h>
|
||||
|
||||
#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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.ext(attrs_int, bitsery::ext::StdMap{1 << 20},
|
||||
[](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, std::string& key, double& value) {
|
||||
s.text1b(key, 1024);
|
||||
s.value8b(value);
|
||||
});
|
||||
s.ext(attrs_string, bitsery::ext::StdMap{1 << 20},
|
||||
[](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, std::string& key, std::vector<uint8_t>& value) {
|
||||
s.text1b(key, 1024);
|
||||
s.container1b(value, 1 << 20);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, int64> attrs_int;
|
||||
std::unordered_map<std::string, double> attrs_float;
|
||||
std::unordered_map<std::string, std::u16string> attrs_string;
|
||||
std::unordered_map<std::string, std::vector<uint8_t>> attrs_binary;
|
||||
};
|
||||
@@ -0,0 +1,330 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include <cassert>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#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
|
||||
static_assert(sizeof(Steinberg::Vst::TChar) == sizeof(char16_t));
|
||||
return std::u16string(reinterpret_cast<const char16_t*>(string));
|
||||
#else
|
||||
return std::u16string(static_cast<const char16_t*>(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<const char16_t*>(string), length);
|
||||
#else
|
||||
return std::u16string(static_cast<const char16_t*>(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<const Steinberg::Vst::TChar*>(string.c_str());
|
||||
#else
|
||||
return static_cast<const Steinberg::Vst::TChar*>(string.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
UniversalTResult::UniversalTResult() : universal_result(Value::kResultFalse) {}
|
||||
|
||||
UniversalTResult::UniversalTResult(tresult native_result)
|
||||
: universal_result(to_universal_result(native_result)) {}
|
||||
|
||||
UniversalTResult::operator tresult() 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 "<invalid>";
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 = 0;
|
||||
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 VectorStream::write_back(Steinberg::IBStream* stream) const {
|
||||
if (!stream) {
|
||||
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;
|
||||
if (stream->write(const_cast<uint8_t*>(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<size_t>(num_bytes_written) == buffer.size());
|
||||
}
|
||||
|
||||
return Steinberg::kResultOk;
|
||||
}
|
||||
|
||||
size_t VectorStream::size() const {
|
||||
return buffer.size();
|
||||
}
|
||||
|
||||
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<size_t>(numBytes),
|
||||
this->buffer.size() - seek_position);
|
||||
|
||||
std::copy_n(&this->buffer[seek_position], bytes_to_read,
|
||||
reinterpret_cast<uint8_t*>(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<uint8_t*>(buffer), numBytes,
|
||||
this->buffer.begin() + 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;
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <pluginterfaces/base/ftypes.h>
|
||||
#include <pluginterfaces/base/funknown.h>
|
||||
#include <pluginterfaces/base/ibstream.h>
|
||||
#include <pluginterfaces/vst/vsttypes.h>
|
||||
|
||||
// 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::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
|
||||
* 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<decltype(std::declval<Steinberg::TUID>()[0])>,
|
||||
std::extent_v<Steinberg::TUID>>;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* it will add up when plugins start to include audio samples in their presets.
|
||||
*/
|
||||
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?
|
||||
*/
|
||||
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
|
||||
* result values.
|
||||
*/
|
||||
struct Ack {
|
||||
template <typename S>
|
||||
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 <typename T>
|
||||
class PrimitiveWrapper {
|
||||
public:
|
||||
PrimitiveWrapper() {}
|
||||
PrimitiveWrapper(T value) : value(value) {}
|
||||
|
||||
operator T() const { return value; }
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.template value<sizeof(T)>(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
|
||||
* 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:
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Get the native equivalent for the wrapped `tresult` value.
|
||||
*/
|
||||
operator tresult() const;
|
||||
|
||||
/**
|
||||
* Get the original name for the result, e.g. `kResultOk`.
|
||||
*/
|
||||
std::string string() const;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value4b(universal_result);
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* These are the non-COM compatible values copied from
|
||||
* `<pluginterfaces/base/funknown.hh`> 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;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
|
||||
|
||||
/**
|
||||
* Serialize an `IBStream` into an `std::vector<uint8_t>`, 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.
|
||||
*
|
||||
* @throw std::runtime_error If we couldn't read from the stream.
|
||||
*/
|
||||
VectorStream(Steinberg::IBStream* stream);
|
||||
|
||||
virtual ~VectorStream();
|
||||
|
||||
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) const;
|
||||
|
||||
/**
|
||||
* Return the buffer's, used in the logging messages.
|
||||
*/
|
||||
size_t size() const;
|
||||
|
||||
// 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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.container1b(buffer, max_vector_stream_size);
|
||||
// The seek position should always be initialized at 0
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> buffer;
|
||||
size_t seek_position = 0;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "component-handler-proxy.h"
|
||||
|
||||
Vst3ComponentHandlerProxy::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
Vst3ComponentHandlerProxy::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object,
|
||||
size_t owner_instance_id)
|
||||
: 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() {
|
||||
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)
|
||||
}
|
||||
if (YaUnitHandler::supported()) {
|
||||
QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IUnitHandler::iid,
|
||||
Steinberg::Vst::IUnitHandler)
|
||||
}
|
||||
|
||||
*obj = nullptr;
|
||||
return Steinberg::kNoInterface;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#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"
|
||||
|
||||
/**
|
||||
* An abstract class that implements `IComponentHandler`, and optionally also
|
||||
* all other VST3 interfaces an object passed to
|
||||
* `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 YaUnitHandler {
|
||||
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<FUnknown> 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;
|
||||
|
||||
YaComponentHandler::ConstructArgs component_handler_args;
|
||||
YaUnitHandler::ConstructArgs unit_handler_args;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.object(component_handler_args);
|
||||
s.object(unit_handler_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();
|
||||
|
||||
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
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "component-handler.h"
|
||||
|
||||
YaComponentHandler::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaComponentHandler::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(
|
||||
Steinberg::FUnknownPtr<Steinberg::Vst::IComponentHandler>(object)) {}
|
||||
|
||||
YaComponentHandler::YaComponentHandler(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -0,0 +1,154 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginterfaces/vst/ivsteditcontroller.h>
|
||||
|
||||
#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<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
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; }
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.value4b(id);
|
||||
}
|
||||
};
|
||||
|
||||
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 <typename S>
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.value4b(id);
|
||||
}
|
||||
};
|
||||
|
||||
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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.value4b(flags);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API restartComponent(int32 flags) override = 0;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "unit-handler.h"
|
||||
|
||||
YaUnitHandler::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaUnitHandler::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(Steinberg::FUnknownPtr<Steinberg::Vst::IUnitHandler>(object)) {}
|
||||
|
||||
YaUnitHandler::YaUnitHandler(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginterfaces/vst/ivstunits.h>
|
||||
|
||||
#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<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
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; }
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.value4b(unit_id);
|
||||
}
|
||||
};
|
||||
|
||||
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 <typename S>
|
||||
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;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "connection-point-proxy.h"
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#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:
|
||||
// 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
|
||||
* `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
|
||||
@@ -0,0 +1,248 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "event-list.h"
|
||||
|
||||
#include "src/common/utils.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<uint32>(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<uint32>(text.size()),
|
||||
.text = u16string_to_tchar_pointer(text)};
|
||||
}
|
||||
|
||||
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<uint16>(text.size()),
|
||||
.text = u16string_to_tchar_pointer(text)};
|
||||
}
|
||||
|
||||
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<uint16>(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 {
|
||||
// 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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}
|
||||
|
||||
size_t YaEventList::num_events() const {
|
||||
return events.size();
|
||||
}
|
||||
|
||||
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,
|
||||
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<int32>(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<int32>(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;
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitsery/ext/std_variant.h>
|
||||
#include <pluginterfaces/vst/ivstevents.h>
|
||||
|
||||
#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<uint8> buffer;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value4b(type);
|
||||
s.container1b(buffer, 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 <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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;
|
||||
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
|
||||
// around move event types with heap pointers.
|
||||
std::variant<Steinberg::Vst::NoteOnEvent,
|
||||
Steinberg::Vst::NoteOffEvent,
|
||||
YaDataEvent,
|
||||
Steinberg::Vst::PolyPressureEvent,
|
||||
Steinberg::Vst::NoteExpressionValueEvent,
|
||||
YaNoteExpressionTextEvent,
|
||||
YaChordEvent,
|
||||
YaScaleEvent,
|
||||
Steinberg::Vst::LegacyMIDICCOutEvent>
|
||||
payload;
|
||||
|
||||
template <typename S>
|
||||
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& event_list);
|
||||
|
||||
~YaEventList();
|
||||
|
||||
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.
|
||||
*/
|
||||
void write_back_outputs(Steinberg::Vst::IEventList& output_events) const;
|
||||
|
||||
// From `IEventList`
|
||||
virtual int32 PLUGIN_API getEventCount() override;
|
||||
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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.container(events, 1 << 16);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<YaEvent> 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<Steinberg::Vst::Event> reconstructed_events;
|
||||
};
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
void serialize(S& s, PolyPressureEvent& event) {
|
||||
s.value2b(event.channel);
|
||||
s.value2b(event.pitch);
|
||||
s.value4b(event.pressure);
|
||||
s.value4b(event.noteId);
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, NoteExpressionValueEvent& event) {
|
||||
s.value4b(event.typeId);
|
||||
s.value4b(event.noteId);
|
||||
s.value8b(event.value);
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
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
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "host-context-proxy.h"
|
||||
|
||||
Vst3HostContextProxy::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
Vst3HostContextProxy::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object,
|
||||
std::optional<size_t> 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;
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../common.h"
|
||||
#include "host-context/host-application.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
|
||||
|
||||
/**
|
||||
* 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 Vst3HostContextProxy : public YaHostApplication {
|
||||
public:
|
||||
/**
|
||||
* These are the arguments for constructing a
|
||||
* `Vst3HostContextProxyImpl`.
|
||||
*/
|
||||
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<FUnknown> object,
|
||||
std::optional<size_t> owner_instance_id);
|
||||
|
||||
/**
|
||||
* 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<native_size_t> owner_instance_id;
|
||||
|
||||
YaHostApplication::ConstructArgs host_application_args;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.ext(owner_instance_id, bitsery::ext::StdOptional{},
|
||||
[](S& s, native_size_t& instance_id) {
|
||||
s.value8b(instance_id);
|
||||
});
|
||||
s.object(host_application_args);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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 a corresponding `Vst3HostContextProxyImpl` should also be
|
||||
* dropped.
|
||||
*/
|
||||
virtual ~Vst3HostContextProxy();
|
||||
|
||||
DECLARE_FUNKNOWN_METHODS
|
||||
|
||||
/**
|
||||
* 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<size_t> owner_instance_id() const {
|
||||
return arguments.owner_instance_id;
|
||||
}
|
||||
|
||||
private:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "host-application.h"
|
||||
|
||||
YaHostApplication::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaHostApplication::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(
|
||||
Steinberg::FUnknownPtr<Steinberg::Vst::IHostApplication>(object)) {}
|
||||
|
||||
YaHostApplication::YaHostApplication(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -0,0 +1,116 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <bitsery/ext/std_optional.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <pluginterfaces/vst/ivsthostapplication.h>
|
||||
|
||||
#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<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value1b(supported);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate this instance with arguments read from another interface
|
||||
* implementation.
|
||||
*/
|
||||
YaHostApplication(const ConstructArgs&& args);
|
||||
|
||||
inline bool supported() const { return arguments.supported; }
|
||||
|
||||
/**
|
||||
* The response code and resulting value for a call to
|
||||
* `IHostApplication::getName()`.
|
||||
*/
|
||||
struct GetNameResponse {
|
||||
UniversalTResult result;
|
||||
std::u16string name;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.object(result);
|
||||
s.text2b(name, std::extent_v<Steinberg::Vst::String128>);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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<native_size_t> owner_instance_id;
|
||||
|
||||
template <typename S>
|
||||
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;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -0,0 +1,96 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "message.h"
|
||||
|
||||
YaMessagePtr::YaMessagePtr(){FUNKNOWN_CTOR}
|
||||
|
||||
YaMessagePtr::YaMessagePtr(IMessage& message)
|
||||
: message_id(message.getMessageID()
|
||||
? std::make_optional<std::string>(message.getMessageID())
|
||||
: std::nullopt),
|
||||
original_message_ptr(static_cast<native_size_t>(
|
||||
reinterpret_cast<size_t>(&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<IMessage*>(
|
||||
static_cast<size_t>(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() {
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitsery/ext/std_optional.h>
|
||||
#include <pluginterfaces/vst/ivstmessage.h>
|
||||
|
||||
#include "../common.h"
|
||||
#include "attribute-list.h"
|
||||
#include "base.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
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<std::string> 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:
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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<std::string> message_id;
|
||||
|
||||
YaAttributeList attribute_list;
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "param-value-queue.h"
|
||||
|
||||
YaParamValueQueue::YaParamValueQueue(){FUNKNOWN_CTOR}
|
||||
|
||||
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
|
||||
|
||||
// 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
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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<int32>(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;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitsery/traits/vector.h>
|
||||
#include <pluginterfaces/vst/ivstparameterchanges.h>
|
||||
|
||||
#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();
|
||||
|
||||
/**
|
||||
* 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& original_queue);
|
||||
|
||||
~YaParamValueQueue();
|
||||
|
||||
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;
|
||||
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 <typename S>
|
||||
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<int32, double>& pair) {
|
||||
s.value4b(pair.first);
|
||||
s.value8b(pair.second);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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<std::pair<int32, Steinberg::Vst::ParamValue>> queue;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#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
|
||||
|
||||
size_t YaParameterChanges::num_parameters() const {
|
||||
return queues.size();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Steinberg::Vst::IParamValueQueue* PLUGIN_API
|
||||
YaParameterChanges::getParameterData(int32 index) {
|
||||
if (index < static_cast<int32>(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];
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginterfaces/vst/ivstparameterchanges.h>
|
||||
|
||||
#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
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
void write_back_outputs(
|
||||
Steinberg::Vst::IParameterChanges& output_queues) const;
|
||||
|
||||
// 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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.container(queues, 1 << 16);
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* The parameter value changes queues.
|
||||
*/
|
||||
std::vector<YaParamValueQueue> queues;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "plug-frame-proxy.h"
|
||||
|
||||
Vst3PlugFrameProxy::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
Vst3PlugFrameProxy::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> 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;
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#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<FUnknown> 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 <typename S>
|
||||
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
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "plug-frame.h"
|
||||
|
||||
YaPlugFrame::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaPlugFrame::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(Steinberg::FUnknownPtr<Steinberg::IPlugFrame>(object)) {}
|
||||
|
||||
YaPlugFrame::YaPlugFrame(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginterfaces/gui/iplugview.h>
|
||||
|
||||
#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<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
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; }
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
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;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "plug-view-proxy.h"
|
||||
|
||||
Vst3PlugViewProxy::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
Vst3PlugViewProxy::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> 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;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#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<FUnknown> 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 <typename S>
|
||||
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);
|
||||
|
||||
/**
|
||||
* Message to request the Wine plugin host to destroy the `IPlugView*`
|
||||
* returned by the object with the given instance ID. Sent from the
|
||||
* destructor of `Vst3PlugViewProxyImpl`.
|
||||
*/
|
||||
struct Destruct {
|
||||
using Response = Ack;
|
||||
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
template <typename S>
|
||||
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.
|
||||
*/
|
||||
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
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "plug-view.h"
|
||||
|
||||
YaPlugView::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaPlugView::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(Steinberg::FUnknownPtr<Steinberg::IPlugView>(object)) {}
|
||||
|
||||
YaPlugView::YaPlugView(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -0,0 +1,358 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginterfaces/gui/iplugview.h>
|
||||
|
||||
#include "../../common.h"
|
||||
#include "../base.h"
|
||||
#include "../plug-frame-proxy.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<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
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; }
|
||||
|
||||
/**
|
||||
* Message to pass through a call to
|
||||
* `IPlugView::isPlatformTypeSupported(type)` to the Wine plugin host. We
|
||||
* will of course change `kPlatformStringLinux` for `kPlatformStringWin`,
|
||||
* because why would a Windows VST3 plugin have X11 support? (and how would
|
||||
* that even work)
|
||||
*/
|
||||
struct IsPlatformTypeSupported {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
std::string type;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.text1b(type, 128);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API
|
||||
isPlatformTypeSupported(Steinberg::FIDString type) override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IPlugView::attached(parent, type)` to
|
||||
* the Wine plugin host. Like mentioned above we will substitute
|
||||
* `kPlatformStringWin` for `kPlatformStringLinux`.
|
||||
*/
|
||||
struct Attached {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
/**
|
||||
* The parent handle passed by the host. This will be an
|
||||
* `xcb_window_id`, and we'll embed the Wine window into it ourselves.
|
||||
*/
|
||||
native_size_t parent;
|
||||
std::string type;
|
||||
|
||||
template <typename S>
|
||||
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;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IPlugView::removed()` to the Wine
|
||||
* plugin host.
|
||||
*/
|
||||
struct Removed {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API removed() override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IPlugView::onWheel(distance)` to the
|
||||
* Wine plugin host.
|
||||
*/
|
||||
struct OnWheel {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
float distance;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.value4b(distance);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API onWheel(float distance) override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IPlugView::onKeyDown(key, keyCode,
|
||||
* modifiers)` to the Wine plugin host.
|
||||
*/
|
||||
struct OnKeyDown {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
char16 key;
|
||||
int16 key_code;
|
||||
int16 modifiers;
|
||||
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
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;
|
||||
|
||||
/**
|
||||
* The response code and editor size returned by a call to
|
||||
* `IPlugView::getSize(&size)`.
|
||||
*/
|
||||
struct GetSizeResponse {
|
||||
UniversalTResult result;
|
||||
Steinberg::ViewRect updated_size;
|
||||
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.object(size);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API getSize(Steinberg::ViewRect* size) override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IPlugView::onSize(new_size)` to the
|
||||
* Wine plugin host.
|
||||
*/
|
||||
struct OnSize {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
Steinberg::ViewRect new_size;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.object(new_size);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API
|
||||
onSize(Steinberg::ViewRect* newSize) override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IPlugView::onFocus(state)` to the Wine
|
||||
* plugin host.
|
||||
*/
|
||||
struct OnFocus {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
TBool state;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.value1b(state);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API onFocus(TBool state) override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IPlugView::setFrame()` to the Wine
|
||||
* plugin host. We will read what interfaces the passed `IPlugFrame` 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
|
||||
* `Vst3PlugFrameProxy` object should be bound to the `IPlugView` we are
|
||||
* creating it for.
|
||||
*/
|
||||
struct SetFrame {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
Vst3PlugFrameProxy::ConstructArgs plug_frame_args;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.object(plug_frame_args);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API
|
||||
setFrame(Steinberg::IPlugFrame* frame) override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IPlugView::canResize()` to the Wine
|
||||
* plugin host.
|
||||
*/
|
||||
struct CanResize {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API canResize() override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IPlugView::checkSizeConstraint(rect)`
|
||||
* to the Wine plugin host.
|
||||
*/
|
||||
struct CheckSizeConstraint {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t owner_instance_id;
|
||||
|
||||
Steinberg::ViewRect rect;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(owner_instance_id);
|
||||
s.object(rect);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API
|
||||
checkSizeConstraint(Steinberg::ViewRect* rect) override = 0;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
namespace Steinberg {
|
||||
template <typename S>
|
||||
void serialize(S& s, ViewRect& rect) {
|
||||
s.value4b(rect.left);
|
||||
s.value4b(rect.top);
|
||||
s.value4b(rect.right);
|
||||
s.value4b(rect.bottom);
|
||||
}
|
||||
} // namespace Steinberg
|
||||
@@ -0,0 +1,162 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "plugin-factory.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <type_traits>
|
||||
|
||||
#include <public.sdk/source/vst/utility/stringconvert.h>
|
||||
|
||||
YaPluginFactory::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaPluginFactory::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::IPluginFactory> factory) {
|
||||
// `IPluginFactory::getFactoryInfo`
|
||||
if (Steinberg::PFactoryInfo info;
|
||||
factory->getFactoryInfo(&info) == Steinberg::kResultOk) {
|
||||
factory_info = info;
|
||||
}
|
||||
// `IPluginFactory::countClasses`
|
||||
num_classes = factory->countClasses();
|
||||
// `IPluginFactory::getClassInfo`
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Steinberg::FUnknownPtr<Steinberg::IPluginFactory2> factory2(factory);
|
||||
if (!factory2) {
|
||||
return;
|
||||
}
|
||||
|
||||
supports_plugin_factory_2 = true;
|
||||
// `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) {
|
||||
class_infos_2[i] = info;
|
||||
}
|
||||
}
|
||||
|
||||
Steinberg::FUnknownPtr<Steinberg::IPluginFactory3> factory3(factory);
|
||||
if (!factory3) {
|
||||
return;
|
||||
}
|
||||
|
||||
supports_plugin_factory_3 = true;
|
||||
// `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) {
|
||||
class_infos_unicode[i] = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#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 (arguments.supports_plugin_factory_2) {
|
||||
QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory2::iid,
|
||||
Steinberg::IPluginFactory2)
|
||||
}
|
||||
if (arguments.supports_plugin_factory_3) {
|
||||
QUERY_INTERFACE(_iid, obj, Steinberg::IPluginFactory3::iid,
|
||||
Steinberg::IPluginFactory3)
|
||||
}
|
||||
|
||||
*obj = nullptr;
|
||||
return Steinberg::kNoInterface;
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
YaPluginFactory::getFactoryInfo(Steinberg::PFactoryInfo* info) {
|
||||
if (info && arguments.factory_info) {
|
||||
*info = *arguments.factory_info;
|
||||
return Steinberg::kResultOk;
|
||||
} else {
|
||||
return Steinberg::kNotInitialized;
|
||||
}
|
||||
}
|
||||
|
||||
int32 PLUGIN_API YaPluginFactory::countClasses() {
|
||||
return arguments.num_classes;
|
||||
}
|
||||
|
||||
tresult PLUGIN_API YaPluginFactory::getClassInfo(Steinberg::int32 index,
|
||||
Steinberg::PClassInfo* info) {
|
||||
if (index >= static_cast<int32>(arguments.class_infos_1.size())) {
|
||||
return Steinberg::kInvalidArgument;
|
||||
}
|
||||
|
||||
if (arguments.class_infos_1[index]) {
|
||||
*info = *arguments.class_infos_1[index];
|
||||
return Steinberg::kResultOk;
|
||||
} else {
|
||||
return Steinberg::kResultFalse;
|
||||
}
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
YaPluginFactory::getClassInfo2(int32 index, Steinberg::PClassInfo2* info) {
|
||||
if (index >= static_cast<int32>(arguments.class_infos_2.size())) {
|
||||
return Steinberg::kInvalidArgument;
|
||||
}
|
||||
|
||||
if (arguments.class_infos_2[index]) {
|
||||
*info = *arguments.class_infos_2[index];
|
||||
return Steinberg::kResultOk;
|
||||
} else {
|
||||
return Steinberg::kResultFalse;
|
||||
}
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
YaPluginFactory::getClassInfoUnicode(int32 index,
|
||||
Steinberg::PClassInfoW* info) {
|
||||
if (index >= static_cast<int32>(arguments.class_infos_unicode.size())) {
|
||||
return Steinberg::kInvalidArgument;
|
||||
}
|
||||
|
||||
if (arguments.class_infos_unicode[index]) {
|
||||
*info = *arguments.class_infos_unicode[index];
|
||||
return Steinberg::kResultOk;
|
||||
} else {
|
||||
return Steinberg::kResultFalse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitsery/ext/std_optional.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <pluginterfaces/base/ipluginbase.h>
|
||||
|
||||
#include "../../bitsery/ext/vst3.h"
|
||||
#include "base.h"
|
||||
#include "host-context-proxy.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
|
||||
|
||||
/**
|
||||
* Wraps around `IPluginFactory{1,2,3}` for serialization purposes. See
|
||||
* `docs/vst3.md` for more information on how this works.
|
||||
*/
|
||||
class YaPluginFactory : public Steinberg::IPluginFactory3 {
|
||||
public:
|
||||
/**
|
||||
* These are the arguments for constructing a `YaPluginFactoryImpl`.
|
||||
*/
|
||||
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 `known_iids` will be set accordingly.
|
||||
*/
|
||||
ConstructArgs(Steinberg::IPtr<Steinberg::IPluginFactory> factory);
|
||||
|
||||
/**
|
||||
* Whether `factory` supported `IPluginFactory2`.
|
||||
*/
|
||||
bool supports_plugin_factory_2 = false;
|
||||
|
||||
/**
|
||||
* Whether `factory` supported `IPluginFactory3`.
|
||||
*/
|
||||
bool supports_plugin_factory_3 = false;
|
||||
|
||||
/**
|
||||
* For `IPluginFactory::getFactoryInfo`.
|
||||
*/
|
||||
std::optional<Steinberg::PFactoryInfo> 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<std::optional<Steinberg::PClassInfo>> class_infos_1;
|
||||
|
||||
/**
|
||||
* For `IPluginFactory2::getClassInfo2`, works the same way as the
|
||||
* above.
|
||||
*/
|
||||
std::vector<std::optional<Steinberg::PClassInfo2>> class_infos_2;
|
||||
|
||||
/**
|
||||
* For `IPluginFactory3::getClassInfoUnicode`, works the same way as the
|
||||
* above.
|
||||
*/
|
||||
std::vector<std::optional<Steinberg::PClassInfoW>> class_infos_unicode;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
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,
|
||||
[](S& s, std::optional<Steinberg::PClassInfo>& info) {
|
||||
s.ext(info, bitsery::ext::StdOptional{});
|
||||
});
|
||||
s.container(class_infos_2, 2048,
|
||||
[](S& s, std::optional<Steinberg::PClassInfo2>& info) {
|
||||
s.ext(info, bitsery::ext::StdOptional{});
|
||||
});
|
||||
s.container(class_infos_unicode, 2048,
|
||||
[](S& s, std::optional<Steinberg::PClassInfoW>& info) {
|
||||
s.ext(info, bitsery::ext::StdOptional{});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Message to request the `IPluginFactory{,2,3}`'s information from the Wine
|
||||
* plugin host.
|
||||
*/
|
||||
struct Construct {
|
||||
using Response = ConstructArgs;
|
||||
|
||||
template <typename S>
|
||||
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 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();
|
||||
|
||||
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;
|
||||
/**
|
||||
* See the implementation in `YaPluginFactoryImpl` for how this is handled.
|
||||
*/
|
||||
virtual tresult PLUGIN_API createInstance(Steinberg::FIDString cid,
|
||||
Steinberg::FIDString _iid,
|
||||
void** obj) override = 0;
|
||||
|
||||
// From `IPluginFactory2`
|
||||
tresult PLUGIN_API getClassInfo2(int32 index,
|
||||
Steinberg::PClassInfo2* info) override;
|
||||
|
||||
// From `IPluginFactory3`
|
||||
tresult PLUGIN_API
|
||||
getClassInfoUnicode(int32 index, Steinberg::PClassInfoW* info) override;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IPluginFactory3::setHostContext()` to
|
||||
* 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;
|
||||
|
||||
Vst3HostContextProxy::ConstructArgs host_context_args;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.object(host_context_args);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API
|
||||
setHostContext(Steinberg::FUnknown* context) override = 0;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// Serialization functions have to live in the same namespace as the objects
|
||||
// they're serializing
|
||||
namespace Steinberg {
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "plugin-proxy.h"
|
||||
|
||||
Vst3PluginProxy::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
Vst3PluginProxy::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object,
|
||||
size_t instance_id)
|
||||
: instance_id(instance_id),
|
||||
audio_processor_args(object),
|
||||
component_args(object),
|
||||
connection_point_args(object),
|
||||
edit_controller_args(object),
|
||||
edit_controller_2_args(object),
|
||||
plugin_base_args(object),
|
||||
program_list_data_args(object),
|
||||
unit_data_args(object),
|
||||
unit_info_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)),
|
||||
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)),
|
||||
YaUnitData(std::move(args.unit_data_args)),
|
||||
YaUnitInfo(std::move(args.unit_info_args)),
|
||||
arguments(std::move(args)){FUNKNOWN_CTOR}
|
||||
|
||||
Vst3PluginProxy::~Vst3PluginProxy() {
|
||||
FUNKNOWN_DTOR
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
|
||||
IMPLEMENT_REFCOUNT(Vst3PluginProxy)
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
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`
|
||||
if (Steinberg::FUnknownPrivate ::iidEqual(_iid,
|
||||
Steinberg::FUnknown::iid)) {
|
||||
addRef();
|
||||
*obj = static_cast<Steinberg ::IPluginBase*>(
|
||||
static_cast<YaPluginBase*>(this));
|
||||
return ::Steinberg ::kResultOk;
|
||||
}
|
||||
if (Steinberg::FUnknownPrivate ::iidEqual(
|
||||
_iid, Steinberg::IPluginBase::iid)) {
|
||||
addRef();
|
||||
*obj = static_cast<Steinberg ::IPluginBase*>(
|
||||
static_cast<YaPluginBase*>(this));
|
||||
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 (YaConnectionPoint::supported()) {
|
||||
QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IConnectionPoint::iid,
|
||||
Steinberg::Vst::IConnectionPoint)
|
||||
}
|
||||
if (YaEditController::supported()) {
|
||||
QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IEditController::iid,
|
||||
Steinberg::Vst::IEditController)
|
||||
}
|
||||
if (YaEditController2::supported()) {
|
||||
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 (YaUnitData::supported()) {
|
||||
QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IUnitData::iid,
|
||||
Steinberg::Vst::IUnitData)
|
||||
}
|
||||
if (YaUnitInfo::supported()) {
|
||||
QUERY_INTERFACE(_iid, obj, Steinberg::Vst::IUnitInfo::iid,
|
||||
Steinberg::Vst::IUnitInfo)
|
||||
}
|
||||
|
||||
*obj = nullptr;
|
||||
return Steinberg::kNoInterface;
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitsery/ext/std_variant.h>
|
||||
|
||||
#include "../common.h"
|
||||
#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"
|
||||
#include "plugin/program-list-data.h"
|
||||
#include "plugin/unit-data.h"
|
||||
#include "plugin/unit-info.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 YaConnectionPoint,
|
||||
public YaEditController,
|
||||
public YaEditController2,
|
||||
public YaPluginBase,
|
||||
public YaProgramListData,
|
||||
public YaUnitData,
|
||||
public YaUnitInfo {
|
||||
public:
|
||||
/**
|
||||
* These are the arguments for constructing 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<FUnknown> 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;
|
||||
YaConnectionPoint::ConstructArgs connection_point_args;
|
||||
YaEditController::ConstructArgs edit_controller_args;
|
||||
YaEditController2::ConstructArgs edit_controller_2_args;
|
||||
YaPluginBase::ConstructArgs plugin_base_args;
|
||||
YaProgramListData::ConstructArgs program_list_data_args;
|
||||
YaUnitData::ConstructArgs unit_data_args;
|
||||
YaUnitInfo::ConstructArgs unit_info_args;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
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);
|
||||
s.object(program_list_data_args);
|
||||
s.object(unit_data_args);
|
||||
s.object(unit_info_args);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Message to request the Wine plugin host to instantiate a new IComponent
|
||||
* to pass through a call to `IComponent::createInstance(cid,
|
||||
* <requested_interface>::iid, ...)`.
|
||||
*/
|
||||
struct Construct {
|
||||
using Response = std::variant<ConstructArgs, UniversalTResult>;
|
||||
|
||||
ArrayUID cid;
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.container1b(cid);
|
||||
s.value4b(requested_interface);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
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
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
/**
|
||||
* Message to pass through a call to
|
||||
* `{IComponent,IEditController}::setState(state)` to the Wine plugin host.
|
||||
*/
|
||||
struct SetState {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
VectorStream state;
|
||||
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
template <typename S>
|
||||
void serialize(
|
||||
S& s,
|
||||
std::variant<Vst3PluginProxy::ConstructArgs, UniversalTResult>& result) {
|
||||
s.ext(result, bitsery::ext::StdVariant{});
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "audio-processor.h"
|
||||
|
||||
YaAudioProcessor::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaAudioProcessor::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(
|
||||
Steinberg::FUnknownPtr<Steinberg::Vst::IAudioProcessor>(object)) {}
|
||||
|
||||
YaAudioProcessor::YaAudioProcessor(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -0,0 +1,313 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitsery/ext/std_optional.h>
|
||||
#include <pluginterfaces/vst/ivstaudioprocessor.h>
|
||||
|
||||
#include "../../common.h"
|
||||
#include "../base.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 `Vst3PluginProxy`.
|
||||
*/
|
||||
class YaAudioProcessor : public Steinberg::Vst::IAudioProcessor {
|
||||
public:
|
||||
/**
|
||||
* These are the arguments for creating a `YaAudioProcessor`.
|
||||
*/
|
||||
struct ConstructArgs {
|
||||
ConstructArgs();
|
||||
|
||||
/**
|
||||
* Check whether an existing implementation implements `IAudioProcessor`
|
||||
* and read arguments from it.
|
||||
*/
|
||||
ConstructArgs(Steinberg::IPtr<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value1b(supported);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate this instance with arguments read from another interface
|
||||
* implementation.
|
||||
*/
|
||||
YaAudioProcessor(const ConstructArgs&& args);
|
||||
|
||||
inline bool supported() const { 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<Steinberg::Vst::SpeakerArrangement> inputs;
|
||||
int32 num_ins;
|
||||
std::vector<Steinberg::Vst::SpeakerArrangement> outputs;
|
||||
int32 num_outs;
|
||||
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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<uint32>;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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<uint32>;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
void serialize(S& s, Steinberg::Vst::RoutingInfo& info) {
|
||||
s.value4b(info.mediaType);
|
||||
s.value4b(info.busIndex);
|
||||
s.value4b(info.channel);
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
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
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "component.h"
|
||||
|
||||
YaComponent::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaComponent::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(Steinberg::FUnknownPtr<Steinberg::Vst::IComponent>(object)) {}
|
||||
|
||||
YaComponent::YaComponent(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -0,0 +1,297 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitsery/ext/std_optional.h>
|
||||
#include <bitsery/traits/array.h>
|
||||
#include <pluginterfaces/vst/ivstcomponent.h>
|
||||
|
||||
#include "../../../bitsery/ext/vst3.h"
|
||||
#include "../../common.h"
|
||||
#include "../base.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
|
||||
|
||||
/**
|
||||
* Wraps around `IComponent` for serialization purposes. This is instantiated as
|
||||
* 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:
|
||||
/**
|
||||
* These are the arguments for creating a `YaComponent`.
|
||||
*/
|
||||
struct ConstructArgs {
|
||||
ConstructArgs();
|
||||
|
||||
/**
|
||||
* Check whether an existing implementation implements `IComponent` and
|
||||
* read arguments from it.
|
||||
*/
|
||||
ConstructArgs(Steinberg::IPtr<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value1b(supported);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate this instance with arguments read from another interface
|
||||
* implementation.
|
||||
*/
|
||||
YaComponent(const ConstructArgs&& args);
|
||||
|
||||
inline bool supported() const { return arguments.supported; }
|
||||
|
||||
/**
|
||||
* The response code and returned CID for a call to
|
||||
* `IComponent::getControllerClassId()`.
|
||||
*/
|
||||
struct GetControllerClassIdResponse {
|
||||
UniversalTResult result;
|
||||
ArrayUID editor_cid;
|
||||
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
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
|
||||
* Wine plugin host.
|
||||
*/
|
||||
struct SetIoMode {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
Steinberg::Vst::IoMode mode;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.value4b(mode);
|
||||
}
|
||||
};
|
||||
|
||||
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 = PrimitiveWrapper<int32>;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
Steinberg::Vst::BusType type;
|
||||
Steinberg::Vst::BusDirection dir;
|
||||
|
||||
template <typename S>
|
||||
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;
|
||||
|
||||
/**
|
||||
* The response code and returned bus information for a call to
|
||||
* `IComponent::getBusInfo(type, dir, index, bus <out>)`.
|
||||
*/
|
||||
struct GetBusInfoResponse {
|
||||
UniversalTResult result;
|
||||
Steinberg::Vst::BusInfo updated_bus;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.object(result);
|
||||
s.object(updated_bus);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IComponent::getBusInfo(type, dir,
|
||||
* index, bus <out>)` 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 <typename S>
|
||||
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,
|
||||
int32 index,
|
||||
Steinberg::Vst::BusInfo& bus /*out*/) override = 0;
|
||||
|
||||
/**
|
||||
* The response code and returned routing information for a call to
|
||||
* `IComponent::getRoutingInfo(in_info, out_info <out>)`.
|
||||
*/
|
||||
struct GetRoutingInfoResponse {
|
||||
UniversalTResult result;
|
||||
Steinberg::Vst::RoutingInfo updated_in_info;
|
||||
Steinberg::Vst::RoutingInfo updated_out_info;
|
||||
|
||||
template <typename S>
|
||||
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(in_info,
|
||||
* out_info <out>)` 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 <typename S>
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
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,
|
||||
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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.value1b(state);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API setActive(TBool 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;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "connection-point.h"
|
||||
|
||||
YaConnectionPoint::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaConnectionPoint::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(
|
||||
Steinberg::FUnknownPtr<Steinberg::Vst::IConnectionPoint>(object)) {}
|
||||
|
||||
YaConnectionPoint::Vst3ConnectionPointProxyConstructArgs::
|
||||
Vst3ConnectionPointProxyConstructArgs() {}
|
||||
|
||||
YaConnectionPoint::Vst3ConnectionPointProxyConstructArgs::
|
||||
Vst3ConnectionPointProxyConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> 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)) {}
|
||||
@@ -0,0 +1,209 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include <bitsery/ext/std_optional.h>
|
||||
#include <bitsery/ext/std_variant.h>
|
||||
#include <pluginterfaces/vst/ivstmessage.h>
|
||||
|
||||
#include "../../common.h"
|
||||
#include "../base.h"
|
||||
#include "../message.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).
|
||||
*/
|
||||
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<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value1b(supported);
|
||||
}
|
||||
};
|
||||
|
||||
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<FUnknown> 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 <typename S>
|
||||
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.
|
||||
*/
|
||||
YaConnectionPoint(const ConstructArgs&& args);
|
||||
|
||||
inline bool supported() const { return arguments.supported; }
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
*/
|
||||
std::variant<native_size_t, Vst3ConnectionPointProxyConstructArgs>
|
||||
other;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(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)`
|
||||
* to the Wine plugin host.
|
||||
*/
|
||||
struct Disconnect {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
std::optional<native_size_t> other_instance_id;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.ext(other_instance_id, bitsery::ext::StdOptional{},
|
||||
[](S& s, native_size_t& instance_id) {
|
||||
s.value8b(instance_id);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API disconnect(IConnectionPoint* other) override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IConnectionPoint::notify(message)` to
|
||||
* 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. 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;
|
||||
|
||||
YaMessagePtr message_ptr;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.object(message_ptr);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API
|
||||
notify(Steinberg::Vst::IMessage* message) override = 0;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "edit-controller-2.h"
|
||||
|
||||
YaEditController2::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaEditController2::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(
|
||||
Steinberg::FUnknownPtr<Steinberg::Vst::IEditController2>(object)) {}
|
||||
|
||||
YaEditController2::YaEditController2(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginterfaces/vst/ivsteditcontroller.h>
|
||||
|
||||
#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<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
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; }
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IEditController2::setKnobMode(mode)`
|
||||
* to the Wine plugin host.
|
||||
*/
|
||||
struct SetKnobMode {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
Steinberg::Vst::KnobMode mode;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.value4b(mode);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API
|
||||
setKnobMode(Steinberg::Vst::KnobMode mode) override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to
|
||||
* `IEditController2::openHelp(only_check)` to the Wine plugin host.
|
||||
*/
|
||||
struct OpenHelp {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
TBool only_check;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.value1b(only_check);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API openHelp(TBool onlyCheck) override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to
|
||||
* `IEditController2::openAboutBox(only_check)` to the Wine plugin host.
|
||||
*/
|
||||
struct OpenAboutBox {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
TBool only_check;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.value1b(only_check);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API openAboutBox(TBool onlyCheck) override = 0;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "edit-controller.h"
|
||||
|
||||
YaEditController::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaEditController::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(
|
||||
Steinberg::FUnknownPtr<Steinberg::Vst::IEditController>(object)) {}
|
||||
|
||||
YaEditController::YaEditController(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -0,0 +1,416 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitsery/ext/std_optional.h>
|
||||
#include <pluginterfaces/vst/ivsteditcontroller.h>
|
||||
|
||||
#include "../../common.h"
|
||||
#include "../base.h"
|
||||
#include "../component-handler-proxy.h"
|
||||
#include "../plug-view-proxy.h"
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
|
||||
|
||||
/**
|
||||
* Wraps around `IEditController` for serialization purposes. This is
|
||||
* instantiated as part of `Vst3PluginProxy`.
|
||||
*/
|
||||
class YaEditController : public Steinberg::Vst::IEditController {
|
||||
public:
|
||||
/**
|
||||
* These are the arguments for creating a `YaEditController`.
|
||||
*/
|
||||
struct ConstructArgs {
|
||||
ConstructArgs();
|
||||
|
||||
/**
|
||||
* Check whether an existing implementation implements
|
||||
* `IEditController` and read arguments from it.
|
||||
*/
|
||||
ConstructArgs(Steinberg::IPtr<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value1b(supported);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate this instance with arguments read from another interface
|
||||
* implementation.
|
||||
*/
|
||||
YaEditController(const ConstructArgs&& args);
|
||||
|
||||
inline bool supported() const { return arguments.supported; }
|
||||
|
||||
/**
|
||||
* Message to pass through a call to
|
||||
* `IEditController::setComponentState(state)` to the Wine plugin host.
|
||||
*/
|
||||
struct SetComponentState {
|
||||
using Response = UniversalTResult;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
VectorStream state;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.object(state);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IEditController::getParameterCount()`
|
||||
* to the Wine plugin host.
|
||||
*/
|
||||
struct GetParameterCount {
|
||||
using Response = PrimitiveWrapper<int32>;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
}
|
||||
};
|
||||
|
||||
virtual int32 PLUGIN_API getParameterCount() override = 0;
|
||||
|
||||
/**
|
||||
* The response code and returned parameter information for a call to
|
||||
* `IEditController::getParameterInfo(param_index, &info)`.
|
||||
*/
|
||||
struct GetParameterInfoResponse {
|
||||
UniversalTResult result;
|
||||
Steinberg::Vst::ParameterInfo updated_info;
|
||||
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
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;
|
||||
|
||||
/**
|
||||
* The response code and returned parameter information for a call to
|
||||
* `IEditController::getParamStringByValue(id, value_normalized,
|
||||
* &string)`.
|
||||
*/
|
||||
struct GetParamStringByValueResponse {
|
||||
UniversalTResult result;
|
||||
|
||||
std::u16string string;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.object(result);
|
||||
s.container2b(string, std::extent_v<Steinberg::Vst::String128>);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
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*/,
|
||||
Steinberg::Vst::String128 string /*out*/) override = 0;
|
||||
|
||||
/**
|
||||
* The response code and returned parameter information for a call to
|
||||
* `IEditController::getParamValueByString(id, string,
|
||||
* &&value_normalized)`.
|
||||
*/
|
||||
struct GetParamValueByStringResponse {
|
||||
UniversalTResult result;
|
||||
|
||||
Steinberg::Vst::ParamValue value_normalized;
|
||||
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.value4b(id);
|
||||
s.container2b(string, std::extent_v<Steinberg::Vst::String128>);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API getParamValueByString(
|
||||
Steinberg::Vst::ParamID id,
|
||||
Steinberg::Vst::TChar* string /*in*/,
|
||||
Steinberg::Vst::ParamValue& valueNormalized /*out*/) override = 0;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to
|
||||
* `IEditController::normalizedParamToPlain(id, value_normalized)` to the
|
||||
* Wine plugin host.
|
||||
*/
|
||||
struct NormalizedParamToPlain {
|
||||
using Response = PrimitiveWrapper<Steinberg::Vst::ParamValue>;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
Steinberg::Vst::ParamID id;
|
||||
Steinberg::Vst::ParamValue value_normalized;
|
||||
|
||||
template <typename S>
|
||||
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;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to
|
||||
* `IEditController::plainParamToNormalized(id, plain_value)` to the Wine
|
||||
* plugin host.
|
||||
*/
|
||||
struct PlainParamToNormalized {
|
||||
using Response = PrimitiveWrapper<Steinberg::Vst::ParamValue>;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
Steinberg::Vst::ParamID id;
|
||||
Steinberg::Vst::ParamValue plain_value;
|
||||
|
||||
template <typename S>
|
||||
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;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to
|
||||
* `IEditController::getParamNormalized(id)` to the Wine plugin host.
|
||||
*/
|
||||
struct GetParamNormalized {
|
||||
using Response = PrimitiveWrapper<Steinberg::Vst::ParamValue>;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
Steinberg::Vst::ParamID id;
|
||||
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
Vst3ComponentHandlerProxy::ConstructArgs component_handler_proxy_args;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.object(component_handler_proxy_args);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API setComponentHandler(
|
||||
Steinberg::Vst::IComponentHandler* handler) override = 0;
|
||||
|
||||
/**
|
||||
* The `IPlugView` proxy arguments returned from a call to
|
||||
* `IEditController::createView(name)`. If not empty, then we'll use this to
|
||||
* construct a proxy object that can send control messages to the plugin
|
||||
* instance's actual `IPlugView` object.
|
||||
*/
|
||||
struct CreateViewResponse {
|
||||
std::optional<Vst3PlugViewProxy::ConstructArgs> plug_view_args;
|
||||
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.text1b(name, 128);
|
||||
}
|
||||
};
|
||||
|
||||
virtual Steinberg::IPlugView* PLUGIN_API
|
||||
createView(Steinberg::FIDString name) override = 0;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
template <typename S>
|
||||
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
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "plugin-base.h"
|
||||
|
||||
YaPluginBase::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaPluginBase::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(Steinberg::FUnknownPtr<Steinberg::IPluginBase>(object)) {}
|
||||
|
||||
YaPluginBase::YaPluginBase(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginterfaces/base/ipluginbase.h>
|
||||
|
||||
#include "../../common.h"
|
||||
#include "../base.h"
|
||||
#include "../host-context-proxy.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
|
||||
* `Vst3PluginProxy`.
|
||||
*/
|
||||
class YaPluginBase : public Steinberg::IPluginBase {
|
||||
public:
|
||||
/**
|
||||
* These are the arguments for creating a `YaPluginBase`.
|
||||
*/
|
||||
struct ConstructArgs {
|
||||
ConstructArgs();
|
||||
|
||||
/**
|
||||
* Check whether an existing implementation implements `IPluginBase` and
|
||||
* read arguments from it.
|
||||
*/
|
||||
ConstructArgs(Steinberg::IPtr<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value1b(supported);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate this instance with arguments read from another interface
|
||||
* implementation.
|
||||
*/
|
||||
YaPluginBase(const ConstructArgs&& args);
|
||||
|
||||
inline bool supported() const { return arguments.supported; }
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IPluginBase::initialize()` to the Wine
|
||||
* 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;
|
||||
|
||||
Vst3HostContextProxy::ConstructArgs host_context_args;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
s.object(host_context_args);
|
||||
}
|
||||
};
|
||||
|
||||
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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
}
|
||||
};
|
||||
|
||||
virtual tresult PLUGIN_API terminate() override = 0;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "program-list-data.h"
|
||||
|
||||
YaProgramListData::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaProgramListData::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(
|
||||
Steinberg::FUnknownPtr<Steinberg::Vst::IProgramListData>(object)) {}
|
||||
|
||||
YaProgramListData::YaProgramListData(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -0,0 +1,159 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginterfaces/vst/ivstunits.h>
|
||||
|
||||
#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<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
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; }
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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,
|
||||
Steinberg::IBStream* data) override = 0;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "unit-data.h"
|
||||
|
||||
YaUnitData::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaUnitData::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(
|
||||
Steinberg::FUnknownPtr<Steinberg::Vst::IUnitData>(object)) {}
|
||||
|
||||
YaUnitData::YaUnitData(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -0,0 +1,150 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginterfaces/vst/ivstunits.h>
|
||||
|
||||
#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<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
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; }
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "unit-info.h"
|
||||
|
||||
YaUnitInfo::ConstructArgs::ConstructArgs() {}
|
||||
|
||||
YaUnitInfo::ConstructArgs::ConstructArgs(
|
||||
Steinberg::IPtr<Steinberg::FUnknown> object)
|
||||
: supported(Steinberg::FUnknownPtr<Steinberg::Vst::IUnitInfo>(object)) {}
|
||||
|
||||
YaUnitInfo::YaUnitInfo(const ConstructArgs&& args)
|
||||
: arguments(std::move(args)) {}
|
||||
@@ -0,0 +1,462 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pluginterfaces/vst/ivstunits.h>
|
||||
|
||||
#include "../../common.h"
|
||||
#include "../base.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<Steinberg::FUnknown> object);
|
||||
|
||||
/**
|
||||
* Whether the object supported this interface.
|
||||
*/
|
||||
bool supported;
|
||||
|
||||
template <typename S>
|
||||
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; }
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IUnitInfo::getUnitCount()` to the Wine
|
||||
* plugin host.
|
||||
*/
|
||||
struct GetUnitCount {
|
||||
using Response = PrimitiveWrapper<int32>;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
}
|
||||
};
|
||||
|
||||
virtual int32 PLUGIN_API getUnitCount() override = 0;
|
||||
|
||||
/**
|
||||
* The response code and returned unit information for a call to
|
||||
* `IUnitInfo::getUnitInfo(unit_index, &info)`.
|
||||
*/
|
||||
struct GetUnitInfoResponse {
|
||||
UniversalTResult result;
|
||||
Steinberg::Vst::UnitInfo info;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.object(result);
|
||||
s.object(info);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IUnitInfo::getUnitInfo(unit_index,
|
||||
* &info)` to the Wine plugin host.
|
||||
*/
|
||||
struct GetUnitInfo {
|
||||
using Response = GetUnitInfoResponse;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
int32 unit_index;
|
||||
|
||||
template <typename S>
|
||||
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;
|
||||
|
||||
/**
|
||||
* Message to pass through a call to `IUnitInfo::getProgramListCount()` to
|
||||
* the Wine plugin host.
|
||||
*/
|
||||
struct GetProgramListCount {
|
||||
using Response = PrimitiveWrapper<int32>;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
}
|
||||
};
|
||||
|
||||
virtual int32 PLUGIN_API getProgramListCount() override = 0;
|
||||
|
||||
/**
|
||||
* The response code and returned unit information for a call to
|
||||
* `IUnitInfo::getProgramListInfo(list_index, &info)`.
|
||||
*/
|
||||
struct GetProgramListInfoResponse {
|
||||
UniversalTResult result;
|
||||
Steinberg::Vst::ProgramListInfo info;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.object(result);
|
||||
s.object(info);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Message to pass through a call to
|
||||
* `IUnitInfo::getProgramListInfo(list_index, &info)` to the Wine plugin
|
||||
* host.
|
||||
*/
|
||||
struct GetProgramListInfo {
|
||||
using Response = GetProgramListInfoResponse;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
int32 list_index;
|
||||
|
||||
template <typename S>
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.object(result);
|
||||
s.text2b(name, std::extent_v<Steinberg::Vst::String128>);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
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,
|
||||
Steinberg::Vst::String128 name /*out*/) override = 0;
|
||||
|
||||
/**
|
||||
* The response code and returned value for a call to
|
||||
* `IUnitInfo::getPrograminfo(list_id, program_index, attribute_name,
|
||||
* &attribute_value)`.
|
||||
*/
|
||||
struct GetProgramInfoResponse {
|
||||
UniversalTResult result;
|
||||
std::u16string attribute_value;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.object(result);
|
||||
s.text2b(attribute_value, std::extent_v<Steinberg::Vst::String128>);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
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,
|
||||
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 <typename S>
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
void serialize(S& s) {
|
||||
s.object(result);
|
||||
s.text2b(name, std::extent_v<Steinberg::Vst::String128>);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 <typename S>
|
||||
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,
|
||||
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<Steinberg::Vst::UnitID>;
|
||||
|
||||
native_size_t instance_id;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(instance_id);
|
||||
}
|
||||
};
|
||||
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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 <typename S>
|
||||
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,
|
||||
Steinberg::IBStream* data) override = 0;
|
||||
|
||||
protected:
|
||||
ConstructArgs arguments;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
template <typename S>
|
||||
void serialize(S& s, UnitInfo& info) {
|
||||
s.value4b(info.id);
|
||||
s.value4b(info.parentUnitId);
|
||||
s.text2b(info.name);
|
||||
s.value4b(info.programListId);
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, ProgramListInfo& info) {
|
||||
s.value4b(info.id);
|
||||
s.text2b(info.name);
|
||||
s.value4b(info.programCount);
|
||||
}
|
||||
} // namespace Vst
|
||||
} // namespace Steinberg
|
||||
@@ -0,0 +1,240 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "process-data.h"
|
||||
|
||||
#include "src/common/utils.h"
|
||||
|
||||
YaAudioBusBuffers::YaAudioBusBuffers() {}
|
||||
|
||||
YaAudioBusBuffers::YaAudioBusBuffers(int32 sample_size,
|
||||
size_t num_channels,
|
||||
size_t num_samples)
|
||||
: buffers(sample_size == Steinberg::Vst::SymbolicSampleSizes::kSample64
|
||||
? decltype(buffers)(std::vector<std::vector<double>>(
|
||||
num_channels,
|
||||
std::vector<double>(num_samples, 0.0)))
|
||||
: decltype(buffers)(std::vector<std::vector<float>>(
|
||||
num_channels,
|
||||
std::vector<float>(num_samples, 0.0)))) {}
|
||||
|
||||
YaAudioBusBuffers::YaAudioBusBuffers(
|
||||
int32 sample_size,
|
||||
int32 num_samples,
|
||||
const Steinberg::Vst::AudioBusBuffers& data)
|
||||
: silence_flags(data.silenceFlags) {
|
||||
switch (sample_size) {
|
||||
case Steinberg::Vst::kSample64: {
|
||||
std::vector<std::vector<double>> 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<std::vector<float>> 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() {
|
||||
Steinberg::Vst::AudioBusBuffers reconstructed_buffers;
|
||||
reconstructed_buffers.silenceFlags = silence_flags;
|
||||
std::visit(overload{
|
||||
[&](std::vector<std::vector<double>>& buffers) {
|
||||
buffer_pointers.clear();
|
||||
for (auto& buffer : buffers) {
|
||||
buffer_pointers.push_back(buffer.data());
|
||||
}
|
||||
|
||||
reconstructed_buffers.numChannels = buffers.size();
|
||||
reconstructed_buffers.channelBuffers64 =
|
||||
reinterpret_cast<double**>(buffer_pointers.data());
|
||||
},
|
||||
[&](std::vector<std::vector<float>>& buffers) {
|
||||
buffer_pointers.clear();
|
||||
for (auto& buffer : buffers) {
|
||||
buffer_pointers.push_back(buffer.data());
|
||||
}
|
||||
|
||||
reconstructed_buffers.numChannels = buffers.size();
|
||||
reconstructed_buffers.channelBuffers32 =
|
||||
reinterpret_cast<float**>(buffer_pointers.data());
|
||||
},
|
||||
},
|
||||
buffers);
|
||||
|
||||
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;
|
||||
std::visit(
|
||||
overload{
|
||||
[&](const std::vector<std::vector<double>>& 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<std::vector<float>>& 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)
|
||||
: process_mode(process_data.processMode),
|
||||
symbolic_sample_size(process_data.symbolicSampleSize),
|
||||
num_samples(process_data.numSamples),
|
||||
outputs_num_channels(process_data.numOutputs),
|
||||
// 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<YaEventList>(
|
||||
*process_data.inputEvents)
|
||||
: std::nullopt),
|
||||
output_events_supported(process_data.outputEvents),
|
||||
process_context(process_data.processContext
|
||||
? std::make_optional<Steinberg::Vst::ProcessContext>(
|
||||
*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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)};
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include <bitsery/ext/std_optional.h>
|
||||
#include <bitsery/ext/std_variant.h>
|
||||
#include <pluginterfaces/vst/ivstaudioprocessor.h>
|
||||
|
||||
#include "base.h"
|
||||
#include "event-list.h"
|
||||
#include "parameter-changes.h"
|
||||
|
||||
// This header provides serialization wrappers around `ProcessData`
|
||||
|
||||
/**
|
||||
* A serializable wrapper around `AudioBusBuffers` back by `std::vector<T>`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(int32 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. Similarly the
|
||||
* `ProcessData`' `numSamples` field determines the extent of these arrays.
|
||||
*/
|
||||
YaAudioBusBuffers(int32 sample_size,
|
||||
int32 num_samples,
|
||||
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()`. The object contains pointers to `buffers`, so it
|
||||
* may not outlive this object.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
void write_back_outputs(
|
||||
Steinberg::Vst::AudioBusBuffers& output_buffers) const;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value8b(silence_flags);
|
||||
s.ext(buffers, bitsery::ext::StdVariant{
|
||||
[](S& s, std::vector<std::vector<float>>& buffers) {
|
||||
s.container(buffers, max_num_speakers,
|
||||
[](S& s, auto& channel) {
|
||||
s.container4b(channel, 1 << 16);
|
||||
});
|
||||
},
|
||||
[](S& s, std::vector<std::vector<double>>& buffers) {
|
||||
s.container(buffers, max_num_speakers,
|
||||
[](S& s, auto& channel) {
|
||||
s.container8b(channel, 1 << 16);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* We need these during the reconstruction process to provide a pointer to
|
||||
* an array of pointers to the actual buffers.
|
||||
*/
|
||||
std::vector<void*> buffer_pointers;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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<std::vector<float>>,
|
||||
std::vector<std::vector<double>>>
|
||||
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 {
|
||||
// These fields are directly moved from a `YaProcessData` object. See the
|
||||
// docstrings there for more information
|
||||
std::vector<YaAudioBusBuffers> outputs;
|
||||
std::optional<YaParameterChanges> output_parameter_changes;
|
||||
std::optional<YaEventList> output_events;
|
||||
|
||||
/**
|
||||
* Write all of this output data back to the host's `ProcessData` object.
|
||||
*/
|
||||
void write_back_outputs(Steinberg::Vst::ProcessData& process_data);
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.container(outputs, max_num_speakers);
|
||||
s.ext(output_parameter_changes, bitsery::ext::StdOptional{});
|
||||
s.ext(output_events, bitsery::ext::StdOptional{});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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. 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();
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value4b(process_mode);
|
||||
s.value4b(symbolic_sample_size);
|
||||
s.value4b(num_samples);
|
||||
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
|
||||
}
|
||||
|
||||
// These fields are input and context data read from the original
|
||||
// `ProcessData` object
|
||||
|
||||
/**
|
||||
* The processing mode copied directly from the input struct.
|
||||
*/
|
||||
int32 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.
|
||||
*/
|
||||
int32 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<YaAudioBusBuffers> 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<int32> outputs_num_channels;
|
||||
|
||||
/**
|
||||
* Incoming parameter changes.
|
||||
*/
|
||||
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<YaEventList> 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<Steinberg::Vst::ProcessContext> 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()`.
|
||||
|
||||
/**
|
||||
* 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<YaAudioBusBuffers> outputs;
|
||||
|
||||
/**
|
||||
* The output parameter changes. Will be initialized depending on
|
||||
* `output_parameter_changes_supported`.
|
||||
*/
|
||||
std::optional<YaParameterChanges> output_parameter_changes;
|
||||
|
||||
/**
|
||||
* The output events. Will be initialized depending on
|
||||
* `output_events_supported`.
|
||||
*/
|
||||
std::optional<YaEventList> 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<Steinberg::Vst::AudioBusBuffers> 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<Steinberg::Vst::AudioBusBuffers> outputs_audio_bus_buffers;
|
||||
|
||||
/**
|
||||
* The process data we reconstruct from the other fields during `get()`.
|
||||
*/
|
||||
Steinberg::Vst::ProcessData reconstructed_process_data;
|
||||
};
|
||||
|
||||
namespace Steinberg {
|
||||
namespace Vst {
|
||||
template <typename S>
|
||||
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 <typename S>
|
||||
void serialize(S& s, Steinberg::Vst::Chord& chord) {
|
||||
s.value1b(chord.keyNote);
|
||||
s.value1b(chord.rootNote);
|
||||
s.value2b(chord.chordMask);
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, Steinberg::Vst::FrameRate& frame_rate) {
|
||||
s.value4b(frame_rate.framesPerSecond);
|
||||
s.value4b(frame_rate.flags);
|
||||
}
|
||||
} // namespace Vst
|
||||
} // namespace Steinberg
|
||||
@@ -21,6 +21,15 @@
|
||||
#endif
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
// The cannonical overloading template for `std::visitor`, not sure why this
|
||||
// isn't part of the standard library
|
||||
template <class... Ts>
|
||||
struct overload : Ts... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
template <class... Ts>
|
||||
overload(Ts...) -> overload<Ts...>;
|
||||
|
||||
/**
|
||||
* Return the path to the directory for story temporary files. This will be
|
||||
* `$XDG_RUNTIME_DIR` if set, and `/tmp` otherwise.
|
||||
|
||||
+5
-5
@@ -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
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Generated inside of the build directory
|
||||
#include <src/common/config/config.h>
|
||||
#include <src/common/config/version.h>
|
||||
|
||||
#include "../../common/configuration.h"
|
||||
#include "../../common/utils.h"
|
||||
#include "../host-process.h"
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
*/
|
||||
template <std::derived_from<Sockets> TSockets>
|
||||
class PluginBridge {
|
||||
public:
|
||||
/**
|
||||
* Sets up everything needed to start the host process. Classes deriving
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* @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 <typename F>
|
||||
PluginBridge(PluginType plugin_type, F create_socket_instance)
|
||||
: info(plugin_type),
|
||||
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(info.native_library_path)),
|
||||
generic_logger(Logger::create_from_environment(
|
||||
create_logger_prefix(sockets.base_dir))),
|
||||
plugin_host(
|
||||
config.group
|
||||
? std::unique_ptr<HostProcess>(std::make_unique<GroupHost>(
|
||||
io_context,
|
||||
generic_logger,
|
||||
info,
|
||||
HostRequest{
|
||||
.plugin_type = plugin_type,
|
||||
.plugin_path = info.windows_plugin_path.string(),
|
||||
.endpoint_base_dir = sockets.base_dir.string()},
|
||||
sockets,
|
||||
*config.group))
|
||||
: std::unique_ptr<HostProcess>(
|
||||
std::make_unique<IndividualHost>(
|
||||
io_context,
|
||||
generic_logger,
|
||||
info,
|
||||
HostRequest{.plugin_type = plugin_type,
|
||||
.plugin_path =
|
||||
info.windows_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: '" << 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;
|
||||
init_msg << "sockets: '" << sockets.base_dir.string() << "'"
|
||||
<< std::endl;
|
||||
init_msg << "wine prefix: '";
|
||||
|
||||
std::visit(
|
||||
overload{
|
||||
[&](const OverridenWinePrefix& prefix) {
|
||||
init_msg << prefix.value.string() << " <overridden>";
|
||||
},
|
||||
[&](const boost::filesystem::path& prefix) {
|
||||
init_msg << prefix.string();
|
||||
},
|
||||
[&](const DefaultWinePrefix&) { init_msg << "<default>"; },
|
||||
},
|
||||
info.wine_prefix);
|
||||
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("<defaults>").string() << "'"
|
||||
<< std::endl;
|
||||
|
||||
init_msg << "hosting mode: '";
|
||||
if (config.group) {
|
||||
init_msg << "plugin group \"" << *config.group << "\"";
|
||||
} else {
|
||||
init_msg << "individually";
|
||||
}
|
||||
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;
|
||||
|
||||
init_msg << "other options: ";
|
||||
std::vector<std::string> 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 (config.editor_xembed) {
|
||||
other_options.push_back("editor: XEmbed");
|
||||
}
|
||||
if (!other_options.empty()) {
|
||||
init_msg << join_quoted_strings(other_options) << std::endl;
|
||||
} else {
|
||||
init_msg << "'<none>'" << 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 << " <none>" << std::endl;
|
||||
#endif
|
||||
init_msg << std::endl;
|
||||
|
||||
for (std::string line = ""; std::getline(init_msg, line);) {
|
||||
generic_logger.log(line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about the plugin we're bridging.
|
||||
*/
|
||||
const PluginInfo info;
|
||||
|
||||
boost::asio::io_context io_context;
|
||||
|
||||
/**
|
||||
* The sockets used for communication with the Wine process.
|
||||
*
|
||||
* @remark `sockets.connect()` should not be called directly.
|
||||
* `connect_sockets_guarded()` should be used instead.
|
||||
*
|
||||
* @see PluginBridge::connect_sockets_guarded
|
||||
*/
|
||||
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<HostProcess> 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;
|
||||
|
||||
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;
|
||||
};
|
||||
@@ -14,20 +14,10 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "plugin-bridge.h"
|
||||
#include "vst2.h"
|
||||
|
||||
// Generated inside of the build directory
|
||||
#include <src/common/config/config.h>
|
||||
#include <src/common/config/version.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
|
||||
// boost::filesystem
|
||||
namespace fs = boost::filesystem;
|
||||
#include "../../common/communication/vst2.h"
|
||||
#include "../utils.h"
|
||||
|
||||
intptr_t dispatch_proxy(AEffect*, int, int, intptr_t, void*, float);
|
||||
void process_proxy(AEffect*, float**, float**, int);
|
||||
@@ -41,71 +31,32 @@ 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<PluginBridge*>(plugin.ptr3);
|
||||
Vst2PluginBridge& get_bridge_instance(const AEffect& plugin) {
|
||||
return *static_cast<Vst2PluginBridge*>(plugin.ptr3);
|
||||
}
|
||||
|
||||
PluginBridge::PluginBridge(audioMasterCallback host_callback)
|
||||
: config(load_config_for(get_this_file_location())),
|
||||
vst_plugin_path(find_vst_plugin()),
|
||||
Vst2PluginBridge::Vst2PluginBridge(audioMasterCallback host_callback)
|
||||
: PluginBridge(
|
||||
PluginType::vst2,
|
||||
[](boost::asio::io_context& io_context, const PluginInfo& info) {
|
||||
return Vst2Sockets<std::jthread>(
|
||||
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
|
||||
plugin(),
|
||||
io_context(),
|
||||
sockets(io_context,
|
||||
generate_endpoint_base(
|
||||
vst_plugin_path.filename().replace_extension("").string()),
|
||||
true),
|
||||
host_callback_function(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<HostProcess>(
|
||||
std::make_unique<GroupHost>(io_context,
|
||||
logger,
|
||||
vst_plugin_path,
|
||||
sockets,
|
||||
*config.group))
|
||||
: std::unique_ptr<HostProcess>(
|
||||
std::make_unique<IndividualHost>(io_context,
|
||||
logger,
|
||||
vst_plugin_path,
|
||||
sockets))),
|
||||
has_realtime_priority(set_realtime_priority()),
|
||||
wine_io_handler([&]() { io_context.run(); }) {
|
||||
logger(generic_logger) {
|
||||
log_init_message();
|
||||
|
||||
#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 (!vst_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.
|
||||
@@ -122,7 +73,7 @@ PluginBridge::PluginBridge(audioMasterCallback host_callback)
|
||||
// lockstep anyway
|
||||
host_callback_handler = std::jthread([&]() {
|
||||
sockets.vst_host_callback.receive_events(
|
||||
std::pair<Logger&, bool>(logger, false),
|
||||
std::pair<Vst2Logger&, bool>(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
|
||||
@@ -164,6 +115,15 @@ PluginBridge::PluginBridge(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<uint8_t>& chunk_data,
|
||||
@@ -297,8 +257,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<ChunkData>(response.payload).buffer;
|
||||
chunk.assign(buffer.begin(), buffer.end());
|
||||
@@ -391,12 +351,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
|
||||
@@ -426,21 +386,14 @@ intptr_t PluginBridge::dispatch(AEffect* /*plugin*/,
|
||||
try {
|
||||
// TODO: Add some kind of timeout?
|
||||
return_value = sockets.host_vst_dispatch.send_event(
|
||||
converter, std::pair<Logger&, bool>(logger, true), opcode,
|
||||
index, value, data, option);
|
||||
converter, std::pair<Vst2Logger&, bool>(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
|
||||
logger.log("The plugin crashed during shutdown, ignoring");
|
||||
}
|
||||
|
||||
vst_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();
|
||||
|
||||
delete this;
|
||||
|
||||
return return_value;
|
||||
@@ -478,19 +431,19 @@ intptr_t PluginBridge::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&, bool>(logger, true), opcode, index, value,
|
||||
data, option);
|
||||
converter, std::pair<Vst2Logger&, bool>(logger, true), opcode, index,
|
||||
value, data, option);
|
||||
}
|
||||
|
||||
template <typename T, bool replacing>
|
||||
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<std::vector<T>> input_buffers(plugin.numInputs,
|
||||
std::vector<T>(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};
|
||||
@@ -541,10 +494,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 +506,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<float, true>(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<double, true>(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 +545,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,102 +567,6 @@ void PluginBridge::set_parameter(AEffect* /*plugin*/, int index, float value) {
|
||||
assert(!response.value);
|
||||
}
|
||||
|
||||
void PluginBridge::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 << "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() << " <overridden>";
|
||||
} else {
|
||||
init_msg << find_wineprefix().value_or("<default>").string();
|
||||
}
|
||||
init_msg << "'" << std::endl;
|
||||
|
||||
init_msg << "wine version: '" << 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("<defaults>").string() << "'"
|
||||
<< std::endl;
|
||||
|
||||
init_msg << "hosting mode: '";
|
||||
if (config.group) {
|
||||
init_msg << "plugin group \"" << *config.group << "\"";
|
||||
} else {
|
||||
init_msg << "individually";
|
||||
}
|
||||
if (vst_host->architecture() == PluginArchitecture::vst_32) {
|
||||
init_msg << ", 32-bit";
|
||||
} else {
|
||||
init_msg << ", 64-bit";
|
||||
}
|
||||
init_msg << "'" << std::endl;
|
||||
|
||||
init_msg << "other options: ";
|
||||
std::vector<std::string> 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 << "'<none>'" << 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
|
||||
#if !(defined(WITH_BITBRIDGE) || defined(WITH_WINEDBG))
|
||||
init_msg << " <none>" << 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`
|
||||
|
||||
@@ -19,21 +19,22 @@
|
||||
#include <vestige/aeffectx.h>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/local/stream_protocol.hpp>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "../common/communication.h"
|
||||
#include "../common/configuration.h"
|
||||
#include "../common/logging.h"
|
||||
#include "host-process.h"
|
||||
#include "../../common/communication/vst2.h"
|
||||
#include "../../common/logging/vst2.h"
|
||||
#include "common.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 `<type>{,Plugin}Bridge`
|
||||
* for greppability reasons. The `Plugin` infix is added on the native plugin
|
||||
* side.
|
||||
*/
|
||||
class PluginBridge {
|
||||
class Vst2PluginBridge : PluginBridge<Vst2Sockets<std::jthread>> {
|
||||
public:
|
||||
/**
|
||||
* Initializes the Wine VST bridge. This sets up the sockets for event
|
||||
@@ -45,7 +46,13 @@ 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);
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
@@ -83,10 +90,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,25 +115,12 @@ 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 <typename T, bool replacing>
|
||||
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
|
||||
@@ -135,14 +129,6 @@ class PluginBridge {
|
||||
AEffect plugin;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Format and log all relevant debug information during initialization.
|
||||
*/
|
||||
void log_init_message();
|
||||
|
||||
boost::asio::io_context io_context;
|
||||
Sockets<std::jthread> sockets;
|
||||
|
||||
/**
|
||||
* The thread that handles host callbacks.
|
||||
*/
|
||||
@@ -162,47 +148,10 @@ class PluginBridge {
|
||||
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`.
|
||||
*/
|
||||
Logger 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.
|
||||
*
|
||||
* @see launch_vst_host
|
||||
*/
|
||||
std::unique_ptr<HostProcess> 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 VST 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;
|
||||
Vst2Logger logger;
|
||||
|
||||
/**
|
||||
* A scratch buffer for sending and receiving data during `process`,
|
||||
@@ -0,0 +1,157 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "plug-view-proxy.h"
|
||||
|
||||
Vst3PlugViewProxyImpl::Vst3PlugViewProxyImpl(
|
||||
Vst3PluginBridge& bridge,
|
||||
Vst3PlugViewProxy::ConstructArgs&& args)
|
||||
: Vst3PlugViewProxy(std::move(args)), bridge(bridge) {}
|
||||
|
||||
Vst3PlugViewProxyImpl::~Vst3PlugViewProxyImpl() {
|
||||
// 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
|
||||
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) {
|
||||
// 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,
|
||||
Steinberg::FIDString type) {
|
||||
// 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<native_size_t>(parent),
|
||||
.type = type});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PlugViewProxyImpl::removed() {
|
||||
return bridge.send_message(
|
||||
YaPlugView::Removed{.owner_instance_id = owner_instance_id()});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PlugViewProxyImpl::onWheel(float distance) {
|
||||
return bridge.send_message(YaPlugView::OnWheel{
|
||||
.owner_instance_id = owner_instance_id(), .distance = distance});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PlugViewProxyImpl::onKeyDown(char16 key,
|
||||
int16 keyCode,
|
||||
int16 modifiers) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
return bridge.send_message(YaPlugView::OnFocus{
|
||||
.owner_instance_id = owner_instance_id(), .state = state});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
Vst3PlugViewProxyImpl::setFrame(Steinberg::IPlugFrame* frame) {
|
||||
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() {
|
||||
return bridge.send_message(
|
||||
YaPlugView::CanResize{.owner_instance_id = owner_instance_id()});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
Vst3PlugViewProxyImpl::checkSizeConstraint(Steinberg::ViewRect* rect) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#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;
|
||||
|
||||
/**
|
||||
* 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<Steinberg::IPlugFrame> plug_frame;
|
||||
|
||||
private:
|
||||
Vst3PluginBridge& bridge;
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "plugin-factory.h"
|
||||
|
||||
#include <pluginterfaces/vst/ivstcomponent.h>
|
||||
|
||||
#include "plugin-proxy.h"
|
||||
|
||||
YaPluginFactoryImpl::YaPluginFactoryImpl(Vst3PluginBridge& bridge,
|
||||
YaPluginFactory::ConstructArgs&& args)
|
||||
: YaPluginFactory(std::move(args)), bridge(bridge) {}
|
||||
|
||||
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 || !obj || strnlen(_iid, uid_size) < uid_size) {
|
||||
return Steinberg::kInvalidArgument;
|
||||
}
|
||||
|
||||
ArrayUID cid_array;
|
||||
std::copy(cid, cid + std::extent_v<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. 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;
|
||||
if (Steinberg::FIDStringsEqual(iid_string.c_str(),
|
||||
Steinberg::Vst::IComponent::iid)) {
|
||||
requested_interface = Vst3PluginProxy::Construct::Interface::IComponent;
|
||||
} else if (Steinberg::FIDStringsEqual(
|
||||
iid_string.c_str(), 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
|
||||
// way to convert a `FIDString/char*` into a `FUID`, so this will have
|
||||
// to do.
|
||||
const Steinberg::FUID uid = Steinberg::FUID::fromTUID(
|
||||
*reinterpret_cast<const Steinberg::TUID*>(&*_iid));
|
||||
bridge.logger.log_unknown_interface(
|
||||
"In IPluginFactory::createInstance()", uid);
|
||||
|
||||
*obj = nullptr;
|
||||
return Steinberg::kNotImplemented;
|
||||
}
|
||||
|
||||
std::variant<Vst3PluginProxy::ConstructArgs, UniversalTResult> 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<T>`.
|
||||
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<Steinberg::Vst::IComponent*>(
|
||||
proxy_object);
|
||||
break;
|
||||
case Vst3PluginProxy::Construct::Interface::IEditController:
|
||||
*obj = static_cast<Steinberg::Vst::IEditController*>(
|
||||
proxy_object);
|
||||
break;
|
||||
}
|
||||
|
||||
return Steinberg::kResultOk;
|
||||
},
|
||||
[&](const UniversalTResult& code) -> tresult { return code; }},
|
||||
std::move(result));
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
YaPluginFactoryImpl::setHostContext(Steinberg::FUnknown* 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;
|
||||
|
||||
return bridge.send_message(YaPluginFactory::SetHostContext{
|
||||
.host_context_args = Vst3HostContextProxy::ConstructArgs(
|
||||
host_context, std::nullopt)});
|
||||
} else {
|
||||
bridge.logger.log(
|
||||
"WARNING: Null pointer passed to "
|
||||
"'IPluginFactory3::setHostContext()'");
|
||||
return Steinberg::kInvalidArgument;
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../vst3.h"
|
||||
|
||||
class YaPluginFactoryImpl : public YaPluginFactory {
|
||||
public:
|
||||
YaPluginFactoryImpl(Vst3PluginBridge& bridge,
|
||||
YaPluginFactory::ConstructArgs&& args);
|
||||
|
||||
tresult PLUGIN_API createInstance(Steinberg::FIDString cid,
|
||||
Steinberg::FIDString _iid,
|
||||
void** obj) override;
|
||||
tresult PLUGIN_API setHostContext(Steinberg::FUnknown* context) override;
|
||||
|
||||
// The following pointers are cast from `host_context` if
|
||||
// `IPluginFactory3::setHostContext()` has been called
|
||||
|
||||
Steinberg::FUnknownPtr<Steinberg::Vst::IHostApplication> host_application;
|
||||
|
||||
private:
|
||||
Vst3PluginBridge& bridge;
|
||||
|
||||
/**
|
||||
* An host context if we get passed one through
|
||||
* `IPluginFactory3::setHostContext()`.
|
||||
*/
|
||||
Steinberg::IPtr<Steinberg::FUnknown> host_context;
|
||||
};
|
||||
@@ -0,0 +1,636 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "plugin-proxy.h"
|
||||
|
||||
#include "plug-view-proxy.h"
|
||||
|
||||
Vst3PluginProxyImpl::Vst3PluginProxyImpl(Vst3PluginBridge& bridge,
|
||||
Vst3PluginProxy::ConstructArgs&& args)
|
||||
: Vst3PluginProxy(std::move(args)), bridge(bridge) {
|
||||
bridge.register_plugin_proxy(*this);
|
||||
}
|
||||
|
||||
Vst3PluginProxyImpl::~Vst3PluginProxyImpl() {
|
||||
bridge.send_message(
|
||||
Vst3PluginProxy::Destruct{.instance_id = instance_id()});
|
||||
bridge.unregister_plugin_proxy(*this);
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
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 FUnknown::queryInterface()",
|
||||
Steinberg::FUID::fromTUID(_iid));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::setBusArrangements(
|
||||
Steinberg::Vst::SpeakerArrangement* inputs,
|
||||
int32 numIns,
|
||||
Steinberg::Vst::SpeakerArrangement* outputs,
|
||||
int32 numOuts) {
|
||||
// NOTE: Ardour passes a null pointer when `numIns` or `numOuts` is 0, so we
|
||||
// need to work around that
|
||||
return bridge.send_audio_processor_message(
|
||||
YaAudioProcessor::SetBusArrangements{
|
||||
.instance_id = instance_id(),
|
||||
.inputs =
|
||||
(inputs ? std::vector<Steinberg::Vst::SpeakerArrangement>(
|
||||
inputs, &inputs[numIns])
|
||||
: std::vector<Steinberg::Vst::SpeakerArrangement>()),
|
||||
.num_ins = numIns,
|
||||
.outputs =
|
||||
(outputs ? std::vector<Steinberg::Vst::SpeakerArrangement>(
|
||||
outputs, &outputs[numOuts])
|
||||
: std::vector<Steinberg::Vst::SpeakerArrangement>()),
|
||||
.num_outs = numOuts,
|
||||
});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::getBusArrangement(
|
||||
Steinberg::Vst::BusDirection dir,
|
||||
int32 index,
|
||||
Steinberg::Vst::SpeakerArrangement& 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;
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
Vst3PluginProxyImpl::canProcessSampleSize(int32 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_audio_processor_message(
|
||||
YaAudioProcessor::GetLatencySamples{.instance_id = instance_id()});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
Vst3PluginProxyImpl::setupProcessing(Steinberg::Vst::ProcessSetup& 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_audio_processor_message(YaAudioProcessor::SetProcessing{
|
||||
.instance_id = instance_id(), .state = 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_audio_processor_message(
|
||||
YaAudioProcessor::Process{.instance_id = instance_id(), .data = data});
|
||||
|
||||
response.output_data.write_back_outputs(data);
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
uint32 PLUGIN_API Vst3PluginProxyImpl::getTailSamples() {
|
||||
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_audio_processor_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_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_audio_processor_message(YaComponent::GetBusCount{
|
||||
.instance_id = instance_id(), .type = type, .dir = dir});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
Vst3PluginProxyImpl::getBusInfo(Steinberg::Vst::MediaType type,
|
||||
Steinberg::Vst::BusDirection dir,
|
||||
int32 index,
|
||||
Steinberg::Vst::BusInfo& bus /*out*/) {
|
||||
const GetBusInfoResponse response = bridge.send_audio_processor_message(
|
||||
YaComponent::GetBusInfo{.instance_id = instance_id(),
|
||||
.type = type,
|
||||
.dir = dir,
|
||||
.index = index,
|
||||
.bus = bus});
|
||||
|
||||
bus = response.updated_bus;
|
||||
return response.result;
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::getRoutingInfo(
|
||||
Steinberg::Vst::RoutingInfo& inInfo,
|
||||
Steinberg::Vst::RoutingInfo& outInfo /*out*/) {
|
||||
const GetRoutingInfoResponse response = bridge.send_audio_processor_message(
|
||||
YaComponent::GetRoutingInfo{.instance_id = instance_id(),
|
||||
.in_info = inInfo,
|
||||
.out_info = outInfo});
|
||||
|
||||
inInfo = response.updated_in_info;
|
||||
outInfo = response.updated_out_info;
|
||||
return response.result;
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
Vst3PluginProxyImpl::activateBus(Steinberg::Vst::MediaType type,
|
||||
Steinberg::Vst::BusDirection dir,
|
||||
int32 index,
|
||||
TBool state) {
|
||||
return bridge.send_audio_processor_message(
|
||||
YaComponent::ActivateBus{.instance_id = instance_id(),
|
||||
.type = type,
|
||||
.dir = dir,
|
||||
.index = index,
|
||||
.state = state});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::setActive(TBool state) {
|
||||
return bridge.send_audio_processor_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 = 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 = instance_id()});
|
||||
|
||||
assert(response.updated_state.write_back(state) == Steinberg::kResultOk);
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
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. 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<Vst3PluginProxy*>(other)) {
|
||||
return bridge.send_message(YaConnectionPoint::Connect{
|
||||
.instance_id = instance_id(), .other = other_proxy->instance_id()});
|
||||
} else {
|
||||
connection_point_proxy = other;
|
||||
|
||||
return bridge.send_message(YaConnectionPoint::Connect{
|
||||
.instance_id = instance_id(),
|
||||
.other =
|
||||
Vst3ConnectionPointProxy::ConstructArgs(other, instance_id())});
|
||||
}
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::disconnect(IConnectionPoint* other) {
|
||||
// See `Vst3PluginProxyImpl::connect()`
|
||||
if (auto other_proxy = dynamic_cast<Vst3PluginProxy*>(other)) {
|
||||
return bridge.send_message(YaConnectionPoint::Disconnect{
|
||||
.instance_id = instance_id(),
|
||||
.other_instance_id = other_proxy->instance_id()});
|
||||
} else {
|
||||
const tresult result = bridge.send_message(
|
||||
YaConnectionPoint::Disconnect{.instance_id = instance_id(),
|
||||
.other_instance_id = std::nullopt});
|
||||
connection_point_proxy.reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
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. 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<YaMessagePtr*>(message)) {
|
||||
return bridge.send_message(YaConnectionPoint::Notify{
|
||||
.instance_id = instance_id(), .message_ptr = *message_ptr});
|
||||
} else {
|
||||
bridge.logger.log(
|
||||
"WARNING: Unknown message type passed to "
|
||||
"'IConnectionPoint::notify()', ignoring");
|
||||
return Steinberg::kNotImplemented;
|
||||
}
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
Vst3PluginProxyImpl::setComponentState(Steinberg::IBStream* state) {
|
||||
return bridge.send_message(YaEditController::SetComponentState{
|
||||
.instance_id = instance_id(), .state = state});
|
||||
}
|
||||
|
||||
int32 PLUGIN_API Vst3PluginProxyImpl::getParameterCount() {
|
||||
return bridge.send_message(
|
||||
YaEditController::GetParameterCount{.instance_id = instance_id()});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::getParameterInfo(
|
||||
int32 paramIndex,
|
||||
Steinberg::Vst::ParameterInfo& info /*out*/) {
|
||||
const GetParameterInfoResponse response = bridge.send_message(
|
||||
YaEditController::GetParameterInfo{.instance_id = instance_id(),
|
||||
.param_index = paramIndex,
|
||||
.info = info});
|
||||
|
||||
info = response.updated_info;
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::getParamStringByValue(
|
||||
Steinberg::Vst::ParamID id,
|
||||
Steinberg::Vst::ParamValue valueNormalized /*in*/,
|
||||
Steinberg::Vst::String128 string /*out*/) {
|
||||
const GetParamStringByValueResponse response =
|
||||
bridge.send_message(YaEditController::GetParamStringByValue{
|
||||
.instance_id = 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(
|
||||
Steinberg::Vst::ParamID id,
|
||||
Steinberg::Vst::TChar* string /*in*/,
|
||||
Steinberg::Vst::ParamValue& valueNormalized /*out*/) {
|
||||
const GetParamValueByStringResponse response =
|
||||
bridge.send_message(YaEditController::GetParamValueByString{
|
||||
.instance_id = instance_id(), .id = id, .string = string});
|
||||
|
||||
valueNormalized = response.value_normalized;
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
Steinberg::Vst::ParamValue PLUGIN_API
|
||||
Vst3PluginProxyImpl::normalizedParamToPlain(
|
||||
Steinberg::Vst::ParamID id,
|
||||
Steinberg::Vst::ParamValue valueNormalized) {
|
||||
return bridge.send_message(YaEditController::NormalizedParamToPlain{
|
||||
.instance_id = instance_id(),
|
||||
.id = id,
|
||||
.value_normalized = valueNormalized});
|
||||
}
|
||||
|
||||
Steinberg::Vst::ParamValue PLUGIN_API
|
||||
Vst3PluginProxyImpl::plainParamToNormalized(
|
||||
Steinberg::Vst::ParamID id,
|
||||
Steinberg::Vst::ParamValue plainValue) {
|
||||
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(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(YaEditController::SetParamNormalized{
|
||||
.instance_id = instance_id(), .id = id, .value = value});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::setComponentHandler(
|
||||
Steinberg::Vst::IComponentHandler* handler) {
|
||||
if (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
|
||||
unit_handler = component_handler;
|
||||
|
||||
return bridge.send_message(YaEditController::SetComponentHandler{
|
||||
.instance_id = instance_id(),
|
||||
.component_handler_proxy_args =
|
||||
Vst3ComponentHandlerProxy::ConstructArgs(component_handler,
|
||||
instance_id())});
|
||||
} else {
|
||||
bridge.logger.log(
|
||||
"WARNING: Null pointer passed to "
|
||||
"'IEditController::setComponentHandler()'");
|
||||
return Steinberg::kInvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
Steinberg::IPlugView* PLUGIN_API
|
||||
Vst3PluginProxyImpl::createView(Steinberg::FIDString name) {
|
||||
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.
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
Vst3PluginProxyImpl::setKnobMode(Steinberg::Vst::KnobMode mode) {
|
||||
return bridge.send_message(YaEditController2::SetKnobMode{
|
||||
.instance_id = instance_id(), .mode = mode});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::openHelp(TBool onlyCheck) {
|
||||
return bridge.send_message(YaEditController2::OpenHelp{
|
||||
.instance_id = instance_id(), .only_check = onlyCheck});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::openAboutBox(TBool onlyCheck) {
|
||||
return bridge.send_message(YaEditController2::OpenAboutBox{
|
||||
.instance_id = instance_id(), .only_check = onlyCheck});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::initialize(FUnknown* 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;
|
||||
|
||||
return bridge.send_message(YaPluginBase::Initialize{
|
||||
.instance_id = instance_id(),
|
||||
.host_context_args = Vst3HostContextProxy::ConstructArgs(
|
||||
host_context, instance_id())});
|
||||
} else {
|
||||
bridge.logger.log(
|
||||
"WARNING: Null pointer passed to 'IPluginBase::initialize()'");
|
||||
return Steinberg::kInvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::terminate() {
|
||||
return bridge.send_message(
|
||||
YaPluginBase::Terminate{.instance_id = instance_id()});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::programDataSupported(
|
||||
Steinberg::Vst::ProgramListID listId) {
|
||||
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) {
|
||||
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) {
|
||||
return bridge.send_message(
|
||||
YaProgramListData::SetProgramData{.instance_id = instance_id(),
|
||||
.list_id = listId,
|
||||
.program_index = programIndex,
|
||||
.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()});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
Vst3PluginProxyImpl::getUnitInfo(int32 unitIndex,
|
||||
Steinberg::Vst::UnitInfo& info /*out*/) {
|
||||
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() {
|
||||
return bridge.send_message(
|
||||
YaUnitInfo::GetProgramListCount{.instance_id = instance_id()});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::getProgramListInfo(
|
||||
int32 listIndex,
|
||||
Steinberg::Vst::ProgramListInfo& info /*out*/) {
|
||||
const GetProgramListInfoResponse response =
|
||||
bridge.send_message(YaUnitInfo::GetProgramListInfo{
|
||||
.instance_id = instance_id(), .list_index = listIndex});
|
||||
|
||||
info = response.info;
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
Vst3PluginProxyImpl::getProgramName(Steinberg::Vst::ProgramListID listId,
|
||||
int32 programIndex,
|
||||
Steinberg::Vst::String128 name /*out*/) {
|
||||
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(
|
||||
Steinberg::Vst::ProgramListID listId,
|
||||
int32 programIndex,
|
||||
Steinberg::Vst::CString attributeId /*in*/,
|
||||
Steinberg::Vst::String128 attributeValue /*out*/) {
|
||||
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
|
||||
Vst3PluginProxyImpl::hasProgramPitchNames(Steinberg::Vst::ProgramListID listId,
|
||||
int32 programIndex) {
|
||||
return bridge.send_message(
|
||||
YaUnitInfo::HasProgramPitchNames{.instance_id = instance_id(),
|
||||
.list_id = listId,
|
||||
.program_index = programIndex});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API Vst3PluginProxyImpl::getProgramPitchName(
|
||||
Steinberg::Vst::ProgramListID listId,
|
||||
int32 programIndex,
|
||||
int16 midiPitch,
|
||||
Steinberg::Vst::String128 name /*out*/) {
|
||||
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() {
|
||||
return bridge.send_message(
|
||||
YaUnitInfo::GetSelectedUnit{.instance_id = instance_id()});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
Vst3PluginProxyImpl::selectUnit(Steinberg::Vst::UnitID unitId) {
|
||||
return bridge.send_message(YaUnitInfo::SelectUnit{
|
||||
.instance_id = instance_id(), .unit_id = unitId});
|
||||
}
|
||||
|
||||
tresult PLUGIN_API
|
||||
Vst3PluginProxyImpl::getUnitByBus(Steinberg::Vst::MediaType type,
|
||||
Steinberg::Vst::BusDirection dir,
|
||||
int32 busIndex,
|
||||
int32 channel,
|
||||
Steinberg::Vst::UnitID& unitId /*out*/) {
|
||||
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) {
|
||||
return bridge.send_message(
|
||||
YaUnitInfo::SetUnitProgramData{.instance_id = instance_id(),
|
||||
.list_or_unit_id = listOrUnitId,
|
||||
.program_index = programIndex,
|
||||
.data = data});
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../vst3.h"
|
||||
#include "plug-view-proxy.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 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;
|
||||
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 `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.
|
||||
// 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,
|
||||
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 `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 `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
|
||||
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
|
||||
* callback on a component handler proxy object, we'll pass the call through
|
||||
* to this object.
|
||||
*/
|
||||
Steinberg::IPtr<Steinberg::Vst::IComponentHandler> 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<Steinberg::Vst::IConnectionPoint> connection_point_proxy;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
Vst3PlugViewProxyImpl* last_created_plug_view = nullptr;
|
||||
|
||||
// The following pointers are cast from `host_context` if
|
||||
// `IPluginBase::initialize()` has been called
|
||||
|
||||
Steinberg::FUnknownPtr<Steinberg::Vst::IHostApplication> host_application;
|
||||
|
||||
// The following pointers are cast from `component_handler` if
|
||||
// `IEditController::setComponentHandler()` has been called
|
||||
|
||||
Steinberg::FUnknownPtr<Steinberg::Vst::IUnitHandler> unit_handler;
|
||||
|
||||
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<Steinberg::FUnknown> host_context;
|
||||
};
|
||||
@@ -0,0 +1,220 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "vst3.h"
|
||||
|
||||
#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: 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,
|
||||
[](boost::asio::io_context& io_context, const PluginInfo& info) {
|
||||
return Vst3Sockets<std::jthread>(
|
||||
io_context,
|
||||
generate_endpoint_base(info.native_library_path.filename()
|
||||
.replace_extension("")
|
||||
.string()),
|
||||
true);
|
||||
}),
|
||||
logger(generic_logger) {
|
||||
log_init_message();
|
||||
|
||||
// This will block until all sockets have been connected to by the Wine VST
|
||||
// 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([&]() {
|
||||
sockets.vst_host_callback.receive_messages(
|
||||
std::pair<Vst3Logger&, bool>(logger, false),
|
||||
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);
|
||||
},
|
||||
[&](const YaComponentHandler::PerformEdit& request)
|
||||
-> YaComponentHandler::PerformEdit::Response {
|
||||
return plugin_proxies.at(request.owner_instance_id)
|
||||
.get()
|
||||
.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);
|
||||
},
|
||||
[&](const YaComponentHandler::RestartComponent& request)
|
||||
-> YaComponentHandler::EndEdit::Response {
|
||||
return plugin_proxies.at(request.owner_instance_id)
|
||||
.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_ptr);
|
||||
},
|
||||
[&](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);
|
||||
},
|
||||
[&](const YaUnitHandler::NotifyUnitSelection& request)
|
||||
-> YaUnitHandler::NotifyUnitSelection::Response {
|
||||
return plugin_proxies.at(request.owner_instance_id)
|
||||
.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);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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<Steinberg::IPluginFactory>` and do the reference counting
|
||||
// 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 {
|
||||
// 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.
|
||||
YaPluginFactory::ConstructArgs factory_args =
|
||||
sockets.host_vst_control.send_message(
|
||||
YaPluginFactory::Construct{},
|
||||
std::pair<Vst3Logger&, bool>(logger, true));
|
||||
plugin_factory =
|
||||
new YaPluginFactoryImpl(*this, std::move(factory_args));
|
||||
}
|
||||
|
||||
return plugin_factory;
|
||||
}
|
||||
|
||||
void Vst3PluginBridge::register_plugin_proxy(
|
||||
Vst3PluginProxyImpl& proxy_object) {
|
||||
std::lock_guard lock(plugin_proxies_mutex);
|
||||
|
||||
plugin_proxies.emplace(proxy_object.instance_id(),
|
||||
std::ref<Vst3PluginProxyImpl>(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(
|
||||
Vst3PluginProxyImpl& proxy_object) {
|
||||
std::lock_guard lock(plugin_proxies_mutex);
|
||||
|
||||
plugin_proxies.erase(proxy_object.instance_id());
|
||||
if (proxy_object.YaAudioProcessor::supported() ||
|
||||
proxy_object.YaComponent::supported()) {
|
||||
sockets.remove_audio_processor(proxy_object.instance_id());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "../..//common/serialization/vst3/plugin-factory.h"
|
||||
#include "../../common/communication/vst3.h"
|
||||
#include "../../common/logging/vst3.h"
|
||||
#include "common.h"
|
||||
|
||||
// Forward declarations
|
||||
class Vst3PluginProxyImpl;
|
||||
class YaPluginFactoryImpl;
|
||||
|
||||
/**
|
||||
* 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 `<type>{,Plugin}Bridge`
|
||||
* for greppability reasons. The `Plugin` infix is added on the native plugin
|
||||
* side.
|
||||
*/
|
||||
class Vst3PluginBridge : PluginBridge<Vst3Sockets<std::jthread>> {
|
||||
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();
|
||||
|
||||
/**
|
||||
* 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
|
||||
* `IPluginFactory*` while doing all the reference counting that `IPtr<T>`
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Add a `Vst3PluginProxyImpl` to the list of registered proxy objects so we
|
||||
* can handle host callbacks. This function is called in
|
||||
* `Vst3PluginProxyImpl`'s constructor. If the plugin supports the
|
||||
* `IAudioProcessor` or `IComponent` interfaces, then we'll also connect to
|
||||
* a dedicated audio processing socket.
|
||||
*
|
||||
* @param proxy_object The proxy object so we can access its host context
|
||||
* and unique instance identifier.
|
||||
*
|
||||
* @see plugin_proxies
|
||||
*/
|
||||
void register_plugin_proxy(Vst3PluginProxyImpl& proxy_object);
|
||||
|
||||
/**
|
||||
* 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 proxy_object The proxy object so we can access its unique instance
|
||||
* identifier.
|
||||
*
|
||||
* @see plugin_proxies
|
||||
*/
|
||||
void unregister_plugin_proxy(Vst3PluginProxyImpl& proxy_object);
|
||||
|
||||
/**
|
||||
* 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>
|
||||
typename T::Response send_message(const T& object) {
|
||||
return sockets.host_vst_control.send_message(
|
||||
object, std::pair<Vst3Logger&, bool>(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>
|
||||
typename T::Response send_audio_processor_message(const T& object) {
|
||||
return sockets.send_audio_processor_message(
|
||||
object, std::pair<Vst3Logger&, bool>(logger, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* `vst_host_callback` sockets.
|
||||
*/
|
||||
std::jthread host_callback_handler;
|
||||
|
||||
/**
|
||||
* 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. 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<IpluginFactory>`.
|
||||
*
|
||||
* @related get_plugin_factory
|
||||
*/
|
||||
YaPluginFactoryImpl* plugin_factory = nullptr;
|
||||
|
||||
private:
|
||||
/**
|
||||
* 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<size_t, std::reference_wrapper<Vst3PluginProxyImpl>>
|
||||
plugin_proxies;
|
||||
std::mutex plugin_proxies_mutex;
|
||||
};
|
||||
+40
-59
@@ -21,8 +21,6 @@
|
||||
#include <boost/process/io.hpp>
|
||||
#include <boost/process/start_dir.hpp>
|
||||
|
||||
#include "../common/communication.h"
|
||||
|
||||
namespace bp = boost::process;
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
@@ -88,38 +86,39 @@ 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<std::jthread>& sockets)
|
||||
const PluginInfo& plugin_info,
|
||||
const HostRequest& host_request)
|
||||
: HostProcess(io_context, logger),
|
||||
plugin_arch(find_vst_architecture(plugin_path)),
|
||||
host_path(find_vst_host(plugin_arch, false)),
|
||||
host(launch_host(host_path,
|
||||
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(host_request.plugin_type),
|
||||
#ifdef WITH_WINEDBG
|
||||
plugin_path.filename(),
|
||||
plugin_info.windows_plugin_path.filename(),
|
||||
#else
|
||||
plugin_path,
|
||||
host_request.plugin_path,
|
||||
#endif
|
||||
sockets.base_dir,
|
||||
bp::env = set_wineprefix(),
|
||||
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
|
||||
}
|
||||
|
||||
PluginArchitecture IndividualHost::architecture() {
|
||||
return plugin_arch;
|
||||
}
|
||||
|
||||
fs::path IndividualHost::path() {
|
||||
return host_path;
|
||||
}
|
||||
@@ -135,15 +134,19 @@ void IndividualHost::terminate() {
|
||||
|
||||
GroupHost::GroupHost(boost::asio::io_context& io_context,
|
||||
Logger& logger,
|
||||
fs::path plugin_path,
|
||||
Sockets<std::jthread>& sockets,
|
||||
const PluginInfo& plugin_info,
|
||||
const HostRequest& host_request,
|
||||
Sockets& sockets,
|
||||
std::string group_name)
|
||||
: HostProcess(io_context, logger),
|
||||
plugin_arch(find_vst_architecture(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) {
|
||||
if (plugin_info.windows_plugin_path.string().find(' ') !=
|
||||
std::string::npos) {
|
||||
logger.log("Warning: winedbg does not support paths containing spaces");
|
||||
}
|
||||
#endif
|
||||
@@ -156,35 +159,17 @@ 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);
|
||||
const auto connect = [&io_context, plugin_path, endpoint_base_dir,
|
||||
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);
|
||||
group_socket.connect(group_socket_path.string());
|
||||
|
||||
write_object(
|
||||
group_socket,
|
||||
GroupRequest{.plugin_path = plugin_path.string(),
|
||||
.endpoint_base_dir = endpoint_base_dir.string()});
|
||||
const auto response = read_object<GroupResponse>(group_socket);
|
||||
write_object(group_socket, host_request);
|
||||
const auto response = read_object<HostResponse>(group_socket);
|
||||
assert(response.pid > 0);
|
||||
};
|
||||
|
||||
@@ -197,14 +182,14 @@ 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();
|
||||
|
||||
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
|
||||
@@ -235,10 +220,6 @@ GroupHost::GroupHost(boost::asio::io_context& io_context,
|
||||
}
|
||||
}
|
||||
|
||||
PluginArchitecture GroupHost::architecture() {
|
||||
return plugin_arch;
|
||||
}
|
||||
|
||||
fs::path GroupHost::path() {
|
||||
return host_path;
|
||||
}
|
||||
@@ -253,8 +234,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) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user