diff --git a/meson.build b/meson.build
index 2fd4577d..7241f537 100644
--- a/meson.build
+++ b/meson.build
@@ -160,6 +160,7 @@ wine_threads_dep = declare_dependency(link_args : '-lpthread')
xcb_dep = dependency('xcb')
dl_dep = declare_dependency(link_args : '-ldl')
+rt_dep = declare_dependency(link_args : '-lrt')
wine_ole32_dep = declare_dependency(link_args : '-lole32')
# The SDK includes a comment pragma that would link to this on MSVC
@@ -313,6 +314,7 @@ vst2_plugin_sources = [
'src/common/configuration.cpp',
'src/common/logging/common.cpp',
'src/common/logging/vst2.cpp',
+ 'src/common/audio-shm.cpp',
'src/common/plugins.cpp',
'src/common/utils.cpp',
'src/plugin/bridges/vst2.cpp',
@@ -381,6 +383,7 @@ vst3_plugin_sources = [
'src/common/serialization/vst3/plugin-proxy.cpp',
'src/common/serialization/vst3/plugin-factory-proxy.cpp',
'src/common/serialization/vst3/process-data.cpp',
+ 'src/common/audio-shm.cpp',
'src/common/configuration.cpp',
'src/common/plugins.cpp',
'src/common/utils.cpp',
@@ -400,6 +403,7 @@ host_common_sources = [
'src/common/configuration.cpp',
'src/common/logging/common.cpp',
'src/common/logging/vst2.cpp',
+ 'src/common/audio-shm.cpp',
'src/common/plugins.cpp',
'src/common/utils.cpp',
'src/wine-host/bridges/common.cpp',
@@ -506,6 +510,7 @@ shared_library(
boost_filesystem_dep,
bitsery_dep,
dl_dep,
+ rt_dep,
threads_dep,
tomlplusplus_dep,
],
@@ -526,6 +531,7 @@ if with_vst3
bitsery_dep,
dl_dep,
function2_dep,
+ rt_dep,
threads_dep,
tomlplusplus_dep,
vst3_sdk_native_dep,
@@ -543,6 +549,7 @@ host_64bit_deps = [
boost_filesystem_dep,
bitsery_dep,
function2_dep,
+ rt_dep,
tomlplusplus_dep,
wine_threads_dep,
xcb_dep,
@@ -605,6 +612,7 @@ if with_bitbridge
boost_filesystem_32bit_dep,
bitsery_dep,
function2_dep,
+ rt_dep,
tomlplusplus_dep,
wine_threads_dep,
xcb_32bit_dep,
diff --git a/src/common/audio-shm.cpp b/src/common/audio-shm.cpp
new file mode 100644
index 00000000..9e7d7a77
--- /dev/null
+++ b/src/common/audio-shm.cpp
@@ -0,0 +1,34 @@
+// yabridge: a Wine VST bridge
+// Copyright (C) 2020-2021 Robbert van der Helm
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include "audio-shm.h"
+
+AudioShmBuffer::AudioShmBuffer(const Config& config)
+ : config(config),
+ shm(boost::interprocess::open_or_create,
+ config.name.c_str(),
+ boost::interprocess::read_write) {
+ shm.truncate(config.size);
+ buffer = boost::interprocess::mapped_region(
+ shm, boost::interprocess::read_write, 0, config.size);
+}
+
+AudioShmBuffer::~AudioShmBuffer() noexcept {
+ // If either side drops this object then the buffer should always be
+ // removed, so we'll do it on both sides to reduce the chance that we leak
+ // shared memory
+ boost::interprocess::shared_memory_object::remove(config.name.c_str());
+}
diff --git a/src/common/audio-shm.h b/src/common/audio-shm.h
new file mode 100644
index 00000000..2728c3c9
--- /dev/null
+++ b/src/common/audio-shm.h
@@ -0,0 +1,145 @@
+// yabridge: a Wine VST bridge
+// Copyright (C) 2020-2021 Robbert van der Helm
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#pragma once
+
+#include
+
+#ifdef __WINE__
+#include "../wine-host/boost-fix.h"
+#endif
+#include
+#include
+
+/**
+ * A shared memory object that allows audio buffers to be shared between the
+ * native plugin and the Wine plugin host. This is intended as an optimization,
+ * and it is used alongside yabridge's usual socket based messages. Normally
+ * audio buffers would have to be copied from the host to the native plugin,
+ * sent to the Wine plugin host, and then copied to a buffer on the Wine plugin
+ * host side for them to be processed by the plugin. The results then have to be
+ * sent back to the native plugin, where they finally have to be copied back to
+ * the host's buffers. While this wouldn't be an issue for small amounts of
+ * data, it also increases the overhead of bridging plugins considerably since
+ * there's not much else going on. So to prevent unnecessary copies, we'll
+ * communicate the audio buffer data through shared memory objects so we can
+ * reduce all of the operations described above to one copy from the host to the
+ * shared memory region, and another copy from the shared memory region back to
+ * the host. And since we're still using messages alongside this, we also don't
+ * need any locks.
+ *
+ * This approach introduces a few additional moving parts that we'd rather not
+ * have to deal with, but the benefits likely outweigh the costs. The buffer is
+ * set up on the Wine side after the VST2 or VST3 plugin has finished preparing
+ * for audio processing. The configuration (e.g. name, and dimensions) for this
+ * shared memory object are then sent back to the plugin so the plugin can map
+ * the same shared memory region.
+ */
+class AudioShmBuffer {
+ public:
+ /**
+ * The parameters needed for creating, configuring and connecting to a
+ * shared audio buffer object. This is done on the Wine plugin host. For
+ * this we need to know the plugin's bus/channel configuration, whether the
+ * host is going to ask the plugin to process 32-bit or 64-bit floating
+ * point audio, and the maximum size of the samples per audio buffer. The
+ * bus/channel configuration can be queried directly from the plugin. For
+ * VST2 plugins the other information is passed before the call to
+ * `effMainsChanged` through `effSetProcessPrecision` and `effSetBlockSize`,
+ * which would thus need to be kept track of. For VST3 plugins this is all
+ * sent as part of the `Steinberg::Vst::ProcessSetup` object.
+ */
+ struct Config {
+ /**
+ * The unique identifier for this shared memory object. The backing file
+ * will be created in `/dev/shm` by the operating system.
+ */
+ std::string name;
+
+ /**
+ * The size of the shared memory object. This should be large enough to
+ * hold all input and output buffers.
+ */
+ uint32_t size;
+
+ /**
+ * Offsets **in samples** within the shared memory object for an input
+ * audio channel, indexed by `[bus][channel]`. For VST2 plugins the bus
+ * will always be 0. This can be used later to retrieve a pointer to the
+ * audio channel.
+ */
+ std::vector> input_offsets;
+ /**
+ * Offsets **in samples** within the shared memory object for an output
+ * audio channel, indexed by `[bus][channel]`. For VST2 plugins the bus
+ * will always be 0. This can be used later to retrieve a pointer to the
+ * audio channel.
+ */
+ std::vector> output_offsets;
+
+ template
+ void serialize(S& s) {
+ s.text1b(name, 1024);
+ s.value4b(size);
+ s.container(input_offsets, 8192, [](S& s, auto& offsets) {
+ s.container4b(offsets, 8192);
+ });
+ s.container(output_offsets, 8192, [](S& s, auto& offsets) {
+ s.container4b(offsets, 8192);
+ });
+ }
+ };
+
+ /**
+ * Connect to or create the shared memory object and map it to this
+ * process's memory. The configuration is created on the Wine side using the
+ * process described in `Config`'s docstring.
+ */
+ AudioShmBuffer(const Config& config);
+
+ /**
+ * Destroy the shared memory object. Either side dropping the object will
+ * cause the object to get destroyed in an effort to avoid memory leaks
+ * caused by crashing plugins or hosts.
+ */
+ ~AudioShmBuffer() noexcept;
+
+ /**
+ * Get a pointer to the part of the buffer where this input audio channel is
+ * stored in.
+ */
+ template
+ T* input_channel_ptr(const uint32_t bus, const uint32_t channel) {
+ return reinterpret_cast(buffer.get_address()) +
+ config.input_offsets[bus][channel];
+ }
+
+ /**
+ * Get a pointer to the part of the buffer where this output audio channel
+ * is stored in.
+ */
+ template
+ T* output_channel_ptr(const uint32_t bus, const uint32_t channel) {
+ return reinterpret_cast(buffer.get_address()) +
+ config.output_offsets[bus][channel];
+ }
+
+ const Config config;
+
+ private:
+ boost::interprocess::shared_memory_object shm;
+ boost::interprocess::mapped_region buffer;
+};
diff --git a/src/wine-host/boost-fix.h b/src/wine-host/boost-fix.h
index 3a948cab..6e50caf0 100644
--- a/src/wine-host/boost-fix.h
+++ b/src/wine-host/boost-fix.h
@@ -34,13 +34,15 @@
#undef _WIN64
// This would be the minimal include needed to get Boost to work. The commented
-// out include below is the actual header that would cause compile errors if not
+// out includes are the actual header that would cause compile errors if not
// included here, but including headers from the detail directory directly
// didn't sound like a great idea.
#include
#include
+#include
// #include
+// #include
#pragma pop_macro("WIN32")
#pragma pop_macro("_WIN32")