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:
+61
-10
@@ -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
@@ -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.
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user