fix: pass channel+synth_engine to playNote in all MIDI playback paths

6 call sites were missing channel parameter, causing FluidSynth
to use ch=0 for all tracks -> program_change on channel 0
overwrote instrument across tracks during playback.

Fixes: startTrackPlayback, section sub-track MIDI, playMidiPreviewNote,
piano roll Alt+scroll preview, note click preview, keybed preview.
This commit is contained in:
2026-07-27 20:42:45 +07:00
parent f429f6b1d3
commit 40502eda86
3 changed files with 75 additions and 18 deletions
+61 -10
View File
@@ -4842,8 +4842,14 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
const playing = notes.filter(n =>
currentBeat < n.start_beat && newBeat >= n.start_beat
);
var pvTrk = activeTracks.find(function(t) { return t.id === st.trackId; });
var pvCh = pvTrk ? pvTrk.midiChannel : undefined;
if (pvCh === undefined && pvTrk) {
for (var pvi = 0; pvi < activeTracks.length; pvi++) { if (activeTracks[pvi].id === st.trackId) { pvCh = pvi >= 9 ? pvi + 1 : pvi; break; } }
if (pvCh === undefined || pvCh >= 16) pvCh = 0;
}
playing.forEach(n => {
window.SonicSF.playNote(n.pitch, (n.velocity || 0.8) * 127, 200, ctx.currentTime, st.instrumentProgram, null);
window.SonicSF.playNote(n.pitch, (n.velocity || 0.8) * 127, 200, ctx.currentTime, st.instrumentProgram, null, pvCh, pvTrk ? pvTrk.synth_engine : undefined);
});
}
}
@@ -5118,7 +5124,13 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
if (clickedNoteIdx !== -1 && !e.ctrlKey && !e.shiftKey && !e.altKey) {
if (window.SonicSF) {
const ctx = getAudioContext();
window.SonicSF.playNote(notes[clickedNoteIdx].pitch, 100, 300, ctx.currentTime, st.instrumentProgram, null);
var clTrk = activeTracks.find(function(t) { return t.id === st.trackId; });
var clCh = clTrk ? clTrk.midiChannel : undefined;
if (clCh === undefined && clTrk) {
for (var cli = 0; cli < activeTracks.length; cli++) { if (activeTracks[cli].id === st.trackId) { clCh = cli >= 9 ? cli + 1 : cli; break; } }
if (clCh === undefined || clCh >= 16) clCh = 0;
}
window.SonicSF.playNote(notes[clickedNoteIdx].pitch, 100, 300, ctx.currentTime, st.instrumentProgram, null, clCh, clTrk ? clTrk.synth_engine : undefined);
}
}
@@ -5640,6 +5652,13 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
};
const renderKeybed = () => {
var kbTrk = activeTracks.find(function(t) { return t.id === st.trackId; });
var kbCh = kbTrk ? kbTrk.midiChannel : undefined;
if (kbCh === undefined && kbTrk) {
for (var kbi = 0; kbi < activeTracks.length; kbi++) { if (activeTracks[kbi].id === st.trackId) { kbCh = kbi >= 9 ? kbi + 1 : kbi; break; } }
if (kbCh === undefined || kbCh >= 16) kbCh = 0;
}
var kbSynth = kbTrk ? kbTrk.synth_engine : undefined;
const keys = [];
for (let pitch = 127; pitch >= PITCH_START; pitch--) {
const isBlack = [1, 3, 6, 8, 10].includes(pitch % 12);
@@ -5658,7 +5677,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
keybedMouseDownRef.current = true;
try {
if (window.SonicSF) {
window.SonicSF.playNote(pitch, 100, 500, undefined, st.instrumentProgram, null);
window.SonicSF.playNote(pitch, 100, 500, undefined, st.instrumentProgram, null, kbCh, kbSynth);
}
} catch (err) {
console.error('playNote error:', err);
@@ -5668,7 +5687,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
if (keybedMouseDownRef.current) {
try {
if (window.SonicSF) {
window.SonicSF.playNote(pitch, 100, 200, undefined, st.instrumentProgram, null);
window.SonicSF.playNote(pitch, 100, 200, undefined, st.instrumentProgram, null, kbCh, kbSynth);
}
} catch (err) { console.error('playNote error:', err); }
}
@@ -10114,13 +10133,22 @@ const App = () => {
const track = activeTracks.find(t => t.id === trackId);
const destNode = getOrCreateTrackNode(track, context);
const program = track ? track.instrumentProgram : undefined;
var prevCh = track ? track.midiChannel : undefined;
if (prevCh === undefined && track) {
for (var pi = 0; pi < activeTracks.length; pi++) {
if (activeTracks[pi].id === track.id) { prevCh = pi >= 9 ? pi + 1 : pi; break; }
}
if (prevCh === undefined || prevCh >= 16) prevCh = 0;
}
window.SonicSF.playNote(
pitch,
velocity,
durationMs,
context.currentTime,
program,
destNode
destNode,
prevCh,
track ? track.synth_engine : undefined
);
};
@@ -10163,6 +10191,16 @@ const App = () => {
// MIDI items playback
const midiItems = track.midiItems || [];
if ((track.type === 'MIDI' || midiItems.length > 0) && window.SonicSF) {
var trkCh = track.midiChannel;
if (trkCh === undefined) {
for (var tci = 0; tci < activeTracks.length; tci++) {
if (activeTracks[tci].id === track.id) {
trkCh = tci >= 9 ? tci + 1 : tci;
break;
}
}
if (trkCh === undefined || trkCh >= 16) trkCh = 0;
}
const bpmVal = parseInt(bpm) || 120;
const secondsPerBeat = 60.0 / bpmVal;
midiItems.forEach(item => {
@@ -10186,7 +10224,9 @@ const App = () => {
durationMs,
startTime,
program,
gainNode
gainNode,
trkCh,
track.synth_engine
);
} else {
const playOffset = offsetTime - noteStartSec;
@@ -10197,7 +10237,9 @@ const App = () => {
remainingDurMs,
context.currentTime,
program,
gainNode
gainNode,
trkCh,
track.synth_engine
);
}
}
@@ -10224,7 +10266,7 @@ const App = () => {
const secTracks = sec.tracks || [];
const hasSubSolo = secTracks.some(st => st.solo);
secTracks.forEach(subTrack => {
secTracks.forEach((subTrack, subIdx) => {
const isSubPlayable = hasSubSolo ? subTrack.solo : !subTrack.muted;
if (!isSubPlayable) return;
@@ -10283,6 +10325,11 @@ const App = () => {
if (noteStartMain >= secStart && noteStartMain < secEnd && offsetTime < noteEndMain) {
const notePlayEndMain = Math.min(noteEndMain, secEnd);
const program = subTrack.instrumentProgram !== undefined ? subTrack.instrumentProgram : 0;
var subCh = subTrack.midiChannel;
if (subCh === undefined) {
subCh = subIdx >= 9 ? subIdx + 1 : subIdx;
}
if (subCh === undefined || subCh >= 16) subCh = 0;
if (offsetTime < noteStartMain) {
const delay = noteStartMain - offsetTime;
@@ -10294,7 +10341,9 @@ const App = () => {
playDurMs,
startTime,
program,
subNode
subNode,
subCh,
subTrack.synth_engine
);
} else {
const remainingDurMs = (notePlayEndMain - offsetTime) * 1000;
@@ -10304,7 +10353,9 @@ const App = () => {
remainingDurMs,
context.currentTime,
program,
subNode
subNode,
subCh,
subTrack.synth_engine
);
}
}
File diff suppressed because one or more lines are too long
+6
View File
@@ -473,3 +473,9 @@
- **Các file ảnh hưởng:** `app/static/js/app.jsx` (verify lines 4642-4667, 5905, 6763-6807, 8422-8424), `app/core/render_engine.py` (verify lines 95-115)
- **Ghi chú/Test (nếu có):** No code changes needed. All 4 rules confirmed working.
---
### [2026-07-27 20:37] Task: Fix cross-track instrument interference (missed channel param)
- **Tóm tắt thay đổi:** Sửa 6 vị trí gọi `SonicSF.playNote()` thiếu tham số `channel` + `synthEngine`, khiến FluidSynth mặc định `ch=0` cho mọi track → `program_change` trên channel 0 đè lên nhau giữa các track. Fix: thêm channel computation (từ `track.midiChannel` hoặc track index) + truyền `synth_engine` vào `playNote` tại `startTrackPlayback`, section sub-track MIDI, `playMidiPreviewNote`, piano roll Alt+scroll preview, note click preview, keybed onMouseDown/onMouseEnter. `setTrackInstrumentWithProgram` đã filter đúng `trackId` — lỗi không nằm ở logic set instrument mà ở playback.
- **Các file ảnh hưởng:** `app/static/js/app.jsx` (lines 4846, 5121, 5661-5671, 10117-10124, 10179-10201, 10285-10307)
- **Ghi chú/Test (nếu có):** `npm run build` pass. Cần test: main session play 2 tracks MIDI khác instrument → mỗi track giữ instrument riêng. Piano Roll preview note → không ảnh hưởng track khác.
---