feat: sync loop button with sub-tab + auto-derive loop boundaries
- Transport Loop button now syncs isLooping with active sub-tab - Auto-computes loopEnd from midi notes, section clips, or audio duration - Sub-tab selectionStart=0, selectionEnd=computed boundary on loop enable - Main timeline also auto-derives loop end from track items/clips - All three tab types: MAIN SESSION, SECTION-TAB, PIANO ROLL
This commit is contained in:
+50
-1
@@ -14709,7 +14709,56 @@ const App = () => {
|
||||
}))), /*#__PURE__*/React.createElement("div", {
|
||||
className: "w-[1px] h-5 bg-zinc-800 mx-0.5"
|
||||
}), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => setIsLoopingSelection(prev => !prev),
|
||||
onClick: () => {
|
||||
setIsLoopingSelection(prev => !prev);
|
||||
// Sync loop state with active sub-tab (piano roll / audio editor)
|
||||
const activeSub = activeTab && subTabs.find(s => s.id === activeTab && ['PIANO_ROLL', 'AUDIO_CLIP_EDITOR', 'SECTION_EDITOR'].includes(s.type));
|
||||
if (activeSub) {
|
||||
const newLoop = !activeSub.isLooping;
|
||||
setSubTabs(prev => prev.map(s => s.id === activeTab ? { ...s, isLooping: newLoop } : s));
|
||||
if (newLoop) {
|
||||
const bpmVal = parseInt(bpm) || 120;
|
||||
const beatSec = 60.0 / bpmVal;
|
||||
let maxEnd = 0;
|
||||
if (activeSub.type === 'PIANO_ROLL') {
|
||||
(activeSub.notes || []).forEach(n => {
|
||||
const end = (n.start_beat || 0) + (n.duration_beats || 1);
|
||||
if (end > maxEnd) maxEnd = end;
|
||||
});
|
||||
} else if (activeSub.type === 'SECTION_EDITOR') {
|
||||
(activeSub.clips || []).forEach(c => {
|
||||
const end = (c.startTime || 0) + (c.duration || 0);
|
||||
if (end > maxEnd) maxEnd = end;
|
||||
});
|
||||
} else if (activeSub.type === 'AUDIO_CLIP_EDITOR') {
|
||||
const dur = activeSub.buffer?.duration || 0;
|
||||
if (dur > maxEnd) maxEnd = dur;
|
||||
}
|
||||
const loopEndTime = activeSub.type === 'PIANO_ROLL' ? Math.max(maxEnd, 16) * beatSec + 1.0 : Math.max(maxEnd, 1);
|
||||
setSubTabs(prev => prev.map(s => s.id === activeTab ? { ...s, selectionStart: 0, selectionEnd: loopEndTime } : s));
|
||||
}
|
||||
} else {
|
||||
// Main timeline: auto-derive loop end from tracks
|
||||
const bpmVal = parseInt(bpm) || 120;
|
||||
const secPerBar = (60.0 / bpmVal) * 4;
|
||||
let maxEnd = 0;
|
||||
activeTracks.forEach(t => {
|
||||
(t.clips || []).forEach(c => {
|
||||
const end = (c.startTime || 0) + (c.duration || 0);
|
||||
if (end > maxEnd) maxEnd = end;
|
||||
});
|
||||
(t.items || []).forEach(it => {
|
||||
const end = (it.start || 0) + (it.duration || 4);
|
||||
if (end > maxEnd) maxEnd = end;
|
||||
});
|
||||
});
|
||||
if (maxEnd > 0) {
|
||||
const loopEnd = maxEnd + secPerBar * 2;
|
||||
setSelectionStart(0);
|
||||
setSelectionEnd(loopEnd);
|
||||
}
|
||||
}
|
||||
},
|
||||
className: `w-7 h-7 flex items-center justify-center rounded border transition ${isLoopingSelection ? 'bg-amber-600 text-black border-amber-500 hover:bg-amber-500' : 'bg-zinc-800 text-zinc-200 border-zinc-700 hover:bg-zinc-700'}`,
|
||||
title: isLoopingSelection ? (selLeft !== null && selRight !== null ? "Loop vùng chọn" : "Loop timeline") : "Bật loop"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
|
||||
Reference in New Issue
Block a user