V24: fix master-chain crackle, 1s lag, solo bypass
- worklet: FILL 48->24 (PDC 7936 dung), flush protocol khi resume sau interruption
(clear queue + fade-in/crossfade chi voi block moi), drop block stale
- engine fx_realtime: _flush_input_locked NO-DROP (ring day -> giu pending, cap 2048),
flush_all() dong bo in/out khi worklet resume
- app.jsx: WS flush_done + flushing guard, fallback timer 500ms
- plugins.py: xu ly {cmd:flush} -> flush_done
- index.html: bump v param
This commit is contained in:
+61
-3
@@ -1848,7 +1848,7 @@ async function fxRtStart() {
|
||||
fxRt.sessionId = st.session_id;
|
||||
fxRtGuiPushShm();
|
||||
try {
|
||||
await ctx.audioWorklet.addModule(`${API_BASE_URL}/static/js/sf-fx-realtime.js?v=202608312300`);
|
||||
await ctx.audioWorklet.addModule(`${API_BASE_URL}/static/js/sf-fx-realtime.js?v=202609011200`);
|
||||
if (token !== fxRt.startToken) { fxRt.starting = false; return; }
|
||||
fxRt.node = new AudioWorkletNode(ctx, 'sf-fx-realtime', { numberOfInputs: 1, numberOfOutputs: 1, outputChannelCount: [2] });
|
||||
const u = new URL(API_BASE_URL);
|
||||
@@ -1861,6 +1861,7 @@ async function fxRtStart() {
|
||||
ws.onopen = () => {
|
||||
if (token !== fxRt.startToken) { try { ws.close(); } catch (e) {} fxRt.starting = false; return; }
|
||||
fxRt.active = true;
|
||||
fxRt.flushing = false;
|
||||
fxRt.starting = false;
|
||||
fxRt.inQueue = []; // bỏ audio stale sản xuất trước khi WS open (~4s load VST) — hết lag khởi đầu
|
||||
try { fxRtPushAllParams(); } catch (e) {}
|
||||
@@ -1876,11 +1877,18 @@ async function fxRtStart() {
|
||||
// Chi delay dry path khi crossfade da xong (dry silent) — ap som
|
||||
// trong luc dry con la path nghe duoc se tao gap ~80ms.
|
||||
if (fxRt.fadedIn) fxRtApplyPdc();
|
||||
} else if (d && d.cmd === 'flush_done') {
|
||||
// V24: engine da flush ring xong — mo lai nhan output + bao worklet
|
||||
// (worklet drop block cho ack nay; mat ack -> fallback timer nha).
|
||||
fxRt.flushing = false;
|
||||
if (fxRt.flushTimer) { clearTimeout(fxRt.flushTimer); fxRt.flushTimer = null; }
|
||||
if (fxRt.node) fxRt.node.port.postMessage({ type: 'flush_done' });
|
||||
}
|
||||
} catch (e) {}
|
||||
return;
|
||||
}
|
||||
if (!(ev.data instanceof ArrayBuffer) || !fxRt.node) return;
|
||||
if (fxRt.flushing) return; // V24: output stale trong luc flush — drop
|
||||
const f = new Float32Array(ev.data);
|
||||
const half = f.length / 2;
|
||||
const l = new Float32Array(half);
|
||||
@@ -1940,6 +1948,22 @@ async function fxRtStart() {
|
||||
}, 30);
|
||||
return;
|
||||
}
|
||||
if (d.type === 'flush') {
|
||||
// V24: worklet underrun-resume — yeu cau engine flush ring (xoa backlog
|
||||
// stale) → het lag ~1s sau stall. Fallback timer phong ack mat (WS/
|
||||
// engine loi) → tu nha sau 500ms, worklet nhan block moi binh thuong.
|
||||
if (fxRt.ws && fxRt.ws.readyState === 1) {
|
||||
try { fxRt.ws.send(JSON.stringify({ cmd: 'flush' })); } catch (e) {}
|
||||
}
|
||||
fxRt.flushing = true;
|
||||
if (fxRt.flushTimer) clearTimeout(fxRt.flushTimer);
|
||||
fxRt.flushTimer = setTimeout(() => {
|
||||
fxRt.flushTimer = null;
|
||||
fxRt.flushing = false;
|
||||
if (fxRt.node) fxRt.node.port.postMessage({ type: 'flush_done' });
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
if (d.type === 'set_param') {
|
||||
try { fxRt.ws.send(JSON.stringify({ cmd: 'set_param', slot: d.slot, key: d.key, value: d.value })); } catch (e) {}
|
||||
return;
|
||||
@@ -1985,7 +2009,9 @@ function fxRtStop(silent) {
|
||||
fxRt.startToken++;
|
||||
fxRt.starting = false;
|
||||
if (fxRt.pumpTimer) { clearInterval(fxRt.pumpTimer); fxRt.pumpTimer = null; }
|
||||
if (fxRt.flushTimer) { clearTimeout(fxRt.flushTimer); fxRt.flushTimer = null; }
|
||||
fxRt.inQueue = [];
|
||||
fxRt.flushing = false;
|
||||
if (!fxRt.active && !fxRt.ws && !fxRt.node && !fxRt.sessionId) return;
|
||||
let _fxStopNode = null;
|
||||
try {
|
||||
@@ -24466,11 +24492,11 @@ const App = () => {
|
||||
console.warn('[FX RT track] start fail', trackId, path, e);
|
||||
return;
|
||||
}
|
||||
const entry = { sessionId: st.session_id, ws: null, node: null, ready: false, path, spliced: null, chainKey: fxRtTrackChainKey(chain) + '|vb:' + (track && track.vstFxBypass ? '1' : '0'), paramsBySlot: chain.map(s => fxRtEntryParams(s, null)), inQueue: [], pumpTimer: null, lastInSent: 0 };
|
||||
const entry = { sessionId: st.session_id, ws: null, node: null, ready: false, path, spliced: null, chainKey: fxRtTrackChainKey(chain) + '|vb:' + (track && track.vstFxBypass ? '1' : '0'), paramsBySlot: chain.map(s => fxRtEntryParams(s, null)), inQueue: [], pumpTimer: null, lastInSent: 0, flushing: false, flushTimer: null };
|
||||
fxRtTracks[key] = entry;
|
||||
fxRtGuiPushShm();
|
||||
try {
|
||||
await ctx.audioWorklet.addModule(`${API_BASE_URL}/static/js/sf-fx-realtime.js?v=202608312300`);
|
||||
await ctx.audioWorklet.addModule(`${API_BASE_URL}/static/js/sf-fx-realtime.js?v=202609011200`);
|
||||
const node = new AudioWorkletNode(ctx, 'sf-fx-realtime', { numberOfInputs: 1, numberOfOutputs: 1, outputChannelCount: [2] });
|
||||
entry.node = node;
|
||||
const u = new URL(API_BASE_URL);
|
||||
@@ -24488,7 +24514,19 @@ const App = () => {
|
||||
} catch (e) {}
|
||||
};
|
||||
ws.onmessage = (ev) => {
|
||||
if (typeof ev.data === 'string') {
|
||||
try {
|
||||
const d = JSON.parse(ev.data);
|
||||
if (d && d.cmd === 'flush_done') {
|
||||
entry.flushing = false;
|
||||
if (entry.flushTimer) { clearTimeout(entry.flushTimer); entry.flushTimer = null; }
|
||||
if (entry.node) entry.node.port.postMessage({ type: 'flush_done' });
|
||||
}
|
||||
} catch (e) {}
|
||||
return;
|
||||
}
|
||||
if (!(ev.data instanceof ArrayBuffer) || !entry.node) return;
|
||||
if (entry.flushing) return; // V24: output stale trong luc flush — drop
|
||||
const f = new Float32Array(ev.data);
|
||||
const half = f.length / 2;
|
||||
const l = new Float32Array(half);
|
||||
@@ -24501,6 +24539,21 @@ const App = () => {
|
||||
entry.node.port.onmessage = (ev) => {
|
||||
const d = ev.data;
|
||||
if (!d || !entry.ws) return;
|
||||
if (d.type === 'flush') {
|
||||
// V24: nhu master — worklet underrun-resume → flush engine ring
|
||||
// (xoa backlog stale) → het lag ~1s sau stall. Fallback phong ack mat.
|
||||
if (entry.ws && entry.ws.readyState === 1) {
|
||||
try { entry.ws.send(JSON.stringify({ cmd: 'flush' })); } catch (e) {}
|
||||
}
|
||||
entry.flushing = true;
|
||||
if (entry.flushTimer) clearTimeout(entry.flushTimer);
|
||||
entry.flushTimer = setTimeout(() => {
|
||||
entry.flushTimer = null;
|
||||
entry.flushing = false;
|
||||
if (entry.node) entry.node.port.postMessage({ type: 'flush_done' });
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
if (d.type === 'set_param') {
|
||||
try { entry.ws.send(JSON.stringify({ cmd: 'set_param', slot: d.slot, key: d.key, value: d.value })); } catch (e) {}
|
||||
return;
|
||||
@@ -24588,7 +24641,9 @@ const App = () => {
|
||||
const entry = fxRtTracks[key];
|
||||
if (!entry) return;
|
||||
if (entry.pumpTimer) { clearInterval(entry.pumpTimer); entry.pumpTimer = null; }
|
||||
if (entry.flushTimer) { clearTimeout(entry.flushTimer); entry.flushTimer = null; }
|
||||
entry.inQueue = [];
|
||||
entry.flushing = false;
|
||||
try {
|
||||
fxRtTrackUnsplice(trackId, path);
|
||||
if (entry.node) { entry.node.port.onmessage = null; try { entry.node.disconnect(); } catch (e) {} entry.node = null; }
|
||||
@@ -25388,6 +25443,9 @@ const App = () => {
|
||||
// Solo playback for Local Selection Loop (LOOP_MAKER.md §2.2)
|
||||
const startLocalTrackPlayback = (trackId, offsetTime) => {
|
||||
const context = getAudioContext();
|
||||
// V24: solo/local path — gọi fxRtStart (idempotent) để âm solo qua master
|
||||
// chain (trước đây path này bỏ qua → solo không qua master FX).
|
||||
try { fxRtStart(); } catch (e) { console.warn('[FX RT] start error:', e); }
|
||||
// V12: parity với startTrackPlayback — loop restart (solo/local) sau
|
||||
// stopAllPlayback(): hủy STOP#2 + báo PLAY (nếu không C++ transportStopped
|
||||
// giữ true → mọi note-on bị drop → solo/local loop pass 2+ câm) + set
|
||||
|
||||
Reference in New Issue
Block a user