fix(bridge): load dung VSTi theo track (V9) - bo early-return mui theo flag, load qua Tauri invoke khi co path, guard path sai (WaveObserver vs Nexus), chi mo GUI khi load OK; sync 6 ban JS + index.html ?v; kem cac fix bridge truoc (bugs 1-4, GUI restore)

This commit is contained in:
2026-08-18 16:56:05 +07:00
parent cbcc1f658c
commit 351ee56080
11 changed files with 2234 additions and 2019 deletions
+87 -17
View File
@@ -795,6 +795,21 @@ function makeDistortionCurve(k) {
return curve;
}
function makeSoftClipCurve(percent) {
const n = 4096;
const curve = new Float32Array(n);
const p = Math.max(0, Math.min(100, percent));
const t = 1 - (p / 100) * 0.8;
const inv = 1 - t;
for (let i = 0; i < n; ++i) {
const x = (i * 2) / (n - 1) - 1;
const ax = Math.abs(x);
if (ax < t) curve[i] = x;
else curve[i] = Math.sign(x) * (t + inv * Math.tanh((ax - t) / inv));
}
return curve;
}
function applyMasteringSettings(s) {
console.log('[Mastering] applyMasteringSettings active=', !!(s && s.masterConnected), 'bypass=', !!(s && s.isBypassed), 'eqActive=', !!(s && s.eqActive), 'masterBus=', !!masterBus);
if (!masterBus || !audioCtx || !s) return;
@@ -858,10 +873,11 @@ function applyMasteringSettings(s) {
const boostLinear = (s.maximizerActive) ? Math.pow(10, clamp(s.maxGain, -60, 30) / 20) : 1.0;
masterBus.maximizerBoostGain.gain.setTargetAtTime(boostLinear, now, 0.01);
// Soft Clipper (identity passthrough when off — never null curve)
// Soft Clipper (identity passthrough when off — never null curve).
// Unity-gain curve: bo gain an cua atan (slope ~2.1x cu cong +6 dB len
// maxGain -> tin hieu >~-12 dBFS bi hard-clip meo "be").
if (s.maximizerActive && s.maxSoftClip > 0) {
const k = 1 + (clamp(s.maxSoftClip, 0, 100) / 100) * 10;
masterBus.maximizerSoftClipper.curve = makeDistortionCurve(k);
masterBus.maximizerSoftClipper.curve = makeSoftClipCurve(s.maxSoftClip);
} else {
masterBus.maximizerSoftClipper.curve = new Float32Array([-1, 1]);
}
@@ -1717,6 +1733,7 @@ function getAudioContext() {
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 = {};
window.__bridgeLoadedPaths = {};
for (var retry = 0; retry < 10; retry++) {
await new Promise(r => setTimeout(r, 100));
var st2 = await window.NativeBridgeService.queryStatus();
@@ -6439,13 +6456,23 @@ const PluginManagerModal = ({ isOpen, onClose, pluginsData, onInsertInstrument }
// 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.
// V9: track.plugin_path/cache path có thể là plugin KHÁC instrumentId (VD
// WaveObserver trong khi track là Nexus) → basename không chứa tên id → trả
// null để engine resolve theo name / listPlugins — tránh LOAD nhầm VSTi.
const _bridgeSafeVstPath = (path, instrumentId) => {
if (!path || !instrumentId) return path;
const base = String(path).split(/[\\/]/).pop().toLowerCase().replace(/\s+/g, '');
const want = String(instrumentId).toLowerCase().replace(/\s+/g, '');
return base.indexOf(want) !== -1 ? path : null;
};
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;
if (!window.__bridgeLoadedPaths) window.__bridgeLoadedPaths = {};
const tId = track.id || track.trackId;
const bch = window.SonicMidiRouter.allocateChannel(tId, false);
try {
// V8 bug 5: engine _resolve_bridge_asset chi match FILE theo filename —
// KHONG walk folder VST3 ("X.vst3/X.vst3.dll") → 404. Dung path da luu
@@ -6459,9 +6486,22 @@ const loadVstToBridge = async (track) => {
if (v0 && v0.path) knownPath = v0.path;
if (v0 && v0.type) btype = v0.type;
}
const r = await window.SonicAPI.bridgeLoad({ name: instrumentId, path: knownPath || null, instrumentType: btype, channel: bch });
// V9: path sai (plugin_path cũ / cache của plugin khác) → bỏ qua, resolve theo tên.
knownPath = _bridgeSafeVstPath(knownPath, instrumentId);
// Đã load ĐÚNG path vào channel này rồi → skip (không LOAD trùng). Bỏ
// early-return mù theo flag — flag sai khi C++ restore instrument khác.
if (window.__bridgeLoadedPaths[bch] === knownPath) return true;
// Có path đáng tin → load thẳng qua Tauri invoke (không cần POST/auth/CORS).
if (knownPath) {
const ok = await window.NativeBridgeService.loadInstrument(knownPath, btype, bch);
if (ok) { window.__bridgeLoadedChannels[bch] = true; window.__bridgeLoadedPaths[bch] = knownPath; }
console.log('[Bridge] auto-load track', track.id, '->', btype, 'ch', bch, ok ? 'OK' : 'FAIL', knownPath);
return !!ok;
}
// Không có path → nhờ engine resolve theo name (POST). Fail (auth/CORS) →
// fallback listPlugins bên dưới.
const r = await window.SonicAPI.bridgeLoad({ name: instrumentId, path: null, instrumentType: btype, channel: bch });
let p = r && r.path ? r.path : null;
if (!p && knownPath) p = knownPath;
if (!p && window.SonicAPI.listPlugins) {
try {
const pl = await window.SonicAPI.listPlugins();
@@ -6476,12 +6516,14 @@ const loadVstToBridge = async (track) => {
}
if (!p) return false;
const ok = await window.NativeBridgeService.loadInstrument(p, btype, bch);
if (ok) window.__bridgeLoadedChannels[bch] = true;
if (ok) { window.__bridgeLoadedChannels[bch] = true; window.__bridgeLoadedPaths[bch] = p; }
console.log('[Bridge] auto-load track', track.id, '->', btype, '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) => {
const ensureAndOpenVstGui = async (track) => {
const trackId = track.id || track.trackId;
const instrumentId = track.instrumentId || (track.synth_engine && track.synth_engine.plugin_id);
if (!instrumentId || (typeof instrumentId === 'string' && instrumentId.startsWith('sf_'))) return;
if (!window.__bridgeLoadedChannels) window.__bridgeLoadedChannels = {};
const bch = (window.SonicMidiRouter && window.SonicMidiRouter.allocateChannel) ? window.SonicMidiRouter.allocateChannel(trackId, false) : 0;
@@ -6497,10 +6539,14 @@ const ensureAndOpenVstGui = async (trackId, instrumentId) => {
} catch (e) { console.warn('[Bridge] GUI via API fail:', trackId, e); }
return;
}
if (!window.__bridgeLoadedChannels[bch]) {
const ok = await loadVstToBridge({ id: trackId, instrumentId });
if (!ok) return;
}
// V9: load (nếu chưa có ĐÚNG instrument) rồi CHỈ mở GUI khi load OK — không
// mở mù khi fail (nếu load Nexus fail mà vẫn OPEN_GUI, C++ mở GUI của
// instrument KHÁC đang restore trên channel — VD WaveObserver thay vì Nexus).
// Bridge restore sẵn ĐÚNG instrument → LOAD trùng path → C++ double-load
// guard skip → ok=true → GUI vẫn mở. Không có instrument → OPEN_GUI defer.
let loadOk = false;
try { loadOk = await loadVstToBridge(track); } catch (e) { console.warn('[Bridge] loadVstToBridge fail:', trackId, e); }
if (!loadOk) return;
try { await openVstGuiRetry(instrumentId, bch, 20); } catch (e) { console.warn('[Bridge] openNativeGUI fail:', e); }
};
// V8 bug 4 + V9 bug 3/6/7: C++ load instrument ASYNC (worker thread) — OPEN_GUI
@@ -10236,7 +10282,7 @@ const beatSec = 60.0 / (parseInt(bpm) || 120);
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: () => {
ensureAndOpenVstGui(st.trackId, st.instrumentId);
ensureAndOpenVstGui(st);
},
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 shrink-0 bg-zinc-800 text-violet-300 border-violet-800 hover:bg-violet-900/40"
@@ -11067,6 +11113,7 @@ const serializeProjectToSchema = (projectId, name, bpmVal, tracksList, subTabsLi
duration: st.duration || 4,
instrument_program: st.instrumentProgram,
instrument_name: st.instrumentName,
instrument_id: st.instrumentId || (st.synth_engine && st.synth_engine.plugin_id) || null,
synth_engine: st.synth_engine || null,
current_time: st.currentTime || 0,
color: st.color || null
@@ -11148,6 +11195,7 @@ const deserializeProjectFromSchema = (schemaObj) => {
duration: st.duration || 4,
instrumentProgram: st.instrument_program,
instrumentName: st.instrument_name,
instrumentId: st.instrument_id || (st.synth_engine && st.synth_engine.plugin_id) || null,
synth_engine: st.synth_engine || null,
currentTime: st.current_time || 0,
color: st.color || null
@@ -16097,7 +16145,10 @@ const App = () => {
// channel, nếu không ensureAndOpenVstGui sẽ bỏ qua load khi chọn lại VST
// (flag cũ còn true) → OPEN_GUI đến bridge khi channel chưa có instrument
// → deferred mãi, nút GUI không mở gì.
if (isSf && window.__bridgeLoadedChannels) delete window.__bridgeLoadedChannels[bch];
if (isSf) {
if (window.__bridgeLoadedChannels) delete window.__bridgeLoadedChannels[bch];
if (window.__bridgeLoadedPaths) delete window.__bridgeLoadedPaths[bch];
}
// VST2 (.dll) scan type la 'VST2' - khong hardcode VST3 (se LOAD sai
// loai -> create_instrument nullptr -> fail im lang).
let btype = isSf ? 'SF2' : 'VST3';
@@ -16113,6 +16164,14 @@ const App = () => {
if (v0 && v0.type) btype = v0.type;
}
}
// V9: path sai (plugin khác instrumentId) → bỏ qua, resolve theo name.
if (!isSf) bpath = _bridgeSafeVstPath(bpath, instrumentId);
// V9: VST→VST đổi instrument khác → path khác path đã load → xóa flag để
// load mới (trước đây chỉ xóa khi isSf — flag cũ làm skip nhầm).
if (window.__bridgeLoadedPaths && window.__bridgeLoadedPaths[bch] !== bpath) {
delete window.__bridgeLoadedPaths[bch];
if (window.__bridgeLoadedChannels) delete window.__bridgeLoadedChannels[bch];
}
(async () => {
let p = bpath;
try {
@@ -16129,7 +16188,12 @@ 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; }
if (ok) {
if (!window.__bridgeLoadedChannels) window.__bridgeLoadedChannels = {};
window.__bridgeLoadedChannels[bch] = true;
if (!window.__bridgeLoadedPaths) window.__bridgeLoadedPaths = {};
window.__bridgeLoadedPaths[bch] = p;
}
if (ok && btype === 'VST3' && p) {
try {
if (!window.__bridgePluginPaths) window.__bridgePluginPaths = {};
@@ -17747,11 +17811,17 @@ const App = () => {
// Rust auto-restart KHONG emit bridge-down khi thanh cong → map cu true →
// GUI click skip load → attach null → nen trang.
window.__bridgeLoadedChannels = {};
window.__bridgeLoadedPaths = {};
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 = {};
window.__bridgeLoadedPaths = {};
// FIX auto-play: bridge restart → page co the con isPlaying=true (stale)
// → master-silence watchdog tu resume phat lai → am tu dong. Stop ngay
// de page khong con "dang phat" — user muon nghe thi bam play.
try { stopAllPlayback(); setIsPlaying(false); } catch (e) { console.warn('[Bridge] stop-on-down fail:', e); }
return;
}
// Bridge (re)connect → load lại toàn bộ VST track (không mở GUI).
@@ -30598,7 +30668,7 @@ STRICT CONSTRAINTS:
})), /*#__PURE__*/React.createElement("span", { className: "truncate text-xs min-w-0" }, 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();
ensureAndOpenVstGui(track.id, track.instrumentId);
ensureAndOpenVstGui(track);
},
title: "Mở lại GUI VSTi",
className: "px-1.5 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 shrink-0"
File diff suppressed because one or more lines are too long