88 lines
3.0 KiB
JavaScript
88 lines
3.0 KiB
JavaScript
// 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 8 block (~42ms @48k) — hap thu jitter pump
|
|
// nhung gioi han backlog: 64 block (~371ms) tao tre ~0.3-0.4s khi play/
|
|
// keyboard preview (audio moi phai cho ring cu chay het).
|
|
// - 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 = 8;
|
|
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);
|