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:
+161
-20
@@ -118,12 +118,20 @@ const masteringChainOn = () => !!(window.currentMasteringSettings && window.curr
|
||||
// (solo/preview/play) PHẢI đi qua mastering chain (user requirement: âm phải
|
||||
// qua chain để đủ lớn). 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 biệt đúng
|
||||
// context (main track vs section clone — id TRÙNG nhau); map keyed theo id
|
||||
// gây collision (clone section ghi đè map của 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 LOẠI nội 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ù ♪ tắt — log: SF → track node
|
||||
// (sfEntry) + noteon chạy 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 → MỌI track qua chain (♪ bị override — user requirement)
|
||||
// ⚠️ GỠ 17:00 — override nuốt nút A/♪ (log: audioBypass=true nhưng
|
||||
@@ -156,6 +172,23 @@ function createMasteringRoute(ctx, track, bus) {
|
||||
return routeObj;
|
||||
}
|
||||
|
||||
// Bypass theo LOẠI nội 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ù ♪ tắt.
|
||||
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 thắng: 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ế thừa từ track gốc):
|
||||
// track gốc có thể audioByPass/masteringBypass=true (data lưu cũ) →
|
||||
// kế thừa làm track MIDI section bị kéo vào dry bus khi play (bypass
|
||||
// mastering dù ♪ tắt). Section default: audio raw (A sáng = bypass),
|
||||
// MIDI qua mastering (♪ tắt).
|
||||
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 của 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 + thẳng 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 của 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 + thẳng 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 tại thời điểm routing.
|
||||
if (node.sfRouteGain && node.sfDryGain) {
|
||||
const _b = effMidiBypass(t.id);
|
||||
// ⚠️ FIX: truyền track OBJECT (t) — trước đây truyền 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ô hiệu 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 của 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 TRỰC TIẾP (không qua parent node:
|
||||
// parent có route riêng với bypass riêng — parent audio_bypass=true →
|
||||
// nối qua parent làm MIDI/audio section bị kéo vào dry bus = bypass
|
||||
// mastering dù sub-track ♪/A đang tắt).
|
||||
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 tại thời điểm play — node
|
||||
// có thể STALE (tạo từ MAIN play với track cùng id — route theo data cũ)
|
||||
// → first play sau double-click vào SECTION-TAB bị bypass. Re-apply
|
||||
// routeBypassFor(track) mỗi lần schedule sẽ hết chu kỳ "stop+play mới đú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 mỗi lần schedule —
|
||||
// sub-node có thể STALE (route theo trạng 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 nhịp (giống 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 nhảy 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 nằm 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 của track này (key
|
||||
// <trackId>_sub_*) — âm section đi thẳng masterBus.input (không qua
|
||||
// node chính) → node chính không có tín hiệu → 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 bởi triggerMidiVuActivity — cả khi play
|
||||
// MIDI item LẪN khi ARM + nhấn phím MIDI keyboard (preview) — nên VU
|
||||
|
||||
Reference in New Issue
Block a user