fix(fx-rt): het crackle master FX - limiter attack smooth + crossfade khi bat/tat chain

- NativeInstrumentEngine: kAttack 0.9->0.1 (het zipper khi sum nhieu track)
- app.jsx: crossfade masterFxDryGain/masterFxGain thay hard-rewire graph luc
  activation/deactivation master chain FX; worklet bao 'started' -> ramp dung
  luc; PDC tre sau khi dry silent (tranh gap 80ms)
- sf-fx-realtime.js: fade-in 512 mau luc FILL xong / resume sau underrun
- index.html + addModule: cache-buster v=202608241200
- rebuild app.precompiled.js, copy bundle vao dist/ + src-tauri/resources/
- keo dai fix cac round truoc: FILL 16, FXRT_BURST_MS, REBUFFER, pacing,
  VST GUI click/focus, solo crackle
- them harness + plans/patch scripts
This commit is contained in:
2026-08-24 07:46:09 +07:00
parent 373a96cf7b
commit 10997a65b9
21 changed files with 1181 additions and 177 deletions
+26
View File
@@ -631,6 +631,11 @@ class FxGuiRequest(BaseModel):
# mở mapping này để PEEK input ring và chạy processAudio() trên instance
# GUI → meter trong plugin nhảy theo âm thật (GUI không consume ring).
shm: str = ""
# preset_b64: preset GUI capture mới nhất (bridge state blob hoặc raw
# .vstpreset b64) — bridge áp sau fx->load() để mở GUI với ĐÚNG cài đặt
# đã chỉnh (trước đây GUI mở lại load default → mất settings khi
# hide/show).
preset_b64: str = ""
class FxGuiCloseRequest(BaseModel):
@@ -682,6 +687,8 @@ async def open_fx_gui(req: FxGuiRequest, current_user: dict = Depends(get_curren
job = {"path": plugin_path, "name": req.name or os.path.basename(plugin_path)}
if req.shm:
job["shm"] = req.shm
if req.preset_b64:
job["preset_b64"] = req.preset_b64
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:
@@ -1003,6 +1010,10 @@ class FxRealtimeStartRequest(BaseModel):
class FxRealtimeStopRequest(BaseModel):
session_id: str = ""
class FxRealtimeSetChainRequest(BaseModel):
session_id: str = ""
fx_chain: list = []
@router.post("/fx-realtime/start")
async def fx_realtime_start(req: FxRealtimeStartRequest,
current_user: dict = Depends(get_current_user)):
@@ -1033,6 +1044,21 @@ async def fx_realtime_stop(req: FxRealtimeStopRequest,
ok = fx_realtime.stop_session(req.session_id or "")
return {"success": True, "stopped": ok}
@router.post("/fx-realtime/set-chain")
async def fx_realtime_set_chain(req: FxRealtimeSetChainRequest,
current_user: dict = Depends(get_current_user)):
"""Cập nhật chain trên session realtime ĐANG CHẠY — KHÔNG restart session.
Engine ghi lại job file (seq++); bridge poll seq → RealtimeFxChain::setChain
(worker load chain mới, swap atomic dưới mutex — audio thread copy
shared_ptr, không dừng) → load/đổi VST FX + đổi preset GUI không ngắt âm/
crackle. Session/WS/SHM giữ nguyên."""
enforce_password_changed(current_user)
sess = fx_realtime.get_session(req.session_id or "")
if not sess:
raise HTTPException(status_code=404, detail="Session realtime không tồn tại")
ok = sess.set_chain(req.fx_chain or [])
return {"success": True, "updated": ok}
@router.websocket("/ws/fx-realtime/{session_id}")
async def ws_fx_realtime(websocket: WebSocket, session_id: str):
"""Vòng lặp FX realtime: receive block input → SHM → bridge → SHM → send