// 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). // // 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.inL = new Float32Array(this.BLOCK); this.inR = new Float32Array(this.BLOCK); this.inCount = 0; this.outQueue = []; // [{l: Float32Array, r: Float32Array}] this.outPos = 0; this.port.onmessage = (e) => { const d = e.data; 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 block 256 → gửi main thread (transferable buffer). 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.BLOCK) { this.inL[this.inCount] = inL[i]; this.inR[this.inCount] = inR[i]; } this.inCount++; if (this.inCount >= this.BLOCK) { const inter = new Float32Array(this.BLOCK * 2); for (let j = 0; j < this.BLOCK; 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);