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 {
|
||||
|
||||
@@ -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).
|
||||
- **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.
|
||||
|
||||
### [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