Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/audio/TransportController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/components/HelpModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ export default function HelpModal({ onClose }: HelpModalProps) {
<li>
<kbd>Esc</kbd> - Close this help modal / deselect clip
</li>
<li>
<kbd>Alt</kbd> + drag clip - Slip mode: let the clip overlap freely with the next
clip instead of pushing it along
</li>
<li>
<kbd>Delete</kbd> / <kbd>Backspace</kbd> - Delete selected clip
</li>
Expand All @@ -117,6 +121,11 @@ export default function HelpModal({ onClose }: HelpModalProps) {
<li>Layer multiple clips to create rich soundscapes</li>
<li>Adjust timing and fades for smooth transitions</li>
<li>Use the progress bar to navigate through your project</li>
<li>
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 <kbd>Alt</kbd>{' '}
while dragging to override and let the clip overlap freely.
</li>
<li>Save your projects frequently to avoid losing work</li>
</ul>
</section>
Expand Down
17 changes: 12 additions & 5 deletions src/components/Timeline/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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);
}

Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading