V21: keep master FX session warm on user stop - no 4s VST reload on replay

- stopAllPlayback(fade=true) giữ master FX session (không teardown) → play
  kế tiếp FX xử lý ngay, hết cửa sổ dry ~4s khi reload VST
- per-track FX vẫn teardown khi user stop (VSTi nhiều track nặng CPU)
- idle timeout 120s (FX_RT_WARM_IDLE_MS): ngừng chơi lâu → teardown giải
  phóng CPU/VST
- fxRtStart/fxRtStop/startSubTabPlayback hủy idle timer khi play
This commit is contained in:
2026-08-26 18:31:25 +07:00
parent 3e6080603c
commit b95c63e7db
3 changed files with 48 additions and 14 deletions
+27 -4
View File
@@ -1714,9 +1714,14 @@ const fxRt = { active: false, starting: false, startToken: 0, sessionId: null, w
// Loop restart/seek/reschedule: stopAllPlayback giữ session FX realtime (không
// teardown) → fxRtStart early-return → KHÔNG reload VST ~4s mỗi pass loop
// (hết bypass rồi mới load lại). Chỉ set TRƯỚC nhánh restart/resume; user
// stop thật (fade=true) / bridge-down / new-project / export-end KHÔNG set →
// teardown bình thường.
// stop thật (fade=true) giữ master FX warm + idle timeout (V21); bridge-down /
// new-project / export-end → teardown bình thường.
let fxRtKeepForRestart = false;
// V21: user stop (fade=true) giữ master FX session warm — play kế tiếp có FX
// ngay, không chờ reload VST ~4s. Idle timeout: ngừng chơi lâu → teardown
// giải phóng CPU/VST (chấp nhận ~4s delay ở play sau khi idle lâu).
const FX_RT_WARM_IDLE_MS = 120000;
let fxRtWarmIdleTimer = null;
// Native VSTi GUI open/reload đang chạy (C++ reloadForGUI mute 1-3s khi mở
// lại GUI sau khi đóng) → master-silence watchdog (750ms) sẽ false-positive →
// rebuild + fxRtStop → dry→wet vài giây. Exempt watchdog trong cửa sổ này.
@@ -1797,6 +1802,9 @@ async function fxRtSetChain(chain) {
}
}
async function fxRtStart() {
// V21: mọi play/chain-change gọi đây — hủy idle timer của lần stop trước
// (không để session warm bị teardown giữa lúc đang chơi).
if (fxRtWarmIdleTimer) { clearTimeout(fxRtWarmIdleTimer); fxRtWarmIdleTimer = null; }
const chain = fxRtChain();
const ck = fxRtChainKey(chain);
if (fxRt.active) {
@@ -1970,6 +1978,7 @@ async function fxRtStart() {
}
}
function fxRtStop(silent) {
if (fxRtWarmIdleTimer) { clearTimeout(fxRtWarmIdleTimer); fxRtWarmIdleTimer = null; }
// Phải bump token TRƯỚC guard early-return: lúc fxRtStart đang in-flight thì
// active/ws/node/sessionId đều chưa có → guard sẽ return, nhưng start vẫn
// phải bị hủy (token mismatch ở checkpoint).
@@ -23645,6 +23654,9 @@ const App = () => {
const startSubTabPlayback = (st, offsetWallTime) => {
const context = getAudioContext();
_nativePlayEpoch++; // new play — vô hiệu doHardStop đang fade
// V21: section play không gọi fxRtStart — hủy idle timer thủ công (không
// để master FX bị teardown giữa lúc section đang phát).
if (fxRtWarmIdleTimer) { clearTimeout(fxRtWarmIdleTimer); fxRtWarmIdleTimer = null; }
restorePendingFadeSession(); // V17: huy fade dang treo — replay khong bi dip gain
if (!st.buffer) {
st.buffer = context.createBuffer(1, 128, context.sampleRate);
@@ -25679,13 +25691,24 @@ const App = () => {
// Realtime VST FX: dừng session + khôi phục output→analyser. Nhánh restart
// (loop/seek/reschedule/solo...) set fxRtKeepForRestart=true → GIỮ session
// (fxRtStart early-return khi active + chain khớp) → hết bypass+reload ~4s
// mỗi pass loop. User stop thật (fade=true) → teardown bình thường.
const _keepFx = fxRtKeepForRestart;
// mỗi pass loop. User stop thật (fade=true) → V21 giữ master FX warm (idle
// timeout FX_RT_WARM_IDLE_MS) — play kế tiếp FX ngay; per-track teardown.
const _keepFx = fxRtKeepForRestart || !!fade;
fxRtKeepForRestart = false;
if (!_keepFx) {
try { fxRtStop(); } catch (e) { console.warn('[FX RT] stop error:', e); }
// Realtime VST FX per-track: dừng mọi session track + restore chain trực tiếp.
try { fxRtTrackStopAll(); } catch (e) { console.warn('[FX RT track] stop error:', e); }
} else if (fade) {
// V21: user stop — GIỮ master FX session warm (play kế tiếp có FX ngay,
// không chờ reload VST ~4s). Per-track vẫn teardown (VSTi nhiều track nặng).
// Idle timeout (FX_RT_WARM_IDLE_MS) giải phóng CPU khi ngừng chơi lâu.
try { fxRtTrackStopAll(); } catch (e) { console.warn('[FX RT track] stop error:', e); }
clearTimeout(fxRtWarmIdleTimer);
fxRtWarmIdleTimer = setTimeout(() => {
fxRtWarmIdleTimer = null;
try { fxRtStop(); } catch (e) { console.warn('[FX RT] idle stop error:', e); }
}, FX_RT_WARM_IDLE_MS);
}
_nativePlayEpoch++; // hủy mọi render/decode pending của lần play cũ
// V11: hủy mọi note timer của lần play cũ (stop/seek/loop restart) — timer