diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index 856a2ab..17ef12b 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -5739,29 +5739,32 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot } return; } - rulerDragRef.current = { startX: e.clientX, startBeat: clickBeat, scrollLeft: e.currentTarget.scrollLeft }; - }, - onMouseMove: e => { - if (!rulerDragRef.current) return; - const rect = e.currentTarget.getBoundingClientRect(); - const x = e.clientX - rect.left + rulerDragRef.current.scrollLeft; - const beat = Math.max(0, x / pixelsPerBeat); - if (Math.abs(e.clientX - rulerDragRef.current.startX) > 5) { - const sBeat = Math.max(0, Math.min(rulerDragRef.current.startBeat, beat)); - const eBeat = Math.max(sBeat + 1, Math.max(rulerDragRef.current.startBeat, beat)); - setLoopStartBeat(sBeat); - setLoopEndBeat(eBeat); - setIsLooping(true); - setSubTabs(prev => prev.map(s => s.id === st.id ? { - ...s, - selectionStart: sBeat * beatSec, - selectionEnd: eBeat * beatSec, - isLooping: true - } : s)); - } - }, - onMouseUp: () => { rulerDragRef.current = null; }, - onMouseLeave: () => { rulerDragRef.current = null; } + const startData = { startX: e.clientX, startBeat: clickBeat, scrollLeft: e.currentTarget.scrollLeft }; + rulerDragRef.current = startData; + const onMove = (ev) => { + const r = rulerScrollRef.current; + if (!r || !rulerDragRef.current) return; + const rRect = r.getBoundingClientRect(); + const bx = ev.clientX - rRect.left + rulerDragRef.current.scrollLeft; + const beat = Math.max(0, bx / pixelsPerBeat); + if (Math.abs(ev.clientX - rulerDragRef.current.startX) > 5) { + const sBeat = Math.max(0, Math.min(rulerDragRef.current.startBeat, beat)); + const eBeat = Math.max(sBeat + 1, Math.max(rulerDragRef.current.startBeat, beat)); + setLoopStartBeat(sBeat); + setLoopEndBeat(eBeat); + setIsLooping(true); + setSubTabs(prev => prev.map(s => s.id === st.id ? { + ...s, + selectionStart: sBeat * beatSec, + selectionEnd: eBeat * beatSec, + isLooping: true + } : s)); + } + }; + 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", { style: { width: `${viewWidth}px`, @@ -5774,7 +5777,65 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot width: `${(loopEndBeat - loopStartBeat) * pixelsPerBeat}px`, 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", { className: "flex-1 flex overflow-hidden min-h-0 relative" }, /*#__PURE__*/React.createElement("div", { @@ -5803,6 +5864,13 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot onMouseLeave: handleGridMouseUp, onContextMenu: handleContextMenu, 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", { style: { height: `${ccHeight}px` }, 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; }, onMouseLeave: () => { ccDragRef.current = null; }, 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()); }; @@ -9304,7 +9379,7 @@ const App = () => { const end = (n.start_beat || 0) + (n.duration_beats || 1); if (end > maxEnd) maxEnd = end; }); - return maxEnd * beatSec + 1.0; + return Math.max(maxEnd * beatSec, 16 * beatSec * 4) + 1.0; })() : st.buffer.duration; if (bufferPos >= effectiveDuration) { stopAllPlayback();