fix: doc-level ruler drag, min 16bars playback, selection overlay, edge/center handles
This commit is contained in:
+100
-25
@@ -5739,29 +5739,32 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rulerDragRef.current = { startX: e.clientX, startBeat: clickBeat, scrollLeft: e.currentTarget.scrollLeft };
|
const startData = { startX: e.clientX, startBeat: clickBeat, scrollLeft: e.currentTarget.scrollLeft };
|
||||||
},
|
rulerDragRef.current = startData;
|
||||||
onMouseMove: e => {
|
const onMove = (ev) => {
|
||||||
if (!rulerDragRef.current) return;
|
const r = rulerScrollRef.current;
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
if (!r || !rulerDragRef.current) return;
|
||||||
const x = e.clientX - rect.left + rulerDragRef.current.scrollLeft;
|
const rRect = r.getBoundingClientRect();
|
||||||
const beat = Math.max(0, x / pixelsPerBeat);
|
const bx = ev.clientX - rRect.left + rulerDragRef.current.scrollLeft;
|
||||||
if (Math.abs(e.clientX - rulerDragRef.current.startX) > 5) {
|
const beat = Math.max(0, bx / pixelsPerBeat);
|
||||||
const sBeat = Math.max(0, Math.min(rulerDragRef.current.startBeat, beat));
|
if (Math.abs(ev.clientX - rulerDragRef.current.startX) > 5) {
|
||||||
const eBeat = Math.max(sBeat + 1, Math.max(rulerDragRef.current.startBeat, beat));
|
const sBeat = Math.max(0, Math.min(rulerDragRef.current.startBeat, beat));
|
||||||
setLoopStartBeat(sBeat);
|
const eBeat = Math.max(sBeat + 1, Math.max(rulerDragRef.current.startBeat, beat));
|
||||||
setLoopEndBeat(eBeat);
|
setLoopStartBeat(sBeat);
|
||||||
setIsLooping(true);
|
setLoopEndBeat(eBeat);
|
||||||
setSubTabs(prev => prev.map(s => s.id === st.id ? {
|
setIsLooping(true);
|
||||||
...s,
|
setSubTabs(prev => prev.map(s => s.id === st.id ? {
|
||||||
selectionStart: sBeat * beatSec,
|
...s,
|
||||||
selectionEnd: eBeat * beatSec,
|
selectionStart: sBeat * beatSec,
|
||||||
isLooping: true
|
selectionEnd: eBeat * beatSec,
|
||||||
} : s));
|
isLooping: true
|
||||||
}
|
} : s));
|
||||||
},
|
}
|
||||||
onMouseUp: () => { rulerDragRef.current = null; },
|
};
|
||||||
onMouseLeave: () => { rulerDragRef.current = null; }
|
const onUp = () => { rulerDragRef.current = null; document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
|
||||||
|
document.addEventListener('mousemove', onMove);
|
||||||
|
document.addEventListener('mouseup', onUp);
|
||||||
|
}
|
||||||
}, /*#__PURE__*/React.createElement("div", {
|
}, /*#__PURE__*/React.createElement("div", {
|
||||||
style: {
|
style: {
|
||||||
width: `${viewWidth}px`,
|
width: `${viewWidth}px`,
|
||||||
@@ -5774,7 +5777,65 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
|||||||
width: `${(loopEndBeat - loopStartBeat) * pixelsPerBeat}px`,
|
width: `${(loopEndBeat - loopStartBeat) * pixelsPerBeat}px`,
|
||||||
top: 0, bottom: 0
|
top: 0, bottom: 0
|
||||||
},
|
},
|
||||||
className: "absolute bg-emerald-500/15 border-l border-r border-emerald-400 pointer-events-none"
|
className: "absolute bg-emerald-500/15 border-l border-r border-emerald-400"
|
||||||
|
}, /*#__PURE__*/React.createElement("div", {
|
||||||
|
style: { position: 'absolute', left: 0, top: 0, bottom: 0, width: '4px', cursor: 'ew-resize' },
|
||||||
|
onMouseDown: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const startBeat = loopStartBeat;
|
||||||
|
const onMove = (ev) => {
|
||||||
|
const r = rulerScrollRef.current;
|
||||||
|
if (!r) return;
|
||||||
|
const rRect = r.getBoundingClientRect();
|
||||||
|
const bx = ev.clientX - rRect.left + r.scrollLeft;
|
||||||
|
const nBeat = Math.max(0, Math.min(loopEndBeat - 1, Math.round(bx / pixelsPerBeat / 4) * 4));
|
||||||
|
setLoopStartBeat(nBeat);
|
||||||
|
setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, selectionStart: nBeat * beatSec } : s));
|
||||||
|
};
|
||||||
|
const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
|
||||||
|
document.addEventListener('mousemove', onMove);
|
||||||
|
document.addEventListener('mouseup', onUp);
|
||||||
|
}
|
||||||
|
}), /*#__PURE__*/React.createElement("div", {
|
||||||
|
style: { position: 'absolute', right: 0, top: 0, bottom: 0, width: '4px', cursor: 'ew-resize' },
|
||||||
|
onMouseDown: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const onMove = (ev) => {
|
||||||
|
const r = rulerScrollRef.current;
|
||||||
|
if (!r) return;
|
||||||
|
const rRect = r.getBoundingClientRect();
|
||||||
|
const bx = ev.clientX - rRect.left + r.scrollLeft;
|
||||||
|
const nBeat = Math.max(loopStartBeat + 1, Math.round(bx / pixelsPerBeat / 4) * 4);
|
||||||
|
setLoopEndBeat(nBeat);
|
||||||
|
setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, selectionEnd: nBeat * beatSec } : s));
|
||||||
|
};
|
||||||
|
const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
|
||||||
|
document.addEventListener('mousemove', onMove);
|
||||||
|
document.addEventListener('mouseup', onUp);
|
||||||
|
}
|
||||||
|
}), /*#__PURE__*/React.createElement("div", {
|
||||||
|
style: { position: 'absolute', left: '4px', right: '4px', top: 0, bottom: 0, cursor: 'grab' },
|
||||||
|
onMouseDown: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const startBeat = loopStartBeat;
|
||||||
|
const range = loopEndBeat - loopStartBeat;
|
||||||
|
const offsetBeat = startBeat + range / 2;
|
||||||
|
const onMove = (ev) => {
|
||||||
|
const r = rulerScrollRef.current;
|
||||||
|
if (!r) return;
|
||||||
|
const rRect = r.getBoundingClientRect();
|
||||||
|
const bx = ev.clientX - rRect.left + r.scrollLeft;
|
||||||
|
const centerBeat = Math.round(bx / pixelsPerBeat / 4) * 4;
|
||||||
|
const halfRange = range / 2;
|
||||||
|
const newStart = Math.max(0, centerBeat - halfRange);
|
||||||
|
setLoopStartBeat(newStart);
|
||||||
|
setLoopEndBeat(newStart + range);
|
||||||
|
setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, selectionStart: newStart * beatSec, selectionEnd: (newStart + range) * beatSec } : s));
|
||||||
|
};
|
||||||
|
const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
|
||||||
|
document.addEventListener('mousemove', onMove);
|
||||||
|
document.addEventListener('mouseup', onUp);
|
||||||
|
}
|
||||||
})))), /*#__PURE__*/React.createElement("div", {
|
})))), /*#__PURE__*/React.createElement("div", {
|
||||||
className: "flex-1 flex overflow-hidden min-h-0 relative"
|
className: "flex-1 flex overflow-hidden min-h-0 relative"
|
||||||
}, /*#__PURE__*/React.createElement("div", {
|
}, /*#__PURE__*/React.createElement("div", {
|
||||||
@@ -5803,6 +5864,13 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
|||||||
onMouseLeave: handleGridMouseUp,
|
onMouseLeave: handleGridMouseUp,
|
||||||
onContextMenu: handleContextMenu,
|
onContextMenu: handleContextMenu,
|
||||||
className: "absolute inset-0 cursor-crosshair"
|
className: "absolute inset-0 cursor-crosshair"
|
||||||
|
}), loopStartBeat !== null && loopEndBeat !== null && loopEndBeat > loopStartBeat && /*#__PURE__*/React.createElement("div", {
|
||||||
|
style: {
|
||||||
|
left: `${loopStartBeat * pixelsPerBeat}px`,
|
||||||
|
width: `${(loopEndBeat - loopStartBeat) * pixelsPerBeat}px`,
|
||||||
|
top: 0, bottom: 0
|
||||||
|
},
|
||||||
|
className: "absolute bg-emerald-500/10 border-l border-r border-emerald-400/50 pointer-events-none"
|
||||||
})))), showCC && /*#__PURE__*/React.createElement("div", {
|
})))), showCC && /*#__PURE__*/React.createElement("div", {
|
||||||
style: { height: `${ccHeight}px` },
|
style: { height: `${ccHeight}px` },
|
||||||
className: "bg-[#161616] border-t border-zinc-900 flex shrink-0 relative"
|
className: "bg-[#161616] border-t border-zinc-900 flex shrink-0 relative"
|
||||||
@@ -5835,6 +5903,13 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
|
|||||||
onMouseUp: () => { ccDragRef.current = null; },
|
onMouseUp: () => { ccDragRef.current = null; },
|
||||||
onMouseLeave: () => { ccDragRef.current = null; },
|
onMouseLeave: () => { ccDragRef.current = null; },
|
||||||
className: "absolute inset-0"
|
className: "absolute inset-0"
|
||||||
|
}), loopStartBeat !== null && loopEndBeat !== null && loopEndBeat > loopStartBeat && /*#__PURE__*/React.createElement("div", {
|
||||||
|
style: {
|
||||||
|
left: `${loopStartBeat * pixelsPerBeat}px`,
|
||||||
|
width: `${(loopEndBeat - loopStartBeat) * pixelsPerBeat}px`,
|
||||||
|
top: 0, bottom: 0
|
||||||
|
},
|
||||||
|
className: "absolute bg-emerald-500/10 border-l border-r border-emerald-400/50 pointer-events-none"
|
||||||
})))), scaleMenuPos && renderScaleContextMenu());
|
})))), scaleMenuPos && renderScaleContextMenu());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -9304,7 +9379,7 @@ const App = () => {
|
|||||||
const end = (n.start_beat || 0) + (n.duration_beats || 1);
|
const end = (n.start_beat || 0) + (n.duration_beats || 1);
|
||||||
if (end > maxEnd) maxEnd = end;
|
if (end > maxEnd) maxEnd = end;
|
||||||
});
|
});
|
||||||
return maxEnd * beatSec + 1.0;
|
return Math.max(maxEnd * beatSec, 16 * beatSec * 4) + 1.0;
|
||||||
})() : st.buffer.duration;
|
})() : st.buffer.duration;
|
||||||
if (bufferPos >= effectiveDuration) {
|
if (bufferPos >= effectiveDuration) {
|
||||||
stopAllPlayback();
|
stopAllPlayback();
|
||||||
|
|||||||
Reference in New Issue
Block a user