Files
SonicForgeStudio/app/static/js/sf-fx-realtime.js
T
admin 0acc3ee7ee fix(fx-rt): burst 4-block input batch qua bridge — hết lag/giật/crackle master FX
- worklet gom 1024 mẫu (4 block) vào 1 frame interleaved, main thread tách 4 WS frame 512 floats
- bridge take=min(avail,4) xử lí 1 call process cho burst → overhead VST3 amortize 4 lần
- engine _pump_output pace theo deadline thay vì sleep cố định (outRead 131→187.5/s)
- FX_RT_ROUNDTRIP_SAMPLES 512→1792 (RTT batching ~7 block)
2026-08-23 11:44:54 +07:00

87 lines
3.4 KiB
JavaScript

// sf-fx-realtime.js — AudioWorkletProcessor nối WebAudio → SHM FX engine.
// process() gom 128-sample quantum thành block 256 stereo float32 (khớp
// FXRT_BLOCK trong FxRealtimeIPC.h) → postMessage cho main thread gửi WS;
// nhận block đã xử lý từ main thread → queue → phát ra output. Queue rỗng →
// zero fill (WS chưa kịp round-trip).
//
// BURST (fix lag/giật/crackle): gom 4 block (1024 mẫu) vào 1 frame rồi post.
// Bridge VST3 (fx_vst_bridge) chạy chậm hơn realtime khi take=1 (256 mẫu:
// ~5.5ms/call > budget 5.33ms → 179/s < 187.5/s → engine drop input →
// crackle). Gửi burst 4 block → bridge take=4 → 1 call process 1024 mẫu
// (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.
class SfFxRealtimeProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.BLOCK = 256; // FXRT_BLOCK
this.BURST = 4; // số block gom 1 frame (bridge batch)
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.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();
};
}
process(inputs, outputs) {
const input = inputs[0];
const output = outputs[0];
const n = output[0] ? output[0].length : 128;
const outL = output[0];
const outR = output[1];
// Góp input thành burst 4 block (1024 mẫu) → gửi main thread
// (transferable buffer). Main thread tách 4 WS frame → bridge batch 4.
if (input && input[0]) {
const inL = input[0];
const inR = input[1] || input[0];
for (let i = 0; i < n; i++) {
if (this.inCount < this.bufLen) {
this.inL[this.inCount] = inL[i];
this.inR[this.inCount] = inR[i];
}
this.inCount++;
if (this.inCount >= this.bufLen) {
const inter = new Float32Array(this.bufLen * 2);
for (let j = 0; j < this.bufLen; j++) {
inter[j * 2] = this.inL[j];
inter[j * 2 + 1] = this.inR[j];
}
this.port.postMessage({ type: 'in', data: inter.buffer }, [inter.buffer]);
this.inCount = 0;
}
}
}
// Phát output từ queue; rỗng → zero.
for (let i = 0; i < n; i++) {
if (this.outQueue.length === 0) {
outL[i] = 0;
if (outR) outR[i] = 0;
continue;
}
const blk = this.outQueue[0];
outL[i] = blk.l[this.outPos];
if (outR) outR[i] = blk.r[this.outPos];
this.outPos++;
if (this.outPos >= this.BLOCK) {
this.outQueue.shift();
this.outPos = 0;
}
}
return true;
}
}
registerProcessor('sf-fx-realtime', SfFxRealtimeProcessor);