FIX: sửa lỗi lưu thay đổi trong SECTION-TAB thì cũng tạo duration trên MAIN SESSION

This commit is contained in:
2026-08-07 11:08:39 +07:00
parent c1aa1eb4d4
commit 173a1f227b
4 changed files with 187 additions and 60 deletions
+82 -37
View File
@@ -6923,7 +6923,18 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, activeTracks, onClos
const [activeRollTool, setActiveRollTool] = React.useState('select');
const [renderTick, setRenderTick] = React.useState(0);
const [ccMode, setCcMode] = React.useState('velocity');
const [rollZoom, setRollZoom] = React.useState(60); // local horizontal zoom factor
const [rollZoom, setRollZoom] = React.useState(() => {
// Persist zoom piano roll qua phiên (localStorage) user yêu cu gi kích
// thưc zoom phiên làm vic trưc.
try {
const v = parseFloat(localStorage.getItem('sf_pr_zoom'));
if (isFinite(v) && v >= 15 && v <= 250) return v;
} catch (e) { }
return 60;
}); // local horizontal zoom factor
React.useEffect(() => {
try { localStorage.setItem('sf_pr_zoom', String(rollZoom)); } catch (e) { }
}, [rollZoom]);
const [aiBarStart, setAiBarStart] = React.useState(0);
const [aiBarEnd, setAiBarEnd] = React.useState(4);
@@ -9264,7 +9275,7 @@ const serializeProjectToSchema = (projectId, name, bpmVal, tracksList, subTabsLi
time_signature_numerator: 4,
time_signature_denominator: 4,
sample_rate: 44100,
zoom: window.__currentZoom || 1.0
zoom: window.__sfTimelineZoom || window.__currentZoom || 1.0
},
main_session: {
id: "main",
@@ -13917,6 +13928,10 @@ const App = () => {
});
React.useEffect(() => {
try { localStorage.setItem('sf_zoom', String(zoom)); } catch (e) { }
// Zoom TIMELINE (App) serialize project dùng cái này (window.__currentZoom
// là zoom ca Media Explorer lưu nhm làm project load ra zoom 1.0
// timeline thu nh góc user phi zoom in mi v đúng).
window.__sfTimelineZoom = zoom;
}, [zoom]);
const [isLoopingSelection, setIsLoopingSelection] = useState(false);
const [beginBar, setBeginBar] = useState(0);
@@ -14856,6 +14871,10 @@ const App = () => {
const prevTab = prevActiveTabRef.current;
prevActiveTabRef.current = activeTab;
updateSfRouting();
// Log chn đoán zoom khi đi tab (user: zoom reset khi chuyn tab)
if (prevTab !== activeTab) {
console.log('[Zoom] đổi tab', prevTab, '→', activeTab, '| zoom=', zoom, '| minZoom=', (minZoom || 0).toFixed(2));
}
// Tab DEACTIVE stop âm ca tab đó (user requirement): ri khi sub-tab
// (PIANO_ROLL/section/audio tab) đang play stop playback ca tab đó.
// Tab mi hot đng bình thưng; MAIN gi hành vi cũ (m piano-roll lúc
@@ -15204,7 +15223,9 @@ const App = () => {
restoredBpm = result.bpm;
restoredSessionTabs = result.sessionTabs;
restoredSubTabs = result.subTabs;
if (result.zoom && setZoom) setZoom(result.zoom);
// B qua zoom 1 (data cũ lưu 1.0 li __currentZoom ca Media
// Explorer) áp dng mi gi zoom hin ti/localStorage hết thu nh.
if (result.zoom > 1 && setZoom) setZoom(result.zoom);
if (result.masteringSettings) setMasteringSettings(result.masteringSettings);
} else {
restoredTracks = (parsed.tracks || []).map(function(t) {
@@ -16644,9 +16665,6 @@ const App = () => {
const handleSaveSectionTab = async (tabId) => {
const tab = sessionTabs.find(s => s.id === tabId);
if (!tab) return;
const bpmVal = parseInt(bpm) || 120;
const secondsPerBeat = 60.0 / bpmVal;
const secondsPerBar = secondsPerBeat * 4;
// Upload buffer-only clips (chưa có serverFileId upload sm tht bi/
// race) TRƯC khi ghi vào section item serialized AUDIO_ITEM có file
@@ -16683,19 +16701,6 @@ const App = () => {
}
return nextTr;
}) : [];
let maxEndTime = 0;
(contentTracks || []).forEach(tr => {
(tr.clips || []).forEach(c => {
const end = (c.startTime || 0) + (c.buffer ? c.buffer.duration / (c.speed || 1.0) : 4);
if (end > maxEndTime) maxEndTime = end;
});
(tr.midiItems || []).forEach(m => {
const end = (m.startTime || 0) + (m.duration || 4);
if (end > maxEndTime) maxEndTime = end;
});
});
const durationSec = Math.max(maxEndTime, 4 * secondsPerBar);
// Đm bo mi clip có serverFileId (upload buffer-only trưc khi lưu)
await ensureClipsUploaded(contentTracks);
@@ -16708,7 +16713,11 @@ const App = () => {
return {
...s,
name: tab.name,
duration: durationSec,
// KHÔNG ghi đè duration: MAIN SESSION ch play ĐÚNG duration
// ca section item (user đã resize th công) mc k SECTION-TAB
// dài bao nhiêu. Trưc đây duration = durationSec (theo content
// tab) item t giãn/thu li mi ln lưu mt kích thưc user
// set. Play MAIN đã clamp theo sec.duration (19154/19191).
tracks: contentTracks
};
})
@@ -17981,6 +17990,34 @@ const App = () => {
let max;
if (activeTab === 'main') {
max = computeMainSessionEndTime(activeTracks);
// Content-min CH khi nút LOOP BT (user: projectEnd ch kích hot khi
// LOOP bt play loop liên tc ti content end): content NGN hơn item
// loop ngn đúng content; content DÀI hơn item loop ti item end.
// LOOP TT play 1 ln ti END ITEMS (computeMainSessionEndTime
// không ct sm theo content).
if (isLoopingSelection) {
(activeTracks || []).forEach(_t => (_t.sections || []).forEach(_s => {
const _tab = sessionTabs.find(st => st.sectionId === _s.sectionId || st.sectionId === _s.id);
let _contentEnd = 0;
if (_tab) {
(_tab.tracks || []).forEach(_tr => {
(_tr.clips || []).forEach(_c => {
const _e = (_c.startTime || 0) + (_c.buffer ? _c.buffer.duration / (_c.speed || 1.0) : 4);
if (_e > _contentEnd) _contentEnd = _e;
});
(_tr.midiItems || []).forEach(_m => {
const _e = (_m.startTime || 0) + (_m.duration || 4);
if (_e > _contentEnd) _contentEnd = _e;
});
});
}
if (_contentEnd > 0) {
const _itemEnd = (_s.start || 0) + (_s.duration || 0);
const _realEnd = (_s.start || 0) + Math.min(_s.duration || 0, _contentEnd);
if (_realEnd < _itemEnd && max <= _itemEnd + 0.001) max = _realEnd;
}
}));
}
} else {
const tab = sessionTabs.find(st => st.id === activeTab);
const tabMax = tab ? computeMainSessionEndTime(tab.tracks || []) : 0;
@@ -17997,7 +18034,7 @@ const App = () => {
max = Math.max(max, currentTime + 60);
}
return Math.max(1, max);
}, [activeTab, activeTracks, sessionTabs, recordingState, currentTime]);
}, [activeTab, activeTracks, sessionTabs, recordingState, currentTime, isLoopingSelection]);
const projectEndRef = useRef(projectEnd);
projectEndRef.current = projectEnd;
const minZoom = useMemo(() => {
@@ -18007,7 +18044,15 @@ const App = () => {
return Math.max(zoom * (maxDuration + leadInMargin), viewportWidth);
}, [zoom, maxDuration, viewportWidth, leadInMargin]);
useEffect(() => {
// KHÔNG ép minZoom khi activeTab là SUB-TAB (PIANO ROLL/audio):
// maxDuration tính theo activeTab=sub-tab ch còn 12 bars buffer
// minZoom LN ép zoom lên (log: vào midi_ ép 32.7837.54) quay li
// MAIN/SECTION zoom gi giá tr b ép (KHÔNG khôi phc) = "zoom reset khi
// chuyn tab". Timeline không hin th sub-tab ép vô nghĩa.
const _tabNow = activeTabRef.current;
if (_tabNow !== 'main' && !(_tabNow && _tabNow.startsWith('session_'))) return;
if (zoom < minZoom) {
console.log('[Zoom] ép lên minZoom=', minZoom.toFixed(2), '(zoom=', zoom.toFixed(2), ') tab=', activeTab, 'maxDuration=', maxDuration.toFixed(2));
setZoom(minZoom);
}
}, [minZoom]);
@@ -18680,6 +18725,7 @@ const App = () => {
// Hết bài: dng/loop li ti ENDTIME THT ca session (projectEnd không
// buffer 12 bars như maxDuration dùng cho scroll/zoom).
if (updatedTime >= projectEndRef.current) {
console.log('[Stop] projectEnd reached — updatedTime=', updatedTime.toFixed(2), 'projectEnd=', projectEndRef.current.toFixed(2), 'isLoopingSelection=', isLoopingSelection);
if (recordingStateRef.current === 'RECORDING') {
setCurrentTime(updatedTime);
animationFrameIdRef.current = requestAnimationFrame(updatePlayhead);
@@ -19197,7 +19243,7 @@ const App = () => {
// Capture items signature at schedule time updatePlayhead so sánh vi
// signature này đ phát hin items đi v trí gia lúc play (re-schedule).
scheduledItemsSigRef.current = buildItemsSignature(allPlayTracks, sessionTabs);
console.log('[Play] offset=' + offsetTime + ' tracks=' + allPlayTracks.length + ' masterBus=' + !!masterBus + ' dest=' + (context.destination ? 'ok' : 'MISSING'));
console.log('[Play] offset=' + offsetTime + ' tracks=' + allPlayTracks.length + ' masterBus=' + !!masterBus + ' dest=' + (context.destination ? 'ok' : 'MISSING') + ' projectEnd=' + (projectEndRef.current || 0).toFixed(2));
const hasSolo = allPlayTracks.some(t => t.solo);
allPlayTracks.forEach(track => {
const isPlayable = hasSolo ? track.solo : !track.muted;
@@ -24980,7 +25026,9 @@ STRICT CONSTRAINTS:
restoredBpm = result.bpm;
restoredSessionTabs = result.sessionTabs;
restoredSubTabs = result.subTabs;
if (result.zoom && setZoom) setZoom(result.zoom);
// B qua zoom 1 (data cũ lưu 1.0 li __currentZoom ca Media
// Explorer) áp dng mi gi zoom hin ti/localStorage.
if (result.zoom > 1 && setZoom) setZoom(result.zoom);
if (result.masteringSettings) setMasteringSettings(result.masteringSettings);
} else {
restoredTracks = (parsed.tracks || []).map(function (t) {
@@ -25784,22 +25832,19 @@ STRICT CONSTRAINTS:
setSubTabs(prev => prev.map(s => s.id === activeTab ? { ...s, selectionStart: 0, selectionEnd: loopEndTime } : s));
}
} else {
// Main timeline / section tab: if the user already selected a loop
// region, KEEP it only auto-derive (0 content end) when no
// selection exists yet. Previously this always overwrote the
// selection with 0 (contentEnd + 2 bars).
// Main timeline / section tab: nếu user đã chn vùng loop riêng
// gi nguyên (loop vùng chn nhánh updatePlayhead selLeft/
// selRight). KHÔNG auto-derive selection: LOOP bt loop ti
// projectEnd (content end khi LOOP 0515/0520) nhánh
// updatePlayhead `updatedTime >= projectEndRef` loop li t 0
// tránh selection 0end items ép loop toàn bài (sai ý "loop ti
// projectEnd").
const hasSel = (selectionMode === 'local' && selLeft !== null && selRight !== null && selRight > selLeft)
|| (selectionStart !== null && selectionEnd !== null);
if (!hasSel) {
// Auto-derive loop region = ĐÚNG endtime ca session (items trên
// track main: clips theo buffer.duration/speed, midiItems, section
// bounds) KHÔNG cng thêm bars buffer (loop không đưc dài hơn
// duration hin có).
const maxEnd = computeMainSessionEndTime(activeTracks);
if (maxEnd > 0) {
setSelectionStart(0);
setSelectionEnd(maxEnd);
}
if (hasSel) {
// gi selection loop vùng chn
} else {
// không selection loop ti projectEnd (không to selection)
}
}
},
File diff suppressed because one or more lines are too long