FIX: thêm nút bypass cho midi items và audio items, Section item
This commit is contained in:
+76
-37
@@ -1143,7 +1143,7 @@ const MixerStrip = ({ track, index, onUpdateTrack, trackVuRefs }) => {
|
|||||||
if (window.__setTrackBypass) window.__setTrackBypass(track.id, 'audio', next);
|
if (window.__setTrackBypass) window.__setTrackBypass(track.id, 'audio', next);
|
||||||
else if (window.__setTrackMasteringBypass) window.__setTrackMasteringBypass(track.id, next);
|
else if (window.__setTrackMasteringBypass) window.__setTrackMasteringBypass(track.id, next);
|
||||||
},
|
},
|
||||||
title: "Bypass Audio (clips + sections): XÁM = bypass đang bật (bỏ FX + mastering), SÁNG XANH = xử lý bình thường",
|
title: "A = Mastering FX Chain cho audio items (clips + sections): XÁM = bypass mastering (vẫn qua track FX Rack), SÁNG XANH = qua mastering",
|
||||||
className: "w-5 h-5 flex items-center justify-center rounded-sm text-[10px] font-mono font-bold border transition " + (isAudioBypassed ? 'bg-[#3a3a3a] text-zinc-400 border-black/60 hover:text-zinc-100' : 'bg-sky-400 text-black border-sky-300')
|
className: "w-5 h-5 flex items-center justify-center rounded-sm text-[10px] font-mono font-bold border transition " + (isAudioBypassed ? 'bg-[#3a3a3a] text-zinc-400 border-black/60 hover:text-zinc-100' : 'bg-sky-400 text-black border-sky-300')
|
||||||
}, "A"), React.createElement("button", {
|
}, "A"), React.createElement("button", {
|
||||||
onClick: e => {
|
onClick: e => {
|
||||||
@@ -1153,7 +1153,7 @@ const MixerStrip = ({ track, index, onUpdateTrack, trackVuRefs }) => {
|
|||||||
if (window.__setTrackBypass) window.__setTrackBypass(track.id, 'midi', next);
|
if (window.__setTrackBypass) window.__setTrackBypass(track.id, 'midi', next);
|
||||||
else if (window.__setTrackMasteringBypass) window.__setTrackMasteringBypass(track.id, next);
|
else if (window.__setTrackMasteringBypass) window.__setTrackMasteringBypass(track.id, next);
|
||||||
},
|
},
|
||||||
title: "Bypass MIDI (soundfont): XÁM = bypass đang bật (bỏ FX + mastering), SÁNG TÍM = xử lý bình thường",
|
title: "♪ = Mastering FX Chain cho MIDI (soundfont): XÁM = bypass mastering (vẫn qua track FX Rack), SÁNG TÍM = qua mastering",
|
||||||
className: "w-5 h-5 flex items-center justify-center rounded-sm text-[10px] font-mono font-bold border transition " + (isMidiBypassed ? 'bg-[#3a3a3a] text-zinc-400 border-black/60 hover:text-zinc-100' : 'bg-fuchsia-400 text-black border-fuchsia-300')
|
className: "w-5 h-5 flex items-center justify-center rounded-sm text-[10px] font-mono font-bold border transition " + (isMidiBypassed ? 'bg-[#3a3a3a] text-zinc-400 border-black/60 hover:text-zinc-100' : 'bg-fuchsia-400 text-black border-fuchsia-300')
|
||||||
}, "\u266A")),
|
}, "\u266A")),
|
||||||
React.createElement("div", {
|
React.createElement("div", {
|
||||||
@@ -1514,7 +1514,6 @@ const TrackStripConsole = ({ track, index, onUpdateTrack, trackVuRefs }) => {
|
|||||||
const [pan, setPan] = React.useState(0.0);
|
const [pan, setPan] = React.useState(0.0);
|
||||||
const [panLabel, setPanLabel] = React.useState('center');
|
const [panLabel, setPanLabel] = React.useState('center');
|
||||||
const [isPhaseInverted, setIsPhaseInverted] = React.useState(false);
|
const [isPhaseInverted, setIsPhaseInverted] = React.useState(false);
|
||||||
const [isFxActive, setIsFxActive] = React.useState(true);
|
|
||||||
const panPointerRef = React.useRef(null);
|
const panPointerRef = React.useRef(null);
|
||||||
const setVuCanvas = React.useCallback(function(el) {
|
const setVuCanvas = React.useCallback(function(el) {
|
||||||
if (el) trackVuRefs.current[track.id + '_mixer'] = el;
|
if (el) trackVuRefs.current[track.id + '_mixer'] = el;
|
||||||
@@ -1659,7 +1658,7 @@ const TrackStripConsole = ({ track, index, onUpdateTrack, trackVuRefs }) => {
|
|||||||
else if (window.__setTrackMasteringBypass) window.__setTrackMasteringBypass(track.id, next);
|
else if (window.__setTrackMasteringBypass) window.__setTrackMasteringBypass(track.id, next);
|
||||||
},
|
},
|
||||||
className: "btn-daw h-[24px] rounded flex items-center justify-center font-extrabold text-[9px] " + (track.audioBypass ? " text-slate-500" : " text-sky-300 bg-sky-500/30 border-sky-400/70"),
|
className: "btn-daw h-[24px] rounded flex items-center justify-center font-extrabold text-[9px] " + (track.audioBypass ? " text-slate-500" : " text-sky-300 bg-sky-500/30 border-sky-400/70"),
|
||||||
title: "Bypass Audio (clips + sections): XÁM = bypass đang bật (bỏ FX + mastering), SÁNG XANH = xử lý bình thường"
|
title: "A = Mastering FX Chain cho audio items (clips + sections): XÁM = bypass mastering (vẫn qua track FX Rack), SÁNG XANH = qua mastering"
|
||||||
}, "A"),
|
}, "A"),
|
||||||
React.createElement("button", {
|
React.createElement("button", {
|
||||||
onClick: function(e) {
|
onClick: function(e) {
|
||||||
@@ -1670,7 +1669,7 @@ const TrackStripConsole = ({ track, index, onUpdateTrack, trackVuRefs }) => {
|
|||||||
else if (window.__setTrackMasteringBypass) window.__setTrackMasteringBypass(track.id, next);
|
else if (window.__setTrackMasteringBypass) window.__setTrackMasteringBypass(track.id, next);
|
||||||
},
|
},
|
||||||
className: "btn-daw h-[24px] rounded flex items-center justify-center font-extrabold text-[10px] " + (track.midiBypass ? " text-slate-500" : " text-fuchsia-300 bg-fuchsia-500/30 border-fuchsia-400/70"),
|
className: "btn-daw h-[24px] rounded flex items-center justify-center font-extrabold text-[10px] " + (track.midiBypass ? " text-slate-500" : " text-fuchsia-300 bg-fuchsia-500/30 border-fuchsia-400/70"),
|
||||||
title: "Bypass MIDI (soundfont): XÁM = bypass đang bật (bỏ FX + mastering), SÁNG TÍM = xử lý bình thường"
|
title: "♪ = Mastering FX Chain cho MIDI (soundfont): XÁM = bypass mastering (vẫn qua track FX Rack), SÁNG TÍM = qua mastering"
|
||||||
}, "\u266A"),
|
}, "\u266A"),
|
||||||
React.createElement("button", {
|
React.createElement("button", {
|
||||||
onClick: function() { if (window.__openFxRack) window.__openFxRack(track.id, track.name); },
|
onClick: function() { if (window.__openFxRack) window.__openFxRack(track.id, track.name); },
|
||||||
@@ -1678,9 +1677,15 @@ const TrackStripConsole = ({ track, index, onUpdateTrack, trackVuRefs }) => {
|
|||||||
title: "Track FX Chain (mastering_expand.md §II.4)"
|
title: "Track FX Chain (mastering_expand.md §II.4)"
|
||||||
}, "FX"),
|
}, "FX"),
|
||||||
React.createElement("button", {
|
React.createElement("button", {
|
||||||
onClick: function() { setIsFxActive(function(p) { return !p; }); },
|
onClick: function() {
|
||||||
className: "btn-daw h-[24px] rounded flex items-center justify-center text-[8px]" + (isFxActive ? " text-emerald-400" : " text-slate-500"),
|
var next = track.fxActive === false;
|
||||||
title: "Toggle FX Power"
|
if (onUpdateTrack) onUpdateTrack(track.id, { fxActive: next });
|
||||||
|
// Live re-route: rebuild both FX chains (audio + SF) so EVERY item
|
||||||
|
// (midi/section/audioclip) immediately follows the FX power state.
|
||||||
|
if (window.__rebuildTrackFxGraph) window.__rebuildTrackFxGraph(track.id);
|
||||||
|
},
|
||||||
|
className: "btn-daw h-[24px] rounded flex items-center justify-center text-[8px]" + (track.fxActive !== false ? " text-emerald-400" : " text-slate-500"),
|
||||||
|
title: "Toggle FX Power: ON (sáng) = MỌI items (MIDI/section/audioclip) qua FX Rack Panel (nút A/♪ chỉ ảnh hưởng Mastering FX Chain); OFF (tối) = bỏ toàn bộ track FX"
|
||||||
}, React.createElement("i", { className: "fa-solid fa-power-off" })),
|
}, React.createElement("i", { className: "fa-solid fa-power-off" })),
|
||||||
React.createElement("button", {
|
React.createElement("button", {
|
||||||
className: "btn-daw h-[24px] rounded text-slate-400 flex items-center justify-center",
|
className: "btn-daw h-[24px] rounded text-slate-400 flex items-center justify-center",
|
||||||
@@ -8437,6 +8442,7 @@ const serializeTracksList = (tracksList, secondsPerBar) => {
|
|||||||
mastering_bypass: t.audioBypass || false,
|
mastering_bypass: t.audioBypass || false,
|
||||||
audio_bypass: t.audioBypass || false,
|
audio_bypass: t.audioBypass || false,
|
||||||
midi_bypass: t.midiBypass || false,
|
midi_bypass: t.midiBypass || false,
|
||||||
|
fx_active: t.fxActive !== false,
|
||||||
instrument_id: t.instrumentId || null,
|
instrument_id: t.instrumentId || null,
|
||||||
instrument_program: t.instrumentProgram !== undefined ? t.instrumentProgram : null,
|
instrument_program: t.instrumentProgram !== undefined ? t.instrumentProgram : null,
|
||||||
instrument_name: t.instrumentName || null,
|
instrument_name: t.instrumentName || null,
|
||||||
@@ -8516,6 +8522,7 @@ const deserializeTracksList = (schemaTracks, secondsPerBar, sectionStore) => {
|
|||||||
masteringBypass: (t.audio_bypass !== undefined ? t.audio_bypass : (t.mastering_bypass || false)),
|
masteringBypass: (t.audio_bypass !== undefined ? t.audio_bypass : (t.mastering_bypass || false)),
|
||||||
audioBypass: (t.audio_bypass !== undefined ? t.audio_bypass : (t.mastering_bypass || false)),
|
audioBypass: (t.audio_bypass !== undefined ? t.audio_bypass : (t.mastering_bypass || false)),
|
||||||
midiBypass: (t.midi_bypass !== undefined ? t.midi_bypass : (t.mastering_bypass || false)),
|
midiBypass: (t.midi_bypass !== undefined ? t.midi_bypass : (t.mastering_bypass || false)),
|
||||||
|
fxActive: t.fx_active !== undefined ? t.fx_active : true,
|
||||||
color: t.color || (t.id === '1' ? '#0f766e' : '#1d4ed8'),
|
color: t.color || (t.id === '1' ? '#0f766e' : '#1d4ed8'),
|
||||||
startTime: t.start_time || 0,
|
startTime: t.start_time || 0,
|
||||||
height: t.height || 140,
|
height: t.height || 140,
|
||||||
@@ -12960,6 +12967,12 @@ const App = () => {
|
|||||||
});
|
});
|
||||||
} else if (which === 'midi') {
|
} else if (which === 'midi') {
|
||||||
trackMidiBypassMap[trackId] = !!on;
|
trackMidiBypassMap[trackId] = !!on;
|
||||||
|
// ♪ bypasses Mastering FX Chain for the soundfont ONLY — the SF path
|
||||||
|
// keeps flowing through its own sfModules (track FX Rack, PWR-controlled).
|
||||||
|
const node = activeTrackNodesRef.current[trackId];
|
||||||
|
if (node && node.sfRouteGain && node.sfDryGain) {
|
||||||
|
setMasteringRoute({ routeGain: node.sfRouteGain, dryGain: node.sfDryGain, _trackId: trackId }, !!on);
|
||||||
|
}
|
||||||
try { if (updateSfRoutingRef.current) updateSfRoutingRef.current(); } catch (e) { }
|
try { if (updateSfRoutingRef.current) updateSfRoutingRef.current(); } catch (e) { }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -13533,6 +13546,12 @@ const App = () => {
|
|||||||
});
|
});
|
||||||
const list = (activeTracksRef.current && activeTracksRef.current.length) ? activeTracksRef.current : all;
|
const list = (activeTracksRef.current && activeTracksRef.current.length) ? activeTracksRef.current : all;
|
||||||
list.forEach(t => {
|
list.forEach(t => {
|
||||||
|
// ♪ state → SF mastering route (sfRouteGain/sfDryGain), live on existing nodes
|
||||||
|
const sn = activeTrackNodesRef.current[t.id];
|
||||||
|
if (sn && sn.sfRouteGain && sn.sfDryGain && (sn.sfRouteGain.gain.value > 0) !== !trackMidiBypassMap[t.id]) {
|
||||||
|
sn.sfRouteGain.gain.value = trackMidiBypassMap[t.id] ? 0 : 1;
|
||||||
|
sn.sfDryGain.gain.value = trackMidiBypassMap[t.id] ? 1 : 0;
|
||||||
|
}
|
||||||
const sig = (t.muted ? '1' : '0') + (t.solo ? '1' : '0') + ':' + (t.volumeDb ?? 0);
|
const sig = (t.muted ? '1' : '0') + (t.solo ? '1' : '0') + ':' + (t.volumeDb ?? 0);
|
||||||
if (trackMuteSoloSigRef.current[t.id] === sig) return;
|
if (trackMuteSoloSigRef.current[t.id] === sig) return;
|
||||||
trackMuteSoloSigRef.current[t.id] = sig;
|
trackMuteSoloSigRef.current[t.id] = sig;
|
||||||
@@ -16939,12 +16958,13 @@ const App = () => {
|
|||||||
pannerNode.connect(scopeSplitter);
|
pannerNode.connect(scopeSplitter);
|
||||||
scopeSplitter.connect(scopeAnalyserL, 0);
|
scopeSplitter.connect(scopeAnalyserL, 0);
|
||||||
scopeSplitter.connect(scopeAnalyserR, 1);
|
scopeSplitter.connect(scopeAnalyserR, 1);
|
||||||
// Dual mastering route: routeGain -> mastering chain (normal, post-FX),
|
// Dual mastering route: routeGain -> mastering chain (normal), dryGain ->
|
||||||
// dryGain -> dry bus (bypass, tapped PRE-FX so the bypassed channel skips
|
// dry bus (skip mastering ONLY). BOTH tap POST-FX (analyser) so the A
|
||||||
// BOTH the track FX chain and the mastering chain at Main out).
|
// button bypasses JUST the Mastering FX Chain — the track FX Rack chain
|
||||||
|
// (fxEntry → modules) is controlled ONLY by the PWR button (fxActive).
|
||||||
const route = createMasteringRoute(context, track, masterBus);
|
const route = createMasteringRoute(context, track, masterBus);
|
||||||
analyserNode.connect(route.routeGain);
|
analyserNode.connect(route.routeGain);
|
||||||
gainNode.connect(route.dryGain);
|
analyserNode.connect(route.dryGain);
|
||||||
|
|
||||||
let fxStopFn;
|
let fxStopFn;
|
||||||
// Track FX chain (unified_fx_rack_panel.md §II): reusable module instances
|
// Track FX chain (unified_fx_rack_panel.md §II): reusable module instances
|
||||||
@@ -16955,21 +16975,24 @@ const App = () => {
|
|||||||
const fxLegacyIn = context.createGain();
|
const fxLegacyIn = context.createGain();
|
||||||
gainNode.connect(fxEntry);
|
gainNode.connect(fxEntry);
|
||||||
const fxChain = track.fxChain || [];
|
const fxChain = track.fxChain || [];
|
||||||
const chainMods = fxChain.filter(m => m && m.active !== false).map(m => {
|
// FX Power (nút PWR): ON = mọi items con (midi/section/audioclip) qua FX
|
||||||
|
// Rack Panel chain; OFF = bỏ toàn bộ track FX (fxChain + chorus/reverb).
|
||||||
|
const fxEnabled = track.fxActive !== false;
|
||||||
|
const chainMods = fxEnabled ? fxChain.filter(m => m && m.active !== false).map(m => {
|
||||||
try { return createTrackFxModule(m.type || m, context, m.params); } catch (e) { return null; }
|
try { return createTrackFxModule(m.type || m, context, m.params); } catch (e) { return null; }
|
||||||
}).filter(Boolean);
|
}).filter(Boolean) : [];
|
||||||
let fxChainTail = fxEntry;
|
let fxChainTail = fxEntry;
|
||||||
chainMods.forEach(mod => {
|
chainMods.forEach(mod => {
|
||||||
fxChainTail.connect(mod.input);
|
fxChainTail.connect(mod.input);
|
||||||
fxChainTail = mod.output;
|
fxChainTail = mod.output;
|
||||||
});
|
});
|
||||||
fxChainTail.connect(fxLegacyIn);
|
fxChainTail.connect(fxLegacyIn);
|
||||||
if (track.fxType === 'chorus') {
|
if (fxEnabled && track.fxType === 'chorus') {
|
||||||
const fxInput = context.createGain();
|
const fxInput = context.createGain();
|
||||||
fxLegacyIn.connect(fxInput);
|
fxLegacyIn.connect(fxInput);
|
||||||
const chorus = createChorusNode(context, fxInput, pannerNode);
|
const chorus = createChorusNode(context, fxInput, pannerNode);
|
||||||
fxStopFn = chorus.stop;
|
fxStopFn = chorus.stop;
|
||||||
} else if (track.fxType === 'reverb') {
|
} else if (fxEnabled && track.fxType === 'reverb') {
|
||||||
const fxInput = context.createGain();
|
const fxInput = context.createGain();
|
||||||
fxLegacyIn.connect(fxInput);
|
fxLegacyIn.connect(fxInput);
|
||||||
createReverbNode(context, fxInput, pannerNode);
|
createReverbNode(context, fxInput, pannerNode);
|
||||||
@@ -16982,7 +17005,7 @@ const App = () => {
|
|||||||
// fully separate from the audio-clips chain — so the A (audio) bypass
|
// fully separate from the audio-clips chain — so the A (audio) bypass
|
||||||
// NEVER affects the soundfont instrument, and the ♪ bypass never touches
|
// NEVER affects the soundfont instrument, and the ♪ bypass never touches
|
||||||
// audio clips/sections.
|
// audio clips/sections.
|
||||||
let sfEntry = null, sfOut = null, sfPan = null, sfAnalyser = null;
|
let sfEntry = null, sfOut = null, sfPan = null, sfAnalyser = null, sfRouteGain = null, sfDryGain = null;
|
||||||
if ((track.midiItems && track.midiItems.length > 0)) {
|
if ((track.midiItems && track.midiItems.length > 0)) {
|
||||||
sfEntry = context.createGain();
|
sfEntry = context.createGain();
|
||||||
sfOut = context.createGain();
|
sfOut = context.createGain();
|
||||||
@@ -16998,11 +17021,26 @@ const App = () => {
|
|||||||
// gain (volume/mute/solo) instead.
|
// gain (volume/mute/solo) instead.
|
||||||
sfEntry.gain.value = 1;
|
sfEntry.gain.value = 1;
|
||||||
sfOut.connect(sfPan);
|
sfOut.connect(sfPan);
|
||||||
sfPan.connect(masterBus.input);
|
// Feed the soundfont into the shared scope analysers too, so the Wave
|
||||||
|
// Observer shows the FULL post-FX track signal: audioclips + section
|
||||||
|
// items (pannerNode) AND midi items (sfOut) mixed.
|
||||||
|
sfOut.connect(scopeSplitter);
|
||||||
|
// ♪ button: bypass Mastering FX Chain for the soundfont ONLY (track FX
|
||||||
|
// Rack modules are still applied — PWR controls those). sfRouteGain →
|
||||||
|
// masterBus.input (mastering), sfDryGain → dry bus (skip mastering).
|
||||||
|
const sfBypass = trackMidiBypassMap[track.id] !== undefined ? !!trackMidiBypassMap[track.id] : !!(track.midiBypass ?? track.masteringBypass);
|
||||||
|
sfRouteGain = context.createGain();
|
||||||
|
sfDryGain = context.createGain();
|
||||||
|
sfRouteGain.gain.value = sfBypass ? 0 : 1;
|
||||||
|
sfDryGain.gain.value = sfBypass ? 1 : 0;
|
||||||
|
sfPan.connect(sfRouteGain);
|
||||||
|
sfPan.connect(sfDryGain);
|
||||||
|
sfRouteGain.connect(masterBus.input);
|
||||||
|
sfDryGain.connect(masterBus.dryInput);
|
||||||
sfOut.connect(sfAnalyser);
|
sfOut.connect(sfAnalyser);
|
||||||
const sfMods = fxChain.filter(m => m && m.active !== false).map(m => {
|
const sfMods = fxEnabled ? fxChain.filter(m => m && m.active !== false).map(m => {
|
||||||
try { return createTrackFxModule(m.type || m, context, m.params); } catch (e) { return null; }
|
try { return createTrackFxModule(m.type || m, context, m.params); } catch (e) { return null; }
|
||||||
}).filter(Boolean);
|
}).filter(Boolean) : [];
|
||||||
let sfTail = sfEntry;
|
let sfTail = sfEntry;
|
||||||
sfMods.forEach(mod => {
|
sfMods.forEach(mod => {
|
||||||
sfTail.connect(mod.input);
|
sfTail.connect(mod.input);
|
||||||
@@ -17010,7 +17048,7 @@ const App = () => {
|
|||||||
});
|
});
|
||||||
sfTail.connect(sfOut);
|
sfTail.connect(sfOut);
|
||||||
}
|
}
|
||||||
node = { gainNode, pannerNode, fxStopFn, analyserNode, route, fxEntry, fxLegacyIn, scopeAnalyserL, scopeAnalyserR, sfEntry, sfOut, sfPan, sfAnalyser };
|
node = { gainNode, pannerNode, fxStopFn, analyserNode, route, fxEntry, fxLegacyIn, scopeAnalyserL, scopeAnalyserR, sfEntry, sfOut, sfPan, sfAnalyser, sfRouteGain, sfDryGain };
|
||||||
// Realtime mute/solo: apply the track's current mute/solo/volume state to
|
// Realtime mute/solo: apply the track's current mute/solo/volume state to
|
||||||
// the fresh node so items of muted/soloed tracks start correctly.
|
// the fresh node so items of muted/soloed tracks start correctly.
|
||||||
const trackList = (activeTracksRef.current && activeTracksRef.current.length) ? activeTracksRef.current : [track];
|
const trackList = (activeTracksRef.current && activeTracksRef.current.length) ? activeTracksRef.current : [track];
|
||||||
@@ -17037,9 +17075,10 @@ const App = () => {
|
|||||||
const track = list.find(t => t.id === trackId);
|
const track = list.find(t => t.id === trackId);
|
||||||
if (!track) return;
|
if (!track) return;
|
||||||
node.fxEntry.disconnect();
|
node.fxEntry.disconnect();
|
||||||
const chainMods = (track.fxChain || []).filter(m => m && m.active !== false).map(m => {
|
const fxEnabled = track.fxActive !== false;
|
||||||
|
const chainMods = fxEnabled ? (track.fxChain || []).filter(m => m && m.active !== false).map(m => {
|
||||||
try { return createTrackFxModule(m.type || m, getAudioContext(), m.params); } catch (e) { return null; }
|
try { return createTrackFxModule(m.type || m, getAudioContext(), m.params); } catch (e) { return null; }
|
||||||
}).filter(Boolean);
|
}).filter(Boolean) : [];
|
||||||
let tail = node.fxEntry;
|
let tail = node.fxEntry;
|
||||||
chainMods.forEach(mod => {
|
chainMods.forEach(mod => {
|
||||||
tail.connect(mod.input);
|
tail.connect(mod.input);
|
||||||
@@ -17049,9 +17088,9 @@ const App = () => {
|
|||||||
// Rebuild the independent SF chain too (same modules, separate instances)
|
// Rebuild the independent SF chain too (same modules, separate instances)
|
||||||
if (node.sfEntry && node.sfOut) {
|
if (node.sfEntry && node.sfOut) {
|
||||||
node.sfEntry.disconnect();
|
node.sfEntry.disconnect();
|
||||||
const sfMods = (track.fxChain || []).filter(m => m && m.active !== false).map(m => {
|
const sfMods = fxEnabled ? (track.fxChain || []).filter(m => m && m.active !== false).map(m => {
|
||||||
try { return createTrackFxModule(m.type || m, getAudioContext(), m.params); } catch (e) { return null; }
|
try { return createTrackFxModule(m.type || m, getAudioContext(), m.params); } catch (e) { return null; }
|
||||||
}).filter(Boolean);
|
}).filter(Boolean) : [];
|
||||||
let sfTail = node.sfEntry;
|
let sfTail = node.sfEntry;
|
||||||
sfMods.forEach(mod => {
|
sfMods.forEach(mod => {
|
||||||
sfTail.connect(mod.input);
|
sfTail.connect(mod.input);
|
||||||
@@ -17082,14 +17121,9 @@ const App = () => {
|
|||||||
if (midiAudible.length === 1) {
|
if (midiAudible.length === 1) {
|
||||||
const t = midiAudible[0];
|
const t = midiAudible[0];
|
||||||
const node = activeTrackNodesRef.current[t.id];
|
const node = activeTrackNodesRef.current[t.id];
|
||||||
// MIDI bypass ON: route the soundfont straight to the dry bus (skips
|
// SF ALWAYS enters the track's own FX chain (sfEntry → sfModules when
|
||||||
// the track FX chain AND the mastering chain at Main out), exactly like
|
// PWR ON). The ♪ button only switches the post-FX route (sfRouteGain /
|
||||||
// the audio-group bypass — CC7 keeps mute/solo working.
|
// sfDryGain) to skip or include the Mastering FX Chain.
|
||||||
if (trackMidiBypassMap[t.id] && masterBus && masterBus.dryInput && window.SonicSF && window.SonicSF.setOutputDestination) {
|
|
||||||
window.SonicSF.setOutputDestination(masterBus.dryInput);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Independent SF FX path — unaffected by the audio (A) bypass toggle.
|
|
||||||
if (node && node.sfEntry && window.SonicSF && window.SonicSF.setOutputDestination) {
|
if (node && node.sfEntry && window.SonicSF && window.SonicSF.setOutputDestination) {
|
||||||
window.SonicSF.setOutputDestination(node.sfEntry);
|
window.SonicSF.setOutputDestination(node.sfEntry);
|
||||||
return;
|
return;
|
||||||
@@ -17112,8 +17146,8 @@ const App = () => {
|
|||||||
window.__getTrackScopeAnalysers = (trackId) => {
|
window.__getTrackScopeAnalysers = (trackId) => {
|
||||||
const node = activeTrackNodesRef.current[trackId];
|
const node = activeTrackNodesRef.current[trackId];
|
||||||
if (!node) return null;
|
if (!node) return null;
|
||||||
// MIDI tracks: show the soundfont's own analyser (post-FX).
|
// The shared scope splitter carries the FULL post-FX track signal:
|
||||||
if (node.sfAnalyser) return { L: node.sfAnalyser, R: node.sfAnalyser, sr: getAudioContext().sampleRate };
|
// pannerNode (audioclips + section items) mixed with sfOut (midi items).
|
||||||
if (!node.scopeAnalyserL || !node.scopeAnalyserR) return null;
|
if (!node.scopeAnalyserL || !node.scopeAnalyserR) return null;
|
||||||
return { L: node.scopeAnalyserL, R: node.scopeAnalyserR, sr: getAudioContext().sampleRate };
|
return { L: node.scopeAnalyserL, R: node.scopeAnalyserR, sr: getAudioContext().sampleRate };
|
||||||
};
|
};
|
||||||
@@ -17397,7 +17431,10 @@ const App = () => {
|
|||||||
source.buffer = clip.buffer;
|
source.buffer = clip.buffer;
|
||||||
source.playbackRate.value = clip.speed || 1.0;
|
source.playbackRate.value = clip.speed || 1.0;
|
||||||
source.connect(gainNode);
|
source.connect(gainNode);
|
||||||
gainNode.connect(pannerNode);
|
// NOTE: do NOT connect gainNode → pannerNode directly here — the track
|
||||||
|
// node already wires gainNode → fxEntry → [FX modules] → fxLegacyIn →
|
||||||
|
// chorus/reverb → pannerNode. A direct tap would BYPASS the FX Rack
|
||||||
|
// chain for audio clips (double path: with + without FX).
|
||||||
const clipStart = clip.startTime || 0;
|
const clipStart = clip.startTime || 0;
|
||||||
const clipDuration = clip.buffer.duration / (clip.speed || 1.0);
|
const clipDuration = clip.buffer.duration / (clip.speed || 1.0);
|
||||||
const clipEnd = clipStart + clipDuration;
|
const clipEnd = clipStart + clipDuration;
|
||||||
@@ -19698,7 +19735,7 @@ const App = () => {
|
|||||||
id: 'midi_track_' + now + '_' + idx,
|
id: 'midi_track_' + now + '_' + idx,
|
||||||
name: midiItem.name || (midiResult.length > 1 ? 'MIDI Track ' + (idx + 1) : (file.name || 'MIDI').replace(/\.midi?$/i, '')),
|
name: midiItem.name || (midiResult.length > 1 ? 'MIDI Track ' + (idx + 1) : (file.name || 'MIDI').replace(/\.midi?$/i, '')),
|
||||||
buffer: null, startTime: 0, volumeDb: 0, pan: 0,
|
buffer: null, startTime: 0, volumeDb: 0, pan: 0,
|
||||||
muted: false, solo: false, masteringBypass: false, audioBypass: false, midiBypass: false, color: colors[idx % colors.length],
|
muted: false, solo: false, masteringBypass: false, audioBypass: false, midiBypass: false, fxActive: true, color: colors[idx % colors.length],
|
||||||
markers: [], serverFileId: null, clips: [], sections: [],
|
markers: [], serverFileId: null, clips: [], sections: [],
|
||||||
midiItems: [midiItem],
|
midiItems: [midiItem],
|
||||||
isArmed: false, monitoringEnabled: true,
|
isArmed: false, monitoringEnabled: true,
|
||||||
@@ -19763,6 +19800,7 @@ const App = () => {
|
|||||||
masteringBypass: false,
|
masteringBypass: false,
|
||||||
audioBypass: false,
|
audioBypass: false,
|
||||||
midiBypass: false,
|
midiBypass: false,
|
||||||
|
fxActive: true,
|
||||||
color: selectColor,
|
color: selectColor,
|
||||||
markers: [],
|
markers: [],
|
||||||
serverFileId: null,
|
serverFileId: null,
|
||||||
@@ -19953,6 +19991,7 @@ const App = () => {
|
|||||||
masteringBypass: false,
|
masteringBypass: false,
|
||||||
audioBypass: false,
|
audioBypass: false,
|
||||||
midiBypass: false,
|
midiBypass: false,
|
||||||
|
fxActive: true,
|
||||||
color: selectColor,
|
color: selectColor,
|
||||||
markers: [],
|
markers: [],
|
||||||
serverFileId: null,
|
serverFileId: null,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -24,7 +24,7 @@
|
|||||||
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
<script src="/static/js/services/midiExtractor.js?v=202607281052"></script>
|
||||||
<script src="/static/js/services/promptTemplateManager.js?v=202607281039"></script>
|
<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/undoRedoEngine.js?v=202607290941"></script>
|
||||||
<script src="/static/js/app.precompiled.js?v=202608034300" defer></script>
|
<script src="/static/js/app.precompiled.js?v=202608034700" defer></script>
|
||||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@@ -1,4 +1,27 @@
|
|||||||
### [2026-08-03] Task: FIX LEAK — clip audio chảy qua sfEntry → masterBus.input → mastering (nguyên nhân "bypass vẫn qua mastering + tăng gain")
|
### [2026-08-03] Task: Fix Wave Observer trong FX Rack không hiển thị Audioclip/Section item
|
||||||
|
- **Tóm tắt thay đổi:** `__getTrackScopeAnalysers` ưu tiên `sfAnalyser` (chỉ tiếng MIDI) khi track có midiItems → scope chỉ hiển thị MIDI, audioclip/section không xuất hiện. FIX: (1) nối thêm `sfOut → scopeSplitter` (cùng splitter với pannerNode) → scopeAnalyserL/R giờ trộn TÍN HIỆU ĐẦY ĐỦ post-FX của track: audioclip + section (pannerNode) + midi (sfOut); (2) bỏ nhánh ưu tiên sfAnalyser trong `__getTrackScopeAnalysers` (luôn dùng scopeAnalyserL/R đã trộn).
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js` (rebuild), `app/templates/index.html` (bump v=202608034700)
|
||||||
|
- **Ghi chú/Test (nếu có):** BUILD OK 937588 bytes, node --check OK, `pytest` 86 passed.
|
||||||
|
---
|
||||||
|
- **Tóm tắt thay đổi:** Theo yêu cầu bắt buộc của user: **PWR sáng (ON) = mọi items (MIDI/section/audioclip) qua FX Rack Panel**; **A và ♪ chỉ dùng với Mastering FX Chain** (không ảnh hưởng track FX):
|
||||||
|
1. **Audio path**: dryGain của route chuyển từ tap PRE-FX (gainNode) sang **POST-FX (analyserNode)** — A bypass giờ chỉ bỏ mastering; track FX Rack (fxEntry → modules) vẫn áp dụng cho clip/section bất kể A.
|
||||||
|
2. **MIDI path**: thêm route riêng `sfRouteGain`/`sfDryGain` (sfPan → cả 2; sfRouteGain → masterBus.input, sfDryGain → masterBus.dryInput) — ♪ bypass chỉ bỏ mastering; SF vẫn qua sfModules (track FX, PWR-controlled). `__setTrackBypass('midi')` toggle trực tiếp route này; `updateSfRouting` bỏ nhánh dryInput (SF LUÔN vào sfEntry); effect sync áp lại khi load/undo.
|
||||||
|
3. Tooltip cả 2 strip cập nhật: "A/♪ = Mastering FX Chain... XÁM = bypass mastering (vẫn qua track FX Rack)".
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js` (rebuild), `app/templates/index.html` (bump v=202608034600)
|
||||||
|
- **Ghi chú/Test (nếu có):** BUILD OK 937380 bytes, node --check OK, `pytest` 86 passed.
|
||||||
|
---
|
||||||
|
- **Tóm tắt thay đổi:** User báo: FX Rack ON chỉ MIDI được xử lí, audioclip/section không. **NGUYÊN NHÂN THẬT**: `startLocalTrackPlayback` (loop selection) có dòng cũ `gainNode.connect(pannerNode)` — connect THẲNG gainNode → pannerNode, BỎ QUA fxEntry → [FX modules] → fxLegacyIn. Node track được CACHE → mọi playback sau (kể cả play thường) giữ đường thẳng này → clip vào panner 2 lần (1 qua FX + 1 thẳng) → FX bị pha loãng/không nghe rõ. FIX: bỏ dòng đó (node đã tự wire đúng). Lưu ý thứ 2 cho user: nếu nút A (bypass audio) đang XÁM, clip/section đi thẳng (dry — bỏ FX + mastering) theo thiết kế — MIDI vẫn qua FX (đường SF riêng) → muốn clip qua FX phải tắt nút A (sáng xanh).
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js` (rebuild), `app/templates/index.html` (bump v=202608034500)
|
||||||
|
- **Ghi chú/Test (nếu có):** BUILD OK 936122 bytes, node --check OK, `pytest` 86 passed.
|
||||||
|
---
|
||||||
|
- **Tóm tắt thay đổi:** Nút PWR trước đây chỉ toggle state local UI (không tác dụng DSP). Giờ thành state THẬT trên track (`fxActive`, serialize `fx_active`, default true):
|
||||||
|
1. **getOrCreateTrackNode**: `fxEnabled = track.fxActive !== false` gate cả 3 nhánh — audio fxChain modules, chorus/reverb legacy, SF chain modules (MIDI) — khi OFF: gainNode → fxLegacyIn → panner (không module nào), SF → sfEntry → thẳng sfOut.
|
||||||
|
2. **rebuildTrackFxGraph**: gate cả 2 chain (audio + SF) — bấm PWR khi đang play re-route ngay.
|
||||||
|
3. **Nút PWR (TrackStripConsole)**: toggle `track.fxActive` + `__rebuildTrackFxGraph` — sáng = ON (mọi items qua FX Rack Panel), tối = OFF (bỏ FX). Bỏ state local isFxActive.
|
||||||
|
4. Track mới default `fxActive: true`; deserialize cũ → true (không phá project cũ).
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/static/js/app.precompiled.js` (rebuild), `app/templates/index.html` (bump v=202608034400)
|
||||||
|
- **Ghi chú/Test (nếu có):** BUILD OK 935880 bytes, node --check OK, `pytest` 86 passed.
|
||||||
|
---
|
||||||
- **Tóm tắt thay đổi:** User (bundle mới nhất, log route ĐÚNG: track1 bypass routeGain=0/dryGain=1) vẫn nghe audioclip qua mastering + tăng gain quá mức. **NGUYÊN NHÂN THẬT**: patch "SF path độc lập" trước đó nối `gainNode.connect(sfEntry)` VĨNH VIỄN — clip audio ở gainNode chảy luôn vào đường SF (`sfEntry → sfModules → sfOut → sfPan → masterBus.input → mastering`) → clip nghe **2 đường cùng lúc** (dry + SF path qua mastering) dù bypass bật → tăng gain + vẫn bị mastering xử lý. FIX:
|
- **Tóm tắt thay đổi:** User (bundle mới nhất, log route ĐÚNG: track1 bypass routeGain=0/dryGain=1) vẫn nghe audioclip qua mastering + tăng gain quá mức. **NGUYÊN NHÂN THẬT**: patch "SF path độc lập" trước đó nối `gainNode.connect(sfEntry)` VĨNH VIỄN — clip audio ở gainNode chảy luôn vào đường SF (`sfEntry → sfModules → sfOut → sfPan → masterBus.input → mastering`) → clip nghe **2 đường cùng lúc** (dry + SF path qua mastering) dù bypass bật → tăng gain + vẫn bị mastering xử lý. FIX:
|
||||||
1. **XÓA `gainNode.connect(sfEntry)`** — SF output vào sfEntry TRỰC TIẾP qua `setOutputDestination(sfEntry)` (updateSfRouting), không đi qua gainNode → clip không bao giờ chảy vào SF chain.
|
1. **XÓA `gainNode.connect(sfEntry)`** — SF output vào sfEntry TRỰC TIẾP qua `setOutputDestination(sfEntry)` (updateSfRouting), không đi qua gainNode → clip không bao giờ chảy vào SF chain.
|
||||||
2. **sfEntry.gain mirror audible gain** (volume/mute/solo) ở 3 nơi: getOrCreateTrackNode (khởi tạo), applyAllTrackMuteSolo, effect sync `[tracks, sessionTabs]` — SF vẫn tuân theo fader/mute/solo.
|
2. **sfEntry.gain mirror audible gain** (volume/mute/solo) ở 3 nơi: getOrCreateTrackNode (khởi tạo), applyAllTrackMuteSolo, effect sync `[tracks, sessionTabs]` — SF vẫn tuân theo fader/mute/solo.
|
||||||
|
|||||||
Reference in New Issue
Block a user