mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-06-15 07:53:55 +02:00
Merge branch 'feature/bitbridge'
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
# yabridge
|
# yabridge
|
||||||
|
|
||||||
Yet Another way to use Windows VST2 plugins in Linux VST hosts. This project
|
Yet Another way to use Windows VST2 plugins in Linux VST hosts, including an
|
||||||
aims to be as transparent as possible to achieve the best possible plugin
|
optional bitbridge to run 32-bit Windows plugins in 64-bit Linux hosts. This
|
||||||
compatibility while also being easy to debug and maintain.
|
project aims to be as transparent as possible to achieve the best possible
|
||||||
|
plugin compatibility while also being easy to debug and maintain.
|
||||||
|
|
||||||
## TODOs
|
## TODOs
|
||||||
|
|
||||||
@@ -13,7 +14,8 @@ There are a few things that should be done before releasing this, including:
|
|||||||
- Add missing details if any to the architecture section.
|
- Add missing details if any to the architecture section.
|
||||||
- Rewrite parts of this readme.
|
- Rewrite parts of this readme.
|
||||||
- Briefly verify that this also works fine in Reaper and Ardour.
|
- Briefly verify that this also works fine in Reaper and Ardour.
|
||||||
- Document wine32 support.
|
- Finish 32-bit bitbridge.
|
||||||
|
- Upstream the updated `lib32-boost-libs` PKGBUILD.
|
||||||
|
|
||||||
## Tested with
|
## Tested with
|
||||||
|
|
||||||
@@ -114,19 +116,39 @@ the following dependencies:
|
|||||||
- Boost
|
- Boost
|
||||||
- xcb
|
- xcb
|
||||||
|
|
||||||
The following dependencies are included as a Meson wrap:
|
The following dependencies are included in the repository as a Meson wrap:
|
||||||
|
|
||||||
- bitsery
|
- bitsery
|
||||||
|
|
||||||
The project can then be compiled as follows:
|
The project can then be compiled as follows:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
meson setup --buildtype=release --cross-file cross-wine64.conf build
|
meson setup --buildtype=release --cross-file cross-wine.conf build
|
||||||
ninja -C build
|
ninja -C build
|
||||||
```
|
```
|
||||||
|
|
||||||
When developing or debugging yabridge you can change the build type to either
|
### 32-bit bitbridge
|
||||||
`debug` enable debug symbols and disable optimizations.
|
|
||||||
|
It's also possible to compile a 32-bit 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 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 be
|
||||||
|
enabled as follows:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# On an existing build
|
||||||
|
meson configure build -Duse-bitbridge=true
|
||||||
|
# Configure a new build from scratch
|
||||||
|
meson setup --buildtype=release --cross-file cross-wine.conf -Duse-bitbridge=true build
|
||||||
|
|
||||||
|
ninja -C build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will produce two files called `yabridge-host-32.exe` and
|
||||||
|
`yabridge-host-32.exe.so`. Yabridge will detect whether the plugin you're trying
|
||||||
|
to load is 32-bit or 64-bit, and will run either `yabridge-host.exe` or
|
||||||
|
`yabridge-host-32.exe` accordingly.
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[binaries]
|
||||||
|
c = 'winegcc'
|
||||||
|
cpp = 'wineg++'
|
||||||
|
ar = 'ar'
|
||||||
|
strip = 'strip'
|
||||||
|
|
||||||
|
[properties]
|
||||||
|
# These would be the arguments for 64-bit compilation. We'll set the `-m64` flag
|
||||||
|
# (or `-m32`) in our meson.build to allow the 32-bit bitbridge to be built in
|
||||||
|
# the same build directory as the plugin and the regular 64-bit version of the
|
||||||
|
# host application to ensure that they are always in sync.
|
||||||
|
# https://github.com/mesonbuild/meson/issues/5125
|
||||||
|
|
||||||
|
# c_args = ['-m64']
|
||||||
|
# cpp_args = ['-m64']
|
||||||
|
# cpp_link_args = ['-m64', '-mwindows']
|
||||||
|
|
||||||
|
cpp_link_args = ['-mwindows']
|
||||||
|
needs_exe_wrapper = true
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
[binaries]
|
|
||||||
c = 'winegcc'
|
|
||||||
cpp = 'wineg++'
|
|
||||||
ar = 'ar'
|
|
||||||
strip = 'strip'
|
|
||||||
|
|
||||||
[properties]
|
|
||||||
c_args=['-m64']
|
|
||||||
cpp_args=['-m64']
|
|
||||||
cpp_link_args=['-m64', '-mwindows']
|
|
||||||
needs_exe_wrapper = true
|
|
||||||
|
|
||||||
[host_machine]
|
|
||||||
system = 'linux'
|
|
||||||
cpu_family = 'x86_64'
|
|
||||||
cpu = 'x86_64'
|
|
||||||
endian = 'little'
|
|
||||||
+65
-21
@@ -12,26 +12,36 @@ winelib_check = '''#ifndef __WINE__
|
|||||||
#error 1
|
#error 1
|
||||||
#endif'''
|
#endif'''
|
||||||
if not meson.get_compiler('cpp').compiles(winelib_check)
|
if not meson.get_compiler('cpp').compiles(winelib_check)
|
||||||
error('You need to set up a cross compiler, check the README for more information.')
|
error('You need to set up a cross compiler, check the README for compilation instructions.')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# Depending on the `use-bitbridge` flag we'll enable building a second 32-bit
|
||||||
|
# host application that can act as a bit bridge for using 32-bit Windows plugins
|
||||||
|
# in 64-bit Linux VST hsots. The plugin will determine which host application to
|
||||||
|
# use based on the `.dll` file it's trying to load.
|
||||||
|
# This setup is necessary until Meson provides a way to have multiple
|
||||||
|
# cross-builds for a single build directory:
|
||||||
|
# https://github.com/mesonbuild/meson/issues/5125
|
||||||
|
host_name_64bit = 'yabridge-host'
|
||||||
|
host_name_32bit = 'yabridge-host-32'
|
||||||
|
|
||||||
# This provides an easy way to start the Wine VST host using winedbg since it
|
# This provides an easy way to start the Wine VST host using winedbg since it
|
||||||
# can be quite a pain to set up
|
# can be quite a pain to set up
|
||||||
compiler_options = []
|
compiler_options = []
|
||||||
if get_option('use-winedbg')
|
if get_option('use-winedbg')
|
||||||
compiler_options += '-DUSE_WINEDBG'
|
compiler_options += '-DUSE_WINEDBG'
|
||||||
endif
|
endif
|
||||||
|
if get_option('use-bitbridge')
|
||||||
# The application consists of a VST plugin (yabridge) that calls a winelib
|
compiler_options += '-DUSE_BITBRIDGE'
|
||||||
# program (yabridge-host) that can host Windows VST plugins. More information
|
endif
|
||||||
# about the way these two components work together can be found in the readme
|
|
||||||
# file.
|
|
||||||
|
|
||||||
# Generate header files for configuration variables such as the current git tag
|
# Generate header files for configuration variables such as the current git tag
|
||||||
# and the last commit hash
|
# and the name of the host binary
|
||||||
subdir('src/common/config')
|
subdir('src/common/config')
|
||||||
|
|
||||||
boost_dep = dependency('boost', modules : ['filesystem'])
|
# Statically link against Boost.Filesystem, otherwise it becomes impossible to
|
||||||
|
# distribute a prebuilt version of yabridge
|
||||||
|
boost_dep = dependency('boost', modules : ['filesystem'], static : true)
|
||||||
bitsery_dep = subproject('bitsery').get_variable('bitsery_dep')
|
bitsery_dep = subproject('bitsery').get_variable('bitsery_dep')
|
||||||
threads_dep = dependency('threads')
|
threads_dep = dependency('threads')
|
||||||
# The built in threads dependency does not know how to handle winegcc
|
# The built in threads dependency does not know how to handle winegcc
|
||||||
@@ -40,6 +50,11 @@ xcb_dep = dependency('xcb')
|
|||||||
|
|
||||||
include_dir = include_directories('src/include')
|
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(
|
shared_library(
|
||||||
'yabridge',
|
'yabridge',
|
||||||
[
|
[
|
||||||
@@ -56,21 +71,50 @@ shared_library(
|
|||||||
link_args : ['-ldl']
|
link_args : ['-ldl']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
host_sources = [
|
||||||
|
'src/common/logging.cpp',
|
||||||
|
'src/common/serialization.cpp',
|
||||||
|
'src/wine-host/editor.cpp',
|
||||||
|
'src/wine-host/editor.cpp',
|
||||||
|
'src/wine-host/plugin-bridge.cpp',
|
||||||
|
'src/wine-host/vst-host.cpp',
|
||||||
|
'src/wine-host/utils.cpp',
|
||||||
|
version_header,
|
||||||
|
]
|
||||||
|
|
||||||
executable(
|
executable(
|
||||||
'yabridge-host',
|
host_name_64bit,
|
||||||
[
|
host_sources,
|
||||||
'src/common/logging.cpp',
|
|
||||||
'src/common/serialization.cpp',
|
|
||||||
'src/wine-host/editor.cpp',
|
|
||||||
'src/wine-host/editor.cpp',
|
|
||||||
'src/wine-host/plugin-bridge.cpp',
|
|
||||||
'src/wine-host/vst-host.cpp',
|
|
||||||
'src/wine-host/utils.cpp',
|
|
||||||
version_header,
|
|
||||||
],
|
|
||||||
native : false,
|
native : false,
|
||||||
include_directories : include_dir,
|
include_directories : include_dir,
|
||||||
dependencies : [boost_dep, bitsery_dep, wine_threads_dep, xcb_dep],
|
dependencies : [boost_dep, bitsery_dep, wine_threads_dep, xcb_dep],
|
||||||
cpp_args : compiler_options,
|
cpp_args : compiler_options + ['-m64'],
|
||||||
link_args : []
|
link_args : ['-m64']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if get_option('use-bitbridge')
|
||||||
|
message('Bitbridge enabled, configuring a 32-bit host application')
|
||||||
|
|
||||||
|
# I honestly have no idea what the correct way to have `find_dependency()` use
|
||||||
|
# `/usr/lib32` instead of `/usr/lib` is. If anyone does know, please tell me!
|
||||||
|
winegcc = meson.get_compiler('cpp', native : false)
|
||||||
|
boost_dep = [winegcc.find_library('boost_filesystem')]
|
||||||
|
xcb_dep = [winegcc.find_library('xcb')]
|
||||||
|
|
||||||
|
executable(
|
||||||
|
host_name_32bit,
|
||||||
|
host_sources,
|
||||||
|
native : false,
|
||||||
|
include_directories : include_dir,
|
||||||
|
dependencies : [boost_dep, bitsery_dep, wine_threads_dep, xcb_dep],
|
||||||
|
# FIXME: 32-bit winegcc defines `__stdcall` differently than the 64-bit
|
||||||
|
# version, and one of the changes is the inclusion of
|
||||||
|
# `__atribute__((__force_align_arg_pointer__))`. For whetever reason
|
||||||
|
# this causes GCC to complain when using function pointers with the
|
||||||
|
# `__stdcall` calling convention in template arguments, although it
|
||||||
|
# otherwise works just fine. We don't ignore any warnings in the
|
||||||
|
# regular host application so this should not cause any issues!
|
||||||
|
cpp_args : compiler_options + ['-m32', '-Wno-ignored-attributes'],
|
||||||
|
link_args : ['-m32']
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
|||||||
+9
-1
@@ -1,6 +1,14 @@
|
|||||||
|
option(
|
||||||
|
'use-bitbridge',
|
||||||
|
type : 'boolean',
|
||||||
|
value : false,
|
||||||
|
description : '''Build a 32-bit host application for hosting 32-bit plugins.
|
||||||
|
See the readme for full instructions on how to use this.'''
|
||||||
|
)
|
||||||
|
|
||||||
option(
|
option(
|
||||||
'use-winedbg',
|
'use-winedbg',
|
||||||
type : 'boolean',
|
type : 'boolean',
|
||||||
value : false,
|
value : false,
|
||||||
description: 'Whether to run the Wien VST host with GDB attached. Might not always be reliable.'
|
description : 'Whether to run the Wien VST host with GDB attached. Might not always be reliable.'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ template <typename B>
|
|||||||
using InputAdapter = bitsery::InputBufferAdapter<B>;
|
using InputAdapter = bitsery::InputBufferAdapter<B>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize an object using bitsery and write it to a socket.
|
* 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 socket The Boost.Asio socket to write to.
|
||||||
* @param object The object to write to the stream.
|
* @param object The object to write to the stream.
|
||||||
@@ -53,8 +54,13 @@ inline void write_object(
|
|||||||
|
|
||||||
// Tell the other side how large the object is so it can prepare a buffer
|
// Tell the other side how large the object is so it can prepare a buffer
|
||||||
// large enough before sending the data
|
// 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::write(socket,
|
||||||
boost::asio::buffer(std::array<size_t, 1>{size}));
|
boost::asio::buffer(std::array<uint64_t, 1>{size}));
|
||||||
const size_t bytes_written =
|
const size_t bytes_written =
|
||||||
boost::asio::write(socket, boost::asio::buffer(buffer, size));
|
boost::asio::write(socket, boost::asio::buffer(buffer, size));
|
||||||
assert(bytes_written == size);
|
assert(bytes_written == size);
|
||||||
@@ -79,7 +85,8 @@ template <typename T, typename Socket>
|
|||||||
inline T& read_object(Socket& socket,
|
inline T& read_object(Socket& socket,
|
||||||
T& object,
|
T& object,
|
||||||
std::vector<uint8_t> buffer = std::vector<uint8_t>(64)) {
|
std::vector<uint8_t> buffer = std::vector<uint8_t>(64)) {
|
||||||
std::array<size_t, 1> message_length;
|
// 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::read(socket, boost::asio::buffer(message_length));
|
||||||
|
|
||||||
// Make sure the buffer is large enough
|
// Make sure the buffer is large enough
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the wine host name, e.g. `yabridge-host.exe` for the regular 64
|
||||||
|
* bit build.
|
||||||
|
*/
|
||||||
|
constexpr char yabridge_wine_host_name[] = "@host_binary_64bit@";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the 32-bit wine host name, e.g. `yabridge-host-32.exe`.` This is
|
||||||
|
* used as a bitbridge to be able to load legacy 32-bit only Windows plugins
|
||||||
|
* from a 64-bit Linux host.
|
||||||
|
*/
|
||||||
|
constexpr char yabridge_wine_host_name_32bit[] = "@host_binary_32bit@";
|
||||||
@@ -1,3 +1,16 @@
|
|||||||
|
# Contains constants determined while configuring the build. As an alternative
|
||||||
|
# to preprocessor macros.
|
||||||
|
config_header = configure_file(
|
||||||
|
input : 'config.h.in',
|
||||||
|
output : 'config.h',
|
||||||
|
configuration : configuration_data(
|
||||||
|
{
|
||||||
|
'host_binary_32bit': host_name_32bit + '.exe',
|
||||||
|
'host_binary_64bit': host_name_64bit + '.exe',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Generate a file containing the last annotated git tag and possibly also the
|
# Generate a file containing the last annotated git tag and possibly also the
|
||||||
# amount of commits since then and the hash of the last commit
|
# amount of commits since then and the hash of the last commit
|
||||||
version_header = vcs_tag(
|
version_header = vcs_tag(
|
||||||
|
|||||||
+5
-2
@@ -209,13 +209,16 @@ void passthrough_event(boost::asio::local::stream_protocol::socket& socket,
|
|||||||
[&](const std::vector<uint8_t>& buffer) -> void* {
|
[&](const std::vector<uint8_t>& buffer) -> void* {
|
||||||
return const_cast<uint8_t*>(buffer.data());
|
return const_cast<uint8_t*>(buffer.data());
|
||||||
},
|
},
|
||||||
[&](size_t& window_handle) -> void* {
|
[&](native_size_t& window_handle) -> void* {
|
||||||
// This is the X11 window handle that the editor should
|
// This is the X11 window handle that the editor should
|
||||||
// reparent itself to. We have a special wrapper around the
|
// reparent itself to. We have a special wrapper around the
|
||||||
// dispatch function that intercepts `effEditOpen` events
|
// dispatch function that intercepts `effEditOpen` events
|
||||||
// and creates a Win32 window and then finally embeds the
|
// and creates a Win32 window and then finally embeds the
|
||||||
// X11 window Wine created into this wnidow handle.
|
// X11 window Wine created into this wnidow handle.
|
||||||
return reinterpret_cast<void*>(window_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; },
|
[&](const AEffect&) -> void* { return nullptr; },
|
||||||
[&](DynamicVstEvents& events) -> void* {
|
[&](DynamicVstEvents& events) -> void* {
|
||||||
|
|||||||
@@ -176,7 +176,9 @@ void Logger::log_event(bool is_dispatch,
|
|||||||
[&](const std::vector<uint8_t>& buffer) {
|
[&](const std::vector<uint8_t>& buffer) {
|
||||||
message << "<" << buffer.size() << " byte chunk>";
|
message << "<" << buffer.size() << " byte chunk>";
|
||||||
},
|
},
|
||||||
[&](const intptr_t&) { message << "<nullptr>"; },
|
[&](const native_size_t& window_id) {
|
||||||
|
message << "<window " << window_id << ">";
|
||||||
|
},
|
||||||
[&](const AEffect&) { message << "<nullptr>"; },
|
[&](const AEffect&) { message << "<nullptr>"; },
|
||||||
[&](const DynamicVstEvents& events) {
|
[&](const DynamicVstEvents& events) {
|
||||||
message << "<" << events.events.size() << " midi_events>";
|
message << "<" << events.events.size() << " midi_events>";
|
||||||
|
|||||||
@@ -57,6 +57,23 @@ constexpr size_t max_midi_events = max_buffer_size / sizeof(size_t);
|
|||||||
*/
|
*/
|
||||||
constexpr size_t binary_buffer_size = 50 << 20;
|
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
|
// The cannonical overloading template for `std::visitor`, not sure why this
|
||||||
// isn't part of the standard library
|
// isn't part of the standard library
|
||||||
template <class... Ts>
|
template <class... Ts>
|
||||||
@@ -248,7 +265,7 @@ struct WantsString {};
|
|||||||
using EventPayload = std::variant<std::nullptr_t,
|
using EventPayload = std::variant<std::nullptr_t,
|
||||||
std::string,
|
std::string,
|
||||||
std::vector<uint8_t>,
|
std::vector<uint8_t>,
|
||||||
size_t,
|
native_size_t,
|
||||||
AEffect,
|
AEffect,
|
||||||
DynamicVstEvents,
|
DynamicVstEvents,
|
||||||
WantsChunkBuffer,
|
WantsChunkBuffer,
|
||||||
@@ -270,7 +287,9 @@ void serialize(S& s, EventPayload& payload) {
|
|||||||
[](S& s, std::vector<uint8_t>& buffer) {
|
[](S& s, std::vector<uint8_t>& buffer) {
|
||||||
s.container1b(buffer, binary_buffer_size);
|
s.container1b(buffer, binary_buffer_size);
|
||||||
},
|
},
|
||||||
[](S& s, size_t& window_handle) { s.value8b(window_handle); },
|
[](S& s, native_size_t& window_handle) {
|
||||||
|
s.value8b(window_handle);
|
||||||
|
},
|
||||||
[](S& s, AEffect& effect) { s.object(effect); },
|
[](S& s, AEffect& effect) { s.object(effect); },
|
||||||
[](S& s, DynamicVstEvents& events) {
|
[](S& s, DynamicVstEvents& events) {
|
||||||
s.container(
|
s.container(
|
||||||
@@ -292,10 +311,7 @@ void serialize(S& s, EventPayload& payload) {
|
|||||||
struct Event {
|
struct Event {
|
||||||
int opcode;
|
int opcode;
|
||||||
int index;
|
int index;
|
||||||
// TODO: This is an intptr_t, if we want to support 32 bit Wine plugins all
|
native_intptr_t value;
|
||||||
// of these these intptr_t types should be replace by `uint64_t` to
|
|
||||||
// remain compatible with the Linux VST plugin.
|
|
||||||
intptr_t value;
|
|
||||||
float option;
|
float option;
|
||||||
/**
|
/**
|
||||||
* The event dispatch function has a void pointer parameter that's often
|
* The event dispatch function has a void pointer parameter that's often
|
||||||
@@ -371,7 +387,7 @@ struct EventResult {
|
|||||||
/**
|
/**
|
||||||
* The result that should be returned from the dispatch function.
|
* The result that should be returned from the dispatch function.
|
||||||
*/
|
*/
|
||||||
intptr_t return_value;
|
native_intptr_t return_value;
|
||||||
/**
|
/**
|
||||||
* Events typically either just return their return value or write a string
|
* Events typically either just return their return value or write a string
|
||||||
* into the void pointer, but sometimes an event response should forward
|
* into the void pointer, but sometimes an event response should forward
|
||||||
|
|||||||
+93
-16
@@ -30,6 +30,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Generated inside of build directory
|
// Generated inside of build directory
|
||||||
|
#include <src/common/config/config.h>
|
||||||
#include <src/common/config/version.h>
|
#include <src/common/config/version.h>
|
||||||
|
|
||||||
#include "../common/communication.h"
|
#include "../common/communication.h"
|
||||||
@@ -40,11 +41,6 @@ namespace bp = boost::process;
|
|||||||
// boost::filesystem
|
// boost::filesystem
|
||||||
namespace fs = boost::filesystem;
|
namespace fs = boost::filesystem;
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the wine VST host binary.
|
|
||||||
*/
|
|
||||||
constexpr auto yabridge_wine_host_name = "yabridge-host.exe";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for generating random identifiers.
|
* Used for generating random identifiers.
|
||||||
*/
|
*/
|
||||||
@@ -53,7 +49,8 @@ constexpr char alphanumeric_characters[] =
|
|||||||
|
|
||||||
std::string create_logger_prefix(const fs::path& socket_path);
|
std::string create_logger_prefix(const fs::path& socket_path);
|
||||||
fs::path find_vst_plugin();
|
fs::path find_vst_plugin();
|
||||||
fs::path find_wine_vst_host();
|
PluginArchitecture find_plugin_architecture(fs::path);
|
||||||
|
fs::path find_wine_vst_host(PluginArchitecture plugin_arch);
|
||||||
std::optional<fs::path> find_wineprefix();
|
std::optional<fs::path> find_wineprefix();
|
||||||
fs::path generate_endpoint_name();
|
fs::path generate_endpoint_name();
|
||||||
bp::environment set_wineprefix();
|
bp::environment set_wineprefix();
|
||||||
@@ -74,8 +71,9 @@ HostBridge& get_bridge_instance(const AEffect& plugin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HostBridge::HostBridge(audioMasterCallback host_callback)
|
HostBridge::HostBridge(audioMasterCallback host_callback)
|
||||||
: vst_host_path(find_wine_vst_host()),
|
: vst_plugin_path(find_vst_plugin()),
|
||||||
vst_plugin_path(find_vst_plugin()),
|
vst_plugin_arch(find_plugin_architecture(vst_plugin_path)),
|
||||||
|
vst_host_path(find_wine_vst_host(vst_plugin_arch)),
|
||||||
// All the fields should be zero initialized because
|
// All the fields should be zero initialized because
|
||||||
// `Vst2PluginInstance::vstAudioMasterCallback` from Bitwig's plugin
|
// `Vst2PluginInstance::vstAudioMasterCallback` from Bitwig's plugin
|
||||||
// bridge will crash otherwise
|
// bridge will crash otherwise
|
||||||
@@ -130,6 +128,21 @@ HostBridge::HostBridge(audioMasterCallback host_callback)
|
|||||||
logger.log("wineprefix: '" +
|
logger.log("wineprefix: '" +
|
||||||
find_wineprefix().value_or("<default>").string() + "'");
|
find_wineprefix().value_or("<default>").string() + "'");
|
||||||
|
|
||||||
|
// Include a list of enabled compile-tiem features, mostly to make debug
|
||||||
|
// logs more useful
|
||||||
|
logger.log("");
|
||||||
|
logger.log("Enabled features:");
|
||||||
|
#ifdef USE_BITBRIDGE
|
||||||
|
logger.log("- bitbridge support");
|
||||||
|
#endif
|
||||||
|
#ifdef USE_WINEDBG
|
||||||
|
logger.log("- winedbg");
|
||||||
|
#endif
|
||||||
|
#if !(defined(USE_BITBRIDGE) || defined(USE_WINEDBG))
|
||||||
|
logger.log(" <none>");
|
||||||
|
#endif
|
||||||
|
logger.log("");
|
||||||
|
|
||||||
// It's very important that these sockets are connected to in the same
|
// It's very important that these sockets are connected to in the same
|
||||||
// order in the Wine VST host
|
// order in the Wine VST host
|
||||||
socket_acceptor.accept(host_vst_dispatch);
|
socket_acceptor.accept(host_vst_dispatch);
|
||||||
@@ -480,8 +493,8 @@ std::string create_logger_prefix(const fs::path& socket_path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the Wine VST hsot (named `yabridge-host.exe`). For this we will search
|
* Finds the Wine VST hsot (either `yabridge-host.exe` or `yabridge-host.exe`
|
||||||
* in two places:
|
* depending on the plugin). For this we will search in two places:
|
||||||
*
|
*
|
||||||
* 1. Alongside libyabridge.so if the file got symlinked. This is useful
|
* 1. Alongside libyabridge.so if the file got symlinked. This is useful
|
||||||
* when developing, as you can simply symlink the the libyabridge.so
|
* when developing, as you can simply symlink the the libyabridge.so
|
||||||
@@ -489,23 +502,31 @@ std::string create_logger_prefix(const fs::path& socket_path) {
|
|||||||
* /usr.
|
* /usr.
|
||||||
* 2. In the regular search path.
|
* 2. In the regular search path.
|
||||||
*
|
*
|
||||||
|
* @param plugin_arch The architecture of the plugin, either 64-bit or 32-bit.
|
||||||
|
* Used to determine which host application to use, if available.
|
||||||
|
*
|
||||||
* @return The a path to the VST host, if found.
|
* @return The a path to the VST host, if found.
|
||||||
* @throw std::runtime_error If the Wine VST host could not be found.
|
* @throw std::runtime_error If the Wine VST host could not be found.
|
||||||
*/
|
*/
|
||||||
fs::path find_wine_vst_host() {
|
fs::path find_wine_vst_host(PluginArchitecture plugin_arch) {
|
||||||
|
auto host_name = yabridge_wine_host_name;
|
||||||
|
if (plugin_arch == PluginArchitecture::vst_32) {
|
||||||
|
host_name = yabridge_wine_host_name_32bit;
|
||||||
|
}
|
||||||
|
|
||||||
fs::path host_path =
|
fs::path host_path =
|
||||||
fs::canonical(boost::dll::this_line_location()).remove_filename() /
|
fs::canonical(boost::dll::this_line_location()).remove_filename() /
|
||||||
yabridge_wine_host_name;
|
host_name;
|
||||||
if (fs::exists(host_path)) {
|
if (fs::exists(host_path)) {
|
||||||
return host_path;
|
return host_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bosot will return an empty path if the file could not be found in the
|
// Bosot will return an empty path if the file could not be found in the
|
||||||
// search path
|
// search path
|
||||||
const fs::path vst_host_path = bp::search_path(yabridge_wine_host_name);
|
const fs::path vst_host_path = bp::search_path(host_name);
|
||||||
if (vst_host_path == "") {
|
if (vst_host_path == "") {
|
||||||
throw std::runtime_error("Could not locate '" +
|
throw std::runtime_error("Could not locate '" + std::string(host_name) +
|
||||||
std::string(yabridge_wine_host_name) + "'");
|
"'");
|
||||||
}
|
}
|
||||||
|
|
||||||
return vst_host_path;
|
return vst_host_path;
|
||||||
@@ -555,10 +576,66 @@ fs::path find_vst_plugin() {
|
|||||||
"VST plugin .dll file.");
|
"VST plugin .dll file.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also resolve symlinks here, mostly for development purposes
|
// Also resolve symlinks here
|
||||||
return fs::canonical(plugin_path);
|
return fs::canonical(plugin_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the architecture of a VST plugin (or rather, a .dll file) based on
|
||||||
|
* it's header values.
|
||||||
|
*
|
||||||
|
* See https://docs.microsoft.com/en-us/windows/win32/debug/pe-format for more
|
||||||
|
* information on the PE32 format.
|
||||||
|
*
|
||||||
|
* @param plugin_path The path to the .dll file we're going to check.
|
||||||
|
*
|
||||||
|
* @return The detected architecture.
|
||||||
|
* @throw std::runtime_error If the file is not a .dll file.
|
||||||
|
*/
|
||||||
|
PluginArchitecture find_plugin_architecture(fs::path plugin_path) {
|
||||||
|
std::ifstream file(plugin_path, std::ifstream::binary | std::ifstream::in);
|
||||||
|
|
||||||
|
// The linker will place the offset where the PE signature is placed at the
|
||||||
|
// end of the MS-DOS stub, at this offset
|
||||||
|
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.
|
||||||
|
// file >> pe_signature_offset;
|
||||||
|
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 PluginArchitecture::vst_32;
|
||||||
|
break;
|
||||||
|
case 0x8664: // IMAGE_FILE_MACHINE_AMD64
|
||||||
|
case 0x0000: // IMAGE_FILE_MACHINE_UNKNOWN
|
||||||
|
return PluginArchitecture::vst_64;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::runtime_error(
|
||||||
|
"'" + plugin_path.string() +
|
||||||
|
"' does not have a supported architecture: " +
|
||||||
|
std::to_string(machine_type));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a unique name for the Unix domain socket endpoint based on the VST
|
* Generate a unique name for the Unix domain socket endpoint based on the VST
|
||||||
* plugin's name. This will also generate the parent directory if it does not
|
* plugin's name. This will also generate the parent directory if it does not
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ class patched_async_pipe : public boost::process::async_pipe {
|
|||||||
typedef typename handle_type::executor_type executor_type;
|
typedef typename handle_type::executor_type executor_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tag to differentiate between 32 and 64-bit plugins, used to determine which
|
||||||
|
* host application to use.
|
||||||
|
*/
|
||||||
|
enum class PluginArchitecture { vst_32, vst_64 };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This handles the communication between the Linux native VST plugin and the
|
* This handles the communication between the Linux native VST plugin and the
|
||||||
* Wine VST host. The functions below should be used as callback functions in an
|
* Wine VST host. The functions below should be used as callback functions in an
|
||||||
@@ -90,14 +96,20 @@ class HostBridge {
|
|||||||
float get_parameter(AEffect* plugin, int index);
|
float get_parameter(AEffect* plugin, int index);
|
||||||
void set_parameter(AEffect* plugin, int index, float value);
|
void set_parameter(AEffect* plugin, int index, float value);
|
||||||
|
|
||||||
/**
|
|
||||||
* The path to `yabridge-host.exe`.
|
|
||||||
*/
|
|
||||||
const boost::filesystem::path vst_host_path;
|
|
||||||
/**
|
/**
|
||||||
* The path to the .dll being loaded in the Wine VST host.
|
* The path to the .dll being loaded in the Wine VST host.
|
||||||
*/
|
*/
|
||||||
const boost::filesystem::path vst_plugin_path;
|
const boost::filesystem::path vst_plugin_path;
|
||||||
|
/**
|
||||||
|
* Whether the plugin is 64-bit or 32-bit.
|
||||||
|
*/
|
||||||
|
const PluginArchitecture vst_plugin_arch;
|
||||||
|
/**
|
||||||
|
* The path to the host application (i.e. a path to either
|
||||||
|
* `yabridge-host.exe` or `yabridge-host-32.exe`). The host application will
|
||||||
|
* be chosen depending on the architecture of the VST plugin .dll file.
|
||||||
|
*/
|
||||||
|
const boost::filesystem::path vst_host_path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This AEffect struct will be populated using the data passed by the Wine
|
* This AEffect struct will be populated using the data passed by the Wine
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
// Generated inside of build directory
|
// Generated inside of build directory
|
||||||
|
#include <src/common/config/config.h>
|
||||||
#include <src/common/config/version.h>
|
#include <src/common/config/version.h>
|
||||||
|
|
||||||
#include "plugin-bridge.h"
|
#include "plugin-bridge.h"
|
||||||
@@ -26,9 +27,14 @@ int main(int argc, char* argv[]) {
|
|||||||
// socket to connect to in plugin/bridge.cpp as the first two arguments of
|
// socket to connect to in plugin/bridge.cpp as the first two arguments of
|
||||||
// this process.
|
// this process.
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
std::cerr
|
std::cerr << "Usage: "
|
||||||
<< "Usage: yabridge-host.exe <vst_plugin_dll> <unix_domain_socket>"
|
#ifdef __i386__
|
||||||
<< std::endl;
|
<< yabridge_wine_host_name_32bit
|
||||||
|
#else
|
||||||
|
<< yabridge_wine_host_name
|
||||||
|
#endif
|
||||||
|
<< " <vst_plugin_dll> <unix_domain_socket>" << std::endl;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +42,9 @@ int main(int argc, char* argv[]) {
|
|||||||
const std::string socket_endpoint_path(argv[2]);
|
const std::string socket_endpoint_path(argv[2]);
|
||||||
|
|
||||||
std::cerr << "Initializing yabridge host version " << yabridge_git_version
|
std::cerr << "Initializing yabridge host version " << yabridge_git_version
|
||||||
|
#ifdef __i386__
|
||||||
|
<< " (32-bit compatibility mode)"
|
||||||
|
#endif
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
try {
|
try {
|
||||||
PluginBridge bridge(plugin_dll_path, socket_endpoint_path);
|
PluginBridge bridge(plugin_dll_path, socket_endpoint_path);
|
||||||
|
|||||||
Reference in New Issue
Block a user