/** * Multi-track edit commands for audio operations across tracks */ import { BaseCommand } from '../command'; import type { Track } from '@/types/track'; import type { Selection } from '@/types/selection'; import { extractBufferSegment, deleteBufferSegment, insertBufferSegment, duplicateBufferSegment, } from '@/lib/audio/buffer-utils'; export type MultiTrackEditType = 'cut' | 'copy' | 'delete' | 'paste' | 'duplicate'; export interface MultiTrackEditParams { type: MultiTrackEditType; trackId: string; beforeBuffer: AudioBuffer | null; afterBuffer: AudioBuffer | null; selection?: Selection; clipboardData?: AudioBuffer; pastePosition?: number; onApply: (trackId: string, buffer: AudioBuffer | null, selection: Selection | null) => void; } /** * Command for multi-track edit operations */ export class MultiTrackEditCommand extends BaseCommand { private type: MultiTrackEditType; private trackId: string; private beforeBuffer: AudioBuffer | null; private afterBuffer: AudioBuffer | null; private selection?: Selection; private clipboardData?: AudioBuffer; private pastePosition?: number; private onApply: (trackId: string, buffer: AudioBuffer | null, selection: Selection | null) => void; constructor(params: MultiTrackEditParams) { super(); this.type = params.type; this.trackId = params.trackId; this.beforeBuffer = params.beforeBuffer; this.afterBuffer = params.afterBuffer; this.selection = params.selection; this.clipboardData = params.clipboardData; this.pastePosition = params.pastePosition; this.onApply = params.onApply; } execute(): void { // For copy, don't modify the buffer, just update selection if (this.type === 'copy') { this.onApply(this.trackId, this.beforeBuffer, this.selection || null); } else { this.onApply(this.trackId, this.afterBuffer, null); } } undo(): void { this.onApply(this.trackId, this.beforeBuffer, null); } getDescription(): string { switch (this.type) { case 'cut': return 'Cut'; case 'copy': return 'Copy'; case 'delete': return 'Delete'; case 'paste': return 'Paste'; case 'duplicate': return 'Duplicate'; default: return 'Edit'; } } } /** * Factory functions to create multi-track edit commands */ export function createMultiTrackCutCommand( trackId: string, buffer: AudioBuffer, selection: Selection, onApply: (trackId: string, buffer: AudioBuffer | null, selection: Selection | null) => void ): MultiTrackEditCommand { const afterBuffer = deleteBufferSegment(buffer, selection.start, selection.end); return new MultiTrackEditCommand({ type: 'cut', trackId, beforeBuffer: buffer, afterBuffer, selection, onApply, }); } export function createMultiTrackCopyCommand( trackId: string, buffer: AudioBuffer, selection: Selection, onApply: (trackId: string, buffer: AudioBuffer | null, selection: Selection | null) => void ): MultiTrackEditCommand { // Copy doesn't modify the buffer return new MultiTrackEditCommand({ type: 'copy', trackId, beforeBuffer: buffer, afterBuffer: buffer, selection, onApply, }); } export function createMultiTrackDeleteCommand( trackId: string, buffer: AudioBuffer, selection: Selection, onApply: (trackId: string, buffer: AudioBuffer | null, selection: Selection | null) => void ): MultiTrackEditCommand { const afterBuffer = deleteBufferSegment(buffer, selection.start, selection.end); return new MultiTrackEditCommand({ type: 'delete', trackId, beforeBuffer: buffer, afterBuffer, selection, onApply, }); } export function createMultiTrackPasteCommand( trackId: string, buffer: AudioBuffer | null, clipboardData: AudioBuffer, pastePosition: number, onApply: (trackId: string, buffer: AudioBuffer | null, selection: Selection | null) => void ): MultiTrackEditCommand { const targetBuffer = buffer || createSilentBuffer(clipboardData.sampleRate, clipboardData.numberOfChannels, pastePosition); const afterBuffer = insertBufferSegment(targetBuffer, clipboardData, pastePosition); return new MultiTrackEditCommand({ type: 'paste', trackId, beforeBuffer: buffer, afterBuffer, clipboardData, pastePosition, onApply, }); } export function createMultiTrackDuplicateCommand( trackId: string, buffer: AudioBuffer, selection: Selection, onApply: (trackId: string, buffer: AudioBuffer | null, selection: Selection | null) => void ): MultiTrackEditCommand { const afterBuffer = duplicateBufferSegment(buffer, selection.start, selection.end); return new MultiTrackEditCommand({ type: 'duplicate', trackId, beforeBuffer: buffer, afterBuffer, selection, onApply, }); } /** * Helper function to create a silent buffer */ function createSilentBuffer(sampleRate: number, numberOfChannels: number, duration: number): AudioBuffer { const audioContext = new OfflineAudioContext( numberOfChannels, Math.ceil(duration * sampleRate), sampleRate ); return audioContext.createBuffer(numberOfChannels, Math.ceil(duration * sampleRate), sampleRate); }