fix: SF2 bank select, midi multi-key cut, VSTi reopen hang, multi-track stuck

- NativeInstrumentEngine: track GM bank per channel (CC0/CC32), use bank in programChange
- app.jsx: send CC0/CC32+PROGRAM before notes via __ensureBridgeProgram, dedupe, clear dedupe after async LOAD
- audioRoutingEngine/bridgeAudioNode: idempotent connect (no disconnect-flush on re-connect), fixes note cut & multi-track stuck
- main.cpp: remove 10s poll in OPEN_GUI control job (blocked realtime loop, watchdog race), VstWindowProc stores channel not inst pointer, cleanup gui maps on WM_DESTROY
This commit is contained in:
2026-08-13 12:19:34 +07:00
parent d168328004
commit 3f59c2c4c2
7 changed files with 136 additions and 50 deletions
+37 -9
View File
@@ -68,6 +68,30 @@ const resolveTrackInstrumentCtx = (track, tracks) => {
return { ch, program: undefined, synthEngine: undefined, sfId: undefined, bank: 0, prog: 0 };
};
// Bridge program-change helper (D8/A12): gi CC0/CC32 (bank THT) + PROGRAM
// qua bridge trưc note-on dedupe theo track+bank+program (không spam mi
// note; reset khi load instrument mi). Dùng chung mi đưng: timeline,
// preview, keybed. Trưc đây bank hardcode 0 + preview/keybed KHÔNG gi
// program change SF2 preset bank0 ra piano sai.
window.__ensureBridgeProgram = (trkId, program, synthEngine) => {
try {
if (!window.SonicMidiRouter || !window.SonicMidiRouter.isBridgeActive()) return;
const _bank = synthEngine && synthEngine.soundfont_bank !== undefined ? synthEngine.soundfont_bank : 0;
const _prog = (program !== undefined && program !== null)
? program
: (synthEngine && synthEngine.soundfont_program !== undefined ? synthEngine.soundfont_program : undefined);
if (_prog === undefined || _prog === null) return;
const _key = trkId + ':' + _bank + ':' + _prog;
const _lp = window.__bridgeLastProgram || (window.__bridgeLastProgram = {});
if (_lp[_key]) return;
_lp[_key] = true;
const _isPerc = _bank === 128;
window.SonicMidiRouter.pushEvent({ cmd: 'CC', channel: trkId, pitch: 0, velocity: 0, data2: (_bank >> 7) & 0x7F, percussion: _isPerc });
window.SonicMidiRouter.pushEvent({ cmd: 'CC', channel: trkId, pitch: 32, velocity: 0, data2: _bank & 0x7F, percussion: _isPerc });
window.SonicMidiRouter.pushEvent({ cmd: 'PROGRAM', channel: trkId, pitch: 0, velocity: 0, data2: _prog, percussion: _isPerc });
} catch (e) { console.warn('[Bridge] ensureBridgeProgram error:', e); }
};
// Đm bo FluidSynth channel ca track đã select ĐÚNG instrument trưc khi
// notes bn. Fire-and-forget: playNote t load + retry nếu SF chưa load xong
// (dedup sn trong loadSoundFont) không chn, không gây stall khi m tab.
@@ -9327,6 +9351,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
playNativeSfNote(kbTrk, pitch, 100 / 127, 5000, undefined, 'kb_' + st.trackId + '_' + pitch);
}
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
try { if (window.__ensureBridgeProgram) window.__ensureBridgeProgram(kbTrk ? kbTrk.id : 0, kbCtx.program, kbCtx.synthEngine); } catch (er) {}
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 100 / 127, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128) });
} else if (window.SonicSF && !kbNative) {
// FIX: gi note theo thi gian bm phím durationMs ln (5s)
@@ -9354,6 +9379,7 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
playNativeSfNote(kbTrk, pitch, 100 / 127, 5000, undefined, 'kb_' + st.trackId + '_' + pitch);
}
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
try { if (window.__ensureBridgeProgram) window.__ensureBridgeProgram(kbTrk ? kbTrk.id : 0, kbCtx.program, kbCtx.synthEngine); } catch (er) {}
window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: kbTrk ? kbTrk.id : 0, pitch: pitch, velocity: 100 / 127, percussion: !!(kbCtx && kbCtx.synthEngine && kbCtx.synthEngine.soundfont_bank === 128) });
} else if (window.SonicSF && !(isStandaloneSf() && isSfTrackEngine(kbCtx.synthEngine) && !shouldRouteCarla(kbCtx.synthEngine))) {
// gi note khi kéo qua phím (mouse enter) dng bng mouseup/leave
@@ -15389,6 +15415,13 @@ const App = () => {
if (p) {
const ok = await window.NativeBridgeService.loadInstrument(p, btype, bch);
console.log('[Bridge] track', trackId, '-> bridge', btype, 'ch', bch, ok ? 'OK' : 'FAIL', p);
// Reset program-change dedupe ca track LOAD async (C++ worker): CC/
// PROGRAM gi trưc khi load xong b drop (inst chưa có) note kế tiếp
// phi gi li program change đ ra ĐÚNG instrument.
if (ok && window.__bridgeLastProgram) {
const _pfx = trackId + ':';
Object.keys(window.__bridgeLastProgram).forEach(k => { if (k.indexOf(_pfx) === 0) delete window.__bridgeLastProgram[k]; });
}
// Requirement 2: chn VST3 qua nút Synth load xong m native GUI
// (C++ attach editor vào ca s bridge t to control type=4, hwnd=0).
if (ok && btype === 'VST3' && window.NativeBridgeService.openNativeGUI) {
@@ -21690,6 +21723,7 @@ const App = () => {
if (window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
const isPerc = !!(track && track.synth_engine && track.synth_engine.soundfont_bank === 128);
const tch = track ? track.id : 0;
try { if (window.__ensureBridgeProgram) window.__ensureBridgeProgram(tch, program, track && track.synth_engine); } catch (e) {}
try { window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_ON', channel: tch, pitch: pitch || 60, velocity: velocity, percussion: isPerc }); } catch (e) {}
setTimeout(function () {
try { window.SonicMidiRouter.pushEvent({ cmd: 'NOTE_OFF', channel: tch, pitch: pitch || 60, velocity: 0, percussion: isPerc }); } catch (e) {}
@@ -21726,16 +21760,10 @@ const App = () => {
const trkId = track ? track.id : 0;
// Percussion (bank 128) router allocateChannel ch 9; melodic round-robin.
const isPerc = !!(synthEngine && synthEngine.soundfont_bank === 128);
// D8: track đi instrument gi CC0/CC32 (bank 0) + program change qua
// bridge (A12) mt ln mi program mi mi channel.
// D8: track đi instrument gi CC0/CC32 (bank THT t synthEngine) +
// program change qua bridge (A12) helper dedupe theo track+bank+program.
if (program !== undefined && program !== null) {
const lastP = window.__bridgeLastProgram || (window.__bridgeLastProgram = {});
if (lastP[trkId] !== program) {
lastP[trkId] = program;
try { window.SonicMidiRouter.pushEvent({ cmd: 'CC', channel: trkId, pitch: 0, velocity: 0, data2: 0, percussion: isPerc }); } catch (e) {}
try { window.SonicMidiRouter.pushEvent({ cmd: 'CC', channel: trkId, pitch: 32, velocity: 0, data2: 0, percussion: isPerc }); } catch (e) {}
try { window.SonicMidiRouter.pushEvent({ cmd: 'PROGRAM', channel: trkId, pitch: 0, velocity: 0, data2: program, percussion: isPerc }); } catch (e) {}
}
try { if (window.__ensureBridgeProgram) window.__ensureBridgeProgram(trkId, program, synthEngine); } catch (e) {}
}
setTimeout(function () {
if (!guardPlay()) return;