M24: fix lag am khi switch GUI VST luc play — blocking FX-GUI ops (capture poll/wait_chain_ready) ra khoi event loop qua asyncio.to_thread + lock per-session, pump audio khong stall (pump_gap 60-200ms -> 0)
This commit is contained in:
+91
-54
@@ -693,6 +693,72 @@ class PreviewRequest(BaseModel):
|
||||
preset_data: Optional[str] = None # base64 bytes .vstpreset (project nhúng)
|
||||
|
||||
|
||||
# M24: thao tac GUI native (hide/open/close editor in-process fxrt) co work
|
||||
# blocking (capture_editor_preset poll file time.sleep 0.05 x3s,
|
||||
# wait_chain_ready sleep-poll, terminate proc.wait(5)). Truoc M24 cac sleep nay
|
||||
# chay DONG BO trong async endpoint -> block event loop uvicorn -> pump audio
|
||||
# `_pump_output` (cung loop) stall 60-200ms -> worklet queue can -> underrun ->
|
||||
# am lag roi moi play lai moi lan switch GUI luc dang play (log fxrt_pump.log:
|
||||
# pump_gap ngay sau GUI op). Fix: chay blocking work trong thread
|
||||
# (asyncio.to_thread) duoi lock per-session de GIU thu tu hide->open nhu cu.
|
||||
_FXGUI_OP_LOCKS = {} # session name -> asyncio.Lock
|
||||
_FXGUI_OP_LOCKS_GUARD = threading.Lock()
|
||||
|
||||
def _fxgui_op_lock(session_name):
|
||||
with _FXGUI_OP_LOCKS_GUARD:
|
||||
lock = _FXGUI_OP_LOCKS.get(session_name)
|
||||
if lock is None:
|
||||
lock = asyncio.Lock()
|
||||
_FXGUI_OP_LOCKS[session_name] = lock
|
||||
return lock
|
||||
|
||||
def _fxgui_open_native_blocking(sess, plugin_path):
|
||||
"""M24 worker thread (asyncio.to_thread) cho nhanh mo GUI native: cho chain
|
||||
ready tren bridge, terminate fallback GUI cu, gui ed.open + retry 700ms.
|
||||
Tra dict ket qua native; None neu plugin khong phai slot chain (roi xuong
|
||||
fallback); raise HTTPException 409 khi session chet giua chung."""
|
||||
_slot = sess.chain_slot_for_path(plugin_path)
|
||||
if _slot is None:
|
||||
return None
|
||||
_ready = sess.wait_chain_ready(timeout=15)
|
||||
logger.info("[fxgui] open native path=%s shm=%s slot=%s ready=%s",
|
||||
plugin_path, sess.name, _slot, _ready)
|
||||
if not _ready:
|
||||
# M13: chua READY (proc respawn / chain reload cham) — cho them
|
||||
# 5s; van chet thi ed.open chi vao ctrl ring (C++ KHONG reset
|
||||
# ring khi respawn → proc moi doc duoc) nen van gui duoc.
|
||||
sess.wait_chain_ready(timeout=5)
|
||||
if not sess.alive():
|
||||
logger.warning("[fxgui] open native bo — session chet path=%s", plugin_path)
|
||||
raise HTTPException(status_code=409,
|
||||
detail="Session FX realtime đã đóng — bật lại FX PWR rồi thử mở GUI.")
|
||||
_killed = _terminate_fx_gui_for_path(plugin_path)
|
||||
_already = fx_realtime.editor_find(plugin_path)
|
||||
_same = _already is not None and _already[0] == sess.name
|
||||
_sent = sess.send_editor_cmd("open", slot=_slot)
|
||||
logger.info("[fxgui] open sent=%s killed_fallback=%s already=%s same=%s proc_pid=%s state=%s",
|
||||
_sent, _killed, bool(_already), _same,
|
||||
sess.proc.pid if getattr(sess, "proc", None) else None,
|
||||
getattr(getattr(sess, "h", None), "state", "?"))
|
||||
fx_realtime.editor_register(plugin_path, sess.name, _slot)
|
||||
# M13 workaround click-1: gui doi khi khong hien window ngay (owner
|
||||
# gan sau show → Win32 an window). Gửi lai ed.open sau 700ms = ban
|
||||
# sao click-2 — C++ log "ALREADY open — re-show" thi re-show chinh
|
||||
# la dieu can. ponytail: bo block nay khi log A (prefix p/t) xac
|
||||
# nhan root cause that (show truoc owner).
|
||||
def _retry_open(delay):
|
||||
_time.sleep(delay)
|
||||
try:
|
||||
if sess.alive():
|
||||
sess.send_editor_cmd("open", slot=_slot)
|
||||
logger.info("[fxgui] retry ed.open path=%s slot=%s", plugin_path, _slot)
|
||||
except Exception:
|
||||
pass
|
||||
threading.Thread(target=_retry_open, args=(0.7,), daemon=True).start()
|
||||
return {"success": True, "started": not _same,
|
||||
"already_running": bool(_same), "native": True,
|
||||
"embed_url": None, "session_id": sess.name, "slot": _slot}
|
||||
|
||||
@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.
|
||||
@@ -726,62 +792,25 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
|
||||
# JS set_chain async / watchdog respawn) -> chờ tối đa 15s cho slot xuất hiện;
|
||||
# session chết giữa chừng -> bỏ (fallback embed bị chặn bên dưới cho master).
|
||||
if req.master and _sess is not None and _sess.alive() and _sess.chain_slot_for_path(plugin_path) is None:
|
||||
# M24: poll async (khong time.sleep dong bo — block event loop lam pump
|
||||
# audio stall). Y nghia giu nguyen: cho toi da 15s slot xuat hien.
|
||||
for _ in range(150):
|
||||
if not _sess.alive():
|
||||
break
|
||||
if _sess.chain_slot_for_path(plugin_path) is not None:
|
||||
break
|
||||
_time.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
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.
|
||||
# M8: set_chain (juce bridge) reload async (watchdog respawn) —
|
||||
# cho chain moi ap tren bridge truoc khi ed.open, khong thi C++
|
||||
# bao "slot ngoai chain (M) — bo qua". Khong co reload dang chay
|
||||
# -> tra ngay (khong them latency mo GUI).
|
||||
_ready = _sess.wait_chain_ready(timeout=15)
|
||||
logger.info("[fxgui] open native path=%s shm=%s slot=%s ready=%s",
|
||||
plugin_path, _sess.name, _slot, _ready)
|
||||
if not _ready:
|
||||
# M13: chua READY (proc respawn / chain reload cham) — cho them
|
||||
# 5s; van chet thi ed.open chi vao ctrl ring (C++ KHONG reset
|
||||
# ring khi respawn → proc moi doc duoc) nen van gui duoc.
|
||||
_sess.wait_chain_ready(timeout=5)
|
||||
if not _sess.alive():
|
||||
logger.warning("[fxgui] open native bo — session chet path=%s", plugin_path)
|
||||
raise HTTPException(status_code=409,
|
||||
detail="Session FX realtime đã đóng — bật lại FX PWR rồi thử mở GUI.")
|
||||
_killed = _terminate_fx_gui_for_path(plugin_path)
|
||||
_already = fx_realtime.editor_find(plugin_path)
|
||||
_same = _already is not None and _already[0] == _sess.name
|
||||
_sent = _sess.send_editor_cmd("open", slot=_slot)
|
||||
logger.info("[fxgui] open sent=%s killed_fallback=%s already=%s same=%s proc_pid=%s state=%s",
|
||||
_sent, _killed, bool(_already), _same,
|
||||
_sess.proc.pid if getattr(_sess, "proc", None) else None,
|
||||
getattr(getattr(_sess, "h", None), "state", "?"))
|
||||
fx_realtime.editor_register(plugin_path, _sess.name, _slot)
|
||||
# M13 workaround click-1: gui doi khi khong hien window ngay (owner
|
||||
# gan sau show → Win32 an window). Gửi lai ed.open sau 700ms = ban
|
||||
# sao click-2 — C++ log "ALREADY open — re-show" thi re-show chinh
|
||||
# la dieu can. ponytail: bo block nay khi log A (prefix p/t) xac
|
||||
# nhan root cause that (show truoc owner).
|
||||
def _retry_open(delay):
|
||||
_time.sleep(delay)
|
||||
try:
|
||||
if _sess.alive():
|
||||
_sess.send_editor_cmd("open", slot=_slot)
|
||||
logger.info("[fxgui] retry ed.open path=%s slot=%s", plugin_path, _slot)
|
||||
except Exception:
|
||||
pass
|
||||
threading.Thread(target=_retry_open, args=(0.7,), daemon=True).start()
|
||||
return {"success": True, "started": not _same,
|
||||
"already_running": bool(_same), "native": True,
|
||||
"embed_url": None, "session_id": _sess.name, "slot": _slot}
|
||||
# M24: toan bo nhanh native (wait_chain_ready sleep-poll, terminate
|
||||
# fallback proc.wait, ed.open) chay trong worker thread duoi lock per
|
||||
# session — event loop khong block -> pump audio khong stall khi switch
|
||||
# GUI luc dang play. None khi plugin chua phai slot -> roi xuong
|
||||
# fallback ben duoi nhu cu.
|
||||
async with _fxgui_op_lock(_sess.name):
|
||||
_native = await asyncio.to_thread(
|
||||
_fxgui_open_native_blocking, _sess, plugin_path)
|
||||
if _native is not None:
|
||||
return _native
|
||||
# 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
|
||||
@@ -904,8 +933,12 @@ async def close_fx_gui(req: FxGuiCloseRequest, current_user: dict = Depends(get_
|
||||
# M5: capture state editor TRƯỚC khi đóng (preset user chỉnh trong
|
||||
# GUI → lưu về project như WM_FXGUI_CAPTURE cũ; preset_b64 dùng để
|
||||
# mở lại GUI / nạp chain với đúng cài đặt). Không lỗi nếu rỗng.
|
||||
_preset = _sess.capture_editor_preset(_slot)
|
||||
_sess.send_editor_cmd("close", slot=_slot)
|
||||
# M24: capture (poll file sleep toi 3s) trong thread duoi lock —
|
||||
# khong block event loop/pump audio.
|
||||
async with _fxgui_op_lock(_sess.name):
|
||||
_preset = await asyncio.to_thread(
|
||||
_sess.capture_editor_preset, _slot)
|
||||
_sess.send_editor_cmd("close", slot=_slot)
|
||||
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ó)
|
||||
@@ -958,8 +991,12 @@ async def hide_fx_gui(req: FxGuiNativeRequest,
|
||||
# M13: capture preset TRUOC khi an — dong mastering panel / save project
|
||||
# giua luc chinh GUI native khong mat preset moi (truoc day chi capture o
|
||||
# /fx-gui/close; hide chi an -> chain luu preset cu/rong sau khi reload).
|
||||
_preset = sess.capture_editor_preset(slot)
|
||||
sess.send_editor_cmd("hide", slot=slot)
|
||||
# M24: capture (poll file sleep toi 3s) trong thread duoi lock — day la
|
||||
# blocker chinh moi lan switch GUI (JS fire-and-forget hide truoc open);
|
||||
# truoc day sleep dong bo trong async endpoint -> pump audio stall -> lag.
|
||||
async with _fxgui_op_lock(sess.name):
|
||||
_preset = await asyncio.to_thread(sess.capture_editor_preset, slot)
|
||||
sess.send_editor_cmd("hide", slot=slot)
|
||||
return {"success": True, "applied": True, "native": True,
|
||||
"preset_b64": _preset}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user