feat: integrate multi-track functionality into main AudioEditor

Added comprehensive multi-track support to the main application:
- Added "Tracks" tab to SidePanel with track management controls
- Integrated useMultiTrack and useMultiTrackPlayer hooks into AudioEditor
- Added view mode switching between waveform and tracks views
- Implemented "Convert to Track" to convert current audio buffer to track
- Added TrackList view with multi-track playback controls
- Wired up ImportTrackDialog for importing multiple audio files

Users can now:
- Click "Tracks" tab in side panel to access multi-track mode
- Convert current audio to a track
- Import multiple audio files as tracks
- View and manage tracks in dedicated TrackList view
- Play multiple tracks simultaneously with individual controls

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-17 21:57:31 +01:00
parent 83127b3116
commit 832a18dd9c
2 changed files with 207 additions and 1 deletions

View File

@@ -14,6 +14,8 @@ import {
Link2,
FolderOpen,
Trash2,
Layers,
Plus,
} from 'lucide-react';
import { Button } from '@/components/ui/Button';
import { cn } from '@/lib/utils/cn';
@@ -23,6 +25,7 @@ import type { HistoryState } from '@/lib/history/history-manager';
import type { EffectChain, ChainEffect, EffectPreset } from '@/lib/audio/effects/chain';
import { EffectRack } from '@/components/effects/EffectRack';
import { PresetManager } from '@/components/effects/PresetManager';
import type { Track } from '@/types/track';
export interface SidePanelProps {
// File info
@@ -69,6 +72,13 @@ export interface SidePanelProps {
onDistortion: () => void;
onBitcrusher: () => void;
// Multi-track
tracks?: Track[];
onAddTrack?: () => void;
onImportTracks?: () => void;
onConvertToTrack?: () => void;
onClearTracks?: () => void;
className?: string;
}
@@ -107,10 +117,15 @@ export function SidePanel({
onTimeStretch,
onDistortion,
onBitcrusher,
tracks,
onAddTrack,
onImportTracks,
onConvertToTrack,
onClearTracks,
className,
}: SidePanelProps) {
const [isCollapsed, setIsCollapsed] = React.useState(false);
const [activeTab, setActiveTab] = React.useState<'file' | 'chain' | 'history' | 'info' | 'effects'>('file');
const [activeTab, setActiveTab] = React.useState<'file' | 'chain' | 'history' | 'info' | 'effects' | 'tracks'>('file');
const [presetDialogOpen, setPresetDialogOpen] = React.useState(false);
const fileInputRef = React.useRef<HTMLInputElement>(null);
@@ -174,6 +189,14 @@ export function SidePanel({
>
<Sparkles className="h-4 w-4" />
</Button>
<Button
variant={activeTab === 'tracks' ? 'secondary' : 'ghost'}
size="icon-sm"
onClick={() => setActiveTab('tracks')}
title="Tracks"
>
<Layers className="h-4 w-4" />
</Button>
<Button
variant={activeTab === 'history' ? 'secondary' : 'ghost'}
size="icon-sm"
@@ -591,6 +614,78 @@ export function SidePanel({
</div>
</div>
)}
{activeTab === 'tracks' && (
<div className="space-y-3">
<div className="space-y-2">
<h3 className="text-xs font-semibold text-muted-foreground uppercase">
Multi-Track Mode
</h3>
{tracks && tracks.length > 0 ? (
<div className="space-y-2">
<div className="p-2 bg-secondary/30 rounded text-xs">
<div className="text-foreground font-medium">
{tracks.length} {tracks.length === 1 ? 'track' : 'tracks'} active
</div>
<div className="text-muted-foreground mt-1">
Switch to Tracks view to manage
</div>
</div>
{onClearTracks && (
<Button
variant="outline"
size="sm"
onClick={onClearTracks}
className="w-full"
>
<Trash2 className="h-3.5 w-3.5 mr-1.5 text-destructive" />
Clear All Tracks
</Button>
)}
</div>
) : (
<div className="space-y-2">
<div className="text-xs text-muted-foreground">
Add tracks to work with multiple audio files simultaneously.
</div>
{audioBuffer && onConvertToTrack && (
<Button
variant="outline"
size="sm"
onClick={onConvertToTrack}
className="w-full"
>
<Plus className="h-3.5 w-3.5 mr-1.5" />
Convert Current to Track
</Button>
)}
{onAddTrack && (
<Button
variant="outline"
size="sm"
onClick={onAddTrack}
className="w-full"
>
<Plus className="h-3.5 w-3.5 mr-1.5" />
Add Empty Track
</Button>
)}
{onImportTracks && (
<Button
variant="outline"
size="sm"
onClick={onImportTracks}
className="w-full"
>
<Upload className="h-3.5 w-3.5 mr-1.5" />
Import Audio Files
</Button>
)}
</div>
)}
</div>
</div>
)}
</div>
</div>
);