fix(fxrt): tang cushion worklet 128->256ms + telemetry underrun (giam crackle do main-thread stall)
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user