FIX: (1) masteringConnected default true + ensureMasteringRouting/refreshCarlaStatus ở mọi đường play — MIDI item/preview qua mastering ngay khi chạy app; (2) _instrumentEpoch chống cache stale + retry bank 0 — hết chọn Synth String nghe Piano; (3) /carla-status + auto-open Carla + route exclusive chỉ khi Carla sống (fallback FluidSynth tránh câm); (4) keybed giữ note theo thời gian bấm (auto-off 5s phòng hờ, mouseup dừng ngay)

This commit is contained in:
2026-08-10 09:51:58 +00:00
parent f492104869
commit 75eba27dcf
7 changed files with 135 additions and 26 deletions
+54 -8
View File
@@ -101,6 +101,38 @@ const scheduleCarlaNote = (synthEngine, channel, pitch, velocity, startWallTime,
} catch (e) { /* routing thất bại im lặng — soundfont vẫn chơi qua FluidSynth */ }
};
// ── Carla bridge alive tracking + auto-open ────────────────────────────────
// window.__carlaRunning: undefined = chưa biết | true = đang chạy | false = đã chết.
// Quyết định route MIDI item EXCLUSIVE qua Carla hay fallback FluidSynth —
// tránh "câm toàn phần" khi Carla bị đóng (route chỉ-Carla mà Carla chết = im lặng).
const refreshCarlaStatus = () => {
try {
if (!window.SonicAPI || !window.SonicAPI.carlaStatus) return;
window.SonicAPI.carlaStatus().then(st => {
window.__carlaRunning = !!(st && st.running);
}).catch(() => { window.__carlaRunning = false; });
} catch (e) {}
};
// Carla chưa chạy → TỰ ĐỘNG mở với VSTi của track (fire-and-forget) để MIDI
// item play qua Carla bridge đúng yêu cầu.
const ensureCarlaForPlayback = (synthEngine) => {
try {
if (!window.SonicCarlaMidi || !window.SonicCarlaMidi.shouldRoutePlayback(synthEngine)) return;
if (!window.SonicAPI || !window.SonicAPI.carlaStatus) return;
window.SonicAPI.carlaStatus().then(st => {
if (st && st.running) { window.__carlaRunning = true; return; }
window.__carlaRunning = false;
const pid = synthEngine && synthEngine.plugin_id;
if (pid) {
window.SonicAPI.openInCarla(pid).then(r => {
if (r && r.success) { window.__carlaRunning = true; }
else { window.__carlaRunning = false; }
}).catch(() => { window.__carlaRunning = false; });
}
}).catch(() => { window.__carlaRunning = false; });
} catch (e) {}
};
// Handle ?sfs=<encoded> from double-clicking a .sfs file (opens domain -> loads project)
(function handleSfsDeepLink() {
@@ -9101,7 +9133,10 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
window.triggerMidiVuActivity((st.parent_tab_id && st.parent_tab_id.startsWith('session_') ? '_sess_' : '') + st.trackId, 100);
}
if (window.SonicSF) {
window.SonicSF.playNote(pitch, 100, 500, undefined, kbCtx.program, null, kbCtx.ch, kbCtx.synthEngine);
// ⚠️ FIX: giữ note theo thời gian bấm phím — durationMs lớn (5s)
// chỉ là auto-off phòng hờ; mouseup/mouseleave gọi stopNote dừng
// NGAY (trước đây 500ms → note tự tắt giữa chừng khi giữ phím).
window.SonicSF.playNote(pitch, 100, 5000, undefined, kbCtx.program, null, kbCtx.ch, kbCtx.synthEngine);
}
// MIDI → Carla (track VSTi + ARM + Carla local): preview realtime
if (window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoute(kbCtx.synthEngine, st.isArmed)) {
@@ -9120,7 +9155,8 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
window.triggerMidiVuActivity((st.parent_tab_id && st.parent_tab_id.startsWith('session_') ? '_sess_' : '') + st.trackId, 100);
}
if (window.SonicSF) {
window.SonicSF.playNote(pitch, 100, 200, undefined, kbCtx.program, null, kbCtx.ch, kbCtx.synthEngine);
// giữ note khi kéo qua phím (mouse enter) — dừng bằng mouseup/leave
window.SonicSF.playNote(pitch, 100, 5000, undefined, kbCtx.program, null, kbCtx.ch, kbCtx.synthEngine);
}
if (window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoute(kbCtx.synthEngine, st.isArmed)) {
window.SonicCarlaMidi.noteOn(kbCtx.ch, pitch, 100);
@@ -16392,7 +16428,7 @@ const App = () => {
const [fxRackTarget, setFxRackTarget] = useState(null);
window.__openFxRack = (trackId, trackName) => setFxRackTarget({ trackId, trackName });
const [masteringSettings, setMasteringSettings] = useState({
masterConnected: false,
masterConnected: true, // ⚠️ FIX: bật ON mặc định — MIDI item/preview PHẢI được xử lí qua mastering FX ngay khi chạy app (không cần bật/tắt power)
activeModule: 'eq',
eqActive: true,
imagerActive: true,
@@ -21078,6 +21114,10 @@ const App = () => {
const startTrackPlayback = offsetTime => {
const context = getAudioContext();
// ⚠️ FIX: đồng bộ mastering + Carla status NGAY khi play — MIDI item phải
// qua mastering FX (khi bật) và qua Carla bridge (khi VSTi loaded).
try { if (window.__ensureMasteringRouting) window.__ensureMasteringRouting(); } catch (e) {}
refreshCarlaStatus();
const allPlayTracks = activeTracksRef.current && activeTracksRef.current.length ? activeTracksRef.current : activeTracks;
// Capture items signature at schedule time — updatePlayhead so sánh với
// signature này để phát hiện items đổi vị trí giữa lúc play (re-schedule).
@@ -21134,6 +21174,7 @@ const App = () => {
if ((track.type === 'MIDI' || midiItems.length > 0) && window.SonicSF) {
// Preview cache: capture the soundfont (pre track-FX) for fast offline export
ensureMidiCapture(track, activeTrackNodesRef.current[track.id]);
if (window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(track.synth_engine)) ensureCarlaForPlayback(track.synth_engine);
var trkCh = assignTrackMidiChannel(track, allPlayTracks);
// Ensure instrument is loaded in FluidSynth
if (track.synth_engine && track.synth_engine.type === 'soundfont' && track.synth_engine.soundfont_id) {
@@ -21156,7 +21197,7 @@ const App = () => {
// Track VSTi + Carla local → MIDI item PHẢI play qua Carla bridge
// (chỉ route Carla — KHÔNG play qua FluidSynth GM nữa, tránh âm
// sai instrument chồng lên). Không có Carla → FluidSynth như cũ.
const routeToCarla = !!(window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(track.synth_engine));
const routeToCarla = !!(window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(track.synth_engine)) && window.__carlaRunning !== false;
if (offsetTime < noteStartSec) {
const delay = noteStartSec - offsetTime;
const startTime = context.currentTime + delay;
@@ -21303,7 +21344,8 @@ const App = () => {
var subCh = assignTrackMidiChannel(subTrack, secTracks);
// MIDI item trong SECTION → Carla bridge khi VSTi loaded
// (chỉ route Carla — không play FluidSynth GM sai âm)
const subRouteCarla = !!(window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(subTrack.synth_engine));
const subRouteCarla = !!(window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(subTrack.synth_engine)) && window.__carlaRunning !== false;
if (subRouteCarla || window.SonicCarlaMidi.shouldRoutePlayback(subTrack.synth_engine)) ensureCarlaForPlayback(subTrack.synth_engine);
if (offsetTime < noteStartMain) {
const delay = noteStartMain - offsetTime;
@@ -21372,6 +21414,8 @@ const App = () => {
// Solo playback for Local Selection Loop (LOOP_MAKER.md §2.2)
const startLocalTrackPlayback = (trackId, offsetTime) => {
const context = getAudioContext();
try { if (window.__ensureMasteringRouting) window.__ensureMasteringRouting(); } catch (e) {}
refreshCarlaStatus();
const track = tracks.find(t => t.id === trackId);
if (!track) return;
const clips = track.clips && track.clips.length > 0 ? track.clips : track.buffer ? [{
@@ -21426,7 +21470,7 @@ const App = () => {
const program = track.instrumentProgram !== undefined ? track.instrumentProgram : 0;
// MIDI item → Carla bridge khi VSTi loaded (chỉ route Carla — không
// play FluidSynth GM sai âm chồng lên)
const routeToCarla = !!(window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(track.synth_engine));
const routeToCarla = !!(window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(track.synth_engine)) && window.__carlaRunning !== false;
if (offsetTime < noteStartSec) {
const delay = noteStartSec - offsetTime;
const startTime = context.currentTime + delay;
@@ -21469,6 +21513,8 @@ const App = () => {
const schedulePianoRollMidi = (st, offsetSeconds, notesOverride) => {
if (st.type !== 'PIANO_ROLL') return;
const context = getAudioContext();
try { if (window.__ensureMasteringRouting) window.__ensureMasteringRouting(); } catch (e) {}
refreshCarlaStatus();
const midiNotes = notesOverride || st.notes || [];
const bpmVal = parseInt(bpm) || 120;
const secondsPerBeat = 60.0 / bpmVal;
@@ -21504,7 +21550,7 @@ const App = () => {
const durMs = effectiveDur * 1000;
// Track VSTi + Carla local → note PHẢI play qua Carla bridge (chỉ route
// Carla — không play FluidSynth GM sai âm chồng lên)
const routeToCarla = !!(window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(synthEngine));
const routeToCarla = !!(window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(synthEngine)) && window.__carlaRunning !== false;
if (!routeToCarla && window.SonicSF) {
window.SonicSF.playNote(note.pitch || 60, note.velocity || 0.8, durMs, scheduledTime, instrumentProgram, destNode, mainCh, synthEngine);
}
@@ -21540,7 +21586,7 @@ const App = () => {
var schedTime = startWallTime + effStart;
var durMs = effDur * 1000;
// Ghost note → Carla bridge khi ghost track VSTi (chỉ route Carla)
var gRouteCarla = !!(window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(ghostSynth));
var gRouteCarla = !!(window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(ghostSynth)) && window.__carlaRunning !== false;
if (!gRouteCarla && window.SonicSF) {
window.SonicSF.playNote(note.pitch || 60, note.velocity || 0.8, durMs, schedTime, ghostProg, ghostDest, ghostCh, ghostSynth);
}