fix: ReferenceError assignTrackMidiChannel - chuyển allocator channel lên module-level cho PianoRollTabEditor + bật AudioWorklet thay ScriptProcessor

This commit is contained in:
2026-08-03 11:17:19 +07:00
parent 0182abf7ea
commit 47b1633bd3
5 changed files with 51 additions and 38 deletions
+31 -27
View File
@@ -12,6 +12,37 @@ const API_AUDIO = `${API_BASE_URL}/api/v1/audio`;
const API_MULTITRACK = `${API_BASE_URL}/api/v1/multitrack`;
const API_TASKS = `${API_BASE_URL}/api/v1/audio/tasks`;
// Dedicated per-track MIDI channel allocation
// FluidSynth has 16 channels; if two tracks share a channel, arming one track
// re-selects the other track's program and its instrument changes. Every track
// gets its own stable, unique channel (0-15, skipping 9 which is the classic
// percussion slot) so multi-track ARM / playback never cross-contaminates
// instruments. Module-level so both App and the piano-roll sub-components
// (PianoRollTabEditor etc.) allocate the SAME channel for a track.
const trackMidiChannelsRef = { current: {} };
const ensureTrackMidiChannel = (track, tracks) => {
if (!track) return 0;
const trackList = tracks || [];
const inUse = new Set();
trackList.forEach(tr => { if (tr && tr.id !== track.id && tr.midiChannel !== undefined) inUse.add(tr.midiChannel); });
const cached = trackMidiChannelsRef.current[track.id];
if (cached !== undefined && !inUse.has(cached)) return cached;
const preferred = track.midiChannel !== undefined && !inUse.has(track.midiChannel) ? track.midiChannel : null;
if (preferred !== null) { trackMidiChannelsRef.current[track.id] = preferred; return preferred; }
for (let c = 0; c < 16; c++) {
if (c === 9) continue;
if (!inUse.has(c)) { trackMidiChannelsRef.current[track.id] = c; return c; }
}
trackMidiChannelsRef.current[track.id] = 0;
return 0;
};
const assignTrackMidiChannel = (track, tracks) => {
const ch = ensureTrackMidiChannel(track, tracks);
if (track && track.midiChannel !== ch) track.midiChannel = ch;
return ch;
};
// Handle ?sfs=<encoded> from double-clicking a .sfs file (opens domain -> loads project)
(function handleSfsDeepLink() {
try {
@@ -11101,33 +11132,6 @@ const App = () => {
"Tinkle Bell","Agogo","Steel Drums","Woodblock","Taiko Drum","Melodic Tom","Synth Drum","Reverse Cymbal",
"Guitar Fret Noise","Breath Noise","Seashore","Bird Tweet","Telephone Ring","Helicopter","Applause","Gunshot"
];
// Dedicated per-track MIDI channel allocation
// FluidSynth has 16 channels; if two tracks share a channel, arming one track
// re-selects the other track's program and its instrument changes. Every track
// gets its own stable, unique channel (0-15, skipping 9 which is the classic
// percussion slot) so multi-track ARM never cross-contaminates instruments.
const trackMidiChannelsRef = useRef({});
const ensureTrackMidiChannel = (track, tracks) => {
if (!track) return 0;
const trackList = tracks || [];
const inUse = new Set();
trackList.forEach(tr => { if (tr && tr.id !== track.id && tr.midiChannel !== undefined) inUse.add(tr.midiChannel); });
const cached = trackMidiChannelsRef.current[track.id];
if (cached !== undefined && !inUse.has(cached)) return cached;
const preferred = track.midiChannel !== undefined && !inUse.has(track.midiChannel) ? track.midiChannel : null;
if (preferred !== null) { trackMidiChannelsRef.current[track.id] = preferred; return preferred; }
for (let c = 0; c < 16; c++) {
if (c === 9) continue;
if (!inUse.has(c)) { trackMidiChannelsRef.current[track.id] = c; return c; }
}
trackMidiChannelsRef.current[track.id] = 0;
return 0;
};
const assignTrackMidiChannel = (track, tracks) => {
const ch = ensureTrackMidiChannel(track, tracks);
if (track && track.midiChannel !== ch) track.midiChannel = ch;
return ch;
};
const setTrackInstrumentWithProgram = (trackId, instrumentId, programNumber, displayName, bankNumber) => {
const isSfInstrument = instrumentId && typeof instrumentId === 'string' && instrumentId.startsWith('sf_');
const sfBank = bankNumber !== undefined ? bankNumber : (isSfInstrument ? 0 : undefined);