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:
+80
-23
@@ -1443,6 +1443,8 @@ function getAudioContext() {
|
|||||||
try {
|
try {
|
||||||
console.log('[Bridge] Requesting restart to match sampleRate:', audioCtx.sampleRate);
|
console.log('[Bridge] Requesting restart to match sampleRate:', audioCtx.sampleRate);
|
||||||
await window.__TAURI__.core.invoke('restart_bridge_with_sample_rate', { sampleRate: audioCtx.sampleRate });
|
await window.__TAURI__.core.invoke('restart_bridge_with_sample_rate', { sampleRate: audioCtx.sampleRate });
|
||||||
|
// Bridge restart → mất hết instrument → reset map để reconnect reload.
|
||||||
|
window.__bridgeLoadedChannels = {};
|
||||||
for (var retry = 0; retry < 10; retry++) {
|
for (var retry = 0; retry < 10; retry++) {
|
||||||
await new Promise(r => setTimeout(r, 100));
|
await new Promise(r => setTimeout(r, 100));
|
||||||
var st2 = await window.NativeBridgeService.queryStatus();
|
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 rồi mới open — tránh nền trắng khi bridge
|
||||||
|
// restart làm mất instrument). __bridgeLoadedChannels (ch→true) đánh dấu đã
|
||||||
|
// load trong session; reset khi bridge down/restart → load lại đú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
|
// 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
|
// 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.
|
// the soundfonts; the per-track channel re-selection happens at note time.
|
||||||
const preloadTrackInstruments = async (tracks) => {
|
const preloadTrackInstruments = async (tracks) => {
|
||||||
if (!window.SonicSF || !window.SonicSF.selectInstrument) return;
|
|
||||||
const list = tracks || [];
|
const list = tracks || [];
|
||||||
for (let i = 0; i < list.length; i++) {
|
if (window.SonicSF && window.SonicSF.selectInstrument) {
|
||||||
const t = list[i];
|
for (let i = 0; i < list.length; i++) {
|
||||||
const se = t.synth_engine;
|
const t = list[i];
|
||||||
const sfId = (se && se.soundfont_id) || t.soundfont_id;
|
const se = t.synth_engine;
|
||||||
if (!sfId) continue;
|
const sfId = (se && se.soundfont_id) || t.soundfont_id;
|
||||||
const bank = se ? (se.soundfont_bank !== undefined ? se.soundfont_bank : 0) : (t.soundfont_bank !== undefined ? t.soundfont_bank : 0);
|
if (!sfId) continue;
|
||||||
const prog = se ? (se.soundfont_program !== undefined ? se.soundfont_program : 0) : (t.soundfont_program !== undefined ? t.soundfont_program : 0);
|
const bank = se ? (se.soundfont_bank !== undefined ? se.soundfont_bank : 0) : (t.soundfont_bank !== undefined ? t.soundfont_bank : 0);
|
||||||
const ch = t.midiChannel !== undefined ? t.midiChannel : (i % 16);
|
const prog = se ? (se.soundfont_program !== undefined ? se.soundfont_program : 0) : (t.soundfont_program !== undefined ? t.soundfont_program : 0);
|
||||||
try {
|
const ch = t.midiChannel !== undefined ? t.midiChannel : (i % 16);
|
||||||
await window.SonicSF.selectInstrument(ch, bank, prog, sfId);
|
try {
|
||||||
} catch (e) {}
|
await window.SonicSF.selectInstrument(ch, bank, prog, sfId);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Lỗi 3: mở lại project → VST3 đã lưu phải load lại 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 || '',
|
value: selectedMidiInputId || '',
|
||||||
onChange: e => onMidiInputSelect(e.target.value),
|
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]"
|
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),
|
onClick: () => onInstrumentSelect && onInstrumentSelect(st.trackId),
|
||||||
title: st.instrumentName || "Synth",
|
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'}`
|
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", {
|
}, 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: () => {
|
onClick: () => {
|
||||||
if (!window.SonicMidiRouter || !window.SonicMidiRouter.isBridgeActive() || !window.NativeBridgeService) return;
|
ensureAndOpenVstGui(st.trackId, st.instrumentId);
|
||||||
const bch = window.SonicMidiRouter.allocateChannel(st.trackId, false);
|
|
||||||
window.NativeBridgeService.openNativeGUI(st.instrumentId, bch);
|
|
||||||
},
|
},
|
||||||
title: "Mở lại GUI VSTi",
|
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"
|
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", {
|
}, 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"
|
className: "flex items-center gap-1 ml-1 text-xs"
|
||||||
}, React.createElement("span", { className: "text-zinc-500" }, "AI:"), React.createElement("input", {
|
}, React.createElement("span", { className: "text-zinc-500" }, "AI:"), React.createElement("input", {
|
||||||
type: "number", value: aiBarStart, onChange: e => setAiBarStart(parseInt(e.target.value) || 0),
|
type: "number", value: aiBarStart, onChange: e => setAiBarStart(parseInt(e.target.value) || 0),
|
||||||
@@ -15430,6 +15472,7 @@ const App = () => {
|
|||||||
const _pfx = trackId + ':';
|
const _pfx = trackId + ':';
|
||||||
Object.keys(window.__bridgeLastProgram).forEach(k => { if (k.indexOf(_pfx) === 0) delete window.__bridgeLastProgram[k]; });
|
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: chọn VST3 qua nút Synth → load xong mở native GUI
|
// Requirement 2: chọn VST3 qua nút Synth → load xong mở native GUI
|
||||||
// (C++ attach editor vào cửa sổ bridge tự tạo — control type=4, hwnd=0).
|
// (C++ attach editor vào cửa sổ bridge tự tạo — control type=4, hwnd=0).
|
||||||
if (ok && btype === 'VST3' && window.NativeBridgeService.openNativeGUI) {
|
if (ok && btype === 'VST3' && window.NativeBridgeService.openNativeGUI) {
|
||||||
@@ -17017,6 +17060,22 @@ const App = () => {
|
|||||||
setBridgeUi(s => ({ ...s, connected: !!window.NativeBridgeService.isBridgeConnected }));
|
setBridgeUi(s => ({ ...s, connected: !!window.NativeBridgeService.isBridgeConnected }));
|
||||||
window.NativeBridgeService.onStatusChange(function (st) {
|
window.NativeBridgeService.onStatusChange(function (st) {
|
||||||
setBridgeUi(s => ({ ...s, connected: !!st.connected }));
|
setBridgeUi(s => ({ ...s, connected: !!st.connected }));
|
||||||
|
if (!st.connected) {
|
||||||
|
// Bridge down/restart → C++ mất hết instrument → reset map để lần tới
|
||||||
|
// load lại (nút GUI ensure-load trước khi open — tránh cửa sổ trắng).
|
||||||
|
window.__bridgeLoadedChannels = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Bridge (re)connect → load lại 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);
|
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", {
|
})), /*#__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) => {
|
onClick: (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!window.SonicMidiRouter || !window.SonicMidiRouter.isBridgeActive() || !window.NativeBridgeService) return;
|
ensureAndOpenVstGui(track.id, track.instrumentId);
|
||||||
const bch = window.SonicMidiRouter.allocateChannel(track.id, false);
|
|
||||||
window.NativeBridgeService.openNativeGUI(track.instrumentId, bch);
|
|
||||||
},
|
},
|
||||||
title: "Mở lại GUI VSTi",
|
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"
|
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" }), /*#__PURE__*/React.createElement("span", { className: "truncate text-[10px]" }, "GUI")) : null), /*#__PURE__*/React.createElement("div", {
|
}, /*#__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),
|
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",
|
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()
|
onClick: e => e.stopPropagation()
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -50,7 +50,7 @@
|
|||||||
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
||||||
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
||||||
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
||||||
<script src="/static/js/app.precompiled.js?v=202608121800" defer></script>
|
<script src="/static/js/app.precompiled.js?v=202608132011" defer></script>
|
||||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
Reference in New Issue
Block a user