fix: equal bar widths + selection tempo/time toggle
- WaveformLane bar grid: integer beat loop (bn*beatDuration) instead of accumulating t += beatDuration — no floating point drift - Add selectionFollowsTempo toggle button (♪T/⏱T) in transport bar - ♪T (tempo): selection recalculates with BPM - ⏱T (time): selection fixed in seconds regardless of BPM
This commit is contained in:
+13
-6
@@ -457,13 +457,15 @@ const WaveformLane = ({
|
||||
const PADDING_LEFT = 0;
|
||||
const tStart = (scrollLeftVal - leadIn * zoom) / zoom - PADDING_LEFT;
|
||||
const tEnd = (scrollLeftVal + drawWidth - leadIn * zoom) / zoom + CLIP_BUFFER / zoom;
|
||||
const firstBeat = Math.floor(tStart / beatDuration) * beatDuration;
|
||||
const firstBeatNum = Math.floor(tStart / beatDuration);
|
||||
const lastBeatNum = Math.ceil(tEnd / beatDuration);
|
||||
let snapDivisor = 1;
|
||||
if (snapValue && snapValue !== 'free') {
|
||||
if (snapValue === '4') snapDivisor = 4; else if (snapValue === '1') snapDivisor = 1; else if (snapValue === '1/2') snapDivisor = 0.5; else if (snapValue === '1/4') snapDivisor = 0.25; else if (snapValue === '1/8') snapDivisor = 0.125; else if (snapValue === '1/16') snapDivisor = 0.0625; else if (snapValue === '1/32') snapDivisor = 0.03125;
|
||||
}
|
||||
for (let t = firstBeat; t <= tEnd; t += beatDuration) {
|
||||
const beatNum = Math.floor(t / beatDuration) + 1;
|
||||
for (let bn = firstBeatNum; bn <= lastBeatNum; bn++) {
|
||||
const t = bn * beatDuration;
|
||||
const beatNum = bn + 1;
|
||||
const isBar = beatNum % 4 === 1;
|
||||
const localX = (t - tStart) * zoom;
|
||||
if (localX < -CLIP_BUFFER || localX > drawWidth + CLIP_BUFFER) continue;
|
||||
@@ -6431,8 +6433,8 @@ const App = () => {
|
||||
const oldSpb = prevBpmRef.current ? (60.0 / parseFloat(prevBpmRef.current)) * 4 : null;
|
||||
const bpmVal = parseFloat(bpm) || 120;
|
||||
const secondsPerBar = (60.0 / bpmVal) * 4;
|
||||
// Recalculate range loop selection to maintain bar count
|
||||
if (oldSpb && selectionStart !== null && selectionEnd !== null && selectionEnd > selectionStart) {
|
||||
// Recalculate range loop selection to maintain bar count (tempo mode only)
|
||||
if (oldSpb && selectionFollowsTempo && selectionStart !== null && selectionEnd !== null && selectionEnd > selectionStart) {
|
||||
const startBar = selectionStart / oldSpb;
|
||||
const endBar = selectionEnd / oldSpb;
|
||||
if (endBar - startBar > 0.01) {
|
||||
@@ -6663,6 +6665,7 @@ const App = () => {
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [selectionStart, setSelectionStart] = useState(null);
|
||||
const [selectionEnd, setSelectionEnd] = useState(null);
|
||||
const [selectionFollowsTempo, setSelectionFollowsTempo] = useState(true);
|
||||
const selectionRef = useRef({ start: null, end: null });
|
||||
selectionRef.current = { start: selectionStart, end: selectionEnd };
|
||||
const [selectionMode, setSelectionMode] = useState(null); // 'global' (from ruler) | 'local' (from track)
|
||||
@@ -14894,7 +14897,11 @@ const App = () => {
|
||||
value: numberBar,
|
||||
readOnly: true,
|
||||
className: "w-14 bg-black text-zinc-400 text-[14px] px-1 py-0.5 rounded border border-zinc-800 text-center font-mono"
|
||||
}), /*#__PURE__*/React.createElement("div", {
|
||||
}), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => setSelectionFollowsTempo(prev => !prev),
|
||||
className: `px-1.5 py-0.5 text-[14px] rounded border font-bold ${selectionFollowsTempo ? 'bg-amber-700 text-white border-amber-500' : 'bg-zinc-800 text-zinc-400 border-zinc-700'}`,
|
||||
title: selectionFollowsTempo ? "Selection theo tempo (đổi BPM → selection thay đổi)" : "Selection theo thời gian (cố định)"
|
||||
}, selectionFollowsTempo ? "♪T" : "⏱T"), /*#__PURE__*/React.createElement("div", {
|
||||
className: "w-[1px] h-6 bg-zinc-800 mx-1.5"
|
||||
}), /*#__PURE__*/React.createElement("span", {
|
||||
className: "text-[14px] text-zinc-500"
|
||||
|
||||
Reference in New Issue
Block a user