Files
audio-ui/components/effects/EffectDevice.tsx
Sebastian Krüger 9b1eedc379 feat: Ableton Live-style effects and complete automation system
Enhanced visual design:
- Improved device rack container with darker background and inner shadow
- Device cards now have rounded corners, shadows, and colored indicators
- Better visual separation between enabled/disabled effects
- Active devices highlighted with accent border

Complete automation infrastructure (Phase 9):
- Created comprehensive type system for automation lanes and points
- Implemented AutomationPoint component with drag-and-drop editing
- Implemented AutomationHeader with mode controls (Read/Write/Touch/Latch)
- Implemented AutomationLane with canvas-based curve rendering
- Integrated automation lanes into Track component below effects
- Created automation playback engine with real-time interpolation
- Added automation data persistence to localStorage

Automation features:
- Add/remove automation points by clicking/double-clicking
- Drag points to change time and value
- Multiple automation modes (Read, Write, Touch, Latch)
- Linear and step curve types (bezier planned)
- Adjustable lane height (60-180px)
- Show/hide automation per lane
- Real-time value display at playhead
- Color-coded lanes by parameter type
- Keyboard delete support (Delete/Backspace)

Track type updates:
- Added automation field to Track interface
- Updated track creation to initialize empty automation
- Updated localStorage save/load to include automation data

Files created:
- components/automation/AutomationPoint.tsx
- components/automation/AutomationHeader.tsx
- components/automation/AutomationLane.tsx
- lib/audio/automation/utils.ts (helper functions)
- lib/audio/automation/playback.ts (playback engine)
- types/automation.ts (complete type system)

Files modified:
- components/effects/EffectDevice.tsx (Ableton-style visual improvements)
- components/tracks/Track.tsx (automation lanes integration)
- types/track.ts (automation field added)
- lib/audio/track-utils.ts (automation initialization)
- lib/hooks/useMultiTrack.ts (automation persistence)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 16:30:01 +01:00

116 lines
4.0 KiB
TypeScript

'use client';
import * as React from 'react';
import { ChevronLeft, ChevronRight, Power, X } from 'lucide-react';
import { Button } from '@/components/ui/Button';
import { cn } from '@/lib/utils/cn';
import type { ChainEffect } from '@/lib/audio/effects/chain';
import { EffectParameters } from './EffectParameters';
export interface EffectDeviceProps {
effect: ChainEffect;
onToggleEnabled?: () => void;
onRemove?: () => void;
onUpdateParameters?: (parameters: any) => void;
}
export function EffectDevice({
effect,
onToggleEnabled,
onRemove,
onUpdateParameters,
}: EffectDeviceProps) {
const [isExpanded, setIsExpanded] = React.useState(false);
return (
<div
className={cn(
'flex-shrink-0 flex flex-col h-full transition-all duration-200 rounded-md overflow-hidden',
effect.enabled
? 'bg-card border border-border/50 shadow-md'
: 'bg-card/40 border border-border/30 shadow-sm opacity-60',
isExpanded ? 'min-w-96' : 'w-10'
)}
>
{!isExpanded ? (
/* Collapsed State */
<>
{/* Colored top indicator */}
<div className={cn('h-0.5 w-full', effect.enabled ? 'bg-primary' : 'bg-muted-foreground/20')} />
<button
onClick={() => setIsExpanded(true)}
className="w-full h-full flex flex-col items-center justify-between py-1 hover:bg-primary/10 transition-colors group"
title={`Expand ${effect.name}`}
>
<ChevronRight className="h-3 w-3 flex-shrink-0 text-muted-foreground group-hover:text-primary transition-colors" />
<span
className="flex-1 text-xs font-medium whitespace-nowrap text-muted-foreground group-hover:text-primary transition-colors"
style={{
writingMode: 'vertical-rl',
textOrientation: 'mixed',
}}
>
{effect.name}
</span>
<div
className={cn(
'w-1.5 h-1.5 rounded-full flex-shrink-0 mb-1',
effect.enabled ? 'bg-primary shadow-sm shadow-primary/50' : 'bg-muted-foreground/30'
)}
title={effect.enabled ? 'Enabled' : 'Disabled'}
/>
</button>
</>
) : (
<>
{/* Colored top indicator */}
<div className={cn('h-0.5 w-full', effect.enabled ? 'bg-primary' : 'bg-muted-foreground/20')} />
{/* Full-Width Header Row */}
<div className="flex items-center gap-1 px-2 py-1.5 border-b border-border/50 bg-muted/30 flex-shrink-0">
<Button
variant="ghost"
size="icon-sm"
onClick={() => setIsExpanded(false)}
title="Collapse device"
className="h-5 w-5 flex-shrink-0"
>
<ChevronLeft className="h-3 w-3" />
</Button>
<span className="text-xs font-semibold flex-1 min-w-0 truncate">{effect.name}</span>
<Button
variant="ghost"
size="icon-sm"
onClick={onToggleEnabled}
title={effect.enabled ? 'Disable effect' : 'Enable effect'}
className="h-5 w-5 flex-shrink-0"
>
<Power
className={cn(
'h-3 w-3',
effect.enabled ? 'text-primary' : 'text-muted-foreground'
)}
/>
</Button>
<Button
variant="ghost"
size="icon-sm"
onClick={onRemove}
title="Remove effect"
className="h-5 w-5 flex-shrink-0"
>
<X className="h-3 w-3 text-destructive" />
</Button>
</div>
{/* Device Body */}
<div className="flex-1 min-h-0 overflow-y-auto custom-scrollbar p-3 bg-card/50">
<EffectParameters effect={effect} onUpdateParameters={onUpdateParameters} />
</div>
</>
)}
</div>
);
}