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.