From 6bc197b909519147efb7b968d87aab19f807247e Mon Sep 17 00:00:00 2001 From: locphamtran Date: Tue, 11 Aug 2026 16:09:54 +0700 Subject: [PATCH] T9: VST3 GUI bridge DLL (native_host/vst3_host_bridge.cpp, CMakeLists.txt) --- native_host/CMakeLists.txt | 34 +++++ native_host/vst3_host_bridge.cpp | 236 +++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 native_host/CMakeLists.txt create mode 100644 native_host/vst3_host_bridge.cpp diff --git a/native_host/CMakeLists.txt b/native_host/CMakeLists.txt new file mode 100644 index 0000000..c84e7a1 --- /dev/null +++ b/native_host/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.20) +project(sonicforge_native_host LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT DEFINED VST3_SDK_ROOT) + set(VST3_SDK_ROOT "" CACHE PATH "Path to VST3 SDK root") +endif() + +add_library(vst3_host_bridge SHARED + vst3_host_bridge.cpp + ${VST3_SDK_ROOT}/public.sdk/source/common/commonstringconvert.cpp + ${VST3_SDK_ROOT}/public.sdk/source/common/threadchecker_win32.cpp + ${VST3_SDK_ROOT}/public.sdk/source/vst/hosting/module.cpp + ${VST3_SDK_ROOT}/public.sdk/source/vst/hosting/module_win32.cpp + ${VST3_SDK_ROOT}/public.sdk/source/vst/hosting/plugprovider.cpp + ${VST3_SDK_ROOT}/public.sdk/source/vst/hosting/hostclasses.cpp + ${VST3_SDK_ROOT}/public.sdk/source/vst/hosting/pluginterfacesupport.cpp + ${VST3_SDK_ROOT}/public.sdk/source/vst/hosting/connectionproxy.cpp + ${VST3_SDK_ROOT}/public.sdk/source/vst/utility/stringconvert.cpp + ${VST3_SDK_ROOT}/public.sdk/source/vst/vstinitiids.cpp + ${VST3_SDK_ROOT}/pluginterfaces/base/funknown.cpp + ${VST3_SDK_ROOT}/pluginterfaces/base/ustring.cpp + ${VST3_SDK_ROOT}/pluginterfaces/base/coreiids.cpp + ${VST3_SDK_ROOT}/base/source/baseiids.cpp +) + +target_include_directories(vst3_host_bridge PRIVATE ${VST3_SDK_ROOT}) +target_compile_definitions(vst3_host_bridge PRIVATE + _CRT_SECURE_NO_WARNINGS + NOMINMAX + WIN32_LEAN_AND_MEAN +) diff --git a/native_host/vst3_host_bridge.cpp b/native_host/vst3_host_bridge.cpp new file mode 100644 index 0000000..9865b8a --- /dev/null +++ b/native_host/vst3_host_bridge.cpp @@ -0,0 +1,236 @@ +// vst3_host_bridge.cpp — VST3 GUI bridge (T9, spec vsti_gui) +// +// DLL export C API để attach VST3 editor vào HWND cha (cửa sổ nổi `vst_gui_*` +// từ T8). Flow chuẩn hosting: Module::create(path) → PlugProvider → +// getController → createView(kEditor) → getSize → attached(hwnd, +// kPlatformTypeHWND). Đóng: view->removed() + release (IPtr). +// +// QUYẾT ĐỊNH (T9): VST3 SDK THUẦN (không JUCE) — nhẹ, license BSD-3 sạch; +// T10 VST2 dùng vestige.h clean-room riêng; T12 native audio loop cũng SDK +// thuần. Nâng cấp nếu cần: gộp engine bằng JUCE khi có yêu cầu rõ ràng. +// +// T11 sẽ mở rộng ComponentHandler::performEdit → đẩy lên JS (vst_param_changed); +// hiện tại no-op để plugin không crash khi user chỉnh param trong editor. +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include + +#include +#include +#include +#include +#include +#include + +#include "public.sdk/source/vst/hosting/module.h" +#include "public.sdk/source/vst/hosting/plugprovider.h" +#include "pluginterfaces/base/funknown.h" +#include "pluginterfaces/gui/iplugview.h" +#include "pluginterfaces/vst/ivstaudioprocessor.h" +#include "pluginterfaces/vst/ivstcomponent.h" +#include "pluginterfaces/vst/ivsteditcontroller.h" + +using namespace Steinberg; +using namespace Steinberg::Vst; + +namespace { + +// ComponentHandler tối thiểu — T11: performEdit → JS param sync. +class ComponentHandler : public IComponentHandler +{ +public: + ComponentHandler () = default; + tresult PLUGIN_API queryInterface (const TUID _iid, void** obj) override + { + if (FUnknownPrivate::iidEqual (_iid, IComponentHandler::iid) || + FUnknownPrivate::iidEqual (_iid, FUnknown::iid)) + { + *obj = this; + addRef (); + return kResultOk; + } + *obj = nullptr; + return kNoInterface; + } + uint32 PLUGIN_API addRef () override { return 1; } + uint32 PLUGIN_API release () override { return 1; } + tresult PLUGIN_API beginEdit (ParamID /*id*/) override { return kResultOk; } + tresult PLUGIN_API performEdit (ParamID /*id*/, ParamValue /*valueNormalized*/) override + { + return kResultOk; + } + tresult PLUGIN_API endEdit (ParamID /*id*/) override { return kResultOk; } + tresult PLUGIN_API restartComponent (int32 /*flags*/) override { return kResultOk; } +}; + +struct Instance +{ + VST3::Hosting::Module::Ptr module; + IPtr plugProvider; + IPtr controller; + IPtr view; + ComponentHandler handler; +}; + +std::mutex g_mutex; +std::map> g_instances; +int32 g_next_handle = 1; + +void set_err (char* err, int32 err_cap, const char* msg) +{ + if (err && err_cap > 0) + { + std::snprintf (err, static_cast (err_cap), "%s", msg); + } +} + +} // namespace + +extern "C" { + +// Attach VST3 editor vào parent_hwnd. module_path_utf8: đường dẫn tới .vst3 +// (UTF-8). plugin_name: tên class VST3 (ClassInfo::name — khớp plugin_id JS). +// Trả handle (>= 1) hoặc 0 + err. out_w/out_h: kích thước ưa thích của editor. +__declspec (dllexport) int32 SF_VST3_Attach (const char* module_path_utf8, + const char* plugin_name, + HWND parent_hwnd, + int32* out_w, + int32* out_h, + char* err, + int32 err_cap) +{ + std::lock_guard lock (g_mutex); + if (!module_path_utf8 || !plugin_name || !parent_hwnd) + { + set_err (err, err_cap, "null arg"); + return 0; + } + auto inst = std::make_unique (); + + std::string loadError; + inst->module = VST3::Hosting::Module::create (module_path_utf8, loadError); + if (!inst->module) + { + set_err (err, err_cap, "cannot load VST3 module"); + return 0; + } + + VST3::Hosting::ClassInfo classInfo; + bool found = false; + auto factory = inst->module->getFactory (); + for (auto& ci : factory.classInfos ()) + { + if (ci.category () == kVstAudioEffectClass && ci.name () == plugin_name) + { + classInfo = ci; + found = true; + break; + } + } + if (!found) + { + set_err (err, err_cap, "no VST3 audio effect class with that name"); + return 0; + } + + inst->plugProvider = IPtr (new PlugProvider (factory, classInfo, true)); + if (!inst->plugProvider->initialize ()) + { + set_err (err, err_cap, "plugin initialize failed"); + return 0; + } + + IEditController* rawController = inst->plugProvider->getController (); // +1 ref + if (!rawController) + { + set_err (err, err_cap, "plugin has no edit controller"); + return 0; + } + inst->controller = IPtr (rawController); // nhận +1 + + inst->controller->setComponentHandler (&inst->handler); + + inst->view = inst->controller->createView (ViewType::kEditor); + if (!inst->view) + { + set_err (err, err_cap, "plugin has no editor view"); + return 0; + } + + ViewRect r {}; + if (inst->view->getSize (&r) == kResultTrue) + { + if (out_w) + *out_w = r.getWidth (); + if (out_h) + *out_h = r.getHeight (); + } + + if (inst->view->attached ((void*) parent_hwnd, kPlatformTypeHWND) != kResultTrue) + { + set_err (err, err_cap, "view attach failed"); + return 0; + } + + int32 handle = g_next_handle++; + g_instances[handle] = std::move (inst); + return handle; +} + +// Đóng editor: removed() + release (IPtr tự release khi xóa Instance). +__declspec (dllexport) int32 SF_VST3_Close (int32 handle, char* err, int32 err_cap) +{ + std::lock_guard lock (g_mutex); + auto it = g_instances.find (handle); + if (it == g_instances.end ()) + { + set_err (err, err_cap, "bad handle"); + return -1; + } + if (it->second->view) + { + it->second->view->removed (); + it->second->view = nullptr; + } + g_instances.erase (it); + return 0; +} + +// Kích thước ưa thích hiện tại của editor. +__declspec (dllexport) int32 SF_VST3_GetSize (int32 handle, int32* out_w, int32* out_h) +{ + std::lock_guard lock (g_mutex); + auto it = g_instances.find (handle); + if (it == g_instances.end ()) + return -1; + ViewRect r {}; + if (it->second->view && it->second->view->getSize (&r) == kResultTrue) + { + if (out_w) + *out_w = r.getWidth (); + if (out_h) + *out_h = r.getHeight (); + return 0; + } + return -2; +} + +// Báo view: parent HWND đã được resize (w,h) — plugin cập nhật nội dung. +__declspec (dllexport) int32 SF_VST3_Resize (int32 handle, int32 w, int32 h) +{ + std::lock_guard lock (g_mutex); + auto it = g_instances.find (handle); + if (it == g_instances.end ()) + return -1; + if (it->second->view) + { + ViewRect r = {}; + r.right = w; + r.bottom = h; + if (it->second->view->onSize (&r) == kResultTrue) + return 0; + } + return -2; +} + +} // extern "C"