From 135daf6e73f97905b6412b258c902abba945e048 Mon Sep 17 00:00:00 2001 From: hashmapenjoyer Date: Thu, 16 Apr 2026 23:33:30 -0400 Subject: [PATCH] changed clip dragging to ripple edit instead of slip --- src/audio/TransportController.ts | 2 +- src/components/HelpModal.tsx | 9 +++++++++ src/components/Timeline/Timeline.tsx | 17 ++++++++++++----- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/audio/TransportController.ts b/src/audio/TransportController.ts index 8f3573a..deed3e0 100644 --- a/src/audio/TransportController.ts +++ b/src/audio/TransportController.ts @@ -76,7 +76,7 @@ export class TransportController { this.transportTimeAtPlay = this.pausedAt; this.scheduler.start(this.pausedAt, this.contextTimeAtPlay); } else { - // paused/stopped: playhead moves but no audio — notify so song title updates + // paused/stopped: playhead moves but no audio, notify so song title updates this.onSeek(this.pausedAt); } } diff --git a/src/components/HelpModal.tsx b/src/components/HelpModal.tsx index 256217e..44d7bec 100644 --- a/src/components/HelpModal.tsx +++ b/src/components/HelpModal.tsx @@ -92,6 +92,10 @@ export default function HelpModal({ onClose }: HelpModalProps) {
  • Esc - Close this help modal
  • +
  • + Alt + drag clip - Slip mode: let the clip overlap freely with the next + clip instead of pushing it along +
  • @@ -101,6 +105,11 @@ export default function HelpModal({ onClose }: HelpModalProps) {
  • Layer multiple clips to create rich soundscapes
  • Adjust timing and fades for smooth transitions
  • Use the progress bar to navigate through your project
  • +
  • + Dragging a clip on the timeline only edits the transition on its left side. Clips to + the right ripple along to preserve their existing transitions. Hold Alt{' '} + while dragging to override and let the clip overlap freely. +
  • Save your projects frequently to avoid losing work
  • diff --git a/src/components/Timeline/Timeline.tsx b/src/components/Timeline/Timeline.tsx index 21b7b4a..5800104 100644 --- a/src/components/Timeline/Timeline.tsx +++ b/src/components/Timeline/Timeline.tsx @@ -221,8 +221,14 @@ export default function Timeline({ sfxClips, libraryItems, onSfxChange }: Timeli // 2. setTransition / removeTransition — based on overlap with neighbours // // Overlap in pixels / pxPerSec = the crossfade duration in seconds. + // Ripple vs slip semantics: + // Default (ripple): only recompute the LEFT-side transition. The engine packs clips + // back-to-back, so leaving the right transition alone naturally pushes downstream + // clips along by the same delta, preserving B-C when the user only meant to edit A-B. + // Slip (Alt held): recompute both sides as a free-form move. After a reorder the old + // right neighbour isn't adjacent anymore, so we fall back to slip there too. const commitMusicDrop = useCallback( - (drag: MusicDragState) => { + (drag: MusicDragState, isSlip: boolean) => { const pps = pxPerSecRef.current; const droppedStartSec = drag.currentLeftPx / pps; const draggedEntry = timeline[drag.originalIndex]; @@ -239,8 +245,9 @@ export default function Timeline({ sfxClips, libraryItems, onSfxChange }: Timeli })); withMids.sort((a, b) => a.midSec - b.midSec); const newIndex = withMids.findIndex((o) => o.entryId === drag.entryId); + const reordered = newIndex !== drag.originalIndex; - if (newIndex !== drag.originalIndex) { + if (reordered) { engine.playlist.reorder(drag.originalIndex, newIndex); } @@ -261,7 +268,7 @@ export default function Timeline({ sfxClips, libraryItems, onSfxChange }: Timeli } } - if (rightNeighbour) { + if (rightNeighbour && (isSlip || reordered)) { const overlapSec = droppedStartSec + clipDurationSec - rightNeighbour.absoluteStart; if (overlapSec > 0.1) { engine.playlist.setTransition(dragged.entryId, rightNeighbour.entryId, overlapSec); @@ -307,10 +314,10 @@ export default function Timeline({ sfxClips, libraryItems, onSfxChange }: Timeli } }; - const onMouseUp = () => { + const onMouseUp = (e: MouseEvent) => { if (!dragRef.current) return; if (dragRef.current.kind === 'music') { - commitMusicDrop(dragRef.current); + commitMusicDrop(dragRef.current, e.altKey); setMusicDragOverride(null); } else { commitSfxDrop(dragRef.current);