V45.27: fix (1) PR tab preview cam khi sub-tab PR khong ARM - ARM MAIN/SECTION cuc bo, khong leak sang PR (3 nhanh: vkbNoteOn prArmedIds + attachMidiHandler bridge bPrArmed + SonicSF arSubs them && isArmed); (2) crackle preview VST2 instrument loop/tremolo - ring worklet bridgeAudioWorklet 16->32 block (~170ms) hap thu stall render loop C++ khi instrument xu li loop/tremolo nang CPU (8->16 truoc da dung tremolo, 16 chua du); them stats overrun/underrun tu worklet -> getDiag/preview_diag (ws=ov/un) de xac minh con crackle khong. v=202608311809

This commit is contained in:
2026-08-31 18:10:47 +07:00
parent 72ae2c8a9a
commit b64343aac6
5 changed files with 59 additions and 30 deletions
+29 -6
View File
@@ -1,23 +1,29 @@
// 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 16 block (~85ms @48k) — hap thu jitter pump
// (option 2: 8->16 de dung tremolo crackle; backlog lon hon ~85ms)
// 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).
// - Ring block 256 mau, toi da 32 block (~170ms @48k) — hap thu jitter pump
// (V45.27: 16->32 de dung crackle VST2 loop/tremolo — render loop C++ bi
// stall khi instrument xu li loop/tremolo nang CPU; 8->16 truoc do da dung
// tremolo crackle, 16 van chua du cho stall >85ms). Flush o play start
// (app.jsx) xoa ring nen do tre khong tang: ring thuong can o preview.
// - 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).
// - Stats: dem overrun/underrun, gui ve main thread moi ~0.5s — kiem tra
// con crackle nao la do ring can (underrun) hay ring tran (overrun).
class BridgeAudioSinkProcessor extends AudioWorkletProcessor {
constructor() {
super();
this._ring = [];
this._MAX = 16;
this._MAX = 32;
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._wasUnderrun = false;
this._statAccum = 0;
this._stats = { overruns: 0, underruns: 0 };
this.port.onmessage = (e) => {
const m = e.data;
if (!m) return;
@@ -27,6 +33,7 @@ class BridgeAudioSinkProcessor extends AudioWorkletProcessor {
// overrun: bo block cu nhat; lan consume toi se crossfade ranh gioi
this._ring.shift();
this._skipFade = 64;
this._stats.overruns++;
}
} else if (m.type === 'flush') {
this._ring.length = 0;
@@ -44,6 +51,7 @@ class BridgeAudioSinkProcessor extends AudioWorkletProcessor {
let w = 0;
while (w < n) {
if (this._ring.length) {
if (this._wasUnderrun) this._wasUnderrun = false;
const blk = this._ring[0];
const avail = blk.l.length;
const take = Math.min(avail, n - w);
@@ -71,7 +79,8 @@ class BridgeAudioSinkProcessor extends AudioWorkletProcessor {
blk.r = blk.r.subarray(take);
}
} else {
// underrun: fade ve 0 (khong click)
// underrun: fade ve 0 (khong click) — dem 1 lan moi lan roi vao trang thai trong
if (!this._wasUnderrun) { this._wasUnderrun = true; this._stats.underruns++; }
for (let i = w; i < n; i++) {
this._gain = Math.max(0, this._gain - 1.0 / 256.0);
L[i] = this._lastOutL * this._gain;
@@ -82,6 +91,20 @@ class BridgeAudioSinkProcessor extends AudioWorkletProcessor {
w = n;
}
}
// Stats: gui ve main thread moi ~0.5s (24000 mau @48k) — log overrun/underrun
this._statAccum += n;
if (this._statAccum >= 24000) {
this._statAccum = 0;
try {
this.port.postMessage({
type: 'stats',
overruns: this._stats.overruns,
underruns: this._stats.underruns,
ring: this._ring.length,
max: this._MAX
});
} catch (e) {}
}
return true;
}
}