v19: fix CC11 piano roll (epoch rieng cho CC11 timers) + Synth/GUI piano roll cho section subtrack

This commit is contained in:
2026-08-26 18:07:26 +07:00
parent e53a31b525
commit 1f78a834a8
3 changed files with 78 additions and 24 deletions
+57 -14
View File
@@ -230,6 +230,11 @@ const stopAllNativeSfNotes = () => {
// Epoch guard: stop/rebuild tăng _nativePlayEpoch → mọi render pending của
// lần play cũ resolve nhưng bị skip (không duplicate schedule).
let _nativePlayEpoch = 0;
// CC11 timers epoch rieng: chi tang o stopAllPlayback (cung pattern bridgeNoteEpochRef
// cho note-on). schedulePianoRollMidi + startSubTabPlayback moi lan play deu tang
// _nativePlayEpoch -> CC11 timers capture epoch giua 2 lan ++ bi stale -> piano roll
// KHONG co CC11 realtime (main session co vi startTrackPlayback chi ++ 1 lan).
let _cc11Epoch = 0;
// V17: fade session dang treo (gain ramp FADE_MS) — restore khi play moi /
// stop moi / doHardStop de gain KHONG bi ket ~0.0001 (root cause "am bi fade out").
let _pendingFadeSession = null;
@@ -751,7 +756,7 @@ const scheduleNoteExpressionCC11 = (track, note, ch, startWallTime, startSec, du
try {
const curve = Array.isArray(note && note.expression_curve) ? note.expression_curve : [];
if (!curve || !curve.length) return;
const epoch = _nativePlayEpoch;
const epoch = _cc11Epoch;
const isPerc = !!(track && track.synth_engine && track.synth_engine.soundfont_bank === 128) || !!(track && track.is_percussion);
const ctx = getAudioContext();
const GRID_MS = 16;
@@ -7243,7 +7248,7 @@ const loadAnyTrackToBridge = async (track) => {
try { return await loadVstToBridge(track); } catch (e) { return false; }
};
const ensureAndOpenVstGui = async (track) => {
const trackId = track.id || track.trackId;
const trackId = track.trackId || track.id;
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 = {};
@@ -11335,7 +11340,8 @@ 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);
const _trk = activeTracks.find(t => t.id === st.trackId);
ensureAndOpenVstGui(_trk || 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"
@@ -17888,6 +17894,15 @@ const App = () => {
var selTracks = activeTracksRef.current || tracks;
var selTrk = null;
for (var si = 0; si < selTracks.length; si++) { if (selTracks[si].id === trackId) { selTrk = selTracks[si]; break; } }
// Piano roll cua SECTION subTrack: trackId nam trong session tab (KHONG
// co trong activeTracksRef) -> tim them trong sessionTabsRef.
if (!selTrk && sessionTabsRef && sessionTabsRef.current) {
for (var sti = 0; sti < sessionTabsRef.current.length && !selTrk; sti++) {
for (var sji = 0; sji < (sessionTabsRef.current[sti].tracks || []).length; sji++) {
if (sessionTabsRef.current[sti].tracks[sji].id === trackId) { selTrk = sessionTabsRef.current[sti].tracks[sji]; break; }
}
}
}
var loaded = null;
if (selTrk && selTrk.instrumentId && String(selTrk.instrumentId).indexOf('sf_') === 0) {
var sfEng = selTrk.synth_engine || {};
@@ -18093,19 +18108,30 @@ const App = () => {
}
} catch (e) {}
var mch = curTrk ? assignTrackMidiChannel(curTrk, mt) : (sfBank === 128 ? 9 : 0);
const hasInstrument = !!instrumentId;
const instrType = isSfInstrument ? 'soundfont' : (hasInstrument ? 'vst3' : 'default');
const synthEngine = hasInstrument ? {
type: instrType,
plugin_id: instrumentId,
soundfont_bank: sfBank !== undefined ? sfBank : 0,
soundfont_program: sfProg !== undefined ? sfProg : 0,
soundfont_id: isSfInstrument ? instrumentId.replace('sf_', '') : ''
} : undefined;
updateActiveTracks(prev => prev.map(t => {
if (t.id !== trackId) return t;
const hasInstrument = !!instrumentId;
const instrType = isSfInstrument ? 'soundfont' : (hasInstrument ? 'vst3' : 'default');
const synthEngine = hasInstrument ? {
type: instrType,
plugin_id: instrumentId,
soundfont_bank: sfBank !== undefined ? sfBank : 0,
soundfont_program: sfProg !== undefined ? sfProg : 0,
soundfont_id: isSfInstrument ? instrumentId.replace('sf_', '') : ''
} : undefined;
return { ...t, midiChannel: mch, instrumentId, instrumentProgram: sfProg, instrumentName: displayName, soundfont_bank: sfBank, soundfont_program: sfProg, synth_engine: synthEngine, type: hasInstrument ? 'MIDI' : (t.type === 'MIDI' ? 'audio' : t.type) };
}));
// Piano roll cua SECTION subTrack: trackId thuoc session tab (khong trong
// activeTracks) -> cap nhat track trong tab do de lan play sau dung inst.
if (!curTrk && sessionTabsRef && sessionTabsRef.current) {
try { setSessionTabs(prev => prev.map(sec => (sec.tracks || []).some(t => t.id === trackId) ? {
...sec,
tracks: (sec.tracks || []).map(t => t.id === trackId ? { ...t, midiChannel: mch, instrumentId, instrumentProgram: sfProg, instrumentName: displayName, soundfont_bank: sfBank, soundfont_program: sfProg, synth_engine: synthEngine, type: hasInstrument ? 'MIDI' : (t.type === 'MIDI' ? 'audio' : t.type) } : t)
} : sec)); } catch (e) { console.warn('[Instr] section sync fail:', e); }
}
// Dong bo snapshot instrument cua PIANO_ROLL subtab dang mo cho track nay
// (toolbar Synth/GUI hien thi dung instrument vua chon).
try { if (setSubTabs) setSubTabs(prev => prev.map(s => s.type === 'PIANO_ROLL' && s.trackId === trackId ? { ...s, instrumentId, instrumentName: displayName, instrumentProgram: sfProg, synth_engine: synthEngine } : s)); } catch (e) { console.warn('[Instr] subtab sync fail:', e); }
setInstrumentDropdownTrackId(null);
setInstrumentDropdownBtnRect(null);
setSynthCategory(null);
@@ -19159,7 +19185,12 @@ const App = () => {
};
const setTrackInstrumentWithUndo = (trackId, instrumentId, displayName, bankNumber, programNumber) => {
const track = activeTracks.find(t => t.id === trackId);
let track = activeTracks.find(t => t.id === trackId);
if (!track && sessionTabsRef && sessionTabsRef.current) {
for (let _ti = 0; _ti < sessionTabsRef.current.length && !track; _ti++) {
track = (sessionTabsRef.current[_ti].tracks || []).find(t => t.id === trackId);
}
}
if (!track) return;
const oldInstrumentId = track.instrumentId;
const oldInstrumentName = track.instrumentName;
@@ -25656,6 +25687,7 @@ const App = () => {
// cũ bắn vào vòng mới = NOTE_OFF cắt note đang kêu → crack (xem ref).
bridgeNoteEpochRef.current++;
try { clearAllPendingNoteOffs(); } catch (e) {}
_cc11Epoch++; // CC11 timers lan play cu vo hieu (da clear o tren)
try { clearAllCC11Timers(); } catch (e) {}
try { if (subTabsRef.current) subTabsRef.current = subTabsRef.current.map(s => ({ ...s, isPlaying: false })); } catch (e) {}
// Fade-out khi user nhấn Stop/Pause (fade=true): ramp gain track + bridge
@@ -35230,7 +35262,18 @@ STRICT CONSTRAINTS:
value: instrumentSelectorTrackId || '',
onChange: e => { var tid = e.target.value; if (tid) { openInstrumentSelector(tid); } },
className: "bg-[#1e1e1e] text-zinc-300 border border-zinc-700 rounded px-2 py-1 text-xs font-mono cursor-pointer"
}, activeTracks.map(function(at) { return /*#__PURE__*/React.createElement("option", { key: at.id, value: at.id }, at.name + ' (' + (at.instrumentName || 'Synth') + ')'); })),
}, (function() {
var _opts = activeTracks.map(function(at) { return { id: at.id, name: at.name || at.id, inst: at.instrumentName || 'Synth' }; });
if (sessionTabsRef && sessionTabsRef.current) {
(sessionTabsRef.current || []).forEach(function(sec) {
(sec.tracks || []).forEach(function(tr) {
if (_opts.some(function(o) { return o.id === tr.id; })) return;
_opts.push({ id: tr.id, name: tr.name || tr.id, inst: tr.instrumentName || 'Synth' });
});
});
}
return _opts.map(function(o) { return /*#__PURE__*/React.createElement("option", { key: o.id, value: o.id }, o.name + ' (' + o.inst + ')'); });
}())),
/*#__PURE__*/React.createElement("button", { onClick: closeInstrumentSelector, className: "text-slate-400 hover:text-slate-200" }, "\u2715")
)
),
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -51,7 +51,7 @@
<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/chordTheory.js?v=202608211300"></script>
<script src="/static/js/app.precompiled.js?v=202608302200" defer></script>
<script src="/static/js/app.precompiled.js?v=202608310900" defer></script>
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
<style>
:root {