M6: fix 2-GUI khi mở VST FX native (mastering) — một plugin chỉ một GUI

Bug .24: mastering panel load VST FX → 1 floating native (FX slot N) + 1 embed
cùng lúc; floating không đóng được qua UI.

Backend (/fx-gui):
- native branch terminate bridge fx-gui fallback cũ cùng plugin_path trước
  ed.open (loại instance VST thứ 2 + window rời)
- fallback spawn chặn khi editor native đang mở ở session sống → trả native
  (không spawn embed/float thứ 2)
- _terminate_fx_gui_for_path dùng chung cho /fx-gui/close

Frontend (M6 — xử lý r.native):
- openMasterVstGui/openVstGui/auto-open/reopen: nhánh native → state
  {path, url:null, native:true} (không còn toast 'Không mở được GUI embed'),
  marker gui_open_path/vstGuiOpenPath giữ như embed
- closeMasterGui/closeFxGui native → SonicAPI.closeFxGui (engine capture preset
  + ed.close); unmount/hide panel → SonicAPI.hideFxGui
- panel render: GUI native mở = indicator + nút Đóng GUI (thay VstGuiEmbed)
- api.js: closeFxGui/hideFxGui/showFxGui endpoints
This commit is contained in:
2026-09-04 18:26:40 +07:00
parent cede51ae0c
commit ec2c925d09
3 changed files with 90 additions and 22 deletions
+38 -9
View File
@@ -722,6 +722,12 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
if _sess is not None and _sess.alive():
_slot = _sess.chain_slot_for_path(plugin_path)
if _slot is not None:
# Bug 2-GUI: fallback bridge fx-gui (embed/float) còn sống cho cùng
# plugin — instance VST THỨ 2 + window riêng; mở native phải loại nó
# trước, nếu không 1 plugin hiện 2 GUI (1 floating native + 1 embed)
# và floating không tắt được qua UI. Terminate proc cũ → frontend
# embed panel của nó onDead → tự đóng.
_terminate_fx_gui_for_path(plugin_path)
_already = fx_realtime.editor_find(plugin_path)
_same = _already is not None and _already[0] == _sess.name
_sess.send_editor_cmd("open", slot=_slot)
@@ -729,6 +735,15 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
return {"success": True, "started": not _same,
"already_running": bool(_same), "native": True,
"embed_url": None, "session_id": _sess.name, "slot": _slot}
# Tránh mở 2 GUI cùng plugin: editor NATIVE (instance DSP trong session
# fxrt) của plugin này đang mở ở session SỐNG → KHÔNG spawn instance 2 /
# bridge fx-gui nữa (nguồn CPU 100% + 2 GUI); trả native để frontend hiện
# đúng trạng thái cửa sổ ngoài (M6).
_nsess, _nslot = _fxrt_native_editor(plugin_path)
if _nsess is not None:
return {"success": True, "started": False, "already_running": True,
"native": True, "embed_url": None,
"session_id": _nsess.name, "slot": _nslot}
# Tránh mở 2 GUI cùng plugin: nếu tiến trình bridge fx-gui cho plugin này
# đang chạy → trả started=False (frontend có thể focus window cũ).
for proc in _FX_GUI_PROCESSES:
@@ -842,15 +857,8 @@ async def close_fx_gui(req: FxGuiCloseRequest, current_user: dict = Depends(get_
return {"success": True, "closed": True, "native": True,
"preset_b64": _preset}
# session đã chết — rơi xuống spawn fallback (đóng bridge fx-gui cũ nếu có)
for proc in _FX_GUI_PROCESSES:
if proc is not None and proc.poll() is None and getattr(proc, "_sf_plugin_path", None) == plugin_path:
proc.terminate()
try:
proc.wait(timeout=5)
except Exception:
proc.kill()
_prune_fx_gui_processes()
return {"success": True, "closed": True}
if _terminate_fx_gui_for_path(plugin_path):
return {"success": True, "closed": True}
return {"success": True, "closed": False}
@@ -908,6 +916,27 @@ def _prune_fx_gui_processes():
global _FX_GUI_PROCESSES
_FX_GUI_PROCESSES = [p for p in _FX_GUI_PROCESSES if p is not None and p.poll() is None]
def _terminate_fx_gui_for_path(plugin_path):
"""Terminate MỌI bridge fx-gui (embed/float fallback) còn sống cho plugin.
Dùng khi mở native editor thay thế fallback — tránh 2 instance VST + 2 GUI
cùng 1 plugin (Bug 2-GUI). Trả True nếu có proc đã bị chấm dứt."""
_prune_fx_gui_processes()
killed = False
for proc in list(_FX_GUI_PROCESSES):
if proc.poll() is None and getattr(proc, "_sf_plugin_path", None) == plugin_path:
try:
proc.terminate()
try:
proc.wait(timeout=5)
except Exception:
proc.kill()
killed = True
except Exception:
pass
if killed:
_prune_fx_gui_processes()
return killed
@router.post("/preview")