FIX: lỗi bypass của SECTION-TAB
This commit is contained in:
+126
-19
@@ -118,11 +118,11 @@ 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) => {
|
||||
if (masteringChainOn()) return false;
|
||||
// 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);
|
||||
};
|
||||
const effAudioBypass = (track) => {
|
||||
if (masteringChainOn()) return false;
|
||||
// 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);
|
||||
};
|
||||
|
||||
@@ -139,7 +139,9 @@ function createMasteringRoute(ctx, track, bus) {
|
||||
bypass = !!(track.audioBypass ?? track.masteringBypass);
|
||||
}
|
||||
// Mastering chain ON → MỌI track qua chain (♪ bị override — user requirement)
|
||||
if (masteringChainOn()) bypass = false;
|
||||
// ⚠️ GỠ 17:00 — override nuốt nút A/♪ (log: audioBypass=true nhưng
|
||||
// routeGain=1) — rules hiện tại: NÚT A/♪ quyết định bypass, không phụ thuộc
|
||||
// mastering ON.
|
||||
const routeGain = ctx.createGain();
|
||||
const dryGain = ctx.createGain();
|
||||
const masterDest = bus ? bus.input : ctx.destination;
|
||||
@@ -6882,7 +6884,7 @@ const AIPresetModal = ({ isOpen, onClose }) => {
|
||||
}))) : null);
|
||||
};
|
||||
|
||||
const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClose, onUpdateNotes, onSaveNotes, setSubTabs, onPlayPause, onStop, isPlaying, playPreviewNote, showToast, midiDevices, recordingState, recTempMidiNotes, onRecord, selectedMidiInputId, onMidiInputSelect, activeMidiPitches, onInstrumentSelect, onRescheduleMidi, onSeekPlayhead, snapValue, onSnapChange, onRealtimePlay }) => {
|
||||
const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClose, onUpdateNotes, onSaveNotes, setSubTabs, onPlayPause, onStop, isPlaying, playPreviewNote, showToast, midiDevices, recordingState, recTempMidiNotes, onRecord, selectedMidiInputId, onMidiInputSelect, activeMidiPitches, onInstrumentSelect, onRescheduleMidi, onSeekPlayhead, snapValue, onSnapChange, onRealtimePlay, onCopyNotes, clipboardNotes }) => {
|
||||
const [activeRollTool, setActiveRollTool] = React.useState('select');
|
||||
const [renderTick, setRenderTick] = React.useState(0);
|
||||
const [ccMode, setCcMode] = React.useState('velocity');
|
||||
@@ -8594,7 +8596,25 @@ const beatSec = 60.0 / (parseInt(bpm) || 120);
|
||||
onChange: function(e) { setHumanizeStrength(parseFloat(e.target.value)); },
|
||||
className: "px-1 py-1 rounded text-xs bg-zinc-900 border border-zinc-700 text-zinc-300 cursor-pointer",
|
||||
title: "Cường độ humanize"
|
||||
}, React.createElement("option", { key: "l", value: 0.05 }, "Nh\u1EB9"), React.createElement("option", { key: "m", value: 0.10 }, "V\u1EEBa"), React.createElement("option", { key: "s", value: 0.18 }, "M\u1EA1nh")), React.createElement("div", {
|
||||
}, React.createElement("option", { key: "l", value: 0.05 }, "Nh\u1EB9"), React.createElement("option", { key: "m", value: 0.10 }, "V\u1EEBa"), React.createElement("option", { key: "s", value: 0.18 }, "M\u1EA1nh")), React.createElement("button", {
|
||||
onClick: function() {
|
||||
if (notes && notes.length) {
|
||||
if (onCopyNotes) onCopyNotes(notes);
|
||||
showToast('Đã copy ' + notes.length + ' nốt vào clipboard — mở tab khác + Paste.', 'success');
|
||||
} else { showToast('Không có nốt để copy.', 'warning'); }
|
||||
},
|
||||
className: "px-2 py-1 rounded text-xs bg-cyan-900/40 text-cyan-300 border border-cyan-700/60 hover:bg-cyan-800/50 transition",
|
||||
title: "Copy toàn bộ notes vào clipboard (paste sang PIANO ROLL TAB khác)"
|
||||
}, "\uD83D\uDCCB Copy"), React.createElement("button", {
|
||||
onClick: function() {
|
||||
if (!clipboardNotes || !clipboardNotes.length) { showToast('Clipboard trống — bấm Copy trước.', 'warning'); return; }
|
||||
pushToUndo(notes);
|
||||
setNotes(prev => [...(prev || []), ...clipboardNotes.map(function(n) { return { ...n, id: 'note_cp_' + Date.now() + '_' + Math.floor(Math.random() * 100000) }; })]);
|
||||
showToast('Đã paste ' + clipboardNotes.length + ' nốt từ clipboard.', 'success');
|
||||
},
|
||||
className: "px-2 py-1 rounded text-xs bg-teal-900/40 text-teal-300 border border-teal-700/60 hover:bg-teal-800/50 transition",
|
||||
title: "Paste notes từ clipboard vào tab này (append cuối)"
|
||||
}, "\uD83D\uDCE5 Paste"), React.createElement("div", {
|
||||
key: "transpose", className: "flex items-center gap-1"
|
||||
}, React.createElement("input", {
|
||||
key: "in", type: "number", step: 1, min: -24, max: 24, value: transposeSemis,
|
||||
@@ -9020,7 +9040,13 @@ const serializeTracksList = (tracksList, secondsPerBar) => {
|
||||
});
|
||||
};
|
||||
|
||||
const deserializeTracksList = (schemaTracks, secondsPerBar, sectionStore) => {
|
||||
const deserializeTracksList = (schemaTracks, secondsPerBar, sectionStore, visitedSections, depth) => {
|
||||
// Guard CHU TRÌNH: SECTION_ITEM trỏ về section đang được deserialize (hoặc
|
||||
// chu trình A→B→A) → recursion vô hạn → RangeError: Maximum call stack size
|
||||
// exceeded khi restore project. Visited-set chặn cycle; depth chặn lồng sâu.
|
||||
const visited = visitedSections || new Set();
|
||||
const curDepth = depth || 0;
|
||||
if (curDepth > 12) return [];
|
||||
return (schemaTracks || []).map(t => {
|
||||
const clips = [];
|
||||
const sections = [];
|
||||
@@ -9060,6 +9086,7 @@ const deserializeTracksList = (schemaTracks, secondsPerBar, sectionStore) => {
|
||||
const src = item.source_data || {};
|
||||
const secId = src.referenced_section_id;
|
||||
const secContainer = sectionStore ? sectionStore[secId] : null;
|
||||
const isCycle = secContainer ? visited.has(secId) : false;
|
||||
sections.push({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
@@ -9067,7 +9094,7 @@ const deserializeTracksList = (schemaTracks, secondsPerBar, sectionStore) => {
|
||||
duration: item.duration_bars * secondsPerBar,
|
||||
length_bars: item.duration_bars || 4,
|
||||
sectionId: secId,
|
||||
tracks: secContainer ? deserializeTracksList(secContainer.tracks, secondsPerBar, sectionStore) : null
|
||||
tracks: (secContainer && !isCycle) ? deserializeTracksList(secContainer.tracks, secondsPerBar, sectionStore, new Set(visited).add(secId), curDepth + 1) : null
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -14077,9 +14104,9 @@ const App = () => {
|
||||
trackAudioBypassMap[trackId] = !!on;
|
||||
trackMasteringBypassMap[trackId] = !!on;
|
||||
// Live re-route the main track node AND every section sub-node of this
|
||||
// track (keys "<trackId>_sub_<subId>") so section items bypass instantly.
|
||||
// track (keys "<parent>_sub_<trackId>") so section items bypass instantly.
|
||||
Object.keys(activeTrackNodesRef.current).forEach(function(k) {
|
||||
if (k === trackId || k.indexOf(trackId + '_sub_') === 0) {
|
||||
if (k === trackId || k.endsWith('_sub_' + trackId)) {
|
||||
const n = activeTrackNodesRef.current[k];
|
||||
if (n && n.route) setMasteringRoute(n.route, !!on);
|
||||
}
|
||||
@@ -14092,6 +14119,13 @@ const App = () => {
|
||||
if (node && node.sfRouteGain && node.sfDryGain) {
|
||||
setMasteringRoute({ routeGain: node.sfRouteGain, dryGain: node.sfDryGain, _trackId: trackId }, !!on);
|
||||
}
|
||||
// SECTION: nút ♪ cũng toggle route của sub-node MIDI (key <parent>_sub_<trackId>)
|
||||
Object.keys(activeTrackNodesRef.current).forEach(function(k) {
|
||||
if (k.endsWith('_sub_' + trackId)) {
|
||||
const sn = activeTrackNodesRef.current[k];
|
||||
if (sn && sn.route) setMasteringRoute(sn.route, !!on);
|
||||
}
|
||||
});
|
||||
try { if (updateSfRoutingRef.current) updateSfRoutingRef.current(); } catch (e) { }
|
||||
}
|
||||
};
|
||||
@@ -14627,6 +14661,7 @@ const App = () => {
|
||||
const [subTabGainVal, setSubTabGainVal] = useState(100);
|
||||
const [subTabPitchVal, setSubTabPitchVal] = useState(0);
|
||||
const [subTabs, setSubTabs] = useState([]); // [{id, label, trackId, startTime, endTime, buffer, effects, currentTime, selectionStart, selectionEnd, isPlaying}, ...]
|
||||
const [pianoRollClipboard, setPianoRollClipboard] = useState(null); // notes copy giữa các PIANO ROLL TAB
|
||||
const [sessionTabs, setSessionTabs] = useState([]); // [{id, name, tracks}, ...]
|
||||
const [tabContextMenu, setTabContextMenu] = useState(null); // { x, y, tabId, tabType }
|
||||
const handleSetTabColor = (tabId, tabType, color) => {
|
||||
@@ -15516,7 +15551,7 @@ const App = () => {
|
||||
}
|
||||
if (maxVal === 0) return st;
|
||||
const scale = 1.0 / maxVal;
|
||||
const clonedBuffer = ctx.createBuffer(1, data.length, st.buffer.sampleRate);
|
||||
const clonedBuffer = ctx.createBuffer(1, Math.max(1, data.length), st.buffer.sampleRate);
|
||||
const clonedData = clonedBuffer.getChannelData(0);
|
||||
clonedData.set(data);
|
||||
for (let i = startSample; i < endSample; i++) {
|
||||
@@ -15539,7 +15574,7 @@ const App = () => {
|
||||
const startSample = Math.floor(left * st.buffer.sampleRate);
|
||||
const endSample = Math.floor(right * st.buffer.sampleRate);
|
||||
const scale = Math.pow(10, gainDb / 20);
|
||||
const clonedBuffer = ctx.createBuffer(1, data.length, st.buffer.sampleRate);
|
||||
const clonedBuffer = ctx.createBuffer(1, Math.max(1, data.length), st.buffer.sampleRate);
|
||||
const clonedData = clonedBuffer.getChannelData(0);
|
||||
clonedData.set(data);
|
||||
for (let i = startSample; i < endSample; i++) {
|
||||
@@ -15563,7 +15598,7 @@ const App = () => {
|
||||
const endSample = Math.floor(right * st.buffer.sampleRate);
|
||||
const durationSamples = endSample - startSample;
|
||||
if (durationSamples <= 0) return st;
|
||||
const clonedBuffer = ctx.createBuffer(1, data.length, st.buffer.sampleRate);
|
||||
const clonedBuffer = ctx.createBuffer(1, Math.max(1, data.length), st.buffer.sampleRate);
|
||||
const clonedData = clonedBuffer.getChannelData(0);
|
||||
clonedData.set(data);
|
||||
if (type === 'in') {
|
||||
@@ -15599,14 +15634,14 @@ const App = () => {
|
||||
const data = st.buffer.getChannelData(0);
|
||||
const startSample = Math.floor(left * sr);
|
||||
const endSample = Math.floor(right * sr);
|
||||
const len = endSample - startSample;
|
||||
const len = Math.max(1, endSample - startSample); // guard 0 frames → createBuffer crash
|
||||
const cutBuffer = ctx.createBuffer(1, len, sr);
|
||||
cutBuffer.copyToChannel(data.subarray(startSample, endSample), 0);
|
||||
clipboardRef.current = {
|
||||
buffer: cutBuffer,
|
||||
name: 'Subtab Clip'
|
||||
};
|
||||
const newBuffer = ctx.createBuffer(1, data.length - len, sr);
|
||||
const newBuffer = ctx.createBuffer(1, Math.max(1, data.length - len), sr);
|
||||
const newData = newBuffer.getChannelData(0);
|
||||
let idx = 0;
|
||||
for (let i = 0; i < startSample; i++) newData[idx++] = data[i];
|
||||
@@ -15634,7 +15669,7 @@ const App = () => {
|
||||
const data = st.buffer.getChannelData(0);
|
||||
const startSample = Math.floor(left * sr);
|
||||
const endSample = Math.floor(right * sr);
|
||||
const len = endSample - startSample;
|
||||
const len = Math.max(1, endSample - startSample); // guard 0 frames → createBuffer crash
|
||||
const copyBuffer = ctx.createBuffer(1, len, sr);
|
||||
copyBuffer.copyToChannel(data.subarray(startSample, endSample), 0);
|
||||
clipboardRef.current = {
|
||||
@@ -15689,7 +15724,7 @@ const App = () => {
|
||||
const data = st.buffer.getChannelData(0);
|
||||
const insertTime = st.currentTime || 0;
|
||||
const insertSample = Math.floor(insertTime * sr);
|
||||
const newBuffer = ctx.createBuffer(1, data.length + clipBuf.length, sr);
|
||||
const newBuffer = ctx.createBuffer(1, Math.max(1, data.length + clipBuf.length), sr);
|
||||
const newData = newBuffer.getChannelData(0);
|
||||
let idx = 0;
|
||||
for (let i = 0; i < insertSample; i++) newData[idx++] = data[i];
|
||||
@@ -18730,8 +18765,29 @@ const App = () => {
|
||||
const volLinear = volDb <= -50 ? 0 : Math.pow(10, volDb / 20);
|
||||
gainNode.gain.setValueAtTime(volLinear, context.currentTime);
|
||||
const parentNode = getOrCreateTrackNode(track, context);
|
||||
gainNode.connect(parentNode);
|
||||
node = { gainNode, pannerNode: null };
|
||||
// Route RIÊNG cho sub-track (nút A/♪ trên section strip): mastering path →
|
||||
// parent; dry path → masterBus.dryInput — bypass áp đúng track section.
|
||||
// Dùng map THẬT (trackAudioBypassMap/trackMidiBypassMap) — KHÔNG qua
|
||||
// effAudioBypass (override mastering-ON ép false → route luôn no-bypass
|
||||
// lúc tạo node → nút A phải bấm 2 lần; stop+play → node tái tạo → mất).
|
||||
// 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));
|
||||
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);
|
||||
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 } };
|
||||
activeTrackNodesRef.current[subKey] = node;
|
||||
}
|
||||
return node.gainNode;
|
||||
@@ -21423,6 +21479,9 @@ const App = () => {
|
||||
const rearrangeNewId = (curTracks.length + 1).toString();
|
||||
const colors = ['#0f766e', '#1d4ed8', '#701a75', '#a21caf', '#b45309'];
|
||||
const selectColor = colors[curTracks.length % colors.length];
|
||||
// SECTION-TAB: audioclip item mặc định BYPASS mastering (raw) — nút A xám;
|
||||
// MAIN session: qua mastering (A sáng). User requirement 15:00.
|
||||
const isSectionCtx = activeTabRef.current && activeTabRef.current.startsWith('session_');
|
||||
updateActiveTracks(prev => [...prev, {
|
||||
id: rearrangeNewId,
|
||||
name: `Track ${rearrangeNewId}`,
|
||||
@@ -21433,7 +21492,7 @@ const App = () => {
|
||||
muted: false,
|
||||
solo: false,
|
||||
masteringBypass: false,
|
||||
audioBypass: false,
|
||||
audioBypass: isSectionCtx ? true : false,
|
||||
midiBypass: false,
|
||||
fxActive: true,
|
||||
color: selectColor,
|
||||
@@ -24464,6 +24523,52 @@ STRICT CONSTRAINTS:
|
||||
return () => { if (masterVUAnimRef.current) cancelAnimationFrame(masterVUAnimRef.current); };
|
||||
}, [isPlaying]);
|
||||
|
||||
const handleOpenProject = async (projectId, projectName) => {
|
||||
setAppWarningModal({
|
||||
title: "Mở dự án",
|
||||
message: "Bạn có muốn mở dự án này? Các thay đổi chưa lưu trên workspace hiện tại sẽ bị mất.",
|
||||
isAlert: false,
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
const proj = await window.SonicAPI.getCloudProject(projectId);
|
||||
const parsed = JSON.parse(proj.data_json);
|
||||
let restoredTracks = [];
|
||||
let restoredBpm = bpm;
|
||||
let restoredSessionTabs = [];
|
||||
let restoredSubTabs = [];
|
||||
if (parsed.main_session) {
|
||||
const result = deserializeProjectFromSchema(parsed);
|
||||
restoredTracks = result.tracks;
|
||||
restoredBpm = result.bpm;
|
||||
restoredSessionTabs = result.sessionTabs;
|
||||
restoredSubTabs = result.subTabs;
|
||||
if (result.masteringSettings) setMasteringSettings(result.masteringSettings);
|
||||
} else {
|
||||
restoredTracks = (parsed.tracks || []).map(function (t) {
|
||||
const rest = Object.assign({}, t); delete rest.height;
|
||||
return Object.assign({}, rest, { buffer: null, channelInfo: t.channelInfo || null, clips: t.clips || [], serverFileId: t.serverFileId || null });
|
||||
});
|
||||
}
|
||||
setTracks(restoredTracks);
|
||||
loadAudioBuffersForTracks(restoredTracks).catch(function (err) { console.warn('loadAudioBuffersForTracks error:', err); });
|
||||
trackMidiChannelsRef.current = {};
|
||||
preloadTrackInstruments(restoredTracks).catch(function (err) { console.warn('preloadTrackInstruments error:', err); });
|
||||
setBpm(restoredBpm.toString());
|
||||
setSelectedTrackId(restoredTracks[0]?.id || '1');
|
||||
setProjectName(proj.name);
|
||||
setCurrentProjectId(proj.id);
|
||||
if (restoredSessionTabs.length > 0) setSessionTabs(restoredSessionTabs);
|
||||
if (restoredSubTabs.length > 0) setSubTabs(restoredSubTabs);
|
||||
localStorage.setItem('sonic_project_name', proj.name);
|
||||
localStorage.setItem('sonic_project_id', proj.id);
|
||||
showToast('Đã nạp dự án "' + proj.name + '" thành công!', "success");
|
||||
} catch (e) {
|
||||
showToast(e.message || "Lỗi khi nạp dự án", "error");
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
|
||||
className: "h-full w-full flex flex-col bg-[#1e1e1e]"
|
||||
}, /*#__PURE__*/React.createElement("header", {
|
||||
@@ -26641,6 +26746,8 @@ STRICT CONSTRAINTS:
|
||||
activeMidiPitches: activeMidiPitches,
|
||||
onInstrumentSelect: (trackId) => { openInstrumentSelector(trackId); },
|
||||
onRealtimePlay: handlePianoRollRealtimePlay,
|
||||
onCopyNotes: (n) => setPianoRollClipboard(JSON.parse(JSON.stringify(n || []))),
|
||||
clipboardNotes: pianoRollClipboard,
|
||||
onRescheduleMidi: (updatedNotes) => {
|
||||
const playingSub = subTabs.find(s => s.id === activeTab && s.type === 'PIANO_ROLL' && s.isPlaying);
|
||||
if (playingSub) {
|
||||
|
||||
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/promptTemplateManager.js?v=202607281039"></script>
|
||||
<script src="/static/js/services/undoRedoEngine.js?v=202607290941"></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608061400" defer></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608061830" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||
<style>
|
||||
:root {
|
||||
|
||||
@@ -2104,3 +2104,73 @@
|
||||
(3) (Đã có) AI Var clone `midiChannel: undefined` (23288) + `ensureTrackMidiChannel` tự cấp channel riêng khi trùng (30: `!inUse.has(track.midiChannel)`) — collision được xử lý khi assign.
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061400), `wiki.md`. Rebuild precompiled.
|
||||
- **Ghi chú/Test:** `npm run build` → hard refresh → section: tạo AI Var mới → kéo volume từng track → âm đổi theo track; mute track trước → track sau vẫn đúng volume. Nếu track AI Var CŨ (tạo trước fix midiChannel) vẫn dùng chung channel → xóa + tạo lại.
|
||||
|
||||
### [2026-08-06 14:30] Task: Nút A (audio bypass) trên section strip không auto-áp — sub-node thiếu route
|
||||
- **Báo cáo user:** nút A xám (bypass on) nhưng audio không bypass — phải bấm 2 lần mới có tác dụng.
|
||||
- **Nguyên nhân:** sub-node section (key `<parent>_sub_<subId>`) chỉ có gainNode → parent — **KHÔNG có route (routeGain/dryGain)** → `__setTrackBypass` scan node theo `indexOf(trackId + '_sub_')` (sai key — sub-node key bắt đầu bằng PARENT id) → nút A không áp được; node play section không có gì để toggle.
|
||||
- **FIX (app.jsx):**
|
||||
(1) `getOrCreateSubTrackNode`: thêm **route riêng** cho sub-track — `routeGain` (mastering path → parent) + `dryGain` (→ masterBus.dryInput) — gain khởi tạo theo `effAudioBypass(subTrack)` — nút A áp ngay từ lúc tạo node.
|
||||
(2) `__setTrackBypass` (audio): scan `k === trackId || k.endsWith('_sub_' + trackId)` (đúng key sub-node của track đó) → `setMasteringRoute` realtime.
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061430), `wiki.md`. Rebuild precompiled.
|
||||
- **Ghi chú/Test:** `npm run build` → hard refresh → section: nút A track audioclip → xám = bypass ngay (nghe hết mastering); sáng = qua mastering.
|
||||
|
||||
### [2026-08-06 15:00] Task: Nút A section — route tạo node dùng effAudioBypass bị override mastering-ON → bypass mất khi stop+play
|
||||
- **Báo cáo user:** A mặc định tắt → play không bypass; bấm A ON rồi OFF mới bypass; stop + play lại → NOT BYPASS.
|
||||
- **Nguyên nhân:** sub-node route tạo bằng `effAudioBypass(subTrack)` — hàm này override `false` khi mastering ON (yêu cầu v03:00) → route luôn no-bypass lúc TẠO node → phải bấm A 2 lần (__setTrackBypass set route trực tiếp); stop → node bị xóa/tái tạo → route lại ép false → mất bypass.
|
||||
- **FIX (app.jsx getOrCreateSubTrackNode):** dùng **map THẬT** `trackAudioBypassMap[subTrack.id]` (trạng thái nút A) — không qua effAudioBypass → route tạo theo nút A; stop+play → node tái tạo → route đúng trạng thái nút A.
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061500), `wiki.md`. Rebuild precompiled.
|
||||
- **Ghi chú/Test:** `npm run build` → hard refresh → section: bật A (xám) → play → bypass ngay; stop + play lại → VẪN bypass; tắt A (sáng) → qua mastering.
|
||||
|
||||
### [2026-08-06 15:30] Task: SECTION-TAB audioclip PHẢI bypass mặc định — addNewTrack context-aware (A xám)
|
||||
- **Yêu cầu user:** nút A mặc định tắt nhưng audioclip item PHẢI bypass khi play; stop + play lại không được mất bypass.
|
||||
- **FIX (app.jsx addNewTrack 21440):** `audioBypass: isSectionCtx ? true : false` — activeTab `session_` → track mới trong SECTION mặc định **BYPASS** (nút A xám, audio raw); MAIN giữ false (qua mastering).
|
||||
- **Kết hợp vòng trước:** sub-node route dùng map THẬT (không override mastering-ON) → route đúng nút A; stop+play → node tái tạo → route đúng trạng thái.
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061530), `wiki.md`. Rebuild precompiled.
|
||||
- **Ghi chú/Test:** `npm run build` → hard refresh → section: tạo track mới + drop audioclip → play → BYPASS ngay (A xám); stop + play lại → VẪN bypass. Track CŨ (tạo trước fix, audioBypass false) → bấm A 1 lần (xám) → từ đó bypass đúng.
|
||||
|
||||
### [2026-08-06 16:00] Task: SECTION-TAB bypass rules — nút A (audio) + nút ♪ (midi) áp đúng cho sub-node
|
||||
- **Yêu cầu user:** nút A tắt → bypass Audioclip; A bật → không bypass; nút ♪ tắt → midi bypass; ♪ bật → midi không bypass.
|
||||
- **FIX (app.jsx):**
|
||||
(1) `getOrCreateSubTrackNode`: subBypass theo LOẠI item của sub-track — có CLIPS → `trackAudioBypassMap` (nút A); có MIDI → `trackMidiBypassMap` (nút ♪) — route tạo đúng trạng thái nút.
|
||||
(2) `__setTrackBypass` nhánh 'midi': **scan sub-node** (`endsWith('_sub_'+trackId)`) → `setMasteringRoute` — nút ♪ trên section strip toggle route sub-node MIDI ngay.
|
||||
- **Kết hợp:** sub-node route (routeGain→parent / dryGain→masterBus.dryInput) — bypass → dry (raw); không bypass → qua mastering.
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061600), `wiki.md`. Rebuild precompiled.
|
||||
- **Ghi chú/Test:** `npm run build` → hard refresh → section: track audioclip — A tắt → bypass, A bật → qua mastering; track MIDI — ♪ tắt → bypass, ♪ bật → qua mastering. Stop+play lại → trạng thái giữ nguyên.
|
||||
|
||||
### [2026-08-06 16:30] Task: ReferenceError handleOpenProject — hàm chỉ tồn tại trong ProfileModal, App render reference sai scope
|
||||
- **Báo cáo user:** bundle 061600 không mở/reload lại dự án — console: `Uncaught ReferenceError: handleOpenProject is not defined`.
|
||||
- **Nguyên nhân:** `handleOpenProject` (5624) nằm BÊN TRONG component ProfileModal — nhưng OpenProjectModal (File → Open Project) nhận `onOpenCloud` closure ở **App render** (27727: `handleOpenProject(projId, projName)`) — reference ngoài scope → ReferenceError khi bấm Mở.
|
||||
- **FIX (app.jsx):** thêm **App-level `handleOpenProject`** (trước render) — logic: getCloudProject → deserializeProjectFromSchema → setTracks/mastering/sessionTabs/subTabs + localStorage — giống ProfileModal. ProfileModal giữ bản riêng (tự scope).
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061630), `wiki.md`. Rebuild precompiled.
|
||||
- **Ghi chú/Test:** `npm run build` → hard refresh → File → Open Project → bấm MỞ → dự án nạp + reload lại đúng.
|
||||
|
||||
### [2026-08-06 17:00] Task: Restore project stack overflow — guard cycle trong deserializeTracksList (SECTION_ITEM tự trỏ)
|
||||
- **Báo cáo user:** reload page → `restoreLastSessionProject failed: RangeError: Maximum call stack size exceeded`.
|
||||
- **Nguyên nhân:** `deserializeTracksList` (9023) → SECTION_ITEM (9059) → gọi lại `deserializeTracksList(secContainer.tracks,...)` — section item trỏ về section ĐANG deserialize (hoặc chu trình A→B→A) → recursion VÔ HẠN → stack overflow.
|
||||
- **FIX (app.jsx deserializeTracksList):**
|
||||
(1) **Visited-set**: truyền `visitedSections` xuống; SECTION_ITEM trỏ về section có trong set (cycle) → `tracks: null` (không đệ quy tiếp).
|
||||
(2) **Depth guard**: `curDepth > 12 → return []` — chặn lồng sâu bất thường.
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061700), `wiki.md`. Rebuild precompiled.
|
||||
- **Ghi chú/Test:** `npm run build` → hard refresh → reload page → dự án restore bình thường (section tự trỏ → deserialize an toàn, tracks null thay vì crash).
|
||||
|
||||
### [2026-08-06 17:30] Task: GỠ override mastering-ON — nút A/♪ quyết định bypass (rules SECTION-TAB)
|
||||
- **Báo cáo user:** rules bypass vẫn chưa áp được — log: `[Bypass] node created track 1 initial audioBypass= true routeGain= 1 dryGain= 0` — bypass TRUE nhưng routeGain=1 (INVERTED).
|
||||
- **Nguyên nhân:** `createMasteringRoute` dòng 142 `if (masteringChainOn()) bypass = false;` + `effAudioBypass/effMidiBypass` override mastering-ON (yêu cầu v03:00) → khi mastering ON, route LUÔN ép qua chain → nút A/♪ bị nuốt.
|
||||
- **FIX (app.jsx):** GỠ override ở 3 chỗ: `createMasteringRoute` + `effMidiBypass` + `effAudioBypass` — **NÚT A/♪ quyết định bypass, không phụ thuộc mastering ON** (rules hiện tại của user thắng yêu cầu cũ v03:00).
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061730), `wiki.md`. Rebuild precompiled.
|
||||
- **Ghi chú/Test:** `npm run build` → hard refresh → SECTION-TAB: A tắt (xám) → routeGain=0/dryGain=1 (bypass) ngay lần play đầu; A bật → qua mastering. Log `[Bypass] node created ... audioBypass= true routeGain= 0 dryGain= 1` ✓.
|
||||
|
||||
### [2026-08-06 18:00] Task: Copy/Paste notes giữa các PIANO ROLL TAB (clipboard)
|
||||
- **Yêu cầu user:** copy midi note từ PIANO ROLL TAB này sang tab khác.
|
||||
- **FIX (app.jsx):**
|
||||
(1) App: state `pianoRollClipboard` + prop `onCopyNotes`/`clipboardNotes` cho PianoRollTabEditor (deep-copy khi copy).
|
||||
(2) Editor toolbar (hàng 2): nút **📋 Copy** (toàn bộ notes → clipboard + toast) + **📥 Paste** (notes clipboard → append cuối tab hiện tại, sinh id MỚI `note_cp_*`, `pushToUndo` → Ctrl+Z hoàn tác).
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061800), `wiki.md`. Rebuild precompiled.
|
||||
- **Ghi chú/Test:** `npm run build` → hard refresh → tab A: Copy → mở tab B → Paste → notes xuất hiện; Ctrl+Z hoàn tác. Verify: 0 double-escape emoji.
|
||||
|
||||
### [2026-08-06 18:30] Task: NotSupportedError createBuffer 0 frames — guard Math.max(1) cho mọi createBuffer
|
||||
- **Báo cáo user:** `Uncaught NotSupportedError: Failed to execute 'createBuffer' ... frames (0) less than or equal to minimum bound (0)`.
|
||||
- **Nguyên nhân:** các op audio (sub-tab cut/copy/paste, normalize/fade/gain) tính `len`/`data.length` = 0 (vùng chọn rỗng hoặc buffer 0 frame) → `createBuffer(..., 0, ...)` → throw.
|
||||
- **FIX (app.jsx — guard `Math.max(1, ...)`):** sub-tab Cut (15637/15644), Copy (15672), Paste (15727), clonedBuffer ×3 (15554/15577/15601). Chỗ 11802 đã guard sẵn.
|
||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608061830), `wiki.md`. Rebuild precompiled.
|
||||
- **Ghi chú/Test:** `npm run build` → hard refresh → thao tác cut/copy audio vùng chọn rỗng → hết crash.
|
||||
|
||||
Reference in New Issue
Block a user