// app/static/js/services/bridgeAudioWorklet.js // VSTi crackle fix: audio sink chay tren audio thread (khong jank main thread). // - Ring block 256 mau, toi da 64 block (~371ms @48k) — hap thu jitter pump. // - Underrun: fade ve 0 thay vi click (hard silence). // - Resume sau silence: fade vao tu 0. // - Overrun: drop block cu nhat + crossfade 64 mau tai diem drop (het click). class BridgeAudioSinkProcessor extends AudioWorkletProcessor { constructor() { super(); this._ring = []; this._MAX = 64; this._gain = 1.0; this._lastOutL = 0.0; this._lastOutR = 0.0; this._skipFade = 0; // >0: crossfade dang chay sau overrun drop this._skipFromL = 0.0; this._skipFromR = 0.0; this.port.onmessage = (e) => { const m = e.data; if (!m) return; if (m.type === 'audio') { this._ring.push({ l: m.l, r: m.r }); while (this._ring.length > this._MAX) { // overrun: bo block cu nhat; lan consume toi se crossfade ranh gioi this._ring.shift(); this._skipFade = 64; } } else if (m.type === 'flush') { this._ring.length = 0; this._gain = 1.0; this._skipFade = 0; } }; } process(inputs, outputs) { const out = outputs[0]; if (!out || out.length < 2) return true; const L = out[0]; const R = out[1]; const n = L.length; let w = 0; while (w < n) { if (this._ring.length) { const blk = this._ring[0]; const avail = blk.l.length; const take = Math.min(avail, n - w); if (this._skipFade > 0) { for (let i = 0; i < take; i++) { const t = i / 64; L[w + i] = (this._lastOutL * (1 - t) + blk.l[i] * t) * this._gain; R[w + i] = (this._lastOutR * (1 - t) + blk.r[i] * t) * this._gain; } this._skipFade = Math.max(0, this._skipFade - take); } else { for (let i = 0; i < take; i++) { L[w + i] = blk.l[i] * this._gain; R[w + i] = blk.r[i] * this._gain; } } this._lastOutL = L[w + take - 1]; this._lastOutR = R[w + take - 1]; if (this._gain < 1.0) this._gain = Math.min(1.0, this._gain + take / 256.0); w += take; if (take >= avail) { this._ring.shift(); } else { blk.l = blk.l.subarray(take); blk.r = blk.r.subarray(take); } } else { // underrun: fade ve 0 (khong click) for (let i = w; i < n; i++) { this._gain = Math.max(0, this._gain - 1.0 / 256.0); L[i] = this._lastOutL * this._gain; R[i] = this._lastOutR * this._gain; } this._lastOutL *= this._gain; this._lastOutR *= this._gain; w = n; } } return true; } } registerProcessor('bridge-audio-sink', BridgeAudioSinkProcessor);