/** * Automation utility functions */ import type { AutomationLane, AutomationPoint, CreateAutomationLaneInput, CreateAutomationPointInput, AutomationParameterId, } from '@/types/automation'; /** * Generate a unique automation point ID */ export function generateAutomationPointId(): string { return `point-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * Generate a unique automation lane ID */ export function generateAutomationLaneId(): string { return `lane-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * Create a new automation point */ export function createAutomationPoint( input: CreateAutomationPointInput ): AutomationPoint { return { id: generateAutomationPointId(), ...input, }; } /** * Create a new automation lane */ export function createAutomationLane( trackId: string, parameterId: AutomationParameterId, parameterName: string, input?: Partial ): AutomationLane { return { id: generateAutomationLaneId(), trackId, parameterId, parameterName, visible: input?.visible ?? true, height: input?.height ?? 80, points: input?.points ?? [], mode: input?.mode ?? 'read', color: input?.color, valueRange: input?.valueRange ?? { min: 0, max: 1, }, }; } /** * Create a volume automation lane */ export function createVolumeAutomationLane(trackId: string): AutomationLane { return createAutomationLane(trackId, 'volume', 'Volume', { valueRange: { min: 0, max: 1, formatter: (value) => `${(value * 100).toFixed(0)}%`, }, color: 'rgb(34, 197, 94)', // green }); } /** * Create a pan automation lane */ export function createPanAutomationLane(trackId: string): AutomationLane { return createAutomationLane(trackId, 'pan', 'Pan', { valueRange: { min: -1, max: 1, formatter: (value) => { const normalized = value * 2 - 1; // Convert 0-1 to -1-1 if (normalized === 0) return 'C'; if (normalized < 0) return `L${Math.abs(Math.round(normalized * 100))}`; return `R${Math.round(normalized * 100)}`; }, }, color: 'rgb(59, 130, 246)', // blue }); } /** * Interpolate automation value at a specific time */ export function interpolateAutomationValue( points: AutomationPoint[], time: number ): number { if (points.length === 0) return 0; const sortedPoints = [...points].sort((a, b) => a.time - b.time); // Before first point if (time <= sortedPoints[0].time) { return sortedPoints[0].value; } // After last point if (time >= sortedPoints[sortedPoints.length - 1].time) { return sortedPoints[sortedPoints.length - 1].value; } // Find surrounding points for (let i = 0; i < sortedPoints.length - 1; i++) { const prevPoint = sortedPoints[i]; const nextPoint = sortedPoints[i + 1]; if (time >= prevPoint.time && time <= nextPoint.time) { // Handle step curve if (prevPoint.curve === 'step') { return prevPoint.value; } // Linear interpolation const timeDelta = nextPoint.time - prevPoint.time; const valueDelta = nextPoint.value - prevPoint.value; const progress = (time - prevPoint.time) / timeDelta; return prevPoint.value + valueDelta * progress; } } return 0; } /** * Apply automation value to track parameter */ export function applyAutomationToTrack( track: any, parameterId: AutomationParameterId, value: number ): any { if (parameterId === 'volume') { return { ...track, volume: value }; } if (parameterId === 'pan') { // Convert 0-1 to -1-1 return { ...track, pan: value * 2 - 1 }; } // Effect parameters (format: "effect.{effectId}.{paramName}") if (parameterId.startsWith('effect.')) { const parts = parameterId.split('.'); if (parts.length === 3) { const [, effectId, paramName] = parts; return { ...track, effectChain: { ...track.effectChain, effects: track.effectChain.effects.map((effect: any) => effect.id === effectId ? { ...effect, parameters: { ...effect.parameters, [paramName]: value, }, } : effect ), }, }; } } return track; }