diff --git a/components/automation/AutomationHeader.tsx b/components/automation/AutomationHeader.tsx index 7df3a9b..a7a2b96 100644 --- a/components/automation/AutomationHeader.tsx +++ b/components/automation/AutomationHeader.tsx @@ -17,6 +17,10 @@ export interface AutomationHeaderProps { onHeightChange?: (delta: number) => void; className?: string; formatter?: (value: number) => string; + // Parameter selection + availableParameters?: Array<{ id: string; name: string }>; + selectedParameterId?: string; + onParameterChange?: (parameterId: string) => void; } const MODE_LABELS: Record = { @@ -44,6 +48,9 @@ export function AutomationHeader({ onHeightChange, className, formatter, + availableParameters, + selectedParameterId, + onParameterChange, }: AutomationHeaderProps) { const modes: AutomationMode[] = ['read', 'write', 'touch', 'latch']; const currentModeIndex = modes.indexOf(mode); @@ -74,10 +81,24 @@ export function AutomationHeader({ /> )} - {/* Parameter name */} - - {parameterName} - + {/* Parameter name / selector */} + {availableParameters && availableParameters.length > 1 ? ( + + ) : ( + + {parameterName} + + )} {/* Current value display */} {currentValue !== undefined && ( diff --git a/components/automation/AutomationLane.tsx b/components/automation/AutomationLane.tsx index 1a05226..14c4e06 100644 --- a/components/automation/AutomationLane.tsx +++ b/components/automation/AutomationLane.tsx @@ -16,6 +16,10 @@ export interface AutomationLaneProps { onUpdatePoint?: (pointId: string, updates: Partial) => void; onRemovePoint?: (pointId: string) => void; className?: string; + // Parameter selection + availableParameters?: Array<{ id: string; name: string }>; + selectedParameterId?: string; + onParameterChange?: (parameterId: string) => void; } export function AutomationLane({ @@ -28,6 +32,9 @@ export function AutomationLane({ onUpdatePoint, onRemovePoint, className, + availableParameters, + selectedParameterId, + onParameterChange, }: AutomationLaneProps) { const canvasRef = React.useRef(null); const containerRef = React.useRef(null); @@ -302,6 +309,9 @@ export function AutomationLane({ onUpdateLane?.({ height: newHeight }); }} formatter={lane.valueRange.formatter} + availableParameters={availableParameters} + selectedParameterId={selectedParameterId} + onParameterChange={onParameterChange} /> {/* Lane canvas area */} diff --git a/components/tracks/Track.tsx b/components/tracks/Track.tsx index 6291bef..d8989a6 100644 --- a/components/tracks/Track.tsx +++ b/components/tracks/Track.tsx @@ -13,6 +13,7 @@ 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'; +import { createAutomationLane } from '@/lib/audio/automation-utils'; import { EffectDevice } from '@/components/effects/EffectDevice'; import { EffectBrowser } from '@/components/effects/EffectBrowser'; @@ -709,19 +710,79 @@ export function Track({ - {/* Automation Lanes */} - {!track.collapsed && track.automation?.showAutomation && ( -
- {track.automation.lanes.map((lane) => ( + {/* Automation Lane */} + {!track.collapsed && track.automation?.showAutomation && (() => { + // Build list of available parameters from track and effects + const availableParameters: Array<{ id: string; name: string }> = [ + { id: 'volume', name: 'Volume' }, + { id: 'pan', name: 'Pan' }, + ]; + + // Add effect parameters + track.effectChain.effects.forEach((effect) => { + if (effect.parameters) { + Object.keys(effect.parameters).forEach((paramKey) => { + const parameterId = `effect.${effect.id}.${paramKey}`; + const paramName = `${effect.name} - ${paramKey.charAt(0).toUpperCase() + paramKey.slice(1)}`; + availableParameters.push({ id: parameterId, name: paramName }); + }); + } + }); + + // Get selected parameter ID (default to volume if not set) + const selectedParameterId = track.automation.selectedParameterId || 'volume'; + + // Find or create lane for selected parameter + let selectedLane = track.automation.lanes.find(lane => lane.parameterId === selectedParameterId); + + // If lane doesn't exist yet, create it + if (!selectedLane) { + const paramInfo = availableParameters.find(p => p.id === selectedParameterId); + if (paramInfo) { + selectedLane = createAutomationLane( + track.id, + selectedParameterId, + paramInfo.name, + { + min: 0, + max: 1, + unit: selectedParameterId === 'volume' ? 'dB' : '', + formatter: selectedParameterId === 'pan' ? (value: number) => { + if (value === 0.5) return 'C'; + if (value < 0.5) return `${Math.abs((0.5 - value) * 200).toFixed(0)}L`; + return `${((value - 0.5) * 200).toFixed(0)}R`; + } : undefined, + } + ); + // Add the new lane to the track + onUpdateTrack(track.id, { + automation: { + ...track.automation, + lanes: [...track.automation.lanes, selectedLane], + selectedParameterId, + }, + }); + } + } + + return selectedLane ? ( +
{ + onUpdateTrack(track.id, { + automation: { ...track.automation, selectedParameterId: parameterId }, + }); + }} onUpdateLane={(updates) => { const updatedLanes = track.automation.lanes.map((l) => - l.id === lane.id ? { ...l, ...updates } : l + l.id === selectedLane.id ? { ...l, ...updates } : l ); onUpdateTrack(track.id, { automation: { ...track.automation, lanes: updatedLanes }, @@ -734,7 +795,7 @@ export function Track({ curve: 'linear', }); const updatedLanes = track.automation.lanes.map((l) => - l.id === lane.id + l.id === selectedLane.id ? { ...l, points: [...l.points, newPoint] } : l ); @@ -744,7 +805,7 @@ export function Track({ }} onUpdatePoint={(pointId, updates) => { const updatedLanes = track.automation.lanes.map((l) => - l.id === lane.id + l.id === selectedLane.id ? { ...l, points: l.points.map((p) => @@ -759,7 +820,7 @@ export function Track({ }} onRemovePoint={(pointId) => { const updatedLanes = track.automation.lanes.map((l) => - l.id === lane.id + l.id === selectedLane.id ? { ...l, points: l.points.filter((p) => p.id !== pointId) } : l ); @@ -768,14 +829,14 @@ export function Track({ }); }} /> - ))} -
- )} +
+ ) : null; + })()} {/* Per-Track Effects Panel */} {!track.collapsed && track.showEffects && ( -
-
+
+
{track.name} - Effects @@ -791,11 +852,11 @@ export function Track({
{track.effectChain.effects.length === 0 ? ( -
+
No effects on this track
) : ( -
+
{track.effectChain.effects.map((effect) => (