'use client'; import * as React from 'react'; import { Music, Plus, Upload, Trash2 } from 'lucide-react'; import { PlaybackControls } from './PlaybackControls'; import { ThemeToggle } from '@/components/layout/ThemeToggle'; import { CommandPalette } from '@/components/ui/CommandPalette'; import { Button } from '@/components/ui/Button'; import type { CommandAction } from '@/components/ui/CommandPalette'; import { useMultiTrack } from '@/lib/hooks/useMultiTrack'; import { useMultiTrackPlayer } from '@/lib/hooks/useMultiTrackPlayer'; import { useEffectChain } from '@/lib/hooks/useEffectChain'; import { useToast } from '@/components/ui/Toast'; import { TrackList } from '@/components/tracks/TrackList'; import { ImportTrackDialog } from '@/components/tracks/ImportTrackDialog'; import { formatDuration } from '@/lib/audio/decoder'; export function AudioEditor() { const [importDialogOpen, setImportDialogOpen] = React.useState(false); const [selectedTrackId, setSelectedTrackId] = React.useState(null); const [zoom, setZoom] = React.useState(1); const [masterVolume, setMasterVolume] = React.useState(0.8); const { addToast } = useToast(); // Multi-track hooks const { tracks, addTrack, addTrackFromBuffer, removeTrack, updateTrack, clearTracks, } = useMultiTrack(); const { isPlaying, currentTime, duration, play, pause, stop, seek, togglePlayPause, } = useMultiTrackPlayer(tracks, masterVolume); // Master effect chain const { chain: masterEffectChain, presets: masterEffectPresets, toggleEffectEnabled: toggleMasterEffect, removeEffect: removeMasterEffect, reorder: reorderMasterEffects, clearChain: clearMasterChain, savePreset: saveMasterPreset, loadPresetToChain: loadMasterPreset, deletePreset: deleteMasterPreset, } = useEffectChain(); // Multi-track handlers const handleImportTracks = () => { setImportDialogOpen(true); }; const handleImportTrack = (buffer: AudioBuffer, name: string) => { addTrackFromBuffer(buffer, name); }; const handleClearTracks = () => { clearTracks(); setSelectedTrackId(null); addToast({ title: 'Tracks Cleared', description: 'All tracks have been removed', variant: 'info', duration: 2000, }); }; const handleRemoveTrack = (trackId: string) => { removeTrack(trackId); if (selectedTrackId === trackId) { setSelectedTrackId(null); } }; // Per-track effect chain handlers const handleToggleTrackEffect = (effectId: string) => { if (!selectedTrack) return; const updatedChain = { ...selectedTrack.effectChain, effects: selectedTrack.effectChain.effects.map((e) => e.id === effectId ? { ...e, enabled: !e.enabled } : e ), }; updateTrack(selectedTrack.id, { effectChain: updatedChain }); }; const handleRemoveTrackEffect = (effectId: string) => { if (!selectedTrack) return; const updatedChain = { ...selectedTrack.effectChain, effects: selectedTrack.effectChain.effects.filter((e) => e.id !== effectId), }; updateTrack(selectedTrack.id, { effectChain: updatedChain }); }; const handleReorderTrackEffects = (fromIndex: number, toIndex: number) => { if (!selectedTrack) return; const effects = [...selectedTrack.effectChain.effects]; const [removed] = effects.splice(fromIndex, 1); effects.splice(toIndex, 0, removed); const updatedChain = { ...selectedTrack.effectChain, effects, }; updateTrack(selectedTrack.id, { effectChain: updatedChain }); }; const handleClearTrackChain = () => { if (!selectedTrack) return; const updatedChain = { ...selectedTrack.effectChain, effects: [], }; updateTrack(selectedTrack.id, { effectChain: updatedChain }); }; // Zoom controls const handleZoomIn = () => { setZoom((prev) => Math.min(20, prev + 1)); }; const handleZoomOut = () => { setZoom((prev) => Math.max(1, prev - 1)); }; const handleFitToView = () => { setZoom(1); }; // Keyboard shortcuts React.useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Spacebar for play/pause - only if not interacting with form elements if (e.code === 'Space') { const target = e.target as HTMLElement; // Don't trigger if user is typing or interacting with buttons/form elements if ( target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLButtonElement || target.getAttribute('role') === 'button' ) { return; } e.preventDefault(); togglePlayPause(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [togglePlayPause]); // Find selected track const selectedTrack = tracks.find((t) => t.id === selectedTrackId); // Command palette actions const commandActions: CommandAction[] = React.useMemo(() => { const actions: CommandAction[] = [ // Playback { id: 'play', label: 'Play', description: 'Start playback', shortcut: 'Space', category: 'playback', action: play, }, { id: 'pause', label: 'Pause', description: 'Pause playback', shortcut: 'Space', category: 'playback', action: pause, }, { id: 'stop', label: 'Stop', description: 'Stop playback', category: 'playback', action: stop, }, // View { id: 'zoom-in', label: 'Zoom In', description: 'Zoom in on waveforms', category: 'view', action: handleZoomIn, }, { id: 'zoom-out', label: 'Zoom Out', description: 'Zoom out on waveforms', category: 'view', action: handleZoomOut, }, { id: 'fit-to-view', label: 'Fit to View', description: 'Reset zoom to fit all tracks', category: 'view', action: handleFitToView, }, // Tracks { id: 'add-track', label: 'Add Empty Track', description: 'Create a new empty track', category: 'tracks', action: () => addTrack(), }, { id: 'import-tracks', label: 'Import Audio Files', description: 'Import multiple audio files as tracks', category: 'tracks', action: handleImportTracks, }, { id: 'clear-tracks', label: 'Clear All Tracks', description: 'Remove all tracks', category: 'tracks', action: handleClearTracks, }, ]; return actions; }, [play, pause, stop, handleZoomIn, handleZoomOut, handleFitToView, handleImportTracks, handleClearTracks, addTrack]); // Keyboard shortcuts React.useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Prevent shortcuts if typing in an input const isTyping = e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement; // Spacebar: Play/Pause (always, unless typing in an input) if (e.code === 'Space' && !isTyping) { e.preventDefault(); togglePlayPause(); return; } if (isTyping) return; // Escape: Clear selection if (e.key === 'Escape') { e.preventDefault(); setSelectedTrackId(null); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [togglePlayPause]); return ( <> {/* Compact Header */}
{/* Left: Logo */}

Audio UI

{/* Track Actions */}
{tracks.length > 0 && ( )}
{/* Right: Command Palette + Theme Toggle */}
{/* Main content area */}
{/* Main canvas area */}
{/* Multi-Track View */}
{/* Transport Controls */}
{/* Import Track Dialog */} setImportDialogOpen(false)} onImportTrack={handleImportTrack} /> ); }