feat: add copy/paste automation data functionality

Implemented automation clipboard system:
- Added separate automationClipboard state for automation points
- Created handleCopyAutomation function to copy automation lane points
- Created handlePasteAutomation function to paste at current time with time offset
- Added Copy and Clipboard icon buttons to AutomationHeader component
- Automation points preserve curve type and value when copied/pasted
- Points are sorted by time after pasting
- Toast notifications for user feedback
- Ready for integration when automation lanes are actively used

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-20 07:51:24 +01:00
parent 08b33aacb5
commit 25ddac349b
2 changed files with 119 additions and 1 deletions

View File

@@ -77,6 +77,7 @@ export function AudioEditor() {
const [isMasterMuted, setIsMasterMuted] = React.useState(false);
const [masterControlsCollapsed, setMasterControlsCollapsed] = React.useState(false);
const [clipboard, setClipboard] = React.useState<AudioBuffer | null>(null);
const [automationClipboard, setAutomationClipboard] = React.useState<{ points: Array<{ time: number; value: number; curve: string }> } | null>(null);
const [recordingTrackId, setRecordingTrackId] = React.useState<string | null>(null);
const [punchInEnabled, setPunchInEnabled] = React.useState(false);
const [punchInTime, setPunchInTime] = React.useState(0);
@@ -863,6 +864,90 @@ export function AudioEditor() {
});
}, [selectedTrackId, tracks, currentTime, addTrackFromBuffer, removeTrack, addToast]);
const handleCopyAutomation = React.useCallback((trackId: string, laneId: string) => {
const track = tracks.find((t) => t.id === trackId);
if (!track) return;
const lane = track.automation.lanes.find((l) => l.id === laneId);
if (!lane || lane.points.length === 0) {
addToast({
title: 'No Automation Data',
description: 'Selected automation lane has no points',
variant: 'error',
duration: 2000,
});
return;
}
// Copy the automation points
setAutomationClipboard({
points: lane.points.map(p => ({
time: p.time,
value: p.value,
curve: p.curve,
})),
});
addToast({
title: 'Automation Copied',
description: `Copied ${lane.points.length} automation point(s)`,
variant: 'success',
duration: 2000,
});
}, [tracks, addToast]);
const handlePasteAutomation = React.useCallback((trackId: string, laneId: string) => {
if (!automationClipboard) {
addToast({
title: 'Nothing to Paste',
description: 'No automation data in clipboard',
variant: 'error',
duration: 2000,
});
return;
}
const track = tracks.find((t) => t.id === trackId);
if (!track) return;
const lane = track.automation.lanes.find((l) => l.id === laneId);
if (!lane) return;
// Generate new IDs for pasted points and shift them to current time
const timeOffset = currentTime;
const newPoints = automationClipboard.points.map((p) => ({
id: `point-${Date.now()}-${Math.random()}`,
time: p.time + timeOffset,
value: p.value,
curve: p.curve as 'linear' | 'bezier' | 'step',
}));
// Merge with existing points and sort by time
const updatedLanes = track.automation.lanes.map((l) => {
if (l.id === laneId) {
return {
...l,
points: [...l.points, ...newPoints].sort((a, b) => a.time - b.time),
};
}
return l;
});
updateTrack(trackId, {
automation: {
...track.automation,
lanes: updatedLanes,
},
});
addToast({
title: 'Automation Pasted',
description: `Pasted ${newPoints.length} automation point(s) at ${formatDuration(currentTime)}`,
variant: 'success',
duration: 2000,
});
}, [automationClipboard, tracks, currentTime, updateTrack, addToast]);
// Export handler
const handleExport = React.useCallback(async (settings: ExportSettings) => {
if (tracks.length === 0) {