fix(vst-bridge): reload VST3 on project open + bridge reconnect; GUI button ensure-load before open

- preloadTrackInstruments: also load VST3 tracks into bridge (no GUI) on project open
- onStatusChange(connected): reload all VST tracks after bridge restart/down recovery
- GUI button (track strip + piano roll): ensure-load then open — no white window when bridge restarted and instrument gone
- __bridgeLoadedChannels map: skip reload if already loaded (avoids plugin state reset); reset on bridge down
- piano roll: wrap Synth+GUI buttons in shrink-0 row so GUI stays on same line; compact track-strip GUI button
This commit is contained in:
locpham
2026-08-13 20:32:08 +07:00
parent 4bb67b54ad
commit e3f5dad2ac
3 changed files with 97 additions and 31 deletions
+80 -23
View File
@@ -1443,6 +1443,8 @@ function getAudioContext() {
try {
console.log('[Bridge] Requesting restart to match sampleRate:', audioCtx.sampleRate);
await window.__TAURI__.core.invoke('restart_bridge_with_sample_rate', { sampleRate: audioCtx.sampleRate });
// Bridge restart mt hết instrument reset map đ reconnect reload.
window.__bridgeLoadedChannels = {};
for (var retry = 0; retry < 10; retry++) {
await new Promise(r => setTimeout(r, 100));
var st2 = await window.NativeBridgeService.queryStatus();
@@ -6161,23 +6163,65 @@ const PluginManagerModal = ({ isOpen, onClose, pluginsData, onInsertInstrument }
);
};
// Bridge VST3 auto-load: dùng chung cho preload project + bridge
// reconnect + nút GUI (ensure-load ri mi open tránh nn trng khi bridge
// restart làm mt instrument). __bridgeLoadedChannels (chtrue) đánh du đã
// load trong session; reset khi bridge down/restart load li đúng lúc.
const loadVstToBridge = async (track) => {
if (!window.NativeBridgeService || !window.SonicMidiRouter || !window.SonicMidiRouter.isBridgeActive()) return false;
const instrumentId = track.instrumentId || (track.synth_engine && track.synth_engine.plugin_id);
if (!instrumentId || (typeof instrumentId === 'string' && instrumentId.startsWith('sf_'))) return false;
if (!window.__bridgeLoadedChannels) window.__bridgeLoadedChannels = {};
const bch = window.SonicMidiRouter.allocateChannel(track.id, false);
if (window.__bridgeLoadedChannels[bch]) return true;
try {
const r = await window.SonicAPI.bridgeLoad({ name: instrumentId, path: null, instrumentType: 'VST3', channel: bch });
const p = r && r.path ? r.path : null;
if (!p) return false;
const ok = await window.NativeBridgeService.loadInstrument(p, 'VST3', bch);
if (ok) window.__bridgeLoadedChannels[bch] = true;
console.log('[Bridge] auto-load track', track.id, '-> VST3 ch', bch, ok ? 'OK' : 'FAIL', p);
return !!ok;
} catch (e) { console.warn('[Bridge] loadVstToBridge fail:', track.id, e); return false; }
};
const ensureAndOpenVstGui = async (trackId, instrumentId) => {
if (!window.SonicMidiRouter || !window.SonicMidiRouter.isBridgeActive() || !window.NativeBridgeService) return;
if (!instrumentId || (typeof instrumentId === 'string' && instrumentId.startsWith('sf_'))) return;
if (!window.__bridgeLoadedChannels) window.__bridgeLoadedChannels = {};
const bch = window.SonicMidiRouter.allocateChannel(trackId, false);
if (!window.__bridgeLoadedChannels[bch]) {
const ok = await loadVstToBridge({ id: trackId, instrumentId });
if (!ok) return;
}
try { await window.NativeBridgeService.openNativeGUI(instrumentId, bch); } catch (e) { console.warn('[Bridge] openNativeGUI fail:', e); }
};
// Module-level so both ProfileModal (open project) and App (auto-restore) can
// warm the FluidSynth font cache for each track's instrument. This only loads
// the soundfonts; the per-track channel re-selection happens at note time.
const preloadTrackInstruments = async (tracks) => {
if (!window.SonicSF || !window.SonicSF.selectInstrument) return;
const list = tracks || [];
for (let i = 0; i < list.length; i++) {
const t = list[i];
const se = t.synth_engine;
const sfId = (se && se.soundfont_id) || t.soundfont_id;
if (!sfId) continue;
const bank = se ? (se.soundfont_bank !== undefined ? se.soundfont_bank : 0) : (t.soundfont_bank !== undefined ? t.soundfont_bank : 0);
const prog = se ? (se.soundfont_program !== undefined ? se.soundfont_program : 0) : (t.soundfont_program !== undefined ? t.soundfont_program : 0);
const ch = t.midiChannel !== undefined ? t.midiChannel : (i % 16);
try {
await window.SonicSF.selectInstrument(ch, bank, prog, sfId);
} catch (e) {}
if (window.SonicSF && window.SonicSF.selectInstrument) {
for (let i = 0; i < list.length; i++) {
const t = list[i];
const se = t.synth_engine;
const sfId = (se && se.soundfont_id) || t.soundfont_id;
if (!sfId) continue;
const bank = se ? (se.soundfont_bank !== undefined ? se.soundfont_bank : 0) : (t.soundfont_bank !== undefined ? t.soundfont_bank : 0);
const prog = se ? (se.soundfont_program !== undefined ? se.soundfont_program : 0) : (t.soundfont_program !== undefined ? t.soundfont_program : 0);
const ch = t.midiChannel !== undefined ? t.midiChannel : (i % 16);
try {
await window.SonicSF.selectInstrument(ch, bank, prog, sfId);
} catch (e) {}
}
}
// Li 3: m li project VST3 đã lưu phi load li vào bridge (không m GUI).
if (window.NativeBridgeService && window.SonicMidiRouter && window.SonicMidiRouter.isBridgeActive()) {
for (let i = 0; i < list.length; i++) {
const t = list[i];
const inst = t.instrumentId || (t.synth_engine && t.synth_engine.plugin_id);
if (!inst || (typeof inst === 'string' && inst.startsWith('sf_'))) continue;
try { await loadVstToBridge(t); } catch (e) {}
}
}
};
@@ -9836,19 +9880,17 @@ const beatSec = 60.0 / (parseInt(bpm) || 120);
value: selectedMidiInputId || '',
onChange: e => onMidiInputSelect(e.target.value),
className: "bg-zinc-800 border border-zinc-700 text-zinc-300 rounded px-1 py-0.5 text-xs outline-none max-w-[100px]"
}, React.createElement("option", { value: "" }, "Input"), React.createElement("option", { value: "ALL" }, "Omni"), (midiDevices || []).map(d => React.createElement("option", { key: d.id, value: d.id }, d.name || d.id))), React.createElement("button", {
}, React.createElement("option", { value: "" }, "Input"), React.createElement("option", { value: "ALL" }, "Omni"), (midiDevices || []).map(d => React.createElement("option", { key: d.id, value: d.id }, d.name || d.id))), React.createElement("div", { className: "flex items-center gap-0.5 shrink-0" }, React.createElement("button", {
onClick: () => onInstrumentSelect && onInstrumentSelect(st.trackId),
title: st.instrumentName || "Synth",
className: `px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 max-w-[70px] ${st.instrumentName ? 'bg-violet-900 text-violet-300 border-violet-700' : 'bg-zinc-800 text-zinc-500 border-transparent hover:text-zinc-300'}`
}, React.createElement("i", { "data-lucide": "music", className: "w-3 h-3 shrink-0" }), React.createElement("span", { className: "truncate text-[9px]" }, activeParentTrackName ? ('(' + activeParentTrackName + ') ' + (st.instrumentName || 'Synth')) : (st.instrumentName || 'Synth'))), (st.instrumentId && typeof st.instrumentId === 'string' && !st.instrumentId.startsWith('sf_') ? React.createElement("button", {
onClick: () => {
if (!window.SonicMidiRouter || !window.SonicMidiRouter.isBridgeActive() || !window.NativeBridgeService) return;
const bch = window.SonicMidiRouter.allocateChannel(st.trackId, false);
window.NativeBridgeService.openNativeGUI(st.instrumentId, bch);
ensureAndOpenVstGui(st.trackId, st.instrumentId);
},
title: "Mở lại GUI VSTi",
className: "px-1.5 py-0.5 text-[10px] rounded font-mono font-bold border transition flex items-center gap-0.5 bg-zinc-800 text-violet-300 border-violet-800 hover:bg-violet-900/40"
}, React.createElement("i", { "data-lucide": "sliders-horizontal", className: "w-3 h-3 shrink-0" }), React.createElement("span", { className: "truncate text-[9px]" }, "GUI")) : null), React.createElement("div", {
className: "px-1 py-0.5 text-[9px] rounded font-mono font-bold border transition flex items-center gap-0.5 shrink-0 bg-zinc-800 text-violet-300 border-violet-800 hover:bg-violet-900/40"
}, React.createElement("i", { "data-lucide": "sliders-horizontal", className: "w-3 h-3 shrink-0" }), React.createElement("span", { className: "truncate text-[9px]" }, "GUI")) : null)), React.createElement("div", {
className: "flex items-center gap-1 ml-1 text-xs"
}, React.createElement("span", { className: "text-zinc-500" }, "AI:"), React.createElement("input", {
type: "number", value: aiBarStart, onChange: e => setAiBarStart(parseInt(e.target.value) || 0),
@@ -15430,6 +15472,7 @@ const App = () => {
const _pfx = trackId + ':';
Object.keys(window.__bridgeLastProgram).forEach(k => { if (k.indexOf(_pfx) === 0) delete window.__bridgeLastProgram[k]; });
}
if (ok) { if (!window.__bridgeLoadedChannels) window.__bridgeLoadedChannels = {}; window.__bridgeLoadedChannels[bch] = true; }
// 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) {
@@ -17017,6 +17060,22 @@ const App = () => {
setBridgeUi(s => ({ ...s, connected: !!window.NativeBridgeService.isBridgeConnected }));
window.NativeBridgeService.onStatusChange(function (st) {
setBridgeUi(s => ({ ...s, connected: !!st.connected }));
if (!st.connected) {
// Bridge down/restart C++ mt hết instrument reset map đ ln ti
// load li (nút GUI ensure-load trưc khi open tránh ca s trng).
window.__bridgeLoadedChannels = {};
return;
}
// Bridge (re)connect load li toàn b VST track (không m GUI).
try {
const _tl = activeTracksRef.current || [];
for (let i = 0; i < _tl.length; i++) {
const _t = _tl[i];
const _inst = _t.instrumentId || (_t.synth_engine && _t.synth_engine.plugin_id);
if (!_inst || (typeof _inst === 'string' && _inst.startsWith('sf_'))) continue;
loadVstToBridge(_t).catch(function () {});
}
} catch (e) { console.warn('[Bridge] reconnect reload fail:', e); }
});
return () => window.NativeBridgeService.onStatusChange(null);
}, []);
@@ -29747,13 +29806,11 @@ STRICT CONSTRAINTS:
})), /*#__PURE__*/React.createElement("span", { className: "truncate text-[10px]" }, track.instrumentName || track.instrumentId || "Synth"), /*#__PURE__*/React.createElement("i", { "data-lucide": "chevron-down", className: "w-3 h-3 shrink-0" }))), (track.instrumentId && typeof track.instrumentId === 'string' && !track.instrumentId.startsWith('sf_') ? /*#__PURE__*/React.createElement("button", {
onClick: (e) => {
e.stopPropagation();
if (!window.SonicMidiRouter || !window.SonicMidiRouter.isBridgeActive() || !window.NativeBridgeService) return;
const bch = window.SonicMidiRouter.allocateChannel(track.id, false);
window.NativeBridgeService.openNativeGUI(track.instrumentId, bch);
ensureAndOpenVstGui(track.id, track.instrumentId);
},
title: "Mở lại GUI VSTi",
className: "px-2 py-1 bg-zinc-800 hover:bg-zinc-700 text-violet-300 border border-violet-800 rounded text-xs font-bold flex items-center gap-1"
}, /*#__PURE__*/React.createElement("i", { "data-lucide": "sliders-horizontal", className: "w-3 h-3" }), /*#__PURE__*/React.createElement("span", { className: "truncate text-[10px]" }, "GUI")) : null), /*#__PURE__*/React.createElement("div", {
className: "px-1.5 py-1 bg-zinc-800 hover:bg-zinc-700 text-violet-300 border border-violet-800 rounded text-[10px] font-bold flex items-center gap-1 shrink-0"
}, /*#__PURE__*/React.createElement("i", { "data-lucide": "sliders-horizontal", className: "w-3 h-3 shrink-0" }), /*#__PURE__*/React.createElement("span", { className: "truncate text-[9px]" }, "GUI")) : null), /*#__PURE__*/React.createElement("div", {
onMouseDown: e => handleTrackResizeMouseDown(e, track.id),
className: "absolute bottom-0 left-0 right-0 h-1 cursor-ns-resize z-30 hover:bg-cyan-500/50 transition-colors",
onClick: e => e.stopPropagation()
File diff suppressed because one or more lines are too long