FIX: Piano roll tab không xuất âm thanh qua mastering chain

This commit is contained in:
2026-08-05 12:10:50 +07:00
parent cfc114b9d7
commit 55d3464b1e
5 changed files with 64 additions and 13 deletions
+32 -4
View File
@@ -17734,16 +17734,22 @@ const App = () => {
// s rebuild loop + panic hy notes ch CÂM TOÀN CC exempt hoàn toàn
// (các fix setValueAtTime/NaN guard đã hết "state is bad" watchdog ch
// còn là lp cu cui cho main/audio-tab).
// PIANO_ROLL: watchdog CH rebuild khi output cha NaN (chain chết
// state-bad). KHÔNG rebuild khi im lng thưng (rests t nhiên gia các
// note false-positive = stopAllPlayback + reschedule = glitch).
const _anySubPlaying = subTabsRef.current.some(s => s.isPlaying);
const activeSub = subTabsRef.current.find(s => s.id === activeTabRef.current);
const isPianoRoll = activeSub && activeSub.type === 'PIANO_ROLL';
if (!isPianoRoll && (isPlaying || _anySubPlaying) && masterBus && masterBus.analyser && activeSourcesRef.current.length > 0) {
if ((isPlaying || _anySubPlaying) && masterBus && masterBus.analyser && (isPianoRoll || activeSourcesRef.current.length > 0)) {
try {
// "Đáng l đang có âm" quyết đnh watchdog có đưc rebuild không:
// - Main / sub-tab audio: source tht đang trong khong phát.
// - PIANO_ROLL: tab đang play (notes đã schedule) = đáng l có âm.
const ctxNow = getAudioContext().currentTime;
let anyPlaying = false;
if (_anySubPlaying) {
if (isPianoRoll) {
anyPlaying = _anySubPlaying;
} else if (_anySubPlaying) {
const _subs = subTabsRef.current || [];
for (let _si = 0; _si < _subs.length; _si++) {
const s = _subs[_si];
@@ -17760,7 +17766,18 @@ const App = () => {
masterBus.analyser.getByteTimeDomainData(d);
let pk = 0;
for (let i = 0; i < d.length; i++) { const v = Math.abs(d[i] - 128) / 128; if (v > pk) pk = v; }
if (pk < 0.001) {
// PIANO_ROLL: chain chết xut NaN getByteTimeDomainData đc NaN
// byte 0/128 pk CAO mù. Check float data (ch piano roll rests
// t nhiên thì KHÔNG rebuild).
let nanOut = false;
if (isPianoRoll) {
try {
const f = new Float32Array(128);
masterBus.analyser.getFloatTimeDomainData(f);
for (let i = 0; i < f.length; i++) { if (!isFinite(f[i])) { nanOut = true; break; } }
} catch (e) {}
}
if (pk < 0.001 || (isPianoRoll && nanOut)) {
masterSilenceFramesRef.current++;
const sinceRebuild = performance.now() - (lastMasterRebuildTimeRef.current || 0);
if (masterSilenceFramesRef.current > 45 && sinceRebuild > 3000) {
@@ -17772,7 +17789,18 @@ const App = () => {
stopAllPlayback();
Object.keys(activeTrackNodesRef.current).forEach(k => { const n = activeTrackNodesRef.current[k]; try { if (n && n.gainNode && n.gainNode.disconnect) n.gainNode.disconnect(); } catch (e) {} });
activeTrackNodesRef.current = {};
try { initMasterBus(getAudioContext()); } catch (e) { console.warn('[Recovery] initMasterBus error:', e); }
// Tháo chain cũ khi destination ri rebuild THT initMasterBus
// early-return khi masterBus còn tn ti recovery trưc đây là
// no-op chain "state is bad" b STUCK vĩnh vin.
try {
if (masterBus) {
try { if (masterBus.analyser) masterBus.analyser.disconnect(); } catch (e) {}
try { if (masterBus.output) masterBus.output.disconnect(); } catch (e) {}
try { if (masterBus.dryOutput) masterBus.dryOutput.disconnect(); } catch (e) {}
}
masterBus = null;
initMasterBus(getAudioContext());
} catch (e) { console.warn('[Recovery] initMasterBus error:', e); }
// Resume ĐÚNG chế đ play hin ti (main hoc sub-tab piano roll)
const curTab = activeTabRef.current;
const subSt = subTabsRef.current.find(s => s.id === curTab);
File diff suppressed because one or more lines are too long
+16 -2
View File
@@ -191,17 +191,31 @@
var spn = _audioCtx.createScriptProcessor(spBufSz, 0, 2);
var lp = _fluidModule._malloc(spBufSz * 4);
var rp = _fluidModule._malloc(spBufSz * 4);
// Heap WASM có thể realloc khi load SoundFont lớn (SGM-V2.01
// ~300MB) → lp/rp DANGLE → đọc vùng nhớ đã free → NaN/garbage
// → master chain "state is bad" → CÂM + stuck. Theo dõi
// buffer + re-malloc khi đổi.
var _heapBufRef = _fluidModule.HEAPU8.buffer;
spn.onaudioprocess = function (e) {
var left = e.outputBuffer.getChannelData(0);
var right = e.outputBuffer.getChannelData(1);
var sz = left.length;
try {
if (_fluidModule.HEAPU8.buffer !== _heapBufRef) {
try { _fluidModule._free(lp); _fluidModule._free(rp); } catch (er2) {}
lp = _fluidModule._malloc(sz * 4);
rp = _fluidModule._malloc(sz * 4);
_heapBufRef = _fluidModule.HEAPU8.buffer;
}
_fluidModule._fluid_synth_write_float(_synthPtr, sz, lp, 0, 1, rp, 0, 1);
var hf = _fluidModule.HEAPF32;
var lpb = lp >> 2, rpb = rp >> 2;
for (var si = 0; si < sz; si++) {
left[si] = hf[lpb + si];
right[si] = hf[rpb + si];
// NaN sweep: mẫu NaN/Inf → 0 (chain biquad
// KHÔNG BAO GIỜ được nhận NaN → không state-bad).
var L = hf[lpb + si], R = hf[rpb + si];
left[si] = isFinite(L) ? L : 0;
right[si] = isFinite(R) ? R : 0;
}
} catch (er) {}
};
+2 -2
View File
@@ -16,7 +16,7 @@
<script src="/static/js/services/audioEngine.js?v=202607271016"></script>
<script src="/static/js/services/storage.js?v=202608038200"></script>
<script src="/static/js/services/soundfontStorage.js?v=202607271016"></script>
<script src="/static/js/services/soundfontPlayer.js?v=202608042158"></script>
<script src="/static/js/services/soundfontPlayer.js?v=202608052230"></script>
<script src="/static/js/services/aiGateway.js?v=202608037200"></script>
<script src="/static/js/services/dawCommandDispatcher.js?v=202607271016"></script>
<script src="/static/js/services/pianoRollTabService.js?v=202607272044"></script>
@@ -24,7 +24,7 @@
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
<script src="/static/js/app.precompiled.js?v=202608042300" defer></script>
<script src="/static/js/app.precompiled.js?v=202608052230" defer></script>
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style>
:root {
+9
View File
@@ -1854,3 +1854,12 @@
(2) **Chống NaN cho chương trình/bank FluidSynth**: Thêm bộ lọc `parseInt``isNaN` kiểm tra biến `usedBank``usedProg` bên trong hàm phát nốt `doNote` của `soundfontPlayer.js`. Tránh truyền giá trị `NaN` trực tiếp vào hàm WASM `_fluid_synth_program_select` có thể làm rối loạn bộ tổng hợp âm bên trong FluidSynth và kết xuất mẫu âm thanh NaN.
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js` (build lại), `app/static/js/services/soundfontPlayer.js`, `app/templates/index.html`, `wiki.md`
- **Ghi chú/Test (nếu có):** `node build.mjs` thành công.
### [2026-08-05 22:30] Task: 2 fix quyết định trên baseline — NaN sweep synth (heap dangle) + watchdog piano-roll rebuild THẬT
- **Báo cáo user lặp lại (cùng text):** piano-roll play → BACK → play → CÂM, chain STUCK. Sau khi loại toàn bộ compressor (v22:00) mà vẫn lỗi → NaN KHÔNG từ compressor.
- **2 lỗ hổng còn lại (baseline cfc114b):**
(1) **Synth render KHÔNG sweep NaN + lp/rp dangle:** `_leftBufPtr/_rightBufPtr` malloc 1 lần ở init; load SGM-V2.01 (~300MB) → heap WASM realloc → pointer đọc vùng free → NaN → chain state-bad. FIX soundfontPlayer.js: theo dõi `HEAPU8.buffer` → re-malloc khi đổi + **NaN sweep (isFinite → 0)** ở mọi block — synth output KHÔNG BAO GIỜ chứa NaN.
(2) **Watchdog recovery là NO-OP:** `initMasterBus` early-return khi masterBus còn tồn tại → [Recovery] không rebuild gì → chain chết STUCK vĩnh viễn. FIX: teardown (disconnect analyser/output/dryOutput + masterBus=null) TRƯỚC initMasterBus → rebuild biquad THẬT + reschedule.
(3) **Watchdog mở cho PIANO_ROLL:** trước đây `!isPianoRoll` (exempt hoàn toàn). Giờ: piano-roll CHỈ rebuild khi output NaN (getFloatTimeDomainData + isFinite — rests tự nhiên KHÔNG trigger, tránh false-positive).
- **Các file ảnh hưởng:** soundfontPlayer.js, app.jsx, index.html (?v=202608052230 cho cả 2), wiki.md. Rebuild precompiled.
- **Ghi chú/Test:** `npm run build` → hard refresh → play → BACK → play từ đầu → KỲ VỌNG: CÓ ÂM (sweep chặn NaN tại nguồn + watchdog cứu nếu còn chết).