diff --git a/app/api/v1/plugins.py b/app/api/v1/plugins.py index 6abe893..71dcadf 100644 --- a/app/api/v1/plugins.py +++ b/app/api/v1/plugins.py @@ -166,10 +166,24 @@ async def list_plugins(current_user: dict = Depends(get_current_user)): d = _effective_dirs() pm = PluginManager(vst_dir=d["vst_dir"], sf_dir=d["soundfont_dir"], upload_sf_dir=UPLOAD_SF_DIR, extra_vst_dirs=d["plugin_dirs"]) avail = pm.list_available() + # Modal chọn instrument (nút Synth TCP) chỉ hiện soundfont + VSTi — loại + # VST FX (Ozone/Sonible/...) khỏi vst_instruments theo is_fx của bridge scan. + # Filter TRƯỚC merge extra: nếu không, entry FX (path .vst3) chiếm id và + # setdefault chặn entry instrument (path .dll) cùng tên → mất cả hai. + fx_paths = set() + try: + from app.core.vst_engine import get_plugin_manager + fx_paths = {f.get("path", "").lower() for f in get_plugin_manager().list_fx()} + except Exception: + pass + if fx_paths: + avail["vst_instruments"] = [v for v in avail["vst_instruments"] + if (v.get("path") or "").lower() not in fx_paths] # Gộp VST từ plugin_dirs user đã scan — list_available() CHỈ quét vst_dir # env (mặc định /opt/daw_engine/vst3) → Synth dropdown không thấy VSTi mà # Plugin Manager đã scan trong thư mục user chọn (bug: nút Synth rỗng). - extra = _scan_vst_in_dirs(d["plugin_dirs"]) + extra = [v for v in _scan_vst_in_dirs(d["plugin_dirs"]) + if (v.get("path") or "").lower() not in fx_paths] by_id = {v["id"]: v for v in avail["vst_instruments"]} for v in extra: by_id.setdefault(v["id"], v) diff --git a/app/core/vst_engine.py b/app/core/vst_engine.py index 1e5a7d3..85c1ff1 100644 --- a/app/core/vst_engine.py +++ b/app/core/vst_engine.py @@ -464,7 +464,7 @@ class PluginManager: plugins = self._scan_plugins() return { "vst_instruments": [ - {"id": k, "name": k, + {"id": k, "name": k, "path": p, "type": "VST3" if p.lower().endswith(".vst3") else "VST2", "has_native_support": True} for k, p in plugins.items() diff --git a/native_bridge/include/INativeInstrument.h b/native_bridge/include/INativeInstrument.h index 9ee23b5..73379a2 100644 --- a/native_bridge/include/INativeInstrument.h +++ b/native_bridge/include/INativeInstrument.h @@ -83,6 +83,13 @@ public: virtual uint32_t bankOf(uint32_t channel) const { return 0; } virtual uint32_t programOf(uint32_t channel) const { return 0; } + // Engines whose render is paced by an external process (sandbox VST2 + // child): the auto-calibration probe must consume at ~real-time speed so + // the child can wake, pick up the MIDI and render each block. Direct + // engines (VST3/VST2-in-proc/SF2/SFZ) render synchronously in + // processAudioBlock and probe at µs speed. + virtual bool probeNeedsRealtime() const { return false; } + // Real-time Audio PCM Float32 rendering loop virtual void processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) = 0; }; diff --git a/native_bridge/src/NativeInstrumentEngine.cpp b/native_bridge/src/NativeInstrumentEngine.cpp index 5af6911..b6db52a 100644 --- a/native_bridge/src/NativeInstrumentEngine.cpp +++ b/native_bridge/src/NativeInstrumentEngine.cpp @@ -556,6 +556,11 @@ float InstrumentEngineManager::calibrate(INativeInstrument* inst, double sampleR const uint32_t bs = blockSize ? blockSize : 512; const uint32_t body = (uint32_t)(sampleRate * 0.6); const uint32_t tail = (uint32_t)(sampleRate * 0.25); + // Sandbox hosts render through a child process at consumer pace: consume + // one block per real-time block (~5.8ms @256/44.1k) so the child wakes + // from its idle Sleep(15), picks up the probe notes and renders steady + // state. Direct engines: no pacing (probe finishes in µs). + const bool realtime = inst->probeNeedsRealtime(); std::vector L(bs + 64, 0.0f), R(bs + 64, 0.0f); const int pitches[3] = { 60, 64, 67 }; float peak = 0.0f; @@ -578,6 +583,7 @@ float InstrumentEngineManager::calibrate(INativeInstrument* inst, double sampleR while (off < body) { uint32_t n = bs; if (off + n > body) n = body - off; if (!renderProbe(n)) { inst->noteOff(0, (uint32_t)p, 0); return 1.0f; } + if (realtime) Sleep((DWORD)(n * 1000.0 / sampleRate + 0.5)); off += n; } inst->noteOff(0, (uint32_t)p, 0); @@ -585,6 +591,7 @@ float InstrumentEngineManager::calibrate(INativeInstrument* inst, double sampleR while (off < tail) { uint32_t n = bs; if (off + n > tail) n = tail - off; if (!renderProbe(n)) return 1.0f; + if (realtime) Sleep((DWORD)(n * 1000.0 / sampleRate + 0.5)); off += n; } } diff --git a/native_bridge/src/SandboxVst2Host.h b/native_bridge/src/SandboxVst2Host.h index c54d347..f1c9c0b 100644 --- a/native_bridge/src/SandboxVst2Host.h +++ b/native_bridge/src/SandboxVst2Host.h @@ -37,6 +37,9 @@ public: bool openGUI(void* parentWindowHandle) override; bool canOpenGUI() const override { return true; } bool ownsGuiWindow() const override { return true; } // child owns its top-level window + // The child renders at consumer pace and sleeps 15ms while idle - the + // calibration probe must run in real-time or it only sees silence/attack. + bool probeNeedsRealtime() const override { return true; } bool attachView(void* parentWindowHandle) override; // bridge OPEN_GUI job calls this bool hasAttachedView() const override { return guiOpen_.load(); } void closeGUI() override; diff --git a/native_bridge/src/plugin_host_main.cpp b/native_bridge/src/plugin_host_main.cpp index 8fa6f64..f0318de 100644 --- a/native_bridge/src/plugin_host_main.cpp +++ b/native_bridge/src/plugin_host_main.cpp @@ -255,6 +255,14 @@ int main(int argc, char* argv[]) { fflush(stdout); inst.openGUI(g_hostHwnd); ShowWindow(g_hostHwnd, SW_SHOW); + // Đưa GUI lên top để tương tác ngay: TOPMOST tạm thời + // (rồi hạ ngay) kéo window lên đỉnh z-order + focus. + SetWindowPos(g_hostHwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + SetWindowPos(g_hostHwnd, HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE); + SetForegroundWindow(g_hostHwnd); + BringWindowToTop(g_hostHwnd); } } else if (c.type == 5) { inst.closeGUI();