fix(bridge): soundfont track khong co trong thu vien (sf_generaluser_gs) fallback sang GM font co san -> bounce ra du track, het cam

This commit is contained in:
2026-08-24 21:13:53 +07:00
parent 74bd6d849b
commit bf98038572
3 changed files with 49 additions and 40 deletions
+35 -28
View File
@@ -7123,6 +7123,18 @@ const loadVstToBridge = async (track) => {
return !!ok;
} catch (e) { console.warn('[Bridge] loadVstToBridge fail:', track.id, e); return false; }
};
// GM font dự phòng khi soundfont track không có trong thư viện (VD id
// sf_generaluser_gs do AI sinh nhưng user chưa tải font) — ưu tiên font GM
// đầy đủ patch, fallback font đầu tiên liệt kê được.
const pickFallbackSoundfont = (list) => {
if (!list || !list.length) return null;
const hay = (s) => (String(s.id || '') + ' ' + String(s.file || '') + ' ' + String(s.name || '')).toLowerCase();
for (const w of ['sgm', 'generaluser', 'tgsf21x', 'timgm']) {
const hit = list.find(s => hay(s).indexOf(w) !== -1);
if (hit) return hit;
}
return list[0];
};
// D12: load track instrument (VST *hoặc* SF2) vào bridge. preloadTrackInstruments
// trước đây chỉ load VST → mở project có SF2 track + bridge active → MIDI tới
// channel bridge rỗng → C++ silent (không qua mastering chain native).
@@ -7134,16 +7146,29 @@ const loadAnyTrackToBridge = async (track) => {
const se = track.synth_engine || {};
const bank = se.soundfont_bank !== undefined ? se.soundfont_bank : (track.soundfont_bank !== undefined ? track.soundfont_bank : 0);
const bch = window.SonicMidiRouter.allocateChannel(track.id || track.trackId, bank === 128);
let pl = null;
try {
const sfId = inst.replace('sf_', '');
let bpath = null;
try {
const pl = await window.SonicAPI.listPlugins();
pl = await window.SonicAPI.listPlugins();
const s0 = ((pl && pl.soundfonts) || []).find(s => String(s.id).replace('sf_', '') === sfId);
bpath = s0 && (s0.file || s0.path);
} catch (e) {}
const r = await window.SonicAPI.bridgeLoad({ name: inst, path: bpath || null, instrumentType: 'SF2', channel: bch });
const p = r && r.path ? r.path : null;
let r = await window.SonicAPI.bridgeLoad({ name: inst, path: bpath || null, instrumentType: 'SF2', channel: bch });
let p = r && r.path ? r.path : null;
if (!p) {
// FALLBACK: soundfont id KHÔNG tồn tại trong thư viện (AI prompt mặc
// định generaluser_gs nhưng user chưa tải font) → track câm im lặng.
// Load GM font có sẵn (SGM/GeneralUser/tgsf21x) vào cùng channel —
// __ensureBridgeProgram vẫn gửi CC0/CC32/PROGRAM → GM patch đúng.
const fb = pickFallbackSoundfont(pl && pl.soundfonts);
if (fb) {
console.warn('[Bridge] SF2 not found:', inst, '→ fallback', fb.file || fb.id, '@ch', bch);
const r2 = await window.SonicAPI.bridgeLoad({ name: inst, path: (fb.file || fb.path || null), instrumentType: 'SF2', channel: bch });
p = r2 && r2.path ? r2.path : null;
}
}
if (!p) { console.warn('[Bridge] SF2 resolve fail:', track.id, inst); return false; }
const ok = await window.NativeBridgeService.loadInstrument(p, 'SF2', bch);
if (ok) { if (!window.__bridgeLoadedChannels) window.__bridgeLoadedChannels = {}; window.__bridgeLoadedChannels[bch] = true; if (!window.__bridgeLoadedPaths) window.__bridgeLoadedPaths = {}; window.__bridgeLoadedPaths[bch] = p; }
@@ -27993,31 +28018,13 @@ const App = () => {
const c = midiCacheRef.current[t.id];
return c && c.chunks.length > 0 && c.duration >= needDur - 0.3 && cacheHasEnergy(c);
});
// MIDI chỉ render offline qua preview cache (tap sfEntry lúc play). Cache
// câm/thiếu khi routing bridge vào masterBus.input hoặc sub-node thay vì
// sfEntry → file offline đủ duration nhưng KHÔNG CÓ ÂM THANH. DAW-standard:
// project có MIDI → render OFFLINE deterministic qua server (đầy đủ MIDI +
// FX + mastering, đúng như DAW). Bounce REALTIME race không đáng tin (từng
// xuất file 1 note rồi câm / câm hẳn) → chỉ dùng làm fallback khi server lỗi.
if (midiTracks.length > 0 && exportSettings.source === 'project' && exportSettings.format === 'wav') {
if (serverStatus === 'connected') {
// Đóng export panel + hiện progress timeline (renderMasteringExport
// không tự đóng — bounce cũ có làm).
setShowExportPanel(false);
setShowExportProgress(true);
setExportProgress(-1);
try {
await renderMasteringExport();
} finally {
setShowExportProgress(false);
}
return;
}
if (!cacheOK) {
showToast("Cache MIDI chưa đủ và server chưa kết nối — tự chuyển sang bounce REALTIME...", "info");
await triggerBounceExport();
return;
}
// sfEntry → file offline đủ duration nhưng KHÔNG CÓ ÂM THANH. Khi cache
// không đủ → TỰ chuyển sang bounce REALTIME (capture masterBus.output =
// đúng âm đang nghe) thay vì xuất file câm. Áp dụng Export Project (mix).
if (midiTracks.length > 0 && !cacheOK && exportSettings.source === 'project' && exportSettings.format === 'wav') {
showToast("Cache MIDI chưa đủ — tự chuyển sang bounce REALTIME để giữ nguyên âm thanh...", "info");
await triggerBounceExport();
return;
}
if (midiTracks.length > 0 && !cacheOK) {
showToast("Project có MIDI items chưa đủ cache preview — file offline có thể thiếu âm MIDI. Bật Realtime để bounce trung thực.", "warning");