feat(native): realtime master FX chain + fx-gui fast start, hide/show, non-topmost

- FxGuiServer: HWND_TOP (no longer always-on-top), /hide + /show endpoints,
  onStarted callback prints SF_FXGUI_PORT before plugin load (fast connect)
- RenderFxJob: RealtimeFxChain (mutex+worker, SEH-guarded, bypass slots,
  deferred Vst3Fx dtor); --fx-gui split into parse/create/load-attach stages
- main: FreeConsole unless SF_KEEP_CONSOLE; control type 6 SET_FX_CHAIN feeds
  g_fxChain on the audio loop; shutdown before CoUninitialize
- plugins.py: CREATE_NO_WINDOW on both Popen paths
- lib.rs: set_fx_chain command -> push_control(6)
- app.jsx/nativeBridgeService: setFxChain sync from MasteringModal; panel close
  uses /hide + refs to fix closure bug; VstGuiEmbed rect refresh -> /show
This commit is contained in:
2026-08-19 09:59:13 +07:00
parent c6de0720f4
commit da0f8fa40e
11 changed files with 391 additions and 40 deletions
+22
View File
@@ -13,6 +13,7 @@
#include <imm.h>
#include <mmsystem.h>
#include <process.h>
#include <cstdlib>
#include <thread>
#else
#include <unistd.h>
@@ -116,6 +117,9 @@ static std::mutex g_guiMutex;
static std::map<uint32_t, void*> g_guiWindows; // channel -> HWND (keep window alive)
static std::map<HWND, uint32_t> g_hwndToCh; // HWND -> channel (WM_DESTROY cleanup)
static ChannelWorker* g_uiWorker = nullptr;
// T2.4: realtime master FX chain (live audio through VST3 FX). Created in
// main() once sampleRate is known; fed via control queue type 6.
static std::unique_ptr<RealtimeFxChain> g_fxChain;
// Same-plugin-DLL reentrancy guards: two threads inside one VST3 DLL (Nexus)
// crash or deadlock. g_attachPaths = lowercase plugin paths whose reload/
@@ -574,6 +578,13 @@ static void post_close_gui(uint32_t ch, HWND hwnd) {
}
int main(int argc, char* argv[]) {
#ifdef _WIN32
// Background daemon: detach the console window unless SF_KEEP_CONSOLE is
// set. Console apps spawned from the GUI (tauri-shell, Popen) flash a
// console window otherwise. Redirected stdio (pipe to bridge.log / the
// fx-gui port reader) survives FreeConsole.
if (!std::getenv("SF_KEEP_CONSOLE")) FreeConsole();
#endif
std::cout << "[NativeBridge] Starting DAW Host Bridge Engine..." << std::endl;
#ifdef _WIN32
// Disable Power Throttling (Efficiency Mode) for this background process
@@ -758,6 +769,8 @@ int main(int argc, char* argv[]) {
if (const char* e = std::getenv("SF_STATE_FILE")) stateFile = e;
if (stateFile.empty()) stateFile = StateStore::defaultPath();
const uint32_t block = AUDIO_BLOCK_SIZE;
// T2.4: realtime master FX chain — created once, fed via control type 6.
g_fxChain = std::make_unique<RealtimeFxChain>();
std::atomic<uint64_t> playheadSamples{0};
// V8 bug 3: khi STOP da xu ly, bo qua NOTE_ON (velocity>0) den sau - JS
// note-on timer co the bay toi sau STOP (guardPlay tre do React re-render)
@@ -1374,6 +1387,8 @@ int main(int argc, char* argv[]) {
} else if (c.type == 5) { // DUMP_STATE (G2.2): Rust pre-kill flush
std::cout << "[NativeBridge] DUMP_STATE - flushing state now" << std::endl;
uiWorker.post([&]() { saveStateJob(); });
} else if (c.type == 6) { // SET_FX_CHAIN (T2.4): arg2 = JSON [{"path","bypass"},...]
if (g_fxChain) g_fxChain->setChain(std::string(c.arg2), sampleRate, (int32_t)block);
}
}
shmIPC->controlQueueCount = 0;
@@ -1479,6 +1494,12 @@ int main(int argc, char* argv[]) {
dispatch(evts[ei]);
}
if (cursor < block) renderSegment(cursor, block);
// T2.4: realtime master FX chain — apply to the just-rendered slot
// (in-place, owned buffers) before publishing.
if (g_fxChain) {
uint32_t slot = shmIPC->bridgeWriteIndex & shmIPC->ringMask;
g_fxChain->process(shmIPC->ringLeft[slot], shmIPC->ringRight[slot], block);
}
shmIPC->bridgeWriteIndex++;
// D. Parent died / window closed -> exit (no orphan process)
@@ -1509,6 +1530,7 @@ int main(int argc, char* argv[]) {
UnmapViewOfFile(shmIPC);
CloseHandle(hMapFile);
timeEndPeriod(1);
if (g_fxChain) g_fxChain->shutdown(); // destroy VST3 FX before COM teardown
CoUninitialize();
#else
std::free(shmIPC);