diff --git a/install/daw_vst_bridge.exe b/install/daw_vst_bridge.exe index 58d88df..d855a20 100644 Binary files a/install/daw_vst_bridge.exe and b/install/daw_vst_bridge.exe differ diff --git a/native_bridge/CMakeLists.txt b/native_bridge/CMakeLists.txt index 7b226ea..27d3e63 100644 --- a/native_bridge/CMakeLists.txt +++ b/native_bridge/CMakeLists.txt @@ -64,10 +64,11 @@ add_executable(daw_vst_bridge # is added to the bridge itself (same approach as the audiohost sample). if(VST3_SDK_TARGET) target_compile_definitions(daw_vst_bridge PRIVATE HAVE_VST3SDK=1) - target_link_libraries(daw_vst_bridge PRIVATE ${VST3_SDK_TARGET} sdk_hosting) + target_link_libraries(daw_vst_bridge PRIVATE ${VST3_SDK_TARGET} sdk_hosting sdk_common) if(WIN32) target_sources(daw_vst_bridge PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vst3sdk/public.sdk/source/vst/hosting/module_win32.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/vst3sdk/public.sdk/source/common/memorystream.cpp ) endif() endif() diff --git a/native_bridge/include/INativeInstrument.h b/native_bridge/include/INativeInstrument.h index 23530a1..b696c41 100644 --- a/native_bridge/include/INativeInstrument.h +++ b/native_bridge/include/INativeInstrument.h @@ -56,6 +56,10 @@ public: // it to dedupe repeated OPEN_GUI (frontend retry spam) — re-attaching a // view that is already attached corrupts plugins (Nexus createView→null). virtual bool hasAttachedView() const { return false; } + // G0.4: serialize/restore the plugin preset across instance reloads + // (GUI close/reopen). Non-VST engines: no-op (nothing to save). + virtual void captureState() {} + virtual void restoreState() {} // Real-time Audio PCM Float32 rendering loop virtual void processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) = 0; diff --git a/native_bridge/include/Vst3Instrument.h b/native_bridge/include/Vst3Instrument.h index 29689bd..5da8f45 100644 --- a/native_bridge/include/Vst3Instrument.h +++ b/native_bridge/include/Vst3Instrument.h @@ -9,6 +9,7 @@ #include "INativeInstrument.h" #include +#include class Vst3Instrument : public INativeInstrument { public: @@ -27,6 +28,10 @@ public: bool openGUI(void* parentWindowHandle) override; bool reloadForGUI() override; bool attachView(void* parentWindowHandle) override; + // G0.4: keep the preset across GUI reopen (reload() recreates the + // instance from scratch — without save/restore the plugin resets). + void captureState(); + void restoreState(); void resizeView(int w, int h) override; bool hasAttachedView() const override; void closeGUI() override; @@ -47,6 +52,9 @@ private: bool hasAttachedOnce_; bool reloading_; // guarded by InstrumentEngineManager::mu_ (set via setReloading) uint32_t channel_ = 0; + // G0.4: serialized IComponent/IEditController state captured before reload. + std::vector savedComponentState_; + std::vector savedControllerState_; }; #endif // VST3_INSTRUMENT_H diff --git a/native_bridge/src/Vst3Instrument.cpp b/native_bridge/src/Vst3Instrument.cpp index a78d937..ba419ad 100644 --- a/native_bridge/src/Vst3Instrument.cpp +++ b/native_bridge/src/Vst3Instrument.cpp @@ -18,6 +18,7 @@ #include "public.sdk/source/vst/hosting/processdata.h" #include "public.sdk/source/vst/hosting/eventlist.h" #include "public.sdk/source/vst/hosting/parameterchanges.h" +#include "public.sdk/source/common/memorystream.h" #include "pluginterfaces/vst/ivstaudioprocessor.h" #include "pluginterfaces/vst/ivstcomponent.h" @@ -525,6 +526,59 @@ bool Vst3Instrument::openGUI(void* parentWindowHandle) { #endif } +// G0.4: capture the plugin's full state (component + edit controller) into +// buffers so a GUI reopen can restore the preset after reload(). +void Vst3Instrument::captureState() { +#ifndef HAVE_VST3SDK + return; +#else + auto* s = static_cast(state_); + if (!s) return; + savedComponentState_.clear(); + savedControllerState_.clear(); + Steinberg::MemoryStream stream; + if (s->component && s->component->getState(&stream) == kResultOk && stream.getSize() > 0) { + savedComponentState_.assign(stream.getData(), stream.getData() + stream.getSize()); + std::cout << "[Vst3Instrument] state captured ch=" << channel_ + << " component=" << savedComponentState_.size() << "B" << std::endl; + } + if (s->controller && !s->controllerIsComponent && + s->controller->getState(&stream) == kResultOk && stream.getSize() > 0) { + savedControllerState_.assign(stream.getData(), stream.getData() + stream.getSize()); + } +#endif +} + +// G0.4: restore the captured state onto the freshly loaded instance. +void Vst3Instrument::restoreState() { +#ifndef HAVE_VST3SDK + return; +#else + auto* s = static_cast(state_); + if (!s) return; + if (!savedComponentState_.empty()) { + Steinberg::MemoryStream stream; + stream.write(savedComponentState_.data(), (int32)savedComponentState_.size(), nullptr); + stream.seek(0, Steinberg::IBStream::kIBSeekSet, nullptr); + bool okComp = s->component && s->component->setState(&stream) == kResultOk; + if (!okComp) { + std::cerr << "[Vst3Instrument] setState(component) failed ch=" << channel_ << std::endl; + } else { + std::cout << "[Vst3Instrument] state restored ch=" << channel_ + << " component=" << savedComponentState_.size() << "B" << std::endl; + } + } + if (!savedControllerState_.empty()) { + Steinberg::MemoryStream stream; + stream.write(savedControllerState_.data(), (int32)savedControllerState_.size(), nullptr); + stream.seek(0, Steinberg::IBStream::kIBSeekSet, nullptr); + if (s->controller && s->controller->setState(&stream) != kResultOk) { + std::cerr << "[Vst3Instrument] setState(controller) failed ch=" << channel_ << std::endl; + } + } +#endif +} + bool Vst3Instrument::reloadForGUI() { #ifndef HAVE_VST3SDK return false; @@ -539,9 +593,13 @@ bool Vst3Instrument::reloadForGUI() { // PHAN NAY PHAI chay tren worker thread — apartment cua channel song // o day; chay tren thread tam thi apartment moi chet ngay sau do // (reopen lan sau -> treo nhu bridge_like). + // G0.4: reload() terminates + recreates the instance — capture the + // preset first, restore it after, so the GUI reopen keeps the sound. + captureState(); if (!reload()) return false; s = static_cast(state_); if (!s || !s->controller) return false; + restoreState(); } return true; #endif diff --git a/native_bridge/src/main.cpp b/native_bridge/src/main.cpp index 3e30532..53f2487 100644 --- a/native_bridge/src/main.cpp +++ b/native_bridge/src/main.cpp @@ -707,7 +707,12 @@ static void post_close_gui(uint32_t ch, HWND hwnd) { // load/assign rebuilds it). std::lock_guard lg(g_loadMutex); try { + // G0.4: reload() terminates + recreates the instance; + // capture the preset first, restore it after, so the + // sound survives a GUI close (reopen keeps it too). + i->captureState(); i->reload(); + i->restoreState(); } catch (...) { std::cerr << "[NativeBridge] closeGUI reload EXCEPTION ch=" << ch << " — plugin threw (createInstance)" << std::endl;