diff --git a/app/storage/sonicforge.db b/app/storage/sonicforge.db index 8555087..0c44863 100644 Binary files a/app/storage/sonicforge.db and b/app/storage/sonicforge.db differ diff --git a/app/templates/index.html b/app/templates/index.html index 35843b5..2f00886 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -200,6 +200,8 @@ markers, selectionMode, localSelectionTrackId, + localSelectionStart, + currentTime, localSelLeft, localSelRight, onTrackLaneMouseDown, @@ -294,7 +296,7 @@ // 1. Draw Clip Layer Background & Border ctx.fillStyle = isClipSelected ? (track.color ? track.color + '44' : 'rgba(6, 182, 212, 0.30)') : (track.color ? track.color + '22' : 'rgba(6, 182, 212, 0.12)'); ctx.strokeStyle = isClipSelected ? '#fbbf24' : (track.color || '#06b6d4'); - ctx.lineWidth = isClipSelected ? 3 : 1.5; + ctx.lineWidth = isClipSelected ? 1 : 1.5; const clipTop = 8; const clipHeight = height - 16; @@ -379,7 +381,7 @@ ctx.fillStyle = 'rgba(245, 158, 11, 0.15)'; ctx.fillRect(hlLeft, 0, hlWidth, height); ctx.strokeStyle = '#f59e0b'; - ctx.lineWidth = 1.5; + ctx.lineWidth = 1; ctx.strokeRect(hlLeft, 0, hlWidth, height); } @@ -537,7 +539,7 @@ onClipDragStart(track.id, clickedClip.id, time - clickedClip.startTime, e.ctrlKey); } } else { - onPlayheadSet(time, e.shiftKey); + onPlayheadSet(time); } return; } @@ -564,7 +566,22 @@ return; } - onPlayheadSet(time, e.shiftKey); + if (e.shiftKey) { + // Shift+click on track: extend LOCAL selection from current start to clicked position + const prevStart = localSelectionStart !== null ? localSelectionStart : currentTime; + const selS = Math.min(prevStart, time); + const selE = Math.max(prevStart, time); + setSelectionMode('local'); + setLocalSelectionTrackId(track.id); + setLocalSelectionStart(selS); + setLocalSelectionEnd(selE); + setSelectionStart(selS); + setSelectionEnd(selE); + setCurrentTime(time); + e.stopPropagation(); + return; + } + onPlayheadSet(time); if (onTrackLaneMouseDown) { onTrackLaneMouseDown(track.id, time, e); } @@ -609,7 +626,7 @@ ); }; - const TempoTrackLane = ({ bpm, zoom, timelineWidth, onPlayheadSet, snapValue }) => { + const TempoTrackLane = ({ bpm, zoom, timelineWidth, onPlayheadSet, snapValue, onRulerMouseDown }) => { const canvasRef = useRef(null); useEffect(() => { @@ -705,7 +722,15 @@ const rect = canvasRef.current.getBoundingClientRect(); const x = e.clientX - rect.left + scrollLeft; const time = Math.max(0, x / zoom); - onPlayheadSet(time, e.shiftKey); + if (e.shiftKey) { + // Shift+click on tempo track: extend global selection from currentTime to clicked position + if (onRulerMouseDown) onRulerMouseDown({ ...e, shiftKey: true }); + else onPlayheadSet(time); + } else { + // Click on tempo track: set playhead and start global selection point + if (onRulerMouseDown) onRulerMouseDown(e); + else onPlayheadSet(time); + } e.stopPropagation(); }} /> @@ -4519,35 +4544,9 @@ document.addEventListener('mouseup', handleMouseUp); }; - // ── Playhead set with seek+play & shift+click selection ── + // ── Playhead set with seek+play ── const handlePlayheadSet = (time, shiftKey) => { - if (shiftKey) { - // Shift+click: extend selection from currentTime to clicked position - const selStart = Math.min(currentTime, time); - const selEnd = Math.max(currentTime, time); - setSelectionStart(selStart); - setSelectionEnd(selEnd); - setSelectionMode('global'); - clearLocalSelection(); - if (isLoopingSelection) { - // If looping, restart loop from new selection start - stopAllPlayback(); - setCurrentTime(selStart); - if (isPlaying) { - setTimeout(() => { - startOffsetTimeRef.current = selStart; - startAudioTimeRef.current = getAudioContext().currentTime; - startTrackPlayback(selStart); - setIsPlaying(true); - }, 50); - } - } else if (isPlaying) { - // Continue playing, just update the playhead visually - setCurrentTime(time); - } else { - setCurrentTime(time); - } - } else if (isPlaying) { + if (isPlaying) { // Click during playback: seek to position and continue playing setCurrentTime(time); stopAllPlayback(); @@ -4582,12 +4581,18 @@ setSelectionMode('global'); rulerDragStartRef.current = time; isDraggingRulerRef.current = true; - setSelectionStart(time); - setSelectionEnd(time); - handlePlayheadSet(time, e.shiftKey); + if (e.shiftKey) { + // Shift+click on ruler: extend global selection from currentTime to clicked position + setSelectionStart(Math.min(currentTime, time)); + setSelectionEnd(Math.max(currentTime, time)); + } else { + setSelectionStart(time); + setSelectionEnd(time); + } + handlePlayheadSet(time); }; - // Global Ruler mousemove is tracked via document listener set up in useEffect + // Global Ruler mousemove is tracked via document listener set up in useEffect useEffect(() => { const handleMouseMove = (e) => { if (!isDraggingRulerRef.current) return; @@ -6912,10 +6917,17 @@ })} + {/* Global selection overlay for main session */} + {selectionMode === 'global' && selLeft !== null && selRight !== null && selRight > selLeft && ( +
+
+
+ )}
+ onPlayheadSet={handlePlayheadSet} snapValue={snapValue} onRulerMouseDown={handleRulerMouseDown} />
@@ -6941,9 +6953,11 @@ onSplitTrackAtTime={handleSplitTrackAtTime} onEditClipInSubTab={handleEditClipInSubTab} snapValue={snapValue} bpm={bpm} - selectionMode={selectionMode} - localSelectionTrackId={localSelectionTrackId} - localSelLeft={localSelectionStart !== null && localSelectionEnd !== null ? Math.min(localSelectionStart, localSelectionEnd) : null} + selectionMode={selectionMode} + localSelectionTrackId={localSelectionTrackId} + localSelectionStart={localSelectionStart} + currentTime={currentTime} + localSelLeft={localSelectionStart !== null && localSelectionEnd !== null ? Math.min(localSelectionStart, localSelectionEnd) : null} localSelRight={localSelectionStart !== null && localSelectionEnd !== null ? Math.max(localSelectionStart, localSelectionEnd) : null} /> {track.buffer && (
@@ -6965,10 +6979,10 @@
-
handleHandleDragStart(e, 'left')}>
-
handleHandleDragStart(e, 'right')}>
+
handleHandleDragStart(e, 'left')}>
+
handleHandleDragStart(e, 'right')}>
)}