feat: add split at cursor functionality
Implemented track splitting at playback cursor: - Added handleSplitAtCursor function to split selected track at current time - Extracts two audio segments: before and after cursor position - Creates two new tracks from the segments with numbered names - Removes original track after split - Added 'Split at Cursor' command to command palette (keyboard shortcut: S) - Validation for edge cases (no track selected, no audio, invalid position) - User feedback via toast notifications 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -803,6 +803,66 @@ export function AudioEditor() {
|
|||||||
});
|
});
|
||||||
}, [tracks, executeCommand, updateTrack, addToast]);
|
}, [tracks, executeCommand, updateTrack, addToast]);
|
||||||
|
|
||||||
|
const handleSplitAtCursor = React.useCallback(() => {
|
||||||
|
if (!selectedTrackId) {
|
||||||
|
addToast({
|
||||||
|
title: 'No Track Selected',
|
||||||
|
description: 'Select a track to split',
|
||||||
|
variant: 'error',
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const track = tracks.find((t) => t.id === selectedTrackId);
|
||||||
|
if (!track || !track.audioBuffer) {
|
||||||
|
addToast({
|
||||||
|
title: 'No Audio',
|
||||||
|
description: 'Selected track has no audio to split',
|
||||||
|
variant: 'error',
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const splitTime = currentTime;
|
||||||
|
const duration = track.audioBuffer.duration;
|
||||||
|
|
||||||
|
// Can't split at the very beginning or end
|
||||||
|
if (splitTime <= 0 || splitTime >= duration) {
|
||||||
|
addToast({
|
||||||
|
title: 'Invalid Split Position',
|
||||||
|
description: 'Cannot split at the beginning or end of the track',
|
||||||
|
variant: 'error',
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract two segments
|
||||||
|
const firstSegment = extractBufferSegment(track.audioBuffer, 0, splitTime);
|
||||||
|
const secondSegment = extractBufferSegment(track.audioBuffer, splitTime, duration);
|
||||||
|
|
||||||
|
// Create two new tracks
|
||||||
|
const trackIndex = tracks.indexOf(track);
|
||||||
|
const firstName = `${track.name} (1)`;
|
||||||
|
const secondName = `${track.name} (2)`;
|
||||||
|
|
||||||
|
// Add the two new tracks
|
||||||
|
addTrackFromBuffer(firstSegment, firstName);
|
||||||
|
addTrackFromBuffer(secondSegment, secondName);
|
||||||
|
|
||||||
|
// Remove the original track
|
||||||
|
removeTrack(track.id);
|
||||||
|
|
||||||
|
addToast({
|
||||||
|
title: 'Track Split',
|
||||||
|
description: `Split track at ${formatDuration(splitTime)}`,
|
||||||
|
variant: 'success',
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
}, [selectedTrackId, tracks, currentTime, addTrackFromBuffer, removeTrack, addToast]);
|
||||||
|
|
||||||
// Export handler
|
// Export handler
|
||||||
const handleExport = React.useCallback(async (settings: ExportSettings) => {
|
const handleExport = React.useCallback(async (settings: ExportSettings) => {
|
||||||
if (tracks.length === 0) {
|
if (tracks.length === 0) {
|
||||||
@@ -1381,6 +1441,14 @@ export function AudioEditor() {
|
|||||||
category: 'edit',
|
category: 'edit',
|
||||||
action: handleDuplicate,
|
action: handleDuplicate,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'split-at-cursor',
|
||||||
|
label: 'Split at Cursor',
|
||||||
|
description: 'Split selected track at current playback position',
|
||||||
|
shortcut: 'S',
|
||||||
|
category: 'edit',
|
||||||
|
action: handleSplitAtCursor,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'select-all',
|
id: 'select-all',
|
||||||
label: 'Select All',
|
label: 'Select All',
|
||||||
|
|||||||
Reference in New Issue
Block a user