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 [activeRollTool, setActiveRollTool] = React.useState('select');
|
||||||
const [renderTick, setRenderTick] = React.useState(0);
|
const [renderTick, setRenderTick] = React.useState(0);
|
||||||
const [ccMode, setCcMode] = React.useState('velocity');
|
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 [aiBarStart, setAiBarStart] = React.useState(0);
|
||||||
const [aiBarEnd, setAiBarEnd] = React.useState(4);
|
const [aiBarEnd, setAiBarEnd] = React.useState(4);
|
||||||
|
|
||||||
@@ -9264,7 +9275,7 @@ const serializeProjectToSchema = (projectId, name, bpmVal, tracksList, subTabsLi
|
|||||||
time_signature_numerator: 4,
|
time_signature_numerator: 4,
|
||||||
time_signature_denominator: 4,
|
time_signature_denominator: 4,
|
||||||
sample_rate: 44100,
|
sample_rate: 44100,
|
||||||
zoom: window.__currentZoom || 1.0
|
zoom: window.__sfTimelineZoom || window.__currentZoom || 1.0
|
||||||
},
|
},
|
||||||
main_session: {
|
main_session: {
|
||||||
id: "main",
|
id: "main",
|
||||||
@@ -13917,6 +13928,10 @@ const App = () => {
|
|||||||
});
|
});
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
try { localStorage.setItem('sf_zoom', String(zoom)); } catch (e) { }
|
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]);
|
}, [zoom]);
|
||||||
const [isLoopingSelection, setIsLoopingSelection] = useState(false);
|
const [isLoopingSelection, setIsLoopingSelection] = useState(false);
|
||||||
const [beginBar, setBeginBar] = useState(0);
|
const [beginBar, setBeginBar] = useState(0);
|
||||||
@@ -14856,6 +14871,10 @@ const App = () => {
|
|||||||
const prevTab = prevActiveTabRef.current;
|
const prevTab = prevActiveTabRef.current;
|
||||||
prevActiveTabRef.current = activeTab;
|
prevActiveTabRef.current = activeTab;
|
||||||
updateSfRouting();
|
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
|
// 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 đó.
|
// (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
|
// 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;
|
restoredBpm = result.bpm;
|
||||||
restoredSessionTabs = result.sessionTabs;
|
restoredSessionTabs = result.sessionTabs;
|
||||||
restoredSubTabs = result.subTabs;
|
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);
|
if (result.masteringSettings) setMasteringSettings(result.masteringSettings);
|
||||||
} else {
|
} else {
|
||||||
restoredTracks = (parsed.tracks || []).map(function(t) {
|
restoredTracks = (parsed.tracks || []).map(function(t) {
|
||||||
@@ -16644,9 +16665,6 @@ const App = () => {
|
|||||||
const handleSaveSectionTab = async (tabId) => {
|
const handleSaveSectionTab = async (tabId) => {
|
||||||
const tab = sessionTabs.find(s => s.id === tabId);
|
const tab = sessionTabs.find(s => s.id === tabId);
|
||||||
if (!tab) return;
|
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/
|
// 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 →
|
// race) TRƯỚC khi ghi vào section item → serialized AUDIO_ITEM có file →
|
||||||
@@ -16683,19 +16701,6 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
return nextTr;
|
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)
|
// Đảm bảo mọi clip có serverFileId (upload buffer-only trước khi lưu)
|
||||||
await ensureClipsUploaded(contentTracks);
|
await ensureClipsUploaded(contentTracks);
|
||||||
|
|
||||||
@@ -16708,7 +16713,11 @@ const App = () => {
|
|||||||
return {
|
return {
|
||||||
...s,
|
...s,
|
||||||
name: tab.name,
|
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
|
tracks: contentTracks
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -17981,6 +17990,34 @@ const App = () => {
|
|||||||
let max;
|
let max;
|
||||||
if (activeTab === 'main') {
|
if (activeTab === 'main') {
|
||||||
max = computeMainSessionEndTime(activeTracks);
|
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 {
|
} else {
|
||||||
const tab = sessionTabs.find(st => st.id === activeTab);
|
const tab = sessionTabs.find(st => st.id === activeTab);
|
||||||
const tabMax = tab ? computeMainSessionEndTime(tab.tracks || []) : 0;
|
const tabMax = tab ? computeMainSessionEndTime(tab.tracks || []) : 0;
|
||||||
@@ -17997,7 +18034,7 @@ const App = () => {
|
|||||||
max = Math.max(max, currentTime + 60);
|
max = Math.max(max, currentTime + 60);
|
||||||
}
|
}
|
||||||
return Math.max(1, max);
|
return Math.max(1, max);
|
||||||
}, [activeTab, activeTracks, sessionTabs, recordingState, currentTime]);
|
}, [activeTab, activeTracks, sessionTabs, recordingState, currentTime, isLoopingSelection]);
|
||||||
const projectEndRef = useRef(projectEnd);
|
const projectEndRef = useRef(projectEnd);
|
||||||
projectEndRef.current = projectEnd;
|
projectEndRef.current = projectEnd;
|
||||||
const minZoom = useMemo(() => {
|
const minZoom = useMemo(() => {
|
||||||
@@ -18007,7 +18044,15 @@ const App = () => {
|
|||||||
return Math.max(zoom * (maxDuration + leadInMargin), viewportWidth);
|
return Math.max(zoom * (maxDuration + leadInMargin), viewportWidth);
|
||||||
}, [zoom, maxDuration, viewportWidth, leadInMargin]);
|
}, [zoom, maxDuration, viewportWidth, leadInMargin]);
|
||||||
useEffect(() => {
|
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) {
|
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);
|
setZoom(minZoom);
|
||||||
}
|
}
|
||||||
}, [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
|
// 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).
|
// buffer 12 bars như maxDuration dùng cho scroll/zoom).
|
||||||
if (updatedTime >= projectEndRef.current) {
|
if (updatedTime >= projectEndRef.current) {
|
||||||
|
console.log('[Stop] projectEnd reached — updatedTime=', updatedTime.toFixed(2), 'projectEnd=', projectEndRef.current.toFixed(2), 'isLoopingSelection=', isLoopingSelection);
|
||||||
if (recordingStateRef.current === 'RECORDING') {
|
if (recordingStateRef.current === 'RECORDING') {
|
||||||
setCurrentTime(updatedTime);
|
setCurrentTime(updatedTime);
|
||||||
animationFrameIdRef.current = requestAnimationFrame(updatePlayhead);
|
animationFrameIdRef.current = requestAnimationFrame(updatePlayhead);
|
||||||
@@ -19197,7 +19243,7 @@ const App = () => {
|
|||||||
// Capture items signature at schedule time — updatePlayhead so sánh với
|
// 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).
|
// signature này để phát hiện items đổi vị trí giữa lúc play (re-schedule).
|
||||||
scheduledItemsSigRef.current = buildItemsSignature(allPlayTracks, sessionTabs);
|
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);
|
const hasSolo = allPlayTracks.some(t => t.solo);
|
||||||
allPlayTracks.forEach(track => {
|
allPlayTracks.forEach(track => {
|
||||||
const isPlayable = hasSolo ? track.solo : !track.muted;
|
const isPlayable = hasSolo ? track.solo : !track.muted;
|
||||||
@@ -24980,7 +25026,9 @@ STRICT CONSTRAINTS:
|
|||||||
restoredBpm = result.bpm;
|
restoredBpm = result.bpm;
|
||||||
restoredSessionTabs = result.sessionTabs;
|
restoredSessionTabs = result.sessionTabs;
|
||||||
restoredSubTabs = result.subTabs;
|
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);
|
if (result.masteringSettings) setMasteringSettings(result.masteringSettings);
|
||||||
} else {
|
} else {
|
||||||
restoredTracks = (parsed.tracks || []).map(function (t) {
|
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));
|
setSubTabs(prev => prev.map(s => s.id === activeTab ? { ...s, selectionStart: 0, selectionEnd: loopEndTime } : s));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Main timeline / section tab: if the user already selected a loop
|
// Main timeline / section tab: nếu user đã chọn vùng loop riêng →
|
||||||
// region, KEEP it — only auto-derive (0 → content end) when no
|
// giữ nguyên (loop vùng chọn — nhánh updatePlayhead selLeft/
|
||||||
// selection exists yet. Previously this always overwrote the
|
// selRight). KHÔNG auto-derive selection: LOOP bật → loop tại
|
||||||
// selection with 0 → (contentEnd + 2 bars).
|
// 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)
|
const hasSel = (selectionMode === 'local' && selLeft !== null && selRight !== null && selRight > selLeft)
|
||||||
|| (selectionStart !== null && selectionEnd !== null);
|
|| (selectionStart !== null && selectionEnd !== null);
|
||||||
if (!hasSel) {
|
if (hasSel) {
|
||||||
// Auto-derive loop region = ĐÚNG endtime của session (items trên
|
// giữ selection — loop vùng chọn
|
||||||
// track main: clips theo buffer.duration/speed, midiItems, section
|
} else {
|
||||||
// bounds) — KHÔNG cộng thêm bars buffer (loop không được dài hơn
|
// không selection → loop tại projectEnd (không tạo selection)
|
||||||
// duration hiện có).
|
|
||||||
const maxEnd = computeMainSessionEndTime(activeTracks);
|
|
||||||
if (maxEnd > 0) {
|
|
||||||
setSelectionStart(0);
|
|
||||||
setSelectionEnd(maxEnd);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
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=202608070445" defer></script>
|
<script src="/static/js/app.precompiled.js?v=202608070520" 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 {
|
||||||
|
|||||||
@@ -2453,3 +2453,59 @@
|
|||||||
- **FIX (app.jsx):** effect `[isPlaying]` mới — detect transition playing→dừng (prevIsPlayingRef): nếu đang ở PIANO ROLL tab (`midi_*`) + tab KHÔNG play → `setSubTabs currentTime = 0` — playhead về đầu. Tab đang play (piano roll play riêng) → giữ nguyên (không đụng).
|
- **FIX (app.jsx):** effect `[isPlaying]` mới — detect transition playing→dừng (prevIsPlayingRef): nếu đang ở PIANO ROLL tab (`midi_*`) + tab KHÔNG play → `setSubTabs currentTime = 0` — playhead về đầu. Tab đang play (piano roll play riêng) → giữ nguyên (không đụng).
|
||||||
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070445), `wiki.md`. Rebuild precompiled.
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070445), `wiki.md`. Rebuild precompiled.
|
||||||
- **Ghi chú/Test:** hard refresh → play MAIN → mở PIANO ROLL (playhead chạy trong item) → đợi play hết (projectEnd) → playhead piano roll TỰ VỀ 0; bấm stop (space) → cũng về 0; piano roll đang play riêng → không bị về 0.
|
- **Ghi chú/Test:** hard refresh → play MAIN → mở PIANO ROLL (playhead chạy trong item) → đợi play hết (projectEnd) → playhead piano roll TỰ VỀ 0; bấm stop (space) → cũng về 0; piano roll đang play riêng → không bị về 0.
|
||||||
|
|
||||||
|
### [2026-08-07 04:50] Task: (1) PIANO ROLL zoom không persist phiên (2) Mở project lưu → timeline zoom out thu nhỏ góc
|
||||||
|
- **Báo cáo user:** (1) PIANO ROLL TAB không giữ kích thước zoom ở phiên làm việc trước; (2) MAIN SESSION — mở dự án đã lưu → timeline bị zoom out thu nhỏ ở góc — zoom in mới về kích thước đã lưu.
|
||||||
|
- **Nguyên nhân:**
|
||||||
|
(1) `rollZoom` (PianoRollTabEditor 6926) là local state khởi tạo 60 — mỗi lần mở tab → reset — không persist.
|
||||||
|
(2) Serialize project (9267) lưu `window.__currentZoom || 1.0` — nhưng `__currentZoom` do MEDIA EXPLORER set (11583) — không phải zoom timeline (App 13910) → project LUÔN lưu 1.0 → load `setZoom(1.0)` → timeline thu nhỏ góc trái.
|
||||||
|
- **FIX (app.jsx):**
|
||||||
|
(1) `rollZoom` khởi tạo từ `localStorage['sf_pr_zoom']` (clamp 15-250) + effect lưu khi đổi.
|
||||||
|
(2) App zoom effect set `window.__sfTimelineZoom = zoom`; serialize dùng `__sfTimelineZoom || __currentZoom || 1.0`; load (15211 + 24987): `result.zoom > 1 && setZoom` — bỏ qua 1.0 (data cũ lỗi) → giữ zoom hiện tại/localStorage.
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070450), `wiki.md`. Rebuild precompiled.
|
||||||
|
- **Ghi chú/Test:** (1) PIANO ROLL zoom in/out → đóng tab → mở lại (hoặc reload) → zoom giữ nguyên; (2) LƯU project (zoom hiện tại) → mở lại → zoom ĐÚNG kích thước đã lưu (không thu nhỏ); project CŨ (lưu 1.0) → mở → giữ zoom hiện tại (không bị thu nhỏ).
|
||||||
|
|
||||||
|
### [2026-08-07 04:55] Task: Zoom reset khi chuyển tab (trong phiên) — thêm log chẩn đoán
|
||||||
|
- **Báo cáo user:** trong phiên làm việc, chuyển tab → kích thước zoom không lưu lại — bị reset về mặc định.
|
||||||
|
- **Trạng thái:** zoom MAIN timeline là App state global (không reset khi chuyển tab); piano roll đã persist localStorage (0450). Nghi vấn: effect minZoom (18009) ép `setZoom(minZoom)` khi vào tab có maxDuration nhỏ (section ngắn) — làm mất zoom đã set. Chưa tìm thấy nơi reset về mặc định cố định.
|
||||||
|
- **FIX (app.jsx):** thêm log chẩn đoán — (1) `[Zoom] đổi tab <prev> → <new> | zoom=<...> | minZoom=<...>` (effect activeTab); (2) `[Zoom] ép lên minZoom=... (zoom=...) tab=... maxDuration=...` (effect minZoom — chỉ khi ép).
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070455), `wiki.md`. Rebuild precompiled.
|
||||||
|
- **Ghi chú/Test:** hard refresh → zoom MAIN (hoặc piano roll) → chuyển tab qua lại → dán log `[Zoom]` — xác định tab nào + giá trị zoom/minZoom khi reset.
|
||||||
|
|
||||||
|
### [2026-08-07 05:00] Task: CHỐT — Zoom bị ép khi chuyển tab (log [Zoom] xác nhận)
|
||||||
|
- **Log user (0455):** vào PIANO ROLL tab → `[Zoom] đổi tab ... → midi_... | zoom=32.78 | minZoom=37.54` + `[Zoom] ép lên minZoom=37.54 (zoom=32.78) tab=midi_... maxDuration=33.88` → về main/section zoom GIỮ 37.54 (không khôi phục 32.78/24.63) = "zoom reset khi chuyển tab".
|
||||||
|
- **Nguyên nhân:** `maxDuration` tính theo activeTab — PIANO ROLL tab (sub-tab) KHÔNG main/không session → `tab=undefined → max=0` → max = 12 bars buffer (33.88s) → `minZoom = viewportWidth/maxDuration` LỚN (37.54) → effect minZoom (18009) ép `setZoom(minZoom)` → zoom bị đẩy lên → quay lại main không khôi phục (state giữ giá trị bị ép).
|
||||||
|
- **FIX (app.jsx effect minZoom):** chỉ ép minZoom khi activeTab là `main` hoặc `session_*` — sub-tab (PIANO ROLL/audio) KHÔNG ép (timeline không hiển thị — ép vô nghĩa + hại). Zoom giữ nguyên khi vào/ra sub-tab.
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070500), `wiki.md`. Rebuild precompiled.
|
||||||
|
- **Ghi chú/Test:** hard refresh → zoom MAIN (vd 24.6) → vào SECTION-TAB → vào PIANO ROLL → KHÔNG còn log `[Zoom] ép lên minZoom` → quay lại MAIN → zoom GIỮ NGUYÊN (không reset).
|
||||||
|
|
||||||
|
### [2026-08-07 05:05] Task: Section item — giữ kích thước đã resize (không tự đổi khi lưu SECTION-TAB) + MAIN play theo duration item
|
||||||
|
- **Yêu cầu user:** (1) resize section item trên MAIN → giữ nguyên kích thước — KHÔNG tự động thay đổi khi lưu SECTION-TAB; (2) MAIN SESSION chỉ play duration của section item — mặc kệ SECTION-TAB duration dài bao nhiêu.
|
||||||
|
- **Nguyên nhân (1):** `handleSaveSectionTab` (16732) ghi đè `duration: durationSec` (tính từ content tab) → mỗi lần lưu tab, section item tự giãn/thu theo content — mất kích thước user đã resize.
|
||||||
|
- **Trạng thái (2):** ĐÃ ĐÚNG — play MAIN clamp theo `sec.duration` (19154-19156 clip / 19191-19192 midi — activeEnd = min(..., secStart + sec.duration)) + `computeMainSessionEndTime` (254) dùng `s.start + s.duration` (item, không phải content) → playhead dừng đúng theo item. Chỉ cần fix (1) để duration không bị đổi.
|
||||||
|
- **FIX (app.jsx handleSaveSectionTab):** bỏ ghi đè `duration` (giữ nguyên kích thước item đã resize) — chỉ cập nhật name + tracks; bỏ luôn code tính `durationSec`/`maxEndTime` (dead).
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070505), `wiki.md`. Rebuild precompiled.
|
||||||
|
- **Ghi chú/Test:** hard refresh → resize section item (vd 8s) → mở SECTION-TAB (content dài hơn) → Lưu section (Ctrl+S) → item trên MAIN GIỮ NGUYÊN 8s; play MAIN → chỉ nghe đúng 8s (clamp theo item — content dài hơn không phát tiếp); playhead dừng tại end item.
|
||||||
|
|
||||||
|
### [2026-08-07 05:10] Task: MAIN SESSION — playhead vẫn di chuyển khi hết item (chưa nhấn stop) — thêm log chẩn đoán
|
||||||
|
- **Báo cáo user:** MAIN SESSION — playhead vẫn di chuyển khi chưa nhấn stop; còn item → play âm; hết item → playhead vẫn chạy tiếp.
|
||||||
|
- **Phân tích:** playhead main dừng tại `projectEnd` (= computeMainSessionEndTime — end item CUỐI, theo s.start + s.duration — KHÔNG buffer). 2 khả năng: (a) hết item nhưng còn item khác dài hơn → playhead chạy qua khoảng trống (âm hết, playhead chạy) — hành vi DAW chuẩn; (b) projectEnd tính SAI (lớn hơn thực tế — item cuối không phải item thật) → playhead chạy quá.
|
||||||
|
- **FIX (app.jsx):** thêm log chẩn đoán — (1) `[Play] ... projectEnd=X.XX` (startTrackPlayback — projectEnd lúc schedule); (2) `[Stop] projectEnd reached — updatedTime=... projectEnd=...` (khi playhead tới projectEnd — xác nhận có dừng hay không).
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070510), `wiki.md`. Rebuild precompiled.
|
||||||
|
- **Ghi chú/Test:** hard refresh → play MAIN (section item / midi item) → đợi hết → dán log: `[Play] ... projectEnd=` (giá trị bao nhiêu — có khớp end item cuối không) + có `[Stop] projectEnd reached` không (dừng đúng hay chạy quá).
|
||||||
|
|
||||||
|
### [2026-08-07 05:15] Task: MAIN SESSION — playhead chạy qua khoảng im sau khi hết content section item
|
||||||
|
- **Log user (0510):** `[Stop] projectEnd reached — updatedTime=22.60 projectEnd=22.59` — playhead DỪNG ĐÚNG tại projectEnd — nhưng projectEnd = 22.59 (duration section item — resize thủ công) trong khi CONTENT thực trong tab ngắn hơn (~8s) → im lặng 8→22.59s → user thấy "hết item mà playhead vẫn di chuyển".
|
||||||
|
- **Nguyên nhân:** projectEnd (main) = computeMainSessionEndTime — section contribution = `s.start + s.duration` (duration ITEM — không phải content thực).
|
||||||
|
- **FIX (app.jsx projectEnd useMemo):** section contribution = `s.start + min(s.duration, CONTENT end từ tab)` — content ngắn hơn item → projectEnd rút về end content (dừng sớm — hết âm); content dài hơn item → giữ item duration (0505 — MAIN chỉ play duration item). Chỉ áp dụng khi section item là end cuối (max hiện tại).
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070515), `wiki.md`. Rebuild precompiled.
|
||||||
|
- **Ghi chú/Test:** hard refresh → play MAIN (section item duration 22.59s, content 8s) → playhead DỪNG tại ~8s (hết content — log `[Stop] projectEnd reached — projectEnd≈8`); content dài hơn item → dừng tại item end (cắt như 0505).
|
||||||
|
|
||||||
|
### [2026-08-07 05:20] Task: projectEnd chỉ kích hoạt khi nút LOOP bật — play loop liên tục
|
||||||
|
- **Yêu cầu user:** projectEnd chỉ được kích hoạt khi nút LOOP được bật (cho phép dự án play loop liên tục). LOOP tắt → play 1 lần tới end items (KHÔNG cắt sớm theo content — bỏ hiệu ứng 0515 khi loop tắt).
|
||||||
|
- **FIX (app.jsx):**
|
||||||
|
(1) `projectEnd` useMemo — content-min (0515: section end = sec.start + min(item duration, content end)) CHỈ áp dụng khi `isLoopingSelection` (deps thêm isLoopingSelection). LOOP TẮT → projectEnd = computeMainSessionEndTime thuần (end items — play đủ, dừng tại đó).
|
||||||
|
(2) Nút LOOP transport (25838-25848): BỎ auto-derive selection 0→end items — LOOP bật không selection → loop tại projectEnd (nhánh updatePlayhead `updatedTime >= projectEndRef` loop lại từ 0 — liên tục). User có selection riêng → giữ loop vùng chọn.
|
||||||
|
- **Hành vi:** LOOP TẮT → playhead chạy tới END ITEMS (22.59) rồi dừng (không cắt 8s content). LOOP BẬT → playhead tới projectEnd (content end ~8s) → LOOP LẠI từ 0 → play liên tục.
|
||||||
|
- **Các file ảnh hưởng:** `app/static/js/app.jsx`, `app/templates/index.html` (?v=202608070520), `wiki.md`. Rebuild precompiled.
|
||||||
|
- **Ghi chú/Test:** hard refresh → (1) LOOP TẮT → play MAIN (section 22.59s, content 8s) → play tới 22.59 mới dừng; (2) bấm LOOP → play → loop liên tục tại ~8s (log `[Stop] projectEnd reached — projectEnd≈8` + loop lại từ 0).
|
||||||
|
|||||||
Reference in New Issue
Block a user