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>
This commit is contained in:
@@ -11,6 +11,9 @@ import { EffectDevice } from '@/components/effects/EffectDevice';
|
||||
import { createEffect, type EffectType } from '@/lib/audio/effects/chain';
|
||||
import { VerticalFader } from '@/components/ui/VerticalFader';
|
||||
import { CircularKnob } from '@/components/ui/CircularKnob';
|
||||
import { AutomationLane } from '@/components/automation/AutomationLane';
|
||||
import type { AutomationLane as AutomationLaneType, AutomationPoint as AutomationPointType } from '@/types/automation';
|
||||
import { createAutomationPoint } from '@/lib/audio/automation/utils';
|
||||
|
||||
export interface TrackProps {
|
||||
track: TrackType;
|
||||
@@ -589,12 +592,12 @@ export function Track({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom: Effects Section (Collapsible, Full Width) */}
|
||||
{/* Bottom: Effects Section (Collapsible, Full Width) - Ableton Style */}
|
||||
{!track.collapsed && (
|
||||
<div className="bg-muted/50 border-b border-border/50">
|
||||
<div className="bg-muted/80 border-b border-border shadow-inner">
|
||||
{/* Effects Header - clickable to toggle */}
|
||||
<div
|
||||
className="flex items-center gap-2 px-3 py-1.5 cursor-pointer hover:bg-accent/30 transition-colors"
|
||||
className="flex items-center gap-2 px-3 py-1.5 cursor-pointer hover:bg-accent/30 transition-colors border-b border-border/30"
|
||||
onClick={() => setShowEffects(!showEffects)}
|
||||
>
|
||||
{showEffects ? (
|
||||
@@ -642,8 +645,8 @@ export function Track({
|
||||
|
||||
{/* Horizontal scrolling device rack - expanded state */}
|
||||
{showEffects && (
|
||||
<div className="h-48 overflow-x-auto custom-scrollbar bg-muted/70">
|
||||
<div className="flex h-full">
|
||||
<div className="h-48 overflow-x-auto custom-scrollbar bg-background/50 p-2">
|
||||
<div className="flex h-full gap-2">
|
||||
{track.effectChain.effects.length === 0 ? (
|
||||
<div className="text-xs text-muted-foreground text-center py-8 w-full">
|
||||
No devices. Click + to add an effect.
|
||||
@@ -665,6 +668,69 @@ export function Track({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Automation Lanes */}
|
||||
{!track.collapsed && track.automation?.showAutomation && (
|
||||
<div className="bg-background/30">
|
||||
{track.automation.lanes.map((lane) => (
|
||||
<AutomationLane
|
||||
key={lane.id}
|
||||
lane={lane}
|
||||
duration={duration}
|
||||
zoom={zoom}
|
||||
currentTime={currentTime}
|
||||
onUpdateLane={(updates) => {
|
||||
const updatedLanes = track.automation.lanes.map((l) =>
|
||||
l.id === lane.id ? { ...l, ...updates } : l
|
||||
);
|
||||
onUpdateTrack(track.id, {
|
||||
automation: { ...track.automation, lanes: updatedLanes },
|
||||
});
|
||||
}}
|
||||
onAddPoint={(time, value) => {
|
||||
const newPoint = createAutomationPoint({
|
||||
time,
|
||||
value,
|
||||
curve: 'linear',
|
||||
});
|
||||
const updatedLanes = track.automation.lanes.map((l) =>
|
||||
l.id === lane.id
|
||||
? { ...l, points: [...l.points, newPoint] }
|
||||
: l
|
||||
);
|
||||
onUpdateTrack(track.id, {
|
||||
automation: { ...track.automation, lanes: updatedLanes },
|
||||
});
|
||||
}}
|
||||
onUpdatePoint={(pointId, updates) => {
|
||||
const updatedLanes = track.automation.lanes.map((l) =>
|
||||
l.id === lane.id
|
||||
? {
|
||||
...l,
|
||||
points: l.points.map((p) =>
|
||||
p.id === pointId ? { ...p, ...updates } : p
|
||||
),
|
||||
}
|
||||
: l
|
||||
);
|
||||
onUpdateTrack(track.id, {
|
||||
automation: { ...track.automation, lanes: updatedLanes },
|
||||
});
|
||||
}}
|
||||
onRemovePoint={(pointId) => {
|
||||
const updatedLanes = track.automation.lanes.map((l) =>
|
||||
l.id === lane.id
|
||||
? { ...l, points: l.points.filter((p) => p.id !== pointId) }
|
||||
: l
|
||||
);
|
||||
onUpdateTrack(track.id, {
|
||||
automation: { ...track.automation, lanes: updatedLanes },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Effect Browser Dialog */}
|
||||
<EffectBrowser
|
||||
open={effectBrowserOpen}
|
||||
|
||||
Reference in New Issue
Block a user