fix: multi-track ARM routes MIDI to all armed tracks

- Use filter(t => t.isArmed) instead of find() to route
  MIDI input to ALL armed tracks simultaneously
- Use getTrackMidiChannel per track instead of raw
  MIDI hardware channel (msg.data[0] & 0x0F)
- NoteOff, CC, PitchBend also routed to each armed
  track's dedicated channel
- Previous code sent to channel 0 + first armed track only
This commit is contained in:
2026-07-27 21:31:10 +07:00
parent 08cd4d72cf
commit 8beef7cb4d
3 changed files with 70 additions and 33 deletions
+55 -26
View File
@@ -6954,7 +6954,7 @@ const App = () => {
input.onmidimessage = msg => {
console.log(`[DevLog] [Raw MIDI] Received from "${input.name}" (ID: ${input.id}) - Data:`, Array.from(msg.data));
if (msg.data.length < 3) return;
const cmd = msg.data[0] >> 4;
const cmd = msg.data[0] >> 4;
const pitch = msg.data[1];
const velocity = msg.data[2];
if (cmd === 0x9 && velocity > 0) {
@@ -6962,25 +6962,30 @@ const App = () => {
setLastMidiNote({ pitch, velocity, length: 0, time: Date.now() });
activeMidiPitchesRef.current.add(pitch);
setActiveMidiPitches(new Set(activeMidiPitchesRef.current));
// Stop previous notes on this channel before playing new note
// Only send All Notes Off when sustain pedal is not active
if (window.SonicSF) {
const ch = msg.data[0] & 0x0F;
if (!window.SonicSF.sustainActive || !window.SonicSF.sustainActive(ch)) {
try { window.SonicSF.controllerChange(ch, 123, 0); } catch (e) {}
}
const arSub = subTabsRef && subTabsRef.current && activeTabRef && subTabsRef.current.find(s => s.id === activeTabRef.current && s.type === 'PIANO_ROLL' && s.isArmed);
if (arSub) {
window.SonicSF.playNote(pitch, velocity, 60000, undefined, arSub.instrumentProgram, null, ch, arSub.synth_engine);
} else {
const armedTrack = activeTracksRef.current ? activeTracksRef.current.find(t => t.isArmed) : null;
if (armedTrack) {
const prog = armedTrack.instrumentProgram;
const se = armedTrack.synth_engine;
const dest = activeTrackNodesRef.current[armedTrack.id]?.gainNode || null;
window.SonicSF.playNote(pitch, velocity, 60000, undefined, prog, dest, ch, se);
}
// Route MIDI input to ALL armed tracks on their dedicated channels
// Do NOT use raw MIDI hardware channel (msg.data[0] & 0x0F)
if (window.SonicSF) {
var allTracks = activeTracksRef.current || [];
var arSubs = subTabsRef && subTabsRef.current ? subTabsRef.current.filter(function(s) { return s.type === 'PIANO_ROLL' && s.isArmed; }) : [];
var armedTracks = allTracks.filter(function(t) { return t.isArmed; });
// Piano Roll arming has priority: route to each armed sub-tab's parent track
if (arSubs.length > 0) {
arSubs.forEach(function(as) {
var asTrk = allTracks.find(function(t) { return t.id === as.trackId; });
var asCh = window.SonicPianoRoll ? window.SonicPianoRoll.getTrackMidiChannel(asTrk, allTracks) : (asTrk ? asTrk.midiChannel : 0);
var asProg = as.instrumentProgram;
var asSe = as.synth_engine;
window.SonicSF.playNote(pitch, velocity, 60000, undefined, asProg, null, asCh, asSe);
});
}
// Route to ALL armed tracks (not just the first one)
armedTracks.forEach(function(at) {
var atCh = window.SonicPianoRoll ? window.SonicPianoRoll.getTrackMidiChannel(at, allTracks) : (at.midiChannel !== undefined ? at.midiChannel : 0);
var atProg = at.instrumentProgram;
var atSe = at.synth_engine;
var atDest = activeTrackNodesRef.current[at.id]?.gainNode || null;
window.SonicSF.playNote(pitch, velocity, 60000, undefined, atProg, atDest, atCh, atSe);
});
}
} else if (cmd === 0x8 || (cmd === 0x9 && velocity === 0)) {
activeMidiPitchesRef.current.delete(pitch);
@@ -6991,29 +6996,53 @@ const App = () => {
lastMidiNoteRef.current = { ...current, length: lenSec };
setLastMidiNote(prev => prev && prev.pitch === pitch ? { ...prev, length: lenSec, time: Date.now() } : prev);
}
// Stop the note immediately via SpessaSynth
// Stop the note on ALL armed tracks' dedicated channels
if (window.SonicSF && window.SonicSF.stopNote) {
const ch = msg.data[0] & 0x0F;
window.SonicSF.stopNote(ch, pitch);
var stopTracks = activeTracksRef.current || [];
stopTracks.forEach(function(st) {
if (!st.isArmed) return;
var stCh = window.SonicPianoRoll ? window.SonicPianoRoll.getTrackMidiChannel(st, stopTracks) : (st.midiChannel !== undefined ? st.midiChannel : 0);
window.SonicSF.stopNote(stCh, pitch);
});
}
}
// Sustain (CC64), Modulation (CC1), Pitch Bend
const midiCh = msg.data[0] & 0x0F;
if (cmd === 0xB) {
// Controller Change: forward all CCs to SpessaSynth
// Controller Change: forward to ALL armed tracks' dedicated channels
const cc = msg.data[1];
const val = msg.data[2];
if (window.SonicSF && window.SonicSF.controllerChange) {
window.SonicSF.controllerChange(midiCh, cc, val);
var ccTracks = activeTracksRef.current || [];
var hasArmed = ccTracks.some(function(t) { return t.isArmed; });
if (hasArmed) {
ccTracks.forEach(function(ct) {
if (!ct.isArmed) return;
var ctCh = window.SonicPianoRoll ? window.SonicPianoRoll.getTrackMidiChannel(ct, ccTracks) : (ct.midiChannel !== undefined ? ct.midiChannel : 0);
window.SonicSF.controllerChange(ctCh, cc, val);
});
} else {
window.SonicSF.controllerChange(midiCh, cc, val);
}
}
} else if (cmd === 0xE) {
// Pitch Bend: 14-bit value (LSB + MSB)
// Pitch Bend: forward to ALL armed tracks' dedicated channels
const lsb = msg.data[1];
const msb = msg.data[2];
const bendVal = (msb << 7) | lsb;
if (window.SonicSF && window.SonicSF.pitchBend) {
window.SonicSF.pitchBend(midiCh, bendVal);
var pbTracks = activeTracksRef.current || [];
var hasArmedPB = pbTracks.some(function(t) { return t.isArmed; });
if (hasArmedPB) {
pbTracks.forEach(function(pt) {
if (!pt.isArmed) return;
var ptCh = window.SonicPianoRoll ? window.SonicPianoRoll.getTrackMidiChannel(pt, pbTracks) : (pt.midiChannel !== undefined ? pt.midiChannel : 0);
window.SonicSF.pitchBend(ptCh, bendVal);
});
} else {
window.SonicSF.pitchBend(midiCh, bendVal);
}
}
}