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:
+82
-37
@@ -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 cầu giữ kích
|
||||
// thước zoom ở phiên làm việc 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 của Media Explorer — lưu nhầm làm project load ra zoom 1.0 →
|
||||
// timeline thu nhỏ ở góc — user phải zoom in mới 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 chẩn đoán zoom khi đổi tab (user: zoom reset khi chuyển tab)
|
||||
if (prevTab !== activeTab) {
|
||||
console.log('[Zoom] đổi tab', prevTab, '→', activeTab, '| zoom=', zoom, '| minZoom=', (minZoom || 0).toFixed(2));
|
||||
}
|
||||
// Tab DEACTIVE → stop âm của tab đó (user requirement): rời khỏi sub-tab
|
||||
// (PIANO_ROLL/section/audio tab) đang play → stop playback của tab đó.
|
||||
// Tab mới hoạt độ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 — lỗi __currentZoom của Media
|
||||
// Explorer) — áp dụng mới giữ zoom hiện tại/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 sớm thất bại/
|
||||
// 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 bảo mọi 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
|
||||
// của section item (user đã resize thủ công) — mặc kệ SECTION-TAB
|
||||
// dài bao nhiêu. Trước đây duration = durationSec (theo content
|
||||
// tab) → item tự giãn/thu lại mỗi lần lưu → mất 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 BẬT (user: projectEnd chỉ kích hoạt khi
|
||||
// LOOP bật — play loop liên tục tại content end): content NGẮN hơn item
|
||||
// → loop ngắn đúng content; content DÀI hơn item → loop tại item end.
|
||||
// LOOP TẮT → play 1 lần tới END ITEMS (computeMainSessionEndTime —
|
||||
// không cắt sớm 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 LỚN → ép zoom lên (log: vào midi_ → ép 32.78→37.54) → quay lại
|
||||
// MAIN/SECTION zoom giữ giá trị bị ép (KHÔNG khôi phục) = "zoom reset khi
|
||||
// chuyển tab". Timeline không hiển 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: dừng/loop lại tại ENDTIME THẬT của 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 với
|
||||
// signature này để phát hiện items đổi vị trí giữa 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 — lỗi __currentZoom của Media
|
||||
// Explorer) — áp dụng mới giữ zoom hiện tại/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 đã chọn vùng loop riêng →
|
||||
// giữ nguyên (loop vùng chọn — nhánh updatePlayhead selLeft/
|
||||
// selRight). KHÔNG auto-derive selection: LOOP bật → loop tại
|
||||
// projectEnd (content end khi LOOP — 0515/0520) — nhánh
|
||||
// updatePlayhead `updatedTime >= projectEndRef` loop lại từ 0 —
|
||||
// tránh selection 0→end items ép loop toàn bài (sai ý "loop tại
|
||||
// projectEnd").
|
||||
const hasSel = (selectionMode === 'local' && selLeft !== null && selRight !== null && selRight > selLeft)
|
||||
|| (selectionStart !== null && selectionEnd !== null);
|
||||
if (!hasSel) {
|
||||
// Auto-derive loop region = ĐÚNG endtime của session (items trên
|
||||
// track main: clips theo buffer.duration/speed, midiItems, section
|
||||
// bounds) — KHÔNG cộng thêm bars buffer (loop không được dài hơn
|
||||
// duration hiện có).
|
||||
const maxEnd = computeMainSessionEndTime(activeTracks);
|
||||
if (maxEnd > 0) {
|
||||
setSelectionStart(0);
|
||||
setSelectionEnd(maxEnd);
|
||||
}
|
||||
if (hasSel) {
|
||||
// giữ selection — loop vùng chọn
|
||||
} else {
|
||||
// không selection → loop tại projectEnd (không tạo selection)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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=202608070445" defer></script>
|
||||
<script src="/static/js/app.precompiled.js?v=202608070520" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=202607271016">
|
||||
<style>
|
||||
:root {
|
||||
|
||||
Reference in New Issue
Block a user