// 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 "public.sdk/source/common/commonstringconvert.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" #include "pluginterfaces/vst/ivstevents.h" #include "AudioEngine.h" #include "NativeMixer.h" #include using namespace Steinberg; using namespace Steinberg::Vst; namespace { // Callback param: plugin đổi param trong editor → đẩy lên host (T11). // handle = instance handle; paramId = ParamID; valueNormalized 0..1. typedef void (__cdecl* SF_ParamChangedCallback)(int32 handle, int32 paramId, double valueNormalized, void* userdata); // ComponentHandler tối thiểu — T11: performEdit → callback 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 { if (cb) cb (handle, static_cast (id), static_cast (valueNormalized), userdata); return kResultOk; } tresult PLUGIN_API endEdit (ParamID /*id*/) override { return kResultOk; } tresult PLUGIN_API restartComponent (int32 /*flags*/) override { return kResultOk; } void setCallback (int32 h, SF_ParamChangedCallback c, void* u) { handle = h; cb = c; userdata = u; } private: int32 handle = 0; SF_ParamChangedCallback cb = nullptr; void* userdata = nullptr; }; struct Instance { VST3::Hosting::Module::Ptr module; IPtr plugProvider; IPtr component; IPtr processor; IPtr controller; IPtr view; ComponentHandler handler; sonicforge::AudioEngine audio; // T12: native audio loop (SPSC + WASAPI) std::atomic audioRunning {false}; sonicforge::NativeMixer mixer; // T13: track gain/pan + master limiter }; // T12: IEventList cố định (không cấp phát trên audio thread). class FixedEventList : public IEventList { public: FixedEventList () = default; tresult PLUGIN_API queryInterface (const TUID _iid, void** obj) override { if (FUnknownPrivate::iidEqual (_iid, IEventList::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; } int32 PLUGIN_API getEventCount () override { return count; } tresult PLUGIN_API getEvent (int32 index, Event& e) override { if (index < 0 || index >= count) return kInvalidArgument; e = events[index]; return kResultOk; } tresult PLUGIN_API addEvent (Event& e) override { if (count >= 512) return kOutOfMemory; events[count++] = e; return kResultOk; } Event events[512]; int32 count = 0; }; // T12: audio thread callback — build EventList + process(). Render thread: // KHÔNG lock, KHÔNG cấp phát (EventList cố định, buffer pre-alloc). bool vst3AudioProcess (const sonicforge::MidiEvent* events, int32 eventCount, float* outL, float* outR, int32 frames, void* userdata) { Instance* inst = static_cast (userdata); IAudioProcessor* proc = inst ? inst->processor.get () : nullptr; if (!proc) return false; FixedEventList list; for (int32 i = 0; i < eventCount; ++i) { Event e = {}; e.sampleOffset = 0; e.ppqPosition = 0.0; e.flags = 0; if (events[i].noteOn) { e.type = Event::kNoteOnEvent; e.noteOn.channel = events[i].channel; e.noteOn.pitch = events[i].pitch; e.noteOn.velocity = events[i].velocity; e.noteOn.noteId = -1; } else { e.type = Event::kNoteOffEvent; e.noteOff.channel = events[i].channel; e.noteOff.pitch = events[i].pitch; e.noteOff.velocity = 0.0f; e.noteOff.noteId = -1; } list.addEvent (e); } ProcessData data = {}; data.processMode = kRealtime; data.symbolicSampleSize = kSample32; data.numSamples = frames; data.numInputs = 0; data.numOutputs = 1; AudioBusBuffers outBus = {}; float* chans[2] = { outL, outR }; outBus.numChannels = 2; outBus.channelBuffers32 = chans; data.outputs = &outBus; data.inputEvents = &list; const tresult res = proc->process (data); inst->mixer.processInPlace (outL, outR, frames); // T13: gain/pan + limiter return res == kResultOk; } 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; } inst->component = inst->plugProvider->getComponent (); // giữ ref if (!inst->component) { set_err (err, err_cap, "plugin has no component"); return 0; } IAudioProcessor* rawProc = nullptr; if (inst->component->queryInterface (IAudioProcessor::iid, reinterpret_cast (&rawProc)) != kResultOk || !rawProc) { set_err (err, err_cap, "plugin has no audio processor"); return 0; } inst->processor = IPtr (rawProc); 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; } // T12: dung audio loop (join render thread) truoc khi pha huy plugin. it->second->audioRunning.store (false, std::memory_order_release); it->second->audio.stop (); if (it->second->processor) it->second->processor->setProcessing (false); 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; } // Đăng ký callback param (T11): plugin đổi param trong editor → cb(handle, // paramId, valueNormalized, userdata). userdata là con trỏ do host giữ. __declspec (dllexport) int32 SF_VST3_SetParamCallback (int32 handle, SF_ParamChangedCallback cb, void* userdata) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; it->second->handler.setCallback (handle, cb, userdata); return 0; } // JS automation → setParamNormalized (T11). __declspec (dllexport) int32 SF_VST3_SetParam (int32 handle, int32 paramId, double valueNormalized) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; if (!it->second->controller) return -2; it->second->controller->setParamNormalized (static_cast (paramId), static_cast (valueNormalized)); return 0; } // Số tham số plugin (để JS dựng UI param list). __declspec (dllexport) int32 SF_VST3_GetParamCount (int32 handle) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; if (!it->second->controller) return -2; return static_cast (it->second->controller->getParameterCount ()); } // Thông tin param thứ index (0-based): id + tên (title) + giá trị normalized. // Trả 0 nếu OK; -1 bad handle; -2 no controller; -3 index ngoài phạm vi. __declspec (dllexport) int32 SF_VST3_GetParamInfo (int32 handle, int32 index, int32* out_id, char* out_title, int32 title_cap, double* out_value) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; IEditController* ctrl = it->second->controller; if (!ctrl) return -2; if (index < 0 || static_cast (index) >= ctrl->getParameterCount ()) return -3; ParameterInfo info = {}; if (ctrl->getParameterInfo (static_cast (index), info) != kResultTrue) return -3; if (out_id) *out_id = static_cast (info.id); if (out_title && title_cap > 0) { std::string titleUtf8 = StringConvert::convert (std::u16string (info.title)); std::strncpy (out_title, titleUtf8.c_str (), static_cast (title_cap - 1)); out_title[title_cap - 1] = '\0'; } if (out_value) *out_value = static_cast (ctrl->getParamNormalized (info.id)); return 0; } // T12: bat native audio loop (SPSC + WASAPI) cho instance nay. Audio thread // goi vst3AudioProcess (EventList + process) moi block. __declspec (dllexport) int32 SF_VST3_AudioStart (int32 handle, int32 sampleRate, int32 blockSize, char* err, int32 err_cap) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; Instance* inst = it->second.get (); if (!inst->processor) return -2; ProcessSetup setup = {}; setup.processMode = kRealtime; setup.symbolicSampleSize = kSample32; setup.maxSamplesPerBlock = blockSize; setup.sampleRate = static_cast (sampleRate); inst->processor->setupProcessing (setup); inst->processor->setProcessing (true); inst->audioRunning.store (true, std::memory_order_release); if (!inst->audio.start (sampleRate, blockSize, &vst3AudioProcess, inst, err, err_cap)) { inst->audioRunning.store (false, std::memory_order_release); inst->processor->setProcessing (false); return -3; } return 0; } __declspec (dllexport) int32 SF_VST3_AudioStop (int32 handle) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; it->second->audioRunning.store (false, std::memory_order_release); it->second->audio.stop (); if (it->second->processor) it->second->processor->setProcessing (false); return 0; } __declspec (dllexport) int32 SF_VST3_AudioUnderruns (int32 handle) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; return it->second->audio.underruns (); } __declspec (dllexport) int32 SF_VST3_AudioLatency (int32 handle) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; return it->second->audio.latencySamples (); } __declspec (dllexport) int32 SF_VST3_AudioBlocks (int32 handle) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; return it->second->audio.blocksRendered (); } // T12: MIDI → SPSC (audio thread xu ly). Khong co duong direct nhu VST2 — // offline render (T16) se drain SPSC rieng. __declspec (dllexport) int32 SF_VST3_SendNoteOn (int32 handle, int32 channel, int32 pitch, int32 velocity) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; sonicforge::MidiEvent ev = {}; ev.channel = channel; ev.pitch = pitch; ev.velocity = static_cast (velocity) / 127.0f; ev.noteOn = true; return it->second->audio.pushMidi (ev) ? 0 : -2; } __declspec (dllexport) int32 SF_VST3_SendNoteOff (int32 handle, int32 channel, int32 pitch) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; sonicforge::MidiEvent ev = {}; ev.channel = channel; ev.pitch = pitch; ev.velocity = 0.0f; ev.noteOn = false; return it->second->audio.pushMidi (ev) ? 0 : -2; } // T13: track gain/pan + master limiter (NativeMixer). Set truoc AudioStart; // audio thread doc state nay (khong lock). __declspec (dllexport) int32 SF_VST3_SetTrackGainPan (int32 handle, float gainDb, float pan) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; it->second->mixer.setTrack (sonicforge::TrackGainPan::fromDbPan (gainDb, pan)); return 0; } __declspec (dllexport) int32 SF_VST3_SetMasterLimiter (int32 handle, int32 active, float thresholdDb) { std::lock_guard lock (g_mutex); auto it = g_instances.find (handle); if (it == g_instances.end ()) return -1; it->second->mixer.setLimiter (active != 0, thresholdDb); return 0; } } // extern "C"