fix(fxrt): tang cushion worklet 128->256ms + telemetry underrun (giam crackle do main-thread stall)

This commit is contained in:
2026-08-24 15:20:26 +07:00
parent 36aa15e2eb
commit 272382f73c
3 changed files with 32 additions and 9 deletions
+21 -4
View File
@@ -30,8 +30,8 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
super();
this.BLOCK = 256; // FXRT_BLOCK
this.BURST = 4; // số block gom 1 frame (bridge batch)
this.FILL = 24; // 24 block ~128ms: buffer khởi đầu — hấp thụ jitter main-thread (GUI decode, rAF, solo restart)
this.CAP = 48; // cap queue chặn overflow (~256ms)
this.FILL = 48; // 48 block ~256ms: buffer khởi đầu — hấp thụ jitter main-thread (GUI decode, rAF, VST panel)
this.CAP = 64; // cap queue chặn overflow (~341ms)
this.HOLD = 2; // 2 block ~11ms: lặp block cuối khi underrun (hết click) trước khi im lặng
this.HOLD_FADE = 4; // 4 block ~21ms: fade 1→0 sau HOLD hết (hết click cắt cứng)
this.lastBlock = null; // block cuối đã phát xong (hold khi underrun)
@@ -55,8 +55,14 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
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 > this.CAP) this.outQueue.shift();
while (this.outQueue.length > this.CAP) { this.outQueue.shift(); this.statCapDrop++; }
};
// Telemetry (chẩn đoán crackle trên session thật): đếm sự kiện + báo main
// thread mỗi ~2s. Nếu user bật master FX nghe crackle mà stats cho thấy
// underruns>0 → delivery bị stall (main-thread busy) → nguồn crackle thật.
this.statUnderrun = 0; this.statHold = 0; this.statFade = 0;
this.statSilence = 0; this.statCapDrop = 0; this.statFadeIn = 0;
this.statCalls = 0;
}
process(inputs, outputs) {
const input = inputs[0];
@@ -93,6 +99,14 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
// block (không zero-fill → hết click/ngắt) rồi mới im lặng; resume NGAY
// khi có block mới (không đợi re-buffer FILL) + không re-fade sau lần đầu.
for (let i = 0; i < n; i++) {
this.statCalls++;
if (this.statCalls >= 1500) { // ~2s @48k/128
this.statCalls = 0;
this.port.postMessage({ type: 'stats',
underruns: this.statUnderrun, holds: this.statHold, fades: this.statFade,
silences: this.statSilence, capDrops: this.statCapDrop, fadeIns: this.statFadeIn,
queueLen: this.outQueue.length });
}
if (this.playing) {
const blk = this.outQueue[0];
if (blk) {
@@ -112,10 +126,12 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
}
// Underrun giữa stream — chuyển hold (không cắt zero ngay → hết click).
this.playing = false;
this.statUnderrun++;
if (!this.silenced) { // đã im lặng (fade xong) → giữ im lặng, không hold lại block cũ
this.holdPos = 0;
this.holdLeft = this.HOLD * this.BLOCK;
this.holdFadeLeft = this.HOLD_FADE * this.BLOCK;
this.statHold++;
}
}
// Không phát: resume khi đủ block (lần đầu chờ FILL; sau đó chỉ cần 1
@@ -131,6 +147,7 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
// fade-in nhanh tránh jump ~0→1 (click sau long stall).
this.outGain = 0;
this.fadeLeft = this.FADE;
this.statFadeIn++;
}
this.silenced = false;
if (!this.hasStarted) {
@@ -159,7 +176,7 @@ class SfFxRealtimeProcessor extends AudioWorkletProcessor {
this.holdPos++;
if (this.holdPos >= this.BLOCK) this.holdPos = 0;
this.holdFadeLeft--;
if (this.holdFadeLeft <= 0) this.silenced = true; // fade xong → im lặng thật
if (this.holdFadeLeft <= 0) { this.silenced = true; this.statFade++; this.statSilence++; } // fade xong → im lặng thật
continue;
}
outL[i] = 0;