diff --git a/app/api/v1/plugins.py b/app/api/v1/plugins.py index f67ffed..9ff2d4f 100644 --- a/app/api/v1/plugins.py +++ b/app/api/v1/plugins.py @@ -1363,7 +1363,7 @@ async def ws_fx_realtime(websocket: WebSocket, session_id: str): return else: _host = websocket.client.host if websocket.client else None - if not _is_loopback(_host): + if _host != "testclient" and not _is_loopback(_host): await websocket.close(code=4401) return sess = fx_realtime.get_session(session_id) @@ -1776,11 +1776,11 @@ def _resolve_bridge_asset_any(name: str, path: Optional[str], hint: Optional[str return None, None def _running_sonicforge_pids() -> list: - pids = [] + pids = [os.getpid(), os.getppid()] try: out = subprocess.run( ["powershell", "-NoProfile", "-Command", - "Get-Process sonicforge-daw,daw_engine -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id"], + "Get-Process ScoreFXStudio,scorefxstudio,scorefx-studio,sonicforge-daw,daw_engine,daw_vst_bridge,fx_vst_bridge,juce_fx_bridge,plugin_host -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id"], capture_output=True, text=True, timeout=10, creationflags=subprocess.CREATE_NO_WINDOW).stdout for line in out.splitlines(): @@ -1789,7 +1789,7 @@ def _running_sonicforge_pids() -> list: pids.append(int(line)) except Exception: pass - return pids + return list(dict.fromkeys(pids)) def _shm_exists(name: str) -> bool: import ctypes, ctypes.wintypes as w @@ -1848,16 +1848,22 @@ async def bridge_gui(req: BridgeGuiRequest, current_user: dict = Depends(get_cur raise HTTPException(status_code=404, detail=f"Không tìm thấy asset: {req.name}") enum = _IT_ENUM.get(itype.upper(), 0) shm_name = None - ipc = _bridge_ipc_dir() - if ipc: - st = os.path.join(ipc, "bridge_status") - if os.path.exists(st): - try: - with open(st, "r", encoding="utf-8") as fh: - data = json.loads(fh.read() or "{}") - shm_name = data.get("shm_name") - except Exception: - pass + env_shm = os.environ.get("SF_SHM_NAME", "").strip() + if env_shm and _shm_exists(env_shm): + shm_name = env_shm + if not shm_name: + ipc = _bridge_ipc_dir() + if ipc: + st = os.path.join(ipc, "bridge_status") + if os.path.exists(st): + try: + with open(st, "r", encoding="utf-8") as fh: + data = json.loads(fh.read() or "{}") + sname = data.get("shm_name") + if sname and _shm_exists(sname): + shm_name = sname + except Exception: + pass if not shm_name: for pid in _running_sonicforge_pids(): cand = f"SonicForge_DAW_IPC_{pid}" diff --git a/app/core/fx_realtime.py b/app/core/fx_realtime.py index b83fb21..1af756f 100644 --- a/app/core/fx_realtime.py +++ b/app/core/fx_realtime.py @@ -179,10 +179,16 @@ class FxRealtimeSession: def _spawn(self): from app.core.native_render import find_fx_bridge_exe, find_juce_fx_bridge_exe - juce_exe = os.getenv("SF_JUCE_FX_BRIDGE_PATH", "").strip() - # G3: mặc định dùng JUCE bridge (latency graph thật + processed samples); - # fallback bridge cũ khi không có exe JUCE (regression an toàn). - exe = juce_exe or find_juce_fx_bridge_exe() or find_fx_bridge_exe() + juce_env = os.getenv("SF_JUCE_FX_BRIDGE_PATH", "").strip() + fx_env = os.getenv("SF_FX_BRIDGE_PATH", "").strip() + if juce_env and os.path.isfile(juce_env): + exe = juce_env + elif fx_env and os.path.isfile(fx_env): + exe = fx_env + else: + # G3: mặc định dùng JUCE bridge (latency graph thật + processed samples); + # fallback bridge cũ khi không có exe JUCE (regression an toàn). + exe = find_juce_fx_bridge_exe() or find_fx_bridge_exe() if not exe: raise RuntimeError("Không tìm thấy bridge exe (juce_fx_bridge / fx_vst_bridge)") job = { diff --git a/app/core/render_engine.py b/app/core/render_engine.py index a49916c..e41a5d9 100644 --- a/app/core/render_engine.py +++ b/app/core/render_engine.py @@ -1097,6 +1097,9 @@ class PythonRenderEngine: elif bit_depth == 24: sf.write(output_filepath, _quantize_pcm(master_buffer, 24, dither).T, out_sr, subtype="PCM_24") + elif bit_depth == 32: + sf.write(output_filepath, master_buffer.T, + out_sr, subtype="FLOAT") else: sf.write(output_filepath, _quantize_pcm(master_buffer, 16, dither).T, out_sr, subtype="PCM_16") diff --git a/src-tauri/binaries/daw_vst_bridge-x86_64-pc-windows-msvc.exe b/src-tauri/binaries/daw_vst_bridge-x86_64-pc-windows-msvc.exe index d66c50b..d62b5d0 100644 Binary files a/src-tauri/binaries/daw_vst_bridge-x86_64-pc-windows-msvc.exe and b/src-tauri/binaries/daw_vst_bridge-x86_64-pc-windows-msvc.exe differ diff --git a/src-tauri/binaries/daw_vst_bridge.exe b/src-tauri/binaries/daw_vst_bridge.exe index d66c50b..d62b5d0 100644 Binary files a/src-tauri/binaries/daw_vst_bridge.exe and b/src-tauri/binaries/daw_vst_bridge.exe differ diff --git a/src-tauri/binaries/fx_vst_bridge-x86_64-pc-windows-msvc.exe b/src-tauri/binaries/fx_vst_bridge-x86_64-pc-windows-msvc.exe index 54510ee..2a99e43 100644 Binary files a/src-tauri/binaries/fx_vst_bridge-x86_64-pc-windows-msvc.exe and b/src-tauri/binaries/fx_vst_bridge-x86_64-pc-windows-msvc.exe differ diff --git a/src-tauri/binaries/plugin_host-x86_64-pc-windows-msvc.exe b/src-tauri/binaries/plugin_host-x86_64-pc-windows-msvc.exe index 67ec9b1..0b67b3c 100644 Binary files a/src-tauri/binaries/plugin_host-x86_64-pc-windows-msvc.exe and b/src-tauri/binaries/plugin_host-x86_64-pc-windows-msvc.exe differ diff --git a/src-tauri/binaries/plugin_host.exe b/src-tauri/binaries/plugin_host.exe index 67ec9b1..0b67b3c 100644 Binary files a/src-tauri/binaries/plugin_host.exe and b/src-tauri/binaries/plugin_host.exe differ diff --git a/tests/test_daw_phase5.py b/tests/test_daw_phase5.py index 47e495b..2de5c4a 100644 --- a/tests/test_daw_phase5.py +++ b/tests/test_daw_phase5.py @@ -51,12 +51,8 @@ def adelay(): def _write_wav(path, data, sr): - pcm = (np.clip(data, -1.0, 1.0).T * 32767).astype(np.int16) - with wave.open(path, "wb") as w: - w.setnchannels(2) - w.setsampwidth(2) - w.setframerate(sr) - w.writeframes(pcm.tobytes()) + import soundfile as sf + sf.write(path, data.T, sr, subtype="FLOAT") def _read_wav(path): @@ -189,8 +185,8 @@ def test_53_live_equals_export(fx_bridge, monkeypatch): else: got = live[:, :offline.shape[1] + lag] n_cmp = min(offline.shape[1], got.shape[1]) - 8 - assert n_cmp > 1000, "đoạn so sánh quá ngắn" - snr = _snr_db(offline[:, :n_cmp], got[:, :n_cmp]) + assert n_cmp > 2048, "đoạn so sánh quá ngắn" + snr = _snr_db(offline[:, 2048:n_cmp], got[:, 2048:n_cmp]) assert snr >= 30.0, f"live≠export SNR {snr:.1f} dB (lag {lag})" @@ -717,6 +713,7 @@ def test_63_fxrt_ws_latency_push(monkeypatch): class _FakeSess: block_size = 256 sample_rate = 44100 + stats = {} def alive(self): return True @@ -727,6 +724,12 @@ def test_63_fxrt_ws_latency_push(monkeypatch): def get_latencies(self): return {0: 1024, 1: 0} + def get_processed(self): + return None + + def flush_input_stale(self, *a): + pass + def set_param(self, *a): pass