Skip to content

Commit aac0e60

Browse files
committed
obs-vst3: Add a VST3 host
This adds a VST3 host to obs-studio on Windows, macOS and Linux. Only audio effects are enabled. Instruments and MIDI are not supported. A maximum of 1 main bus, 1 sidechain input bus and 1 main output bus are enabled. Note that VST3 are not supported on Wayland by the SDK which allows only X11 on Linux. Signed-off-by: pkv <[email protected]>
1 parent c11a15b commit aac0e60

21 files changed

+4014
-1
lines changed

cmake/finders/FindVST3SDK.cmake

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,15 @@ if(VST3SDK_PATH)
4141
VST3SDK_REQUIRED_FILES
4242
pluginterfaces/base/funknown.cpp
4343
pluginterfaces/base/coreiids.cpp
44+
public.sdk/source/vst/vstinitiids.cpp
45+
public.sdk/source/vst/hosting/connectionproxy.cpp
4446
public.sdk/source/vst/hosting/eventlist.cpp
4547
public.sdk/source/vst/hosting/hostclasses.cpp
4648
public.sdk/source/vst/hosting/module.cpp
4749
public.sdk/source/vst/hosting/parameterchanges.cpp
4850
public.sdk/source/vst/hosting/pluginterfacesupport.cpp
4951
public.sdk/source/vst/hosting/processdata.cpp
5052
public.sdk/source/vst/hosting/plugprovider.cpp
51-
public.sdk/source/vst/vstinitiids.cpp
5253
public.sdk/source/common/commonstringconvert.cpp
5354
public.sdk/source/common/memorystream.cpp
5455
public.sdk/source/vst/utility/stringconvert.cpp
@@ -59,18 +60,21 @@ if(VST3SDK_PATH)
5960
APPEND
6061
VST3SDK_REQUIRED_FILES
6162
public.sdk/source/vst/hosting/module_win32.cpp
63+
public.sdk/source/common/threadchecker_win32.cpp
6264
)
6365
elseif(OS_MACOS)
6466
list(
6567
APPEND
6668
VST3SDK_REQUIRED_FILES
6769
public.sdk/source/vst/hosting/module_mac.mm
70+
public.sdk/source/common/threadchecker_mac.mm
6871
)
6972
elseif(OS_LINUX)
7073
list(
7174
APPEND
7275
VST3SDK_REQUIRED_FILES
7376
public.sdk/source/vst/hosting/module_linux.cpp
77+
public.sdk/source/common/threadchecker_linux.cpp
7478
)
7579
endif()
7680

@@ -103,6 +107,7 @@ if(VST3SDK_FOUND)
103107
if(NOT TARGET VST3::SDK)
104108
add_library(VST3::SDK INTERFACE IMPORTED)
105109
set_target_properties(VST3::SDK PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${VST3SDK_PATH}")
110+
message(STATUS "Found VST3 SDK at ${VST3SDK_PATH}")
106111
endif()
107112
endif()
108113

plugins/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ add_obs_plugin(
7575
PLATFORMS WINDOWS MACOS LINUX
7676
WITH_MESSAGE
7777
)
78+
add_obs_plugin(obs-vst3 PLATFORMS WINDOWS MACOS LINUX)
7879
add_obs_plugin(obs-webrtc)
7980

8081
check_obs_websocket()

plugins/obs-vst3/CMakeLists.txt

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
cmake_minimum_required(VERSION 3.28...3.30)
2+
3+
option(ENABLE_VST3 "Enable building OBS with VST3 plugin" ON)
4+
5+
if(NOT ENABLE_VST3)
6+
target_disable(obs-vst3)
7+
return()
8+
endif()
9+
10+
project(obs-vst3)
11+
12+
find_package(Qt6 REQUIRED Widgets)
13+
set(CMAKE_AUTOMOC ON)
14+
15+
add_library(obs-vst3 MODULE)
16+
add_library(OBS::vst3 ALIAS obs-vst3)
17+
18+
find_package(VST3SDK QUIET)
19+
20+
if(NOT VST3SDK_FOUND)
21+
message(STATUS "VST3 SDK not found — disabling obs-vst3 plugin.")
22+
target_disable(obs-vst3)
23+
return()
24+
endif()
25+
26+
# SDK compile warnings
27+
set(
28+
SDK_WARN_FLAGS
29+
-Wno-cast-align
30+
-Wno-conversion
31+
-Wno-cpp
32+
-Wno-delete-non-virtual-dtor
33+
-Wno-deprecated
34+
-Wno-deprecated-copy-dtor
35+
-Wno-deprecated-declarations
36+
-Wno-dangling-else
37+
-Wno-extra
38+
-Wno-extra-semi
39+
-Wno-float-equal
40+
-Wno-format
41+
-Wno-format-security
42+
-Wno-format-truncation
43+
-Wno-ignored-qualifiers
44+
-Wno-int-to-pointer-cast
45+
-Wno-missing-braces
46+
-Wno-missing-field-initializers
47+
-Wno-non-virtual-dtor
48+
-Wno-overloaded-virtual
49+
-Wno-parentheses
50+
-Wno-pedantic
51+
-Wno-redundant-decls
52+
-Wno-reorder
53+
-Wno-shadow
54+
-Wno-sign-compare
55+
-Wno-sign-conversion
56+
-Wno-switch-default
57+
-Wno-type-limits
58+
-Wno-unused-but-set-variable
59+
-Wno-unused-function
60+
-Wno-unused-parameter
61+
-Wno-zero-as-null-pointer-constant
62+
)
63+
64+
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
65+
list(APPEND SDK_WARN_FLAGS -Wno-class-memaccess -Wno-maybe-uninitialized)
66+
endif()
67+
68+
list(JOIN SDK_WARN_FLAGS " " SDK_WARN_FLAGS_STR)
69+
70+
# SDK sources
71+
set(VST3_SDK_SOURCES)
72+
foreach(_src IN LISTS VST3SDK_REQUIRED_FILES)
73+
list(APPEND VST3_SDK_SOURCES "${VST3SDK_PATH}/${_src}")
74+
endforeach()
75+
76+
# Main sources
77+
set(VST3_MAIN_SOURCES obs-vst3.cpp VST3HostApp.cpp VST3Plugin.cpp VST3Scanner.cpp )
78+
79+
# Editor window sources
80+
set(VST3EDITORWINDOW_SRC)
81+
if(OS_WINDOWS)
82+
list(APPEND VST3EDITORWINDOW_SRC editor/win/VST3EditorWindow.cpp)
83+
elseif(OS_MACOS)
84+
list(APPEND VST3EDITORWINDOW_SRC editor/mac/VST3EditorWindow.mm)
85+
elseif(OS_LINUX)
86+
list(APPEND VST3EDITORWINDOW_SRC editor/linux/VST3EditorWindow.cpp editor/linux/RunLoopImpl.cpp)
87+
endif()
88+
89+
# Add all sources
90+
target_sources(obs-vst3 PRIVATE ${VST3_MAIN_SOURCES} ${VST3EDITORWINDOW_SRC} ${VST3_SDK_SOURCES})
91+
92+
# Disable warnings for SDK files
93+
if(OS_LINUX OR OS_MACOS)
94+
set_source_files_properties(${VST3_SDK_SOURCES} PROPERTIES COMPILE_FLAGS "${SDK_WARN_FLAGS_STR}")
95+
endif()
96+
97+
# macOS ARC for all .mm files (editor + sdk)
98+
if(OS_MACOS)
99+
set_source_files_properties(
100+
editor/mac/VST3EditorWindow.mm
101+
"${VST3SDK_PATH}/public.sdk/source/vst/hosting/module_mac.mm"
102+
"${VST3SDK_PATH}/public.sdk/source/common/threadchecker_mac.mm"
103+
PROPERTIES COMPILE_OPTIONS "-fobjc-arc" COMPILE_FLAGS "${SDK_WARN_FLAGS_STR}"
104+
)
105+
target_link_libraries(obs-vst3 PRIVATE "-framework Cocoa" "-framework Foundation")
106+
endif()
107+
108+
# Includes
109+
target_include_directories(
110+
obs-vst3
111+
PRIVATE
112+
${CMAKE_CURRENT_SOURCE_DIR}
113+
${VST3SDK_PATH}
114+
${VST3SDK_PATH}/base
115+
${VST3SDK_PATH}/pluginterfaces
116+
${VST3SDK_PATH}/public.sdk/source/vst
117+
${VST3SDK_PATH}/public.sdk/source/vst/hosting
118+
${VST3SDK_PATH}/public.sdk/source/vst/utility
119+
)
120+
121+
target_link_libraries(obs-vst3 PRIVATE OBS::libobs Qt6::Widgets)
122+
123+
# Windows resources
124+
if(OS_WINDOWS)
125+
configure_file(cmake/windows/obs-module.rc.in obs-vst3.rc)
126+
target_sources(obs-vst3 PRIVATE obs-vst3.rc)
127+
configure_file(
128+
${CMAKE_CURRENT_SOURCE_DIR}/cmake/windows/obs-studio.ico
129+
${CMAKE_CURRENT_BINARY_DIR}/obs-studio.ico
130+
COPYONLY
131+
)
132+
endif()
133+
134+
set_target_properties_obs(obs-vst3 PROPERTIES FOLDER plugins PREFIX "")
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/* Copyright (c) 2025 pkv <[email protected]>
2+
*
3+
* This file uses the Steinberg VST3 SDK, which is licensed under MIT license.
4+
* See https://github.com/steinbergmedia/vst3sdk for details.
5+
*
6+
* This file and all modifications by pkv <[email protected]> are licensed under
7+
* the GNU General Public License, version 3 or later, to comply with the SDK license.
8+
*/
9+
#pragma once
10+
#include "pluginterfaces/gui/iplugview.h"
11+
#include "pluginterfaces/gui/iplugviewcontentscalesupport.h"
12+
#include <string>
13+
#ifdef __linux__
14+
#include "editor/linux/RunLoopImpl.h"
15+
#endif
16+
17+
class VST3EditorWindow {
18+
public:
19+
#ifdef __linux__
20+
VST3EditorWindow(Steinberg::IPlugView *view, const std::string &title, Display *display, RunLoopImpl *runloop);
21+
#else
22+
VST3EditorWindow(Steinberg::IPlugView *view, const std::string &title);
23+
#endif
24+
~VST3EditorWindow();
25+
26+
bool create(int width, int height);
27+
void show();
28+
void close();
29+
// Our design of the VST3 GUI is such that it is hidden when one clicks on the x (close), except for linux.
30+
// The reason is that we want to retain position and size of the GUI. But this can create a desync of the
31+
// visibility state. So we need to retrieve whether the GUI was closed with x.
32+
bool getClosedState();
33+
#if defined(__APPLE__)
34+
void setClosedState(bool closed);
35+
#endif
36+
class Impl;
37+
Impl *impl_;
38+
};

plugins/obs-vst3/VST3HostApp.cpp

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/* Copyright (c) 2025 pkv <[email protected]>
2+
* This file is part of obs-vst3.
3+
*
4+
* Portions are derived from EasyVst (https://github.com/iffyloop/EasyVst),
5+
* licensed under Public Domain (Unlicense) or MIT No Attribution.
6+
*
7+
* This file uses the Steinberg VST3 SDK, which is licensed under MIT license.
8+
* See https://github.com/steinbergmedia/vst3sdk for details.
9+
*
10+
* This file and all modifications by pkv <[email protected]> are licensed under
11+
* the GNU General Public License, version 3 or later, to comply with the SDK license.
12+
*/
13+
#include "VST3HostApp.h"
14+
#include "VST3Plugin.h"
15+
16+
using namespace Steinberg;
17+
using namespace Vst;
18+
19+
VST3HostApp::VST3HostApp()
20+
{
21+
addPlugInterfaceSupported(IComponent::iid);
22+
addPlugInterfaceSupported(IAudioProcessor::iid);
23+
addPlugInterfaceSupported(IEditController::iid);
24+
addPlugInterfaceSupported(IConnectionPoint::iid);
25+
}
26+
27+
VST3HostApp::~VST3HostApp() noexcept {FUNKNOWN_DTOR}
28+
29+
/* IComponentHandler methods */
30+
tresult PLUGIN_API VST3HostApp::beginEdit(ParamID)
31+
{
32+
return kResultOk;
33+
}
34+
35+
tresult PLUGIN_API VST3HostApp::performEdit(ParamID id, ParamValue valueNormalized)
36+
{
37+
if (guiToDsp)
38+
guiToDsp->push({id, valueNormalized});
39+
return kResultOk;
40+
}
41+
42+
tresult PLUGIN_API VST3HostApp::endEdit(ParamID)
43+
{
44+
return kResultOk;
45+
}
46+
47+
tresult PLUGIN_API VST3HostApp::restartComponent(int32 flags)
48+
{
49+
if (!plugin)
50+
return kInvalidArgument;
51+
52+
if ((flags & kReloadComponent) || (flags & kLatencyChanged)) {
53+
std::unique_lock<std::mutex> lk(plugin->obsVst3Struct->plugin_state_mutex);
54+
os_atomic_set_bool(&plugin->obsVst3Struct->bypass, true);
55+
56+
if (plugin->audioEffect)
57+
plugin->audioEffect->setProcessing(false);
58+
59+
if (plugin->vstPlug) {
60+
plugin->vstPlug->setActive(false);
61+
plugin->vstPlug->setActive(true);
62+
}
63+
64+
if (plugin->audioEffect)
65+
plugin->audioEffect->setProcessing(true);
66+
67+
uint32 latency = plugin->audioEffect ? plugin->audioEffect->getLatencySamples() : 0;
68+
infovst3plugin("Latency of the plugin is %u samples", latency);
69+
70+
os_atomic_set_bool(&plugin->obsVst3Struct->bypass, false);
71+
return kResultOk;
72+
}
73+
74+
return kNotImplemented;
75+
}
76+
77+
/* IHostApplication methods */
78+
tresult PLUGIN_API VST3HostApp::getName(String128 name)
79+
{
80+
std::memset(name, 0, sizeof(String128));
81+
#if defined(_WIN32)
82+
const char *src = "OBS VST3 Host";
83+
MultiByteToWideChar(CP_UTF8, 0, src, -1, (wchar_t *)name, 128);
84+
#else
85+
std::u16string src = u"OBS VST3 Host";
86+
src.copy(name, src.size());
87+
#endif
88+
return kResultOk;
89+
}
90+
91+
tresult PLUGIN_API VST3HostApp::createInstance(TUID cid, TUID _iid, void **obj)
92+
{
93+
if (FUnknownPrivate::iidEqual(cid, IMessage::iid) && FUnknownPrivate::iidEqual(_iid, IMessage::iid)) {
94+
*obj = new HostMessage;
95+
return kResultTrue;
96+
}
97+
if (FUnknownPrivate::iidEqual(cid, IAttributeList::iid) &&
98+
FUnknownPrivate::iidEqual(_iid, IAttributeList::iid)) {
99+
if (auto al = HostAttributeList::make()) {
100+
*obj = al.take();
101+
return kResultTrue;
102+
}
103+
return kOutOfMemory;
104+
}
105+
*obj = nullptr;
106+
return kResultFalse;
107+
}
108+
109+
/* IUnitHandler methods */
110+
tresult PLUGIN_API VST3HostApp::notifyUnitSelection(UnitID)
111+
{
112+
return kResultTrue;
113+
}
114+
115+
tresult PLUGIN_API VST3HostApp::notifyProgramListChange(ProgramListID, int32)
116+
{
117+
return kResultTrue;
118+
}
119+
120+
/* IPlugInterfaceSupport methods */
121+
tresult PLUGIN_API VST3HostApp::isPlugInterfaceSupported(const TUID _iid)
122+
{
123+
auto uid = FUID::fromTUID(_iid);
124+
if (std::find(mFUIDArray.begin(), mFUIDArray.end(), uid) != mFUIDArray.end())
125+
return kResultTrue;
126+
return kResultFalse;
127+
}
128+
129+
/* FUnknown methods */
130+
tresult PLUGIN_API VST3HostApp::queryInterface(const TUID _iid, void **obj)
131+
{
132+
if (FUnknownPrivate::iidEqual(_iid, IHostApplication::iid)) {
133+
*obj = static_cast<IHostApplication *>(this);
134+
return kResultOk;
135+
}
136+
if (FUnknownPrivate::iidEqual(_iid, IPlugInterfaceSupport::iid)) {
137+
*obj = static_cast<IPlugInterfaceSupport *>(this);
138+
return kResultOk;
139+
}
140+
#ifdef __linux__
141+
if (runLoop && FUnknownPrivate::iidEqual(_iid, Linux::IRunLoop::iid)) {
142+
*obj = static_cast<Linux::IRunLoop *>(runLoop);
143+
return kResultOk;
144+
}
145+
#endif
146+
*obj = nullptr;
147+
return kNoInterface;
148+
}

0 commit comments

Comments
 (0)