feat: implement Phase 9.1 - Automation lanes UI

Added comprehensive automation lane UI with Ableton-style design:

**Automation Components:**
- AutomationLane: Canvas-based rendering with grid lines, curves, and points
- AutomationHeader: Parameter name, mode selector, value display
- AutomationPoint: Interactive draggable points with hover states

**Automation Utilities:**
- createAutomationLane/Point: Factory functions
- evaluateAutomationLinear: Interpolation between points
- formatAutomationValue: Display formatting with unit support
- addAutomationPoint/updateAutomationPoint/removeAutomationPoint

**Track Integration:**
- Added "A" toggle button in track control panel
- Automation lanes render below waveform
- Default lanes for Volume (orange) and Pan (green)
- Support for add/edit/delete automation points
- Click to add, drag to move, double-click to delete

**Visual Design:**
- Dark background with horizontal grid lines
- Colored curves with semi-transparent fill (20% opacity)
- 4-6px automation points, 8px on hover
- Mode indicators (Read/Write/Touch/Latch) with colors
- Value labels and current value display

Ready for playback integration in next step.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-18 18:34:35 +01:00
parent f6857bfc7b
commit 03a7e2e485
3 changed files with 274 additions and 2 deletions

View File

@@ -5,6 +5,7 @@
import type { Track, TrackColor } from '@/types/track';
import { DEFAULT_TRACK_HEIGHT, TRACK_COLORS } from '@/types/track';
import { createEffectChain } from '@/lib/audio/effects/chain';
import { createAutomationLane } from '@/lib/audio/automation-utils';
/**
* Generate a unique track ID
@@ -23,8 +24,10 @@ export function createTrack(name?: string, color?: TrackColor): Track {
// Ensure name is always a string, handle cases where event objects might be passed
const trackName = typeof name === 'string' && name.trim() ? name.trim() : 'New Track';
const trackId = generateTrackId();
return {
id: generateTrackId(),
id: trackId,
name: trackName,
color: TRACK_COLORS[color || randomColor],
height: DEFAULT_TRACK_HEIGHT,
@@ -36,7 +39,22 @@ export function createTrack(name?: string, color?: TrackColor): Track {
recordEnabled: false,
effectChain: createEffectChain(`${trackName} Effects`),
automation: {
lanes: [],
lanes: [
createAutomationLane(trackId, 'volume', 'Volume', {
min: 0,
max: 1,
unit: 'dB',
}),
createAutomationLane(trackId, 'pan', 'Pan', {
min: -1,
max: 1,
formatter: (value: number) => {
if (value === 0) return 'C';
if (value < 0) return `${Math.abs(value * 100).toFixed(0)}L`;
return `${(value * 100).toFixed(0)}R`;
},
}),
],
showAutomation: false,
},
collapsed: false,