feat: open native VST FX GUI from mastering panel + FX rack (bridge --open-fx-gui)

This commit is contained in:
2026-08-17 21:52:40 +07:00
parent b261eb0d7e
commit 38f4cbd8b5
7 changed files with 313 additions and 10 deletions
+66
View File
@@ -499,6 +499,12 @@ class OpenInCarlaRequest(BaseModel):
plugin_path: Optional[str] = None
class FxGuiRequest(BaseModel):
"""Mở native GUI của VST FX qua bridge (Windows desktop)."""
path: str
name: Optional[str] = None
class PreviewRequest(BaseModel):
"""Quick-render preview: pedalboard render clip ngắn bằng ĐÚNG plugin +
preset (cùng code path với export) → trả wav để browser phát.
@@ -677,6 +683,66 @@ async def open_in_carla(req: OpenInCarlaRequest, current_user: dict = Depends(ge
except Exception as e:
raise HTTPException(status_code=500, detail=f"Không mở được Carla: {e}")
@router.post("/fx-gui")
async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_current_user)):
"""Mở native GUI của VST FX (mastering chain / FX rack) qua bridge.
Bridge có mode `--open-fx-gui <job.json>` (Windows): spawn tiến trình riêng
giữ vòng lặp message pump, tạo window 800x600 + attach IPlugView của plugin.
Đây là GUI tách rời — user chỉnh thông số, đóng window là bridge thoát
(exit 0). Không spawn 2 lần cho cùng plugin đang mở (tránh trùng GUI).
"""
enforce_password_changed(current_user)
from app.core.native_render import find_bridge_exe
exe = find_bridge_exe()
if not exe:
raise HTTPException(
status_code=409,
detail="Không tìm thấy bridge native (daw_vst_bridge.exe). "
"Hãy chạy bản desktop đã build đầy đủ.",
)
plugin_path = (req.path or "").strip()
if not plugin_path:
raise HTTPException(status_code=400, detail="Thiếu đường dẫn VST plugin.")
# 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:
if proc is not None and proc.poll() is None and getattr(proc, "_sf_plugin_path", None) == plugin_path:
return {"success": True, "started": False, "already_running": True, "cmd": []}
job = {"path": plugin_path, "name": req.name or os.path.basename(plugin_path)}
fd, job_path = tempfile.mkstemp(prefix="sf_fx_gui_", suffix=".json", dir=tempfile.gettempdir())
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(job, f)
except Exception:
os.close(fd)
raise HTTPException(status_code=500, detail="Không ghi được job file cho bridge.")
cmd = [exe, "--open-fx-gui", job_path]
try:
proc = subprocess.Popen(cmd, close_fds=os.name != "nt")
proc._sf_plugin_path = plugin_path
_register_fx_gui_process(proc)
return {"success": True, "started": True, "already_running": False, "cmd": cmd}
except Exception as e:
try:
os.remove(job_path)
except Exception:
pass
raise HTTPException(status_code=500, detail=f"Không mở được GUI của VST: {e}")
_FX_GUI_PROCESSES = [] # list[subprocess.Popen] — các bridge fx-gui do app spawn
def _register_fx_gui_process(proc):
global _FX_GUI_PROCESSES
_prune_fx_gui_processes()
_FX_GUI_PROCESSES.append(proc)
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]
# ── Carla bridge lifecycle ─────────────────────────────────────────────────
# App spawn Carla (open_in_carla) và PHẢI có khả năng dừng nó khi track chuyển