diff --git a/app/static/js/services/soundfontPlayer.js b/app/static/js/services/soundfontPlayer.js index b5b873a..d4c85e6 100644 --- a/app/static/js/services/soundfontPlayer.js +++ b/app/static/js/services/soundfontPlayer.js @@ -424,18 +424,15 @@ var finalProg = usedProg; var finalSfId = synthEngine ? synthEngine.soundfont_id : undefined; var cachedCh = _channels[ch]; - // A channel explicitly configured with a soundfont (via - // selectInstrument) is authoritative. But the default channel - // state (bank 0, program 0, no soundfont) must NOT mask the - // track's synth engine or the note's program — otherwise notes - // on a freshly-picked instrument are silently dropped. - if (cachedCh && cachedCh.sfId !== undefined) { + // The note's own synth engine (track instrument) is + // authoritative. Channel state is only a cache: it must never + // mask the track's instrument, otherwise multi-track ARM or a + // re-picked instrument plays the wrong soundfont. Without an + // engine, fall back to the soundfont configured on the channel. + if (!synthEngine && cachedCh && cachedCh.sfId !== undefined) { finalBank = cachedCh.bank; finalProg = cachedCh.program; finalSfId = cachedCh.sfId; - } else if (!synthEngine && program !== undefined && cachedCh && cachedCh.program !== undefined && cachedCh.program !== 0 && cachedCh.program !== null) { - finalBank = cachedCh.bank; - finalProg = cachedCh.program; } // Ensure the soundfont is actually loaded before the note plays. // Quick instrument pick on a track does not pre-load it, so load diff --git a/app/templates/index.html b/app/templates/index.html index 9817285..097e874 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -16,7 +16,7 @@ - + diff --git a/wiki.md b/wiki.md index 7a3b051..14e9031 100644 --- a/wiki.md +++ b/wiki.md @@ -1199,3 +1199,8 @@ - **Tóm tắt thay đổi:** Sửa `soundfontPlayer.js` để phím MIDI trên track ARM phát đúng instrument: (1) `_playNoteFluid` không còn để state mặc định của channel (bank 0, program 0, không sfId) che mất `synth_engine` của track → `finalSfId` luôn đúng; (2) tự load soundfont lười (lazy) ngay trong đường phát note nếu font chưa vào `_sfHandleMap` (track pick nhanh từ dropdown không gọi `selectInstrument` → trước đây rơi vào `bank_select`/`program_change` trên synth không có soundfont → câm lặng); (3) `selectInstrument` không còn đổi hướng channel khi đã truyền channel tường minh (sửa lỗi 2 track dùng chung instrument bị giật channel). Bump version cache `soundfontPlayer.js` trong `index.html`. - **Các file ảnh hưởng:** `app/static/js/services/soundfontPlayer.js`, `app/templates/index.html` - **Ghi chú/Test (nếu có):** Test logic bằng harness `node /tmp/kilo/test_sonicsf_fix.js` (mô phỏng FluidSynth): S1 lazy-load + program_select + noteon, S2 channel cấu hình sẵn không sfload lại, S3 program-only fallback, S4 no-instrument vẫn silent, S5 2 track cùng instrument giữ channel riêng — ALL PASSED. Cần hard-refresh trình duyệt để nạp `soundfontPlayer.js` bản mới. + +### [2026-08-03 10:30] Task: Fix ARM nhiều track - instrument sai theo track (channel state che mất synth_engine) +- **Tóm tắt thay đổi:** `_playNoteFluid` trước đây ưu tiên state channel (`_channels[ch].sfId`) làm nguồn instrument cho note → khi ARM nhiều track hoặc track đổi instrument qua dropdown nhanh, note bị phát theo instrument cũ/khác đang "dính" trên channel (leftover state hoặc 2 track trùng channel) → sai instrument từng track. Sửa: `synth_engine` của track là nguồn quyết định; channel state chỉ là cache (chỉ dùng khi note không có engine). Khi note mang engine khác với channel đang giữ, tự động `program_select` lại đúng instrument trước `noteon`; vẫn lazy-load soundfont nếu chưa nạp. Bump version cache `soundfontPlayer.js` trong `index.html`. +- **Các file ảnh hưởng:** `app/static/js/services/soundfontPlayer.js`, `app/templates/index.html` +- **Ghi chú/Test (nếu có):** Harness `node /tmp/kilo/test_multi_track.js` (mô phỏng routing ARM như app.jsx): (A) 3 track 3 instrument distinct channel → noteon đúng, không re-select thừa; (B) 3 track quick-pick (không midiChannel, channel đang giữ instrument cũ) → mỗi track re-select đúng Q1/Q2/Q3 trước noteon; (C) 2 track trùng channel 0 → track 2 re-select program 40 trước noteon. Harness `test_sonicsf_fix.js` (single-track S1-S5) vẫn ALL PASSED. Hard refresh trình duyệt để nạp bản mới.