Skip to content
Closed
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
24 changes: 17 additions & 7 deletions src/components/timeline/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ export function Timeline() {
const zoomAnchorRef = useRef<{ time: number; viewportX: number } | null>(null);
const zoomFrameTimeRef = useRef<number | null>(null);
const handledTimelineZoomRequestIdRef = useRef<number | null>(null);
const { importMultipleFiles, importLoopToTrack, importAssetToTrack, importAudioFileAsNewQuickSampler, importAssetAsQuickSampler } = useAudioImport();
const { importAudioFile, importMultipleFiles, importLoopToTrack, importAssetToTrack, importAudioFileAsNewQuickSampler, importAssetAsQuickSampler } = useAudioImport();
const isTrackListCollapsed = trackListDisplayMode === 'collapsed';

const handleDragOver = useCallback((e: React.DragEvent) => {
Expand Down Expand Up @@ -295,18 +295,23 @@ export function Timeline() {
return;
}

// Audio files -> Quick Sampler, MIDI files -> piano roll tracks
// Audio files -> sample track (Alt+Drop -> Quick Sampler), MIDI files -> piano roll tracks
const wantsQuickSampler = e.altKey;
const files = e.dataTransfer.files;
if (files.length > 0) {
for (const file of Array.from(files)) {
if (file.type.startsWith('audio/') || /\.(wav|mp3|ogg|flac|aac|m4a|webm)$/i.test(file.name)) {
await importAudioFileAsNewQuickSampler(file);
if (wantsQuickSampler) {
await importAudioFileAsNewQuickSampler(file);
} else {
Comment on lines +298 to +306
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drop behavior changed (audio drop => sample track, Alt+Drop => Quick Sampler) in both the main timeline drop zone and the empty-slot drop handler, but there doesn’t appear to be any unit test coverage for these paths. Please add a Timeline drag/drop test (similar to the existing timeline/TrackLane tests) that asserts importAudioFile is called for normal drops and importAudioFileAsNewQuickSampler is called when altKey is true.

Copilot uses AI. Check for mistakes.
await importAudioFile(file);
}
} else if (/\.(mid|midi)$/i.test(file.name)) {
await importMultipleFiles([file]);
}
}
}
}, [addTrack, importMultipleFiles, importLoopToTrack, importAssetToTrack, importAudioFileAsNewQuickSampler, importAssetAsQuickSampler]);
}, [addTrack, importAudioFile, importMultipleFiles, importLoopToTrack, importAssetToTrack, importAudioFileAsNewQuickSampler, importAssetAsQuickSampler]);

const handleTrackHeaderDragStart = useCallback((trackId: string) => {
draggedTrackIdRef.current = trackId;
Expand Down Expand Up @@ -1260,7 +1265,7 @@ function EmptyTrackRow({ slotIndex }: { slotIndex: number }) {
const addTrack = useProjectStore((s) => s.addTrack);
const virtualId = getArrangementEmptyTrackId(slotIndex);
const isSelected = selectedTrackIds.has(virtualId);
const { importLoopToTrack, importAssetToTrack, importAssetAsQuickSampler, importAudioFileAsNewQuickSampler } = useAudioImport();
const { importAudioFile, importLoopToTrack, importAssetToTrack, importAssetAsQuickSampler, importAudioFileAsNewQuickSampler } = useAudioImport();

const laneRef = useRef<HTMLDivElement>(null);
const [dropGhost, setDropGhost] = useState<{ left: number; width: number; name: string } | null>(null);
Expand Down Expand Up @@ -1360,15 +1365,20 @@ function EmptyTrackRow({ slotIndex }: { slotIndex: number }) {
return;
}

const wantsQuickSampler = e.altKey;
const files = e.dataTransfer.files;
if (files.length > 0) {
for (const file of Array.from(files)) {
if (file.type.startsWith('audio/') || /\.(wav|mp3|ogg|flac|aac|m4a|webm)$/i.test(file.name)) {
await importAudioFileAsNewQuickSampler(file);
if (wantsQuickSampler) {
await importAudioFileAsNewQuickSampler(file);
} else {
await importAudioFile(file);
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In EmptyTrackRow's drop handler, the non-Alt path calls importAudioFile(file), but importAudioFile always creates a new sample track at the end and always places the clip at startTime: 0. This ignores the computed startTime (based on the drop X position) and the slotIndex insertion semantics for an empty slot, so the resulting clip/track placement won’t match where the user dropped. Consider creating the new sample track with { order: slotIndex + 1 }, setting its display name, and then importing the file into that track at startTime (e.g., via the existing importAudioToTrack/buffer-to-track path).

Suggested change
await importAudioFile(file);
await importAudioFile(file, {
startTime,
trackOrder: slotIndex + 1,
displayName: file.name,
});

Copilot uses AI. Check for mistakes.
}
}
}
}
}, [hasProject, pixelsPerSecond, addTrack, importLoopToTrack, importAssetToTrack, importAssetAsQuickSampler, importAudioFileAsNewQuickSampler, bpm, tempoMap]);
}, [hasProject, pixelsPerSecond, addTrack, importAudioFile, importLoopToTrack, importAssetToTrack, importAssetAsQuickSampler, importAudioFileAsNewQuickSampler, bpm, tempoMap]);

return (
<div
Expand Down
Loading