Files
audio-ui/components/editor/AudioEditor.tsx

1456 lines
40 KiB
TypeScript
Raw Normal View History

feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
'use client';
import * as React from 'react';
import { Music, Loader2 } from 'lucide-react';
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
import { Waveform } from './Waveform';
import { PlaybackControls } from './PlaybackControls';
import { SidePanel } from '@/components/layout/SidePanel';
import { ThemeToggle } from '@/components/layout/ThemeToggle';
import { CommandPalette } from '@/components/ui/CommandPalette';
import type { CommandAction } from '@/components/ui/CommandPalette';
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
import { useAudioPlayer } from '@/lib/hooks/useAudioPlayer';
import { useHistory } from '@/lib/hooks/useHistory';
2025-11-17 20:27:08 +01:00
import { useEffectChain } from '@/lib/hooks/useEffectChain';
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
import { useToast } from '@/components/ui/Toast';
feat: complete Phase 3 - Advanced waveform visualization and zoom controls Phase 3 Complete Features: ✅ Drag-to-scrub audio functionality ✅ Horizontal zoom (1x-20x) with smooth scaling ✅ Vertical amplitude zoom (0.5x-5x) ✅ Horizontal scrolling for zoomed waveform ✅ Grid lines every second for time reference ✅ Viewport culling for better performance ✅ Zoom controls UI component Components Added: - ZoomControls: Complete zoom control panel with: - Horizontal zoom slider and buttons - Amplitude zoom slider - Zoom in/out buttons - Fit to view button - Real-time zoom level display Waveform Enhancements: - Drag-to-scrub: Click and drag to scrub through audio - Zoom support: View waveform at different zoom levels - Scroll support: Navigate through zoomed waveform - Grid lines: Visual time markers every second - Viewport culling: Only render visible portions - Cursor feedback: Grabbing cursor when dragging AudioEditor Updates: - Integrated zoom and scroll state management - Auto-reset zoom on file clear - Scroll slider appears when zoomed - Smooth zoom transitions Technical Improvements: - Viewport culling: Only render visible waveform portions - Grid rendering: Time-aligned vertical grid lines - Smart scroll clamping: Prevent scrolling beyond bounds - Zoom-aware seeking: Accurate time calculation with zoom - Performance optimized rendering Features Working: ✅ Drag waveform to scrub audio ✅ Zoom in up to 20x for detailed editing ✅ Adjust amplitude for better visualization ✅ Scroll through zoomed waveform ✅ Grid lines show time markers ✅ Smooth cursor interactions Phase 3 Status: 95% complete - Completed: All major features - Optional: Measure/beat markers, OffscreenCanvas, Web Workers Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:44:29 +01:00
import { Slider } from '@/components/ui/Slider';
import { cn } from '@/lib/utils/cn';
import type { Selection, ClipboardData } from '@/types/selection';
import {
extractBufferSegment,
deleteBufferSegment,
insertBufferSegment,
trimBuffer,
} from '@/lib/audio/buffer-utils';
import {
createCutCommand,
createDeleteCommand,
createPasteCommand,
createTrimCommand,
} from '@/lib/history/commands/edit-command';
import {
createGainCommand,
createNormalizePeakCommand,
createNormalizeRMSCommand,
createFadeInCommand,
createFadeOutCommand,
createReverseCommand,
createLowPassFilterCommand,
createHighPassFilterCommand,
createBandPassFilterCommand,
EffectCommand,
} from '@/lib/history/commands/effect-command';
import { applyEffectToSelection, applyAsyncEffectToSelection } from '@/lib/audio/effects/selection';
import { normalizePeak } from '@/lib/audio/effects/normalize';
import { applyFadeIn, applyFadeOut } from '@/lib/audio/effects/fade';
import { reverseAudio } from '@/lib/audio/effects/reverse';
import { applyLowPassFilter, applyHighPassFilter, applyBandPassFilter, applyFilter } from '@/lib/audio/effects/filters';
import type { FilterType } from '@/lib/audio/effects/filters';
import { applyCompressor, applyLimiter, applyGate } from '@/lib/audio/effects/dynamics';
import { applyDelay, applyReverb, applyChorus, applyFlanger, applyPhaser } from '@/lib/audio/effects/time-based';
import { applyPitchShift, applyTimeStretch, applyDistortion, applyBitcrusher } from '@/lib/audio/effects/advanced';
import { EffectParameterDialog, type FilterParameters } from '@/components/effects/EffectParameterDialog';
import { DynamicsParameterDialog, type DynamicsParameters, type DynamicsType } from '@/components/effects/DynamicsParameterDialog';
import { TimeBasedParameterDialog, type TimeBasedParameters, type TimeBasedType } from '@/components/effects/TimeBasedParameterDialog';
import { AdvancedParameterDialog, type AdvancedParameters, type AdvancedType } from '@/components/effects/AdvancedParameterDialog';
const EFFECT_LABELS: Record<string, string> = {
lowpass: 'Low-Pass Filter',
highpass: 'High-Pass Filter',
bandpass: 'Band-Pass Filter',
notch: 'Notch Filter',
lowshelf: 'Low Shelf Filter',
highshelf: 'High Shelf Filter',
peaking: 'Peaking EQ',
compressor: 'Compressor',
limiter: 'Limiter',
gate: 'Gate/Expander',
delay: 'Delay/Echo',
reverb: 'Reverb',
chorus: 'Chorus',
flanger: 'Flanger',
phaser: 'Phaser',
pitch: 'Pitch Shifter',
timestretch: 'Time Stretch',
distortion: 'Distortion',
bitcrusher: 'Bitcrusher',
};
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
export function AudioEditor() {
feat: complete Phase 3 - Advanced waveform visualization and zoom controls Phase 3 Complete Features: ✅ Drag-to-scrub audio functionality ✅ Horizontal zoom (1x-20x) with smooth scaling ✅ Vertical amplitude zoom (0.5x-5x) ✅ Horizontal scrolling for zoomed waveform ✅ Grid lines every second for time reference ✅ Viewport culling for better performance ✅ Zoom controls UI component Components Added: - ZoomControls: Complete zoom control panel with: - Horizontal zoom slider and buttons - Amplitude zoom slider - Zoom in/out buttons - Fit to view button - Real-time zoom level display Waveform Enhancements: - Drag-to-scrub: Click and drag to scrub through audio - Zoom support: View waveform at different zoom levels - Scroll support: Navigate through zoomed waveform - Grid lines: Visual time markers every second - Viewport culling: Only render visible portions - Cursor feedback: Grabbing cursor when dragging AudioEditor Updates: - Integrated zoom and scroll state management - Auto-reset zoom on file clear - Scroll slider appears when zoomed - Smooth zoom transitions Technical Improvements: - Viewport culling: Only render visible waveform portions - Grid rendering: Time-aligned vertical grid lines - Smart scroll clamping: Prevent scrolling beyond bounds - Zoom-aware seeking: Accurate time calculation with zoom - Performance optimized rendering Features Working: ✅ Drag waveform to scrub audio ✅ Zoom in up to 20x for detailed editing ✅ Adjust amplitude for better visualization ✅ Scroll through zoomed waveform ✅ Grid lines show time markers ✅ Smooth cursor interactions Phase 3 Status: 95% complete - Completed: All major features - Optional: Measure/beat markers, OffscreenCanvas, Web Workers Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:44:29 +01:00
// Zoom and scroll state
const [zoom, setZoom] = React.useState(1);
const [scrollOffset, setScrollOffset] = React.useState(0);
const [amplitudeScale, setAmplitudeScale] = React.useState(1);
// Effect dialog state
const [effectDialogOpen, setEffectDialogOpen] = React.useState(false);
const [effectDialogType, setEffectDialogType] = React.useState<'lowpass' | 'highpass' | 'bandpass' | 'notch' | 'lowshelf' | 'highshelf' | 'peaking'>('lowpass');
// Dynamics dialog state
const [dynamicsDialogOpen, setDynamicsDialogOpen] = React.useState(false);
const [dynamicsDialogType, setDynamicsDialogType] = React.useState<DynamicsType>('compressor');
// Time-based dialog state
const [timeBasedDialogOpen, setTimeBasedDialogOpen] = React.useState(false);
const [timeBasedDialogType, setTimeBasedDialogType] = React.useState<TimeBasedType>('delay');
// Advanced dialog state
const [advancedDialogOpen, setAdvancedDialogOpen] = React.useState(false);
const [advancedDialogType, setAdvancedDialogType] = React.useState<AdvancedType>('pitch');
// Drag and drop state
const [isDragging, setIsDragging] = React.useState(false);
const fileInputRef = React.useRef<HTMLInputElement>(null);
// Selection state
const [selection, setSelection] = React.useState<Selection | null>(null);
const [clipboard, setClipboard] = React.useState<ClipboardData | null>(null);
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
const {
loadFile,
loadBuffer,
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
clearFile,
play,
pause,
stop,
seek,
setVolume,
isPlaying,
isPaused,
currentTime,
duration,
volume,
audioBuffer,
fileName,
isLoading,
error,
currentTimeFormatted,
durationFormatted,
} = useAudioPlayer();
const { execute, undo, redo, clear: clearHistory, state: historyState } = useHistory(50);
2025-11-17 20:27:08 +01:00
const {
chain: effectChain,
presets: effectPresets,
toggleEffectEnabled,
removeEffect,
reorder: reorderEffects,
clearChain,
savePreset,
loadPresetToChain,
deletePreset,
} = useEffectChain();
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
const { addToast } = useToast();
const handleFileSelect = async (file: File) => {
try {
await loadFile(file);
addToast({
title: 'File loaded',
description: `Successfully loaded ${file.name}`,
variant: 'success',
duration: 3000,
});
} catch (err) {
addToast({
title: 'Error loading file',
description: err instanceof Error ? err.message : 'Unknown error',
variant: 'error',
duration: 5000,
});
}
};
const handleClear = () => {
clearFile();
feat: complete Phase 3 - Advanced waveform visualization and zoom controls Phase 3 Complete Features: ✅ Drag-to-scrub audio functionality ✅ Horizontal zoom (1x-20x) with smooth scaling ✅ Vertical amplitude zoom (0.5x-5x) ✅ Horizontal scrolling for zoomed waveform ✅ Grid lines every second for time reference ✅ Viewport culling for better performance ✅ Zoom controls UI component Components Added: - ZoomControls: Complete zoom control panel with: - Horizontal zoom slider and buttons - Amplitude zoom slider - Zoom in/out buttons - Fit to view button - Real-time zoom level display Waveform Enhancements: - Drag-to-scrub: Click and drag to scrub through audio - Zoom support: View waveform at different zoom levels - Scroll support: Navigate through zoomed waveform - Grid lines: Visual time markers every second - Viewport culling: Only render visible portions - Cursor feedback: Grabbing cursor when dragging AudioEditor Updates: - Integrated zoom and scroll state management - Auto-reset zoom on file clear - Scroll slider appears when zoomed - Smooth zoom transitions Technical Improvements: - Viewport culling: Only render visible waveform portions - Grid rendering: Time-aligned vertical grid lines - Smart scroll clamping: Prevent scrolling beyond bounds - Zoom-aware seeking: Accurate time calculation with zoom - Performance optimized rendering Features Working: ✅ Drag waveform to scrub audio ✅ Zoom in up to 20x for detailed editing ✅ Adjust amplitude for better visualization ✅ Scroll through zoomed waveform ✅ Grid lines show time markers ✅ Smooth cursor interactions Phase 3 Status: 95% complete - Completed: All major features - Optional: Measure/beat markers, OffscreenCanvas, Web Workers Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:44:29 +01:00
setZoom(1);
setScrollOffset(0);
setAmplitudeScale(1);
setSelection(null);
setClipboard(null);
clearHistory();
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
addToast({
title: 'Audio cleared',
description: 'Audio file has been removed',
variant: 'info',
duration: 2000,
});
};
// Drag and drop handlers
const handleDragEnter = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
};
const handleDragLeave = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
// Only set to false if we're leaving the drop zone entirely
if (e.currentTarget === e.target) {
setIsDragging(false);
}
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
};
const handleDrop = async (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
const files = Array.from(e.dataTransfer.files);
const audioFile = files.find(file => file.type.startsWith('audio/'));
if (audioFile) {
await handleFileSelect(audioFile);
} else {
addToast({
title: 'Invalid file',
description: 'Please drop an audio file',
variant: 'error',
duration: 3000,
});
}
};
const handleDropZoneClick = () => {
fileInputRef.current?.click();
};
const handleFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
handleFileSelect(file);
}
};
// Edit operations
const handleCut = () => {
if (!selection || !audioBuffer) return;
try {
// Copy to clipboard
const clipData = extractBufferSegment(audioBuffer, selection.start, selection.end);
setClipboard({
buffer: clipData,
start: selection.start,
end: selection.end,
duration: selection.end - selection.start,
});
// Create and execute cut command
const command = createCutCommand(audioBuffer, selection, (buffer) => {
loadBuffer(buffer);
});
execute(command);
setSelection(null);
addToast({
title: 'Cut',
description: 'Selection cut to clipboard',
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to cut selection',
variant: 'error',
duration: 3000,
});
}
};
const handleCopy = () => {
if (!selection || !audioBuffer) return;
try {
const clipData = extractBufferSegment(audioBuffer, selection.start, selection.end);
setClipboard({
buffer: clipData,
start: selection.start,
end: selection.end,
duration: selection.end - selection.start,
});
addToast({
title: 'Copied',
description: 'Selection copied to clipboard',
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to copy selection',
variant: 'error',
duration: 3000,
});
}
};
const handlePaste = () => {
if (!clipboard || !audioBuffer) return;
try {
const insertTime = currentTime;
// Create and execute paste command
const command = createPasteCommand(audioBuffer, clipboard.buffer, insertTime, (buffer) => {
loadBuffer(buffer);
});
execute(command);
addToast({
title: 'Pasted',
description: 'Clipboard pasted at current position',
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to paste clipboard',
variant: 'error',
duration: 3000,
});
}
};
const handleDelete = () => {
if (!selection || !audioBuffer) return;
try {
// Create and execute delete command
const command = createDeleteCommand(audioBuffer, selection, (buffer) => {
loadBuffer(buffer);
});
execute(command);
setSelection(null);
addToast({
title: 'Deleted',
description: 'Selection deleted',
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to delete selection',
variant: 'error',
duration: 3000,
});
}
};
const handleTrim = () => {
if (!selection || !audioBuffer) return;
try {
// Create and execute trim command
const command = createTrimCommand(audioBuffer, selection, (buffer) => {
loadBuffer(buffer);
});
execute(command);
setSelection(null);
addToast({
title: 'Trimmed',
description: 'Audio trimmed to selection',
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to trim audio',
variant: 'error',
duration: 3000,
});
}
};
const handleSelectAll = () => {
if (!audioBuffer) return;
setSelection({ start: 0, end: duration });
};
const handleClearSelection = () => {
setSelection(null);
};
// Effect operations
const handleNormalize = () => {
if (!audioBuffer) return;
try {
// Apply to selection or entire buffer
const modifiedBuffer = applyEffectToSelection(
audioBuffer,
selection,
(buf) => normalizePeak(buf, 1.0)
);
const command = new EffectCommand(
audioBuffer,
modifiedBuffer,
(buffer) => loadBuffer(buffer),
selection ? 'Normalize Selection' : 'Normalize'
);
execute(command);
addToast({
title: 'Normalized',
description: selection ? 'Selection normalized to peak' : 'Audio normalized to peak',
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to normalize audio',
variant: 'error',
duration: 3000,
});
}
};
const handleFadeIn = () => {
if (!audioBuffer) return;
if (!selection) {
addToast({
title: 'No Selection',
description: 'Please select a region to apply fade in',
variant: 'info',
duration: 2000,
});
return;
}
try {
const fadeDuration = selection.end - selection.start;
const modifiedBuffer = applyEffectToSelection(
audioBuffer,
selection,
(buf) => applyFadeIn(buf, buf.duration)
);
const command = new EffectCommand(
audioBuffer,
modifiedBuffer,
(buffer) => loadBuffer(buffer),
`Fade In (${fadeDuration.toFixed(2)}s)`
);
execute(command);
addToast({
title: 'Fade In',
description: `Applied fade in (${fadeDuration.toFixed(2)}s)`,
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to apply fade in',
variant: 'error',
duration: 3000,
});
}
};
const handleFadeOut = () => {
if (!audioBuffer) return;
if (!selection) {
addToast({
title: 'No Selection',
description: 'Please select a region to apply fade out',
variant: 'info',
duration: 2000,
});
return;
}
try {
const fadeDuration = selection.end - selection.start;
const modifiedBuffer = applyEffectToSelection(
audioBuffer,
selection,
(buf) => applyFadeOut(buf, buf.duration)
);
const command = new EffectCommand(
audioBuffer,
modifiedBuffer,
(buffer) => loadBuffer(buffer),
`Fade Out (${fadeDuration.toFixed(2)}s)`
);
execute(command);
addToast({
title: 'Fade Out',
description: `Applied fade out (${fadeDuration.toFixed(2)}s)`,
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to apply fade out',
variant: 'error',
duration: 3000,
});
}
};
const handleReverse = () => {
if (!audioBuffer) return;
try {
const modifiedBuffer = applyEffectToSelection(
audioBuffer,
selection,
(buf) => reverseAudio(buf)
);
const command = new EffectCommand(
audioBuffer,
modifiedBuffer,
(buffer) => loadBuffer(buffer),
selection ? 'Reverse Selection' : 'Reverse'
);
execute(command);
addToast({
title: 'Reversed',
description: selection ? 'Selection reversed' : 'Audio reversed',
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to reverse audio',
variant: 'error',
duration: 3000,
});
}
};
const handleLowPassFilter = () => {
setEffectDialogType('lowpass');
setEffectDialogOpen(true);
};
const handleHighPassFilter = () => {
setEffectDialogType('highpass');
setEffectDialogOpen(true);
};
const handleBandPassFilter = () => {
setEffectDialogType('bandpass');
setEffectDialogOpen(true);
};
const handleCompressor = () => {
setDynamicsDialogType('compressor');
setDynamicsDialogOpen(true);
};
const handleLimiter = () => {
setDynamicsDialogType('limiter');
setDynamicsDialogOpen(true);
};
const handleGate = () => {
setDynamicsDialogType('gate');
setDynamicsDialogOpen(true);
};
const handleDelay = () => {
setTimeBasedDialogType('delay');
setTimeBasedDialogOpen(true);
};
const handleReverb = () => {
setTimeBasedDialogType('reverb');
setTimeBasedDialogOpen(true);
};
const handleChorus = () => {
setTimeBasedDialogType('chorus');
setTimeBasedDialogOpen(true);
};
const handleFlanger = () => {
setTimeBasedDialogType('flanger');
setTimeBasedDialogOpen(true);
};
const handlePhaser = () => {
setTimeBasedDialogType('phaser');
setTimeBasedDialogOpen(true);
};
const handlePitchShift = () => {
setAdvancedDialogType('pitch');
setAdvancedDialogOpen(true);
};
const handleTimeStretch = () => {
setAdvancedDialogType('timestretch');
setAdvancedDialogOpen(true);
};
const handleDistortion = () => {
setAdvancedDialogType('distortion');
setAdvancedDialogOpen(true);
};
const handleBitcrusher = () => {
setAdvancedDialogType('bitcrusher');
setAdvancedDialogOpen(true);
};
// Handle effect apply from parameter dialog
const handleEffectApply = async (params: FilterParameters) => {
if (!audioBuffer) return;
try {
const modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyFilter(buf, {
type: params.type,
frequency: params.frequency,
Q: params.Q,
gain: params.gain,
})
);
const effectName = EFFECT_LABELS[params.type] || 'Filter';
const command = new EffectCommand(
audioBuffer,
modifiedBuffer,
(buffer) => loadBuffer(buffer),
selection
? `${effectName} Selection (${params.frequency.toFixed(0)}Hz)`
: `${effectName} (${params.frequency.toFixed(0)}Hz)`
);
execute(command);
addToast({
title: effectName,
description: selection
? `Applied ${effectName.toLowerCase()} to selection (${params.frequency.toFixed(0)}Hz)`
: `Applied ${effectName.toLowerCase()} (${params.frequency.toFixed(0)}Hz)`,
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to apply effect',
variant: 'error',
duration: 3000,
});
}
};
// Handle dynamics apply from parameter dialog
const handleDynamicsApply = async (params: DynamicsParameters) => {
if (!audioBuffer) return;
try {
let modifiedBuffer: AudioBuffer;
let effectName: string;
switch (params.type) {
case 'compressor':
modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyCompressor(buf, params)
);
effectName = 'Compressor';
break;
case 'limiter':
modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyLimiter(buf, params)
);
effectName = 'Limiter';
break;
case 'gate':
modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyGate(buf, params)
);
effectName = 'Gate';
break;
}
const command = new EffectCommand(
audioBuffer,
modifiedBuffer,
(buffer) => loadBuffer(buffer),
selection
? `${effectName} Selection (${params.threshold.toFixed(1)}dB)`
: `${effectName} (${params.threshold.toFixed(1)}dB)`
);
execute(command);
addToast({
title: effectName,
description: selection
? `Applied ${effectName.toLowerCase()} to selection`
: `Applied ${effectName.toLowerCase()}`,
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to apply dynamics effect',
variant: 'error',
duration: 3000,
});
}
};
// Handle time-based apply from parameter dialog
const handleTimeBasedApply = async (params: TimeBasedParameters) => {
if (!audioBuffer) return;
try {
let modifiedBuffer: AudioBuffer;
let effectName: string;
switch (params.type) {
case 'delay':
modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyDelay(buf, params)
);
effectName = 'Delay';
break;
case 'reverb':
modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyReverb(buf, params)
);
effectName = 'Reverb';
break;
case 'chorus':
modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyChorus(buf, params)
);
effectName = 'Chorus';
break;
case 'flanger':
modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyFlanger(buf, params)
);
effectName = 'Flanger';
break;
case 'phaser':
modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyPhaser(buf, params)
);
effectName = 'Phaser';
break;
}
const command = new EffectCommand(
audioBuffer,
modifiedBuffer,
(buffer) => loadBuffer(buffer),
selection
? `${effectName} Selection`
: `${effectName}`
);
execute(command);
addToast({
title: effectName,
description: selection
? `Applied ${effectName.toLowerCase()} to selection`
: `Applied ${effectName.toLowerCase()}`,
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to apply time-based effect',
variant: 'error',
duration: 3000,
});
}
};
const handleAdvancedApply = async (params: AdvancedParameters) => {
if (!audioBuffer) return;
try {
let modifiedBuffer: AudioBuffer;
let effectName: string;
const effectType = params.type;
if (effectType === 'pitch') {
modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyPitchShift(buf, params as any)
);
effectName = 'Pitch Shift';
} else if (effectType === 'timestretch') {
modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyTimeStretch(buf, params as any)
);
effectName = 'Time Stretch';
} else if (effectType === 'bitcrusher') {
modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyBitcrusher(buf, params as any)
);
effectName = 'Bitcrusher';
} else {
modifiedBuffer = await applyAsyncEffectToSelection(
audioBuffer,
selection,
(buf) => applyDistortion(buf, params as any)
);
effectName = 'Distortion';
}
const command = new EffectCommand(
audioBuffer,
modifiedBuffer,
(buffer) => loadBuffer(buffer),
selection
? `${effectName} Selection`
: `${effectName}`
);
execute(command);
addToast({
title: effectName,
description: selection
? `Applied ${effectName.toLowerCase()} to selection`
: `Applied ${effectName.toLowerCase()}`,
variant: 'success',
duration: 2000,
});
} catch (error) {
addToast({
title: 'Error',
description: 'Failed to apply advanced effect',
variant: 'error',
duration: 3000,
});
}
};
feat: complete Phase 3 - Advanced waveform visualization and zoom controls Phase 3 Complete Features: ✅ Drag-to-scrub audio functionality ✅ Horizontal zoom (1x-20x) with smooth scaling ✅ Vertical amplitude zoom (0.5x-5x) ✅ Horizontal scrolling for zoomed waveform ✅ Grid lines every second for time reference ✅ Viewport culling for better performance ✅ Zoom controls UI component Components Added: - ZoomControls: Complete zoom control panel with: - Horizontal zoom slider and buttons - Amplitude zoom slider - Zoom in/out buttons - Fit to view button - Real-time zoom level display Waveform Enhancements: - Drag-to-scrub: Click and drag to scrub through audio - Zoom support: View waveform at different zoom levels - Scroll support: Navigate through zoomed waveform - Grid lines: Visual time markers every second - Viewport culling: Only render visible portions - Cursor feedback: Grabbing cursor when dragging AudioEditor Updates: - Integrated zoom and scroll state management - Auto-reset zoom on file clear - Scroll slider appears when zoomed - Smooth zoom transitions Technical Improvements: - Viewport culling: Only render visible waveform portions - Grid rendering: Time-aligned vertical grid lines - Smart scroll clamping: Prevent scrolling beyond bounds - Zoom-aware seeking: Accurate time calculation with zoom - Performance optimized rendering Features Working: ✅ Drag waveform to scrub audio ✅ Zoom in up to 20x for detailed editing ✅ Adjust amplitude for better visualization ✅ Scroll through zoomed waveform ✅ Grid lines show time markers ✅ Smooth cursor interactions Phase 3 Status: 95% complete - Completed: All major features - Optional: Measure/beat markers, OffscreenCanvas, Web Workers Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:44:29 +01:00
// Zoom controls
const handleZoomIn = () => {
setZoom((prev) => Math.min(20, prev + 1));
};
const handleZoomOut = () => {
setZoom((prev) => Math.max(1, prev - 1));
};
const handleFitToView = () => {
setZoom(1);
setScrollOffset(0);
};
// Auto-adjust scroll when zoom changes
React.useEffect(() => {
if (!audioBuffer) return;
// Reset scroll if zoomed out completely
if (zoom === 1) {
setScrollOffset(0);
}
}, [zoom, audioBuffer]);
// 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();
if (isPlaying) {
pause();
} else {
play();
}
return;
}
// Prevent other shortcuts if typing in an input
if (isTyping) {
return;
}
// Ctrl+Z: Undo
if (e.ctrlKey && !e.shiftKey && e.key === 'z') {
e.preventDefault();
if (undo()) {
addToast({
title: 'Undo',
description: 'Last action undone',
variant: 'info',
duration: 1500,
});
}
}
// Ctrl+Y or Ctrl+Shift+Z: Redo
if ((e.ctrlKey && e.key === 'y') || (e.ctrlKey && e.shiftKey && e.key === 'z')) {
e.preventDefault();
if (redo()) {
addToast({
title: 'Redo',
description: 'Last action redone',
variant: 'info',
duration: 1500,
});
}
}
// Ctrl+A: Select all
if (e.ctrlKey && e.key === 'a') {
e.preventDefault();
handleSelectAll();
}
// Ctrl+X: Cut
if (e.ctrlKey && e.key === 'x') {
e.preventDefault();
handleCut();
}
// Ctrl+C: Copy
if (e.ctrlKey && e.key === 'c') {
e.preventDefault();
handleCopy();
}
// Ctrl+V: Paste
if (e.ctrlKey && e.key === 'v') {
e.preventDefault();
handlePaste();
}
// Delete: Delete selection
if (e.key === 'Delete') {
e.preventDefault();
handleDelete();
}
// Escape: Clear selection
if (e.key === 'Escape') {
e.preventDefault();
handleClearSelection();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [selection, clipboard, audioBuffer, currentTime, undo, redo, addToast, isPlaying, play, pause]);
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
// Show error toast
React.useEffect(() => {
if (error) {
addToast({
title: 'Error',
description: error,
variant: 'error',
duration: 5000,
});
}
}, [error, addToast]);
// 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,
},
// Edit
{
id: 'cut',
label: 'Cut',
description: 'Cut selection to clipboard',
shortcut: 'Ctrl+X',
category: 'edit',
action: handleCut,
},
{
id: 'copy',
label: 'Copy',
description: 'Copy selection to clipboard',
shortcut: 'Ctrl+C',
category: 'edit',
action: handleCopy,
},
{
id: 'paste',
label: 'Paste',
description: 'Paste clipboard at current position',
shortcut: 'Ctrl+V',
category: 'edit',
action: handlePaste,
},
{
id: 'delete',
label: 'Delete',
description: 'Delete selection',
shortcut: 'Del',
category: 'edit',
action: handleDelete,
},
{
id: 'trim',
label: 'Trim to Selection',
description: 'Trim audio to selected region',
category: 'edit',
action: handleTrim,
},
{
id: 'select-all',
label: 'Select All',
description: 'Select entire audio',
shortcut: 'Ctrl+A',
category: 'edit',
action: handleSelectAll,
},
{
id: 'clear-selection',
label: 'Clear Selection',
description: 'Clear current selection',
shortcut: 'Esc',
category: 'edit',
action: handleClearSelection,
},
// View
{
id: 'zoom-in',
label: 'Zoom In',
description: 'Zoom in on waveform',
category: 'view',
action: handleZoomIn,
},
{
id: 'zoom-out',
label: 'Zoom Out',
description: 'Zoom out on waveform',
category: 'view',
action: handleZoomOut,
},
{
id: 'fit-to-view',
label: 'Fit to View',
description: 'Reset zoom to fit entire waveform',
category: 'view',
action: handleFitToView,
},
// File
{
id: 'clear',
label: 'Clear Audio',
description: 'Remove loaded audio file',
category: 'file',
action: handleClear,
},
// History
{
id: 'undo',
label: 'Undo',
description: 'Undo last action',
shortcut: 'Ctrl+Z',
category: 'edit',
action: undo,
},
{
id: 'redo',
label: 'Redo',
description: 'Redo last undone action',
shortcut: 'Ctrl+Y',
category: 'edit',
action: redo,
},
// Effects
{
id: 'normalize',
label: 'Normalize',
description: 'Normalize audio to peak amplitude',
category: 'effects',
action: handleNormalize,
},
{
id: 'fade-in',
label: 'Fade In',
description: 'Apply fade in to selection',
category: 'effects',
action: handleFadeIn,
},
{
id: 'fade-out',
label: 'Fade Out',
description: 'Apply fade out to selection',
category: 'effects',
action: handleFadeOut,
},
{
id: 'reverse',
label: 'Reverse',
description: 'Reverse entire audio',
category: 'effects',
action: handleReverse,
},
{
id: 'lowpass-filter',
label: 'Low-Pass Filter',
description: 'Remove high frequencies (1000Hz cutoff)',
category: 'effects',
action: handleLowPassFilter,
},
{
id: 'highpass-filter',
label: 'High-Pass Filter',
description: 'Remove low frequencies (100Hz cutoff)',
category: 'effects',
action: handleHighPassFilter,
},
{
id: 'bandpass-filter',
label: 'Band-Pass Filter',
description: 'Isolate frequency range (1000Hz center)',
category: 'effects',
action: handleBandPassFilter,
},
{
id: 'compressor',
label: 'Compressor',
description: 'Reduce dynamic range',
category: 'effects',
action: handleCompressor,
},
{
id: 'limiter',
label: 'Limiter',
description: 'Prevent audio from exceeding threshold',
category: 'effects',
action: handleLimiter,
},
{
id: 'gate',
label: 'Gate/Expander',
description: 'Reduce volume of quiet sounds',
category: 'effects',
action: handleGate,
},
{
id: 'delay',
label: 'Delay/Echo',
description: 'Add echo effects with feedback',
category: 'effects',
action: handleDelay,
},
{
id: 'reverb',
label: 'Reverb',
description: 'Add acoustic space and ambience',
category: 'effects',
action: handleReverb,
},
{
id: 'chorus',
label: 'Chorus',
description: 'Thicken sound with modulation',
category: 'effects',
action: handleChorus,
},
{
id: 'flanger',
label: 'Flanger',
description: 'Create sweeping comb-filter effect',
category: 'effects',
action: handleFlanger,
},
{
id: 'phaser',
label: 'Phaser',
description: 'Phase-shifting swoosh effect',
category: 'effects',
action: handlePhaser,
},
{
id: 'pitch',
label: 'Pitch Shifter',
description: 'Change pitch without affecting duration',
category: 'effects',
action: handlePitchShift,
},
{
id: 'timestretch',
label: 'Time Stretch',
description: 'Change duration without affecting pitch',
category: 'effects',
action: handleTimeStretch,
},
{
id: 'distortion',
label: 'Distortion',
description: 'Add overdrive and distortion',
category: 'effects',
action: handleDistortion,
},
{
id: 'bitcrusher',
label: 'Bitcrusher',
description: 'Lo-fi bit depth and sample rate reduction',
category: 'effects',
action: handleBitcrusher,
},
];
return actions;
}, [play, pause, stop, handleCut, handleCopy, handlePaste, handleDelete, handleTrim, handleSelectAll, handleClearSelection, handleZoomIn, handleZoomOut, handleFitToView, handleClear, undo, redo, handleNormalize, handleFadeIn, handleFadeOut, handleReverse, handleLowPassFilter, handleHighPassFilter, handleBandPassFilter, handleCompressor, handleLimiter, handleGate, handleDelay, handleReverb, handleChorus, handleFlanger, handlePhaser, handlePitchShift, handleTimeStretch, handleDistortion, handleBitcrusher]);
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
return (
<>
{/* Compact Header */}
<header className="flex items-center justify-between px-4 py-2 border-b border-border bg-card flex-shrink-0 gap-4">
{/* Left: Logo */}
<div className="flex items-center gap-2 flex-shrink-0">
<Music className="h-5 w-5 text-primary" />
<h1 className="text-lg font-bold text-foreground">Audio UI</h1>
</div>
{/* Right: Command Palette + Theme Toggle */}
<div className="flex items-center gap-2 flex-shrink-0">
<CommandPalette actions={commandActions} />
<ThemeToggle />
</div>
</header>
{/* Main content area */}
<div className="flex flex-1 overflow-hidden">
{/* Side Panel */}
<SidePanel
fileName={fileName}
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
audioBuffer={audioBuffer}
onFileSelect={handleFileSelect}
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
onClear={handleClear}
selection={selection}
historyState={historyState}
2025-11-17 20:27:08 +01:00
effectChain={effectChain}
effectPresets={effectPresets}
onToggleEffect={toggleEffectEnabled}
onRemoveEffect={removeEffect}
onReorderEffects={reorderEffects}
onSavePreset={savePreset}
onLoadPreset={loadPresetToChain}
onDeletePreset={deletePreset}
onClearChain={clearChain}
onNormalize={handleNormalize}
onFadeIn={handleFadeIn}
onFadeOut={handleFadeOut}
onReverse={handleReverse}
onLowPassFilter={handleLowPassFilter}
onHighPassFilter={handleHighPassFilter}
onBandPassFilter={handleBandPassFilter}
onCompressor={handleCompressor}
onLimiter={handleLimiter}
onGate={handleGate}
onDelay={handleDelay}
onReverb={handleReverb}
onChorus={handleChorus}
onFlanger={handleFlanger}
onPhaser={handlePhaser}
onPitchShift={handlePitchShift}
onTimeStretch={handleTimeStretch}
onDistortion={handleDistortion}
onBitcrusher={handleBitcrusher}
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
/>
{/* Main canvas area */}
<main className="flex-1 flex flex-col overflow-hidden bg-background">
{isLoading ? (
<div className="flex-1 flex items-center justify-center">
<div className="flex flex-col items-center gap-3">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<p className="text-sm text-muted-foreground">Loading audio file...</p>
</div>
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
</div>
) : audioBuffer ? (
<>
{/* Waveform - takes maximum space */}
<div className="flex-1 flex flex-col p-4 overflow-hidden">
<Waveform
audioBuffer={audioBuffer}
currentTime={currentTime}
duration={duration}
onSeek={seek}
height={200}
zoom={zoom}
scrollOffset={scrollOffset}
amplitudeScale={amplitudeScale}
selection={selection}
onSelectionChange={setSelection}
/>
feat: complete Phase 3 - Advanced waveform visualization and zoom controls Phase 3 Complete Features: ✅ Drag-to-scrub audio functionality ✅ Horizontal zoom (1x-20x) with smooth scaling ✅ Vertical amplitude zoom (0.5x-5x) ✅ Horizontal scrolling for zoomed waveform ✅ Grid lines every second for time reference ✅ Viewport culling for better performance ✅ Zoom controls UI component Components Added: - ZoomControls: Complete zoom control panel with: - Horizontal zoom slider and buttons - Amplitude zoom slider - Zoom in/out buttons - Fit to view button - Real-time zoom level display Waveform Enhancements: - Drag-to-scrub: Click and drag to scrub through audio - Zoom support: View waveform at different zoom levels - Scroll support: Navigate through zoomed waveform - Grid lines: Visual time markers every second - Viewport culling: Only render visible portions - Cursor feedback: Grabbing cursor when dragging AudioEditor Updates: - Integrated zoom and scroll state management - Auto-reset zoom on file clear - Scroll slider appears when zoomed - Smooth zoom transitions Technical Improvements: - Viewport culling: Only render visible waveform portions - Grid rendering: Time-aligned vertical grid lines - Smart scroll clamping: Prevent scrolling beyond bounds - Zoom-aware seeking: Accurate time calculation with zoom - Performance optimized rendering Features Working: ✅ Drag waveform to scrub audio ✅ Zoom in up to 20x for detailed editing ✅ Adjust amplitude for better visualization ✅ Scroll through zoomed waveform ✅ Grid lines show time markers ✅ Smooth cursor interactions Phase 3 Status: 95% complete - Completed: All major features - Optional: Measure/beat markers, OffscreenCanvas, Web Workers Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:44:29 +01:00
{/* Horizontal scroll for zoomed waveform */}
{zoom > 1 && (
<div className="mt-4 space-y-2">
<label className="text-xs font-medium text-muted-foreground">
Scroll Position
</label>
<Slider
value={scrollOffset}
onChange={setScrollOffset}
min={0}
max={Math.max(0, (800 * zoom) - 800)}
step={1}
/>
</div>
)}
</div>
{/* Playback Controls - fixed at bottom */}
<div className="border-t border-border bg-card p-3">
<PlaybackControls
isPlaying={isPlaying}
isPaused={isPaused}
currentTime={currentTime}
duration={duration}
volume={volume}
onPlay={play}
onPause={pause}
onStop={stop}
onSeek={seek}
onVolumeChange={setVolume}
currentTimeFormatted={currentTimeFormatted}
durationFormatted={durationFormatted}
/>
</div>
</>
) : (
<div
className={cn(
"flex-1 flex items-center justify-center p-8 transition-colors cursor-pointer",
isDragging && "bg-primary/5 border-2 border-dashed border-primary"
feat: complete Phase 3 - Advanced waveform visualization and zoom controls Phase 3 Complete Features: ✅ Drag-to-scrub audio functionality ✅ Horizontal zoom (1x-20x) with smooth scaling ✅ Vertical amplitude zoom (0.5x-5x) ✅ Horizontal scrolling for zoomed waveform ✅ Grid lines every second for time reference ✅ Viewport culling for better performance ✅ Zoom controls UI component Components Added: - ZoomControls: Complete zoom control panel with: - Horizontal zoom slider and buttons - Amplitude zoom slider - Zoom in/out buttons - Fit to view button - Real-time zoom level display Waveform Enhancements: - Drag-to-scrub: Click and drag to scrub through audio - Zoom support: View waveform at different zoom levels - Scroll support: Navigate through zoomed waveform - Grid lines: Visual time markers every second - Viewport culling: Only render visible portions - Cursor feedback: Grabbing cursor when dragging AudioEditor Updates: - Integrated zoom and scroll state management - Auto-reset zoom on file clear - Scroll slider appears when zoomed - Smooth zoom transitions Technical Improvements: - Viewport culling: Only render visible waveform portions - Grid rendering: Time-aligned vertical grid lines - Smart scroll clamping: Prevent scrolling beyond bounds - Zoom-aware seeking: Accurate time calculation with zoom - Performance optimized rendering Features Working: ✅ Drag waveform to scrub audio ✅ Zoom in up to 20x for detailed editing ✅ Adjust amplitude for better visualization ✅ Scroll through zoomed waveform ✅ Grid lines show time markers ✅ Smooth cursor interactions Phase 3 Status: 95% complete - Completed: All major features - Optional: Measure/beat markers, OffscreenCanvas, Web Workers Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:44:29 +01:00
)}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
onDrop={handleDrop}
onClick={handleDropZoneClick}
>
<input
ref={fileInputRef}
type="file"
accept="audio/*"
onChange={handleFileInputChange}
className="hidden"
/>
<div className="max-w-md text-center space-y-4">
<p className="text-lg font-medium text-foreground">
{isDragging ? 'Drop audio file here' : 'No audio file loaded'}
</p>
<p className="text-sm text-muted-foreground">
{isDragging
? 'Release to load the file'
: 'Click here or use the side panel to load an audio file, or drag and drop a file onto this area.'}
</p>
</div>
</div>
)}
</main>
</div>
{/* Effect Parameter Dialog */}
<EffectParameterDialog
open={effectDialogOpen}
onClose={() => setEffectDialogOpen(false)}
effectType={effectDialogType}
onApply={handleEffectApply}
sampleRate={audioBuffer?.sampleRate}
/>
{/* Dynamics Parameter Dialog */}
<DynamicsParameterDialog
open={dynamicsDialogOpen}
onClose={() => setDynamicsDialogOpen(false)}
effectType={dynamicsDialogType}
onApply={handleDynamicsApply}
/>
{/* Time-Based Parameter Dialog */}
<TimeBasedParameterDialog
open={timeBasedDialogOpen}
onClose={() => setTimeBasedDialogOpen(false)}
effectType={timeBasedDialogType}
onApply={handleTimeBasedApply}
/>
{/* Advanced Parameter Dialog */}
<AdvancedParameterDialog
open={advancedDialogOpen}
onClose={() => setAdvancedDialogOpen(false)}
effectType={advancedDialogType}
onApply={handleAdvancedApply}
/>
</>
feat: implement Phase 2 - Web Audio API engine and waveform visualization Phase 2 Complete Features: - Web Audio API context management with browser compatibility - Audio file upload with drag-and-drop support - Audio decoding for multiple formats (WAV, MP3, OGG, FLAC, AAC, M4A) - AudioPlayer class with full playback control - Waveform visualization using Canvas API - Real-time waveform rendering with progress indicator - Playback controls (play, pause, stop, seek) - Volume control with mute/unmute - Timeline scrubbing - Audio file information display Components: - AudioEditor: Main editor container - FileUpload: Drag-and-drop file upload component - AudioInfo: Display audio file metadata - Waveform: Canvas-based waveform visualization - PlaybackControls: Transport controls with volume slider Audio Engine: - lib/audio/context.ts: AudioContext management - lib/audio/decoder.ts: Audio file decoding utilities - lib/audio/player.ts: AudioPlayer class for playback - lib/waveform/peaks.ts: Waveform peak generation Hooks: - useAudioPlayer: Complete audio player state management Types: - types/audio.ts: TypeScript definitions for audio types Features Working: ✓ Load audio files via drag-and-drop or file picker ✓ Display waveform with real-time progress ✓ Play/pause/stop controls ✓ Seek by clicking on waveform or using timeline slider ✓ Volume control with visual feedback ✓ Audio file metadata display (duration, sample rate, channels) ✓ Toast notifications for user feedback ✓ SSR-safe audio context initialization ✓ Dark/light theme support Tech Stack: - Web Audio API for playback - Canvas API for waveform rendering - React 19 hooks for state management - TypeScript for type safety Build verified and working ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 15:32:00 +01:00
);
}