FIX: sửa lỗi chuyển từ tab MAIN SESSION sang SECTION-TAB và PIANO ROLL TAB thì bị bypass

This commit is contained in:
2026-08-07 09:13:03 +07:00
parent 23675dab83
commit 8cdf2942b9
4 changed files with 331 additions and 40 deletions
+161 -20
View File
@@ -118,12 +118,20 @@ const masteringChainOn = () => !!(window.currentMasteringSettings && window.curr
// (solo/preview/play) PHI đi qua mastering chain (user requirement: âm phi
// qua chain đ đ ln). Chain OFF theo maps như cũ.
const effMidiBypass = (track) => {
// G override mastering-ON (17:00) nút quyết đnh bypass (user rules)
return trackMidiBypassMap[track.id] !== undefined ? !!trackMidiBypassMap[track.id] : !!(track.midiBypass ?? track.masteringBypass);
// G override mastering-ON (17:00) nút quyết đnh bypass (user rules).
// ƯU TIÊN track object (field midiBypass): track object phân bit đúng
// context (main track vs section clone id TRÙNG nhau); map keyed theo id
// gây collision (clone section ghi đè map ca main track cùng id MIDI
// section b route theo main = bypass sai). Map ch là fallback realtime.
if (track && track.midiBypass !== undefined) return !!track.midiBypass;
if (track && track.id && trackMidiBypassMap[track.id] !== undefined) return !!trackMidiBypassMap[track.id];
return false;
};
const effAudioBypass = (track) => {
// G override mastering-ON (17:00) nút A quyết đnh bypass (user rules)
return trackAudioBypassMap[track.id] !== undefined ? !!trackAudioBypassMap[track.id] : !!(track.audioBypass ?? track.masteringBypass);
if (track && track.audioBypass !== undefined) return !!track.audioBypass;
if (track && track.id && trackAudioBypassMap[track.id] !== undefined) return !!trackAudioBypassMap[track.id];
return false;
};
// Build the dual routing for one track: routeGain -> mastering chain (normal),
@@ -133,10 +141,18 @@ function createMasteringRoute(ctx, track, bus) {
// falling back to the track object this guarantees the A-button toggle is
// picked up even if a stale track object is passed in.
let bypass = false;
if (track && track.id && trackAudioBypassMap[track.id] !== undefined) {
bypass = !!trackAudioBypassMap[track.id];
// Bypass theo LOI ni dung track (rules nút A/): track MIDI-only nút
// quyết đnh KHÔNG dùng audioBypass (section track default audioBypass=true
// route dry midi section b bypass dù tt log: SF track node
// (sfEntry) + noteon chy nhưng âm dry).
const hasClips = track && ((track.clips && track.clips.length > 0) || !!track.buffer);
const hasMidi = track && track.midiItems && track.midiItems.length > 0;
if (hasMidi && !hasClips) {
// MIDI-only ; ƯU TIÊN field track object (context đúng id section
// clone TRÙNG main track; map keyed theo id b ghi đè chéo bypass sai).
bypass = track.midiBypass !== undefined ? !!track.midiBypass : (track.id && trackMidiBypassMap[track.id] !== undefined ? !!trackMidiBypassMap[track.id] : false);
} else if (track) {
bypass = !!(track.audioBypass ?? track.masteringBypass);
bypass = track.audioBypass !== undefined ? !!track.audioBypass : (track.id && trackAudioBypassMap[track.id] !== undefined ? !!trackAudioBypassMap[track.id] : false);
}
// Mastering chain ON MI track qua chain ( b override user requirement)
// G 17:00 override nut nút A/ (log: audioBypass=true nhưng
@@ -156,6 +172,23 @@ function createMasteringRoute(ctx, track, bus) {
return routeObj;
}
// Bypass theo LOI ni dung track (dùng chung: createMasteringRoute, sync
// effect, startTrackPlayback re-apply) MIDI-only track nút ; có clips
// nút A. Section track default audioBypass=true nếu route đc audioBypass
// cho track MIDI midi b kéo vào dry (b bypass) dù tt.
function routeBypassFor(track) {
const hasC = track && ((track.clips && track.clips.length > 0) || !!track.buffer);
const hasM = track && track.midiItems && track.midiItems.length > 0;
if (hasM && !hasC) {
// MIDI-only ; ƯU TIÊN field track object (context đúng id section
// clone TRÙNG main track; map keyed theo id b ghi đè chéo bypass sai).
routeBypassFor._lastType = 'midi';
return track.midiBypass !== undefined ? !!track.midiBypass : (track.id && trackMidiBypassMap[track.id] !== undefined ? !!trackMidiBypassMap[track.id] : false);
}
routeBypassFor._lastType = 'audio';
return track.audioBypass !== undefined ? !!track.audioBypass : (track.id && trackAudioBypassMap[track.id] !== undefined ? !!trackAudioBypassMap[track.id] : false);
}
// Live-toggle a route. HARD switch: cancel any pending automation and assign
// .value directly (instant, cannot be delayed by the automation queue).
function setMasteringRoute(route, bypass) {
@@ -14731,9 +14764,13 @@ const App = () => {
// tracks state (loads, undo, AI ops). Placed AFTER the tracks/subTabs/
// sessionTabs declarations (TDZ-safe).
useEffect(() => {
const all = [...(tracks || []), ...(sessionTabs || []).reduce((acc, s) => acc.concat(s.tracks || []), [])];
// Sync theo th t: session/clone TRƯC, MAIN tracks SAU khi id trùng
// (section clone gi id sub-track = id track main), MAIN thng: map là
// fallback cho track không có field; clone có field (reset 0330) nên route
// dùng field object (context đúng) map chéo không nh hưng.
const all = [...(sessionTabs || []).reduce((acc, s) => acc.concat(s.tracks || []), []), ...(tracks || [])];
all.forEach(t => {
trackMasteringBypassMap[t.id] = !!t.audioBypass;
trackMasteringBypassMap[t.id] = !!t.masteringBypass;
trackAudioBypassMap[t.id] = !!t.audioBypass;
trackMidiBypassMap[t.id] = !!t.midiBypass;
});
@@ -14896,7 +14933,7 @@ const App = () => {
_n.sfDryGain.gain.value = _b ? 1 : 0;
}
if (_n.route && _n.route.routeGain && _n.route.dryGain) {
const _ab = effAudioBypass(_t);
const _ab = routeBypassFor(_t);
_n.route.routeGain.gain.value = _ab ? 0 : 1;
_n.route.dryGain.gain.value = _ab ? 1 : 0;
}
@@ -16587,6 +16624,14 @@ const App = () => {
instrumentId: t.instrumentId || null,
instrumentProgram: t.instrumentProgram,
instrumentName: t.instrumentName || null,
// Reset bypass theo CONTEXT SECTION (KHÔNG kế tha t track gc):
// track gc có th audioByPass/masteringBypass=true (data lưu cũ)
// kế tha làm track MIDI section b kéo vào dry bus khi play (bypass
// mastering dù tt). Section default: audio raw (A sáng = bypass),
// MIDI qua mastering ( tt).
masteringBypass: false,
audioBypass: true,
midiBypass: false,
_isSectionClone: true
};
}) : tracks.filter(t => t.id === trackId).map(t => ({
@@ -16605,6 +16650,9 @@ const App = () => {
soundfont_program: undefined,
instrument_source: null,
synth_engine: undefined,
masteringBypass: false,
audioBypass: true,
midiBypass: false,
_isSectionClone: true
}));
setSessionTabs(prev => [...prev, { id: tabId, name: tabName, sectionId: sectionId, tracks: clonedTracks, color: section.color || null }]);
@@ -18661,6 +18709,7 @@ const App = () => {
sfDryGain = context.createGain();
sfRouteGain.gain.value = sfBypass ? 0 : 1;
sfDryGain.gain.value = sfBypass ? 1 : 0;
console.log('[Bypass] sfEntry created track', track.id, 'midiBypass=' + sfBypass, sfBypass ? 'DRY' : 'MASTERING', '| midiItems:', (track.midiItems || []).length);
sfPan.connect(sfRouteGain);
sfPan.connect(sfDryGain);
sfRouteGain.connect(masterBus.input);
@@ -18758,7 +18807,16 @@ const App = () => {
const _activeTabId = activeTabRef.current;
const _activeSub = subTabsRef.current ? subTabsRef.current.find(s => s.id === _activeTabId) : null;
if (_activeSub && _activeSub.type === 'PIANO_ROLL') {
const _prNode = activeTrackNodesRef.current[_activeSub.trackId];
// PIANO ROLL ca track TRONG SECTION (parent_tab_id = session_*): id
// track trùng main track id lookup key = id trúng node MAIN (route
// theo main sai context MIDI b kéo dry). ƯU TIÊN SUB-NODE
// (key <parent>_sub_<id> route theo section + thng masterBus);
// không có sub-node null (masterBus.input qua mastering).
let _prNode = activeTrackNodesRef.current[_activeSub.trackId];
if (_activeSub.parent_tab_id && _activeSub.parent_tab_id.startsWith('session_')) {
const _prSubKeys = Object.keys(activeTrackNodesRef.current).filter(k => k.endsWith('_sub_' + _activeSub.trackId));
_prNode = _prSubKeys.length ? activeTrackNodesRef.current[_prSubKeys[0]] : null;
}
if (_prNode && window.SonicSF && window.SonicSF.setOutputDestination) {
if (_prNode.sfEntry) {
window.SonicSF.setOutputDestination(_prNode.sfEntry);
@@ -18779,23 +18837,40 @@ const App = () => {
}
}
const midiAudible = list.filter(t => (t.midiItems && t.midiItems.length > 0) && computeTrackAudibleGain(list, t) > 0);
console.log('[SFRoute] activeTab=' + _activeTabId, 'midiAudible=' + midiAudible.map(t => t.id + '(midi' + (t.midiItems || []).length + ')').join(',') || 'none');
if (midiAudible.length === 1) {
const t = midiAudible[0];
const node = activeTrackNodesRef.current[t.id];
// SECTION-TAB: track id ca section clone TRÙNG main track id node
// chính (key = id) là node MAIN (route theo main SAI context, MIDI
// section b kéo vào dry). ƯU TIÊN SUB-NODE (key <parent>_sub_<id>
// route theo section + thng masterBus.input) không có sub-node
// null (SF v masterBus.input qua mastering, KHÔNG bypass).
let node = activeTrackNodesRef.current[t.id];
if (_activeTabId && _activeTabId.startsWith('session_')) {
const subKeys = Object.keys(activeTrackNodesRef.current).filter(k => k.endsWith('_sub_' + t.id));
node = subKeys.length ? activeTrackNodesRef.current[subKeys[0]] : null;
}
// SF ALWAYS enters the track's own FX chain (sfEntry sfModules when
// PWR ON). The button only switches the post-FX route (sfRouteGain /
// sfDryGain) to skip or include the Mastering FX Chain.
if (node && node.sfEntry && window.SonicSF && window.SonicSF.setOutputDestination) {
console.log('[SFRoute] dest=sfEntry node', t.id, 'sfRouteGain=' + (node.sfRouteGain ? node.sfRouteGain.gain.value : 'MISSING'), 'sfDryGain=' + (node.sfDryGain ? node.sfDryGain.gain.value : 'MISSING'));
window.SonicSF.setOutputDestination(node.sfEntry);
// Ép route qua mastering chain NGAY ti thi đim routing.
if (node.sfRouteGain && node.sfDryGain) {
const _b = effMidiBypass(t.id);
// FIX: truyn track OBJECT (t) trưc đây truyn t.id (string)
// effMidiBypass đc track.id=undefined map lookup sai _b luôn
// false sfRouteGain b ép 1 (không bypass) nút vô hiu qua
// đưng này.
const _b = effMidiBypass(t);
node.sfRouteGain.gain.value = _b ? 0 : 1;
node.sfDryGain.gain.value = _b ? 1 : 0;
console.log('[Bypass] sf route sync track', t.id, 'midiBypass=' + _b, _b ? 'DRY' : 'MASTERING');
}
return;
}
if (node && node.gainNode && window.SonicSF && window.SonicSF.setOutputDestination) {
console.log('[SFRoute] dest=gainNode node', t.id, '(no sfEntry) — route.routeGain=' + (node.route ? node.route.routeGain.gain.value : 'MISSING'), 'route.dryGain=' + (node.route ? node.route.dryGain.gain.value : 'MISSING'));
window.SonicSF.setOutputDestination(node.gainNode);
return;
}
@@ -18909,21 +18984,35 @@ const App = () => {
// RULES: sub-track có CLIPS nút A (audioBypass); có MIDI nút (midiBypass).
const subHasClips = (subTrack.clips && subTrack.clips.length > 0) || !!subTrack.buffer;
const subBypass = subHasClips
? (trackAudioBypassMap[subTrack.id] !== undefined ? !!trackAudioBypassMap[subTrack.id] : !!(subTrack.audioBypass ?? subTrack.masteringBypass))
: (trackMidiBypassMap[subTrack.id] !== undefined ? !!trackMidiBypassMap[subTrack.id] : !!(subTrack.midiBypass ?? subTrack.masteringBypass));
? (subTrack.audioBypass !== undefined ? !!subTrack.audioBypass : (trackAudioBypassMap[subTrack.id] !== undefined ? !!trackAudioBypassMap[subTrack.id] : false))
: (subTrack.midiBypass !== undefined ? !!subTrack.midiBypass : (trackMidiBypassMap[subTrack.id] !== undefined ? !!trackMidiBypassMap[subTrack.id] : false));
const routeGain = context.createGain();
const dryGain = context.createGain();
routeGain.gain.value = subBypass ? 0 : 1;
dryGain.gain.value = subBypass ? 1 : 0;
gainNode.connect(routeGain);
gainNode.connect(dryGain);
routeGain.connect(parentNode);
// analyserNode cho VU meter ca SECTION-TAB track strip (VU tick đc
// node.analyserNode sub-node trưc đây không có VU không animation).
const subAnalyser = context.createAnalyser();
subAnalyser.fftSize = 256;
subAnalyser.smoothingTimeConstant = 0.4;
gainNode.connect(subAnalyser);
subAnalyser.connect(routeGain);
subAnalyser.connect(dryGain);
// Mastering path masterBus.input TRC TIP (không qua parent node:
// parent có route riêng vi bypass riêng parent audio_bypass=true
// ni qua parent làm MIDI/audio section b kéo vào dry bus = bypass
// mastering dù sub-track /A đang tt).
if (window.masterBus && window.masterBus.input) {
routeGain.connect(window.masterBus.input);
} else {
routeGain.connect(parentNode);
}
if (window.masterBus && window.masterBus.dryInput) {
dryGain.connect(window.masterBus.dryInput);
} else {
dryGain.connect(parentNode);
}
node = { gainNode, pannerNode: null, route: { routeGain: routeGain, dryGain: dryGain, _trackId: subTrack.id } };
node = { gainNode, pannerNode: null, analyserNode: subAnalyser, route: { routeGain: routeGain, dryGain: dryGain, _trackId: subTrack.id } };
activeTrackNodesRef.current[subKey] = node;
}
return node.gainNode;
@@ -18964,6 +19053,17 @@ const App = () => {
const gainNode = getOrCreateTrackNode(track, context);
const pannerNode = activeTrackNodesRef.current[track.id].pannerNode;
// Re-apply route gains theo content-type NGAY ti thi đim play node
// có th STALE (to t MAIN play vi track cùng id route theo data cũ)
// first play sau double-click vào SECTION-TAB b bypass. Re-apply
// routeBypassFor(track) mi ln schedule s hết chu k "stop+play mi đúng".
const playNode = activeTrackNodesRef.current[track.id];
if (playNode && playNode.route && playNode.route.routeGain) {
const rb = routeBypassFor(track);
playNode.route.routeGain.gain.value = rb ? 0 : 1;
playNode.route.dryGain.gain.value = rb ? 1 : 0;
console.log('[Bypass] reapply track', track.id, 'contentType=' + (routeBypassFor._lastType ? 'midi' : 'audio'), 'bypass=' + rb, '→ routeGain=' + playNode.route.routeGain.gain.value + '/dryGain=' + playNode.route.dryGain.gain.value);
}
console.log('[Play] track', track.id, track.name, 'node ok:', !!(gainNode && pannerNode), 'muted:', track.muted);
const clips = track.clips && track.clips.length > 0 ? track.clips : track.buffer ? [{
id: 'default',
@@ -19084,6 +19184,15 @@ const App = () => {
const subNode = getOrCreateSubTrackNode(track, subTrack, context);
if (!subNode) return;
// Re-apply route sub-node theo content-type mi ln schedule
// sub-node có th STALE (route theo trng thái cũ) midi section
// b kéo vào dry. routeBypassFor: MIDI-only , clips A.
const subNodeObj = activeTrackNodesRef.current[track.id + '_sub_' + subTrack.id];
if (subNodeObj && subNodeObj.route && subNodeObj.route.routeGain) {
const srb = routeBypassFor(subTrack);
subNodeObj.route.routeGain.gain.value = srb ? 0 : 1;
subNodeObj.route.dryGain.gain.value = srb ? 1 : 0;
}
// 1. Play clips in subTrack
const subClips = subTrack.clips || [];
@@ -19166,6 +19275,14 @@ const App = () => {
subTrack.synth_engine
);
}
// Trigger VU meter flash đúng nhp (ging main-track midi 19066)
// C subTrack (VU section tab) + parent track (VU MAIN section
// item đang play) midiVuActivity keyed theo track id trigger
// c 2 đ VU main nhy theo section.
if (window.triggerMidiVuActivity) {
window.triggerMidiVuActivity(subTrack.id, note.velocity || 0.8);
window.triggerMidiVuActivity(track.id, note.velocity || 0.8);
}
}
});
});
@@ -24603,6 +24720,12 @@ STRICT CONSTRAINTS:
const node = trackNodes[trackId];
const canvas = trackVuRefs.current[key];
if (!canvas) return;
// Section tab: track nodes nm dưi key <parent>_sub_<trackId>
// lookup plain key tr undefined VU không animation. Fallback scan _sub_.
const vNode = node || (function() {
for (var k in trackNodes) { if (k.endsWith('_sub_' + trackId)) return trackNodes[k]; }
return null;
})();
// Only animate when real audio actually passes through the track (solo/mute aware)
var vuTracks = activeTracksRef.current || [];
@@ -24611,8 +24734,8 @@ STRICT CONSTRAINTS:
var isAudible = vuTrack ? (anySoloVU ? !!vuTrack.solo : !vuTrack.muted) : true;
let audioPeak = 0;
if (isAudible && node && node.analyserNode) {
const analyser = node.analyserNode;
if (isAudible && vNode && vNode.analyserNode) {
const analyser = vNode.analyserNode;
const data = new Uint8Array(128);
analyser.getByteTimeDomainData(data);
for (let i = 0; i < data.length; i++) {
@@ -24620,6 +24743,24 @@ STRICT CONSTRAINTS:
if (v > audioPeak) audioPeak = v;
}
}
// Section item trên MAIN track: VU theo sub-nodes ca track này (key
// <trackId>_sub_*) âm section đi thng masterBus.input (không qua
// node chính) node chính không có tín hiu VU track đng yên.
if (audioPeak <= 0.001) {
for (var sk in trackNodes) {
if (sk.indexOf(trackId + '_sub_') === 0) {
const sn = trackNodes[sk];
if (sn && sn.analyserNode) {
const d2 = new Uint8Array(128);
sn.analyserNode.getByteTimeDomainData(d2);
for (let i = 0; i < d2.length; i++) {
const v = Math.abs(d2[i] - 128) / 128;
if (v > audioPeak) audioPeak = v;
}
}
}
}
}
// midiVuActivityRef đưc set bi triggerMidiVuActivity c khi play
// MIDI item LN khi ARM + nhn phím MIDI keyboard (preview) nên VU