fix(fx-rt): het crackle master FX - engine gui block ASAP + worklet FILL buffer 8
_pump_output bo pace realtime (truoc: moi block send cach nhau block_sec lam client queue ~1 -> jitter bridge/WS/GC -> queue can -> zero-fill crackle). Gio gui het burst ASAP; worklet sf-fx-realtime them FILL=8: chi phat khi outQueue >= 8 block, underrun -> re-buffer (im lang cho du FILL) thay vi zero-fill lien tuc. Cap 32 giu nguyen. PDC: FX_RT_ROUNDTRIP_SAMPLES 1792->3840 (RTT ~7 block + FILL 8 block = 15 block @48k) de dry-bypass path khong lech pha voi mastered. Cache-buster v=202608212401 (precompiled + worklet). Rebuild PyInstaller + copy 3 noi deploy. Verify: probe crackle3 25s Ozone rebuffer_events=0 (zero_fill 12 block = startup fill), picker UI PASS (59 VST).
This commit is contained in:
@@ -11,26 +11,34 @@
|
||||
// (amortize overhead: 14-19ms/4 block ≈ 3.5-4.75ms/block) → đủ 187.5/s.
|
||||
// Latency thêm ~4 block (~21ms) — worklet outQueue 32 block hấp thụ.
|
||||
//
|
||||
// Latency ~2 block; backlog > 32 block (~190ms) → drop block cũ nhất. Cap cao
|
||||
// hấp thụ jitter main thread (rAF renderFrame nặng khi mở MASTERING PANEL) mà
|
||||
// không drop. ponytail: thay drop bằng adaptive stretch khi cần chất lượng cao.
|
||||
// FILL (hết crackle do jitter): không phát ngay khi có block đầu — chờ queue
|
||||
// ≥ FILL (8 block ~43ms @48k) rồi mới phát. Engine gửi ASAP (không pace
|
||||
// realtime) → client tích lũy buffer an toàn; mọi jitter (bridge process VST
|
||||
// biến thiên, GC, WS, main thread rAF) bị hấp thụ bởi FILL thay vì queue ≈1
|
||||
// → cạn → zero-fill. Underrun (queue cạn khi đang phát) → re-buffer: im lặng
|
||||
// chờ đủ FILL rồi phát tiếp (1 lần gián đoạn thay vì zero-fill liên tục).
|
||||
// Cap 32 block (~170ms) chặn overflow khi bridge nhanh hơn realtime.
|
||||
// ponytail: thay drop/rebuffer bằng adaptive stretch khi cần chất lượng cao.
|
||||
class SfFxRealtimeProcessor extends AudioWorkletProcessor {
|
||||
constructor() {
|
||||
super();
|
||||
this.BLOCK = 256; // FXRT_BLOCK
|
||||
this.BURST = 4; // số block gom 1 frame (bridge batch)
|
||||
this.FILL = 8; // block buffer tối thiểu trước khi phát
|
||||
this.CAP = 32; // cap queue chặn overflow
|
||||
this.bufLen = this.BLOCK * this.BURST;
|
||||
this.inL = new Float32Array(this.bufLen);
|
||||
this.inR = new Float32Array(this.bufLen);
|
||||
this.inCount = 0;
|
||||
this.outQueue = []; // [{l: Float32Array, r: Float32Array}]
|
||||
this.outPos = 0;
|
||||
this.playing = false; // false → im lặng (đang chờ đủ FILL)
|
||||
this.port.onmessage = (e) => {
|
||||
const d = e.data;
|
||||
if (d && d.type === 'set_param') { this.port.postMessage(d); return; }
|
||||
if (!d || d.type !== 'out' || !d.l || !d.r) return;
|
||||
this.outQueue.push({ l: d.l, r: d.r });
|
||||
while (this.outQueue.length > 32) this.outQueue.shift();
|
||||
while (this.outQueue.length > this.CAP) this.outQueue.shift();
|
||||
};
|
||||
}
|
||||
process(inputs, outputs) {
|
||||
@@ -63,12 +71,17 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
// Phát output từ queue; rỗng → zero.
|
||||
// Phát output: im lặng tới khi queue ≥ FILL; queue cạn khi đang phát
|
||||
// (underrun) → im lặng chờ đủ FILL (re-buffer) thay vì zero-fill liên tục.
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (this.outQueue.length === 0) {
|
||||
outL[i] = 0;
|
||||
if (outR) outR[i] = 0;
|
||||
continue;
|
||||
if (this.playing && this.outQueue.length === 0) this.playing = false;
|
||||
if (!this.playing) {
|
||||
if (this.outQueue.length >= this.FILL) this.playing = true;
|
||||
else {
|
||||
outL[i] = 0;
|
||||
if (outR) outR[i] = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const blk = this.outQueue[0];
|
||||
outL[i] = blk.l[this.outPos];
|
||||
|
||||
Reference in New Issue
Block a user