/** * Automation playback engine * Applies automation to track parameters in real-time during playback */ import type { Track } from '@/types/track'; import type { AutomationLane, AutomationValue } from '@/types/automation'; import { interpolateAutomationValue, applyAutomationToTrack } from './utils'; /** * Get all automation values at a specific time */ export function getAutomationValuesAtTime( track: Track, time: number ): AutomationValue[] { if (!track.automation || track.automation.lanes.length === 0) { return []; } const values: AutomationValue[] = []; for (const lane of track.automation.lanes) { // Skip lanes in write mode (don't apply during playback) if (lane.mode === 'write') continue; // Skip lanes with no points if (lane.points.length === 0) continue; const value = interpolateAutomationValue(lane.points, time); values.push({ parameterId: lane.parameterId, value, time, }); } return values; } /** * Apply automation values to a track * Returns a new track object with automated parameters applied */ export function applyAutomationValues( track: Track, values: AutomationValue[] ): Track { let updatedTrack = track; for (const automation of values) { updatedTrack = applyAutomationToTrack( updatedTrack, automation.parameterId, automation.value ); } return updatedTrack; } /** * Apply automation to all tracks at a specific time * Returns a new tracks array with automation applied */ export function applyAutomationToTracks( tracks: Track[], time: number ): Track[] { return tracks.map((track) => { const automationValues = getAutomationValuesAtTime(track, time); if (automationValues.length === 0) { return track; } return applyAutomationValues(track, automationValues); }); } /** * Record automation point during playback */ export function recordAutomationPoint( lane: AutomationLane, time: number, value: number ): AutomationLane { // In write mode, replace all existing points in the recorded region // For simplicity, just add the point for now // TODO: Implement proper write mode that clears existing points const newPoint = { id: `point-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, time, value, curve: 'linear' as const, }; return { ...lane, points: [...lane.points, newPoint], }; } /** * Automation playback scheduler * Schedules automation updates at regular intervals during playback */ export class AutomationPlaybackScheduler { private intervalId: number | null = null; private updateInterval: number = 50; // Update every 50ms (20 Hz) private onUpdate: ((time: number) => void) | null = null; /** * Start the automation scheduler */ start(onUpdate: (time: number) => void): void { if (this.intervalId !== null) { this.stop(); } this.onUpdate = onUpdate; this.intervalId = window.setInterval(() => { // Get current playback time from your audio engine // This is a placeholder - you'll need to integrate with your actual playback system if (this.onUpdate) { // Call update callback with current time // The callback should get the time from your actual playback system this.onUpdate(0); // Placeholder } }, this.updateInterval); } /** * Stop the automation scheduler */ stop(): void { if (this.intervalId !== null) { window.clearInterval(this.intervalId); this.intervalId = null; } this.onUpdate = null; } /** * Set update interval (in milliseconds) */ setUpdateInterval(interval: number): void { this.updateInterval = Math.max(10, Math.min(1000, interval)); // Restart if already running if (this.intervalId !== null && this.onUpdate) { const callback = this.onUpdate; this.stop(); this.start(callback); } } /** * Check if scheduler is running */ isRunning(): boolean { return this.intervalId !== null; } }