/** * Effect Chain System * Manages chains of audio effects with bypass, reordering, and preset support */ import type { PitchShifterParameters, TimeStretchParameters, DistortionParameters, BitcrusherParameters, } from './advanced'; import type { CompressorParameters, LimiterParameters, GateParameters, } from './dynamics'; import type { DelayParameters, ReverbParameters, ChorusParameters, FlangerParameters, PhaserParameters, } from './time-based'; import type { FilterOptions } from './filters'; // Effect type identifier export type EffectType = // Filters | 'lowpass' | 'highpass' | 'bandpass' | 'notch' | 'lowshelf' | 'highshelf' | 'peaking' // Dynamics | 'compressor' | 'limiter' | 'gate' // Time-based | 'delay' | 'reverb' | 'chorus' | 'flanger' | 'phaser' // Advanced | 'pitch' | 'timestretch' | 'distortion' | 'bitcrusher'; // Union of all effect parameter types export type EffectParameters = | FilterOptions | CompressorParameters | LimiterParameters | GateParameters | DelayParameters | ReverbParameters | ChorusParameters | FlangerParameters | PhaserParameters | PitchShifterParameters | TimeStretchParameters | DistortionParameters | BitcrusherParameters | Record; // For effects without parameters // Effect instance in a chain export interface ChainEffect { id: string; type: EffectType; name: string; enabled: boolean; expanded?: boolean; // UI state for effect device expansion parameters?: EffectParameters; } // Effect chain export interface EffectChain { id: string; name: string; effects: ChainEffect[]; } // Effect preset export interface EffectPreset { id: string; name: string; description?: string; chain: EffectChain; createdAt: number; } /** * Generate a unique ID for effects/chains */ export function generateId(): string { return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * Create a new effect instance */ export function createEffect( type: EffectType, name: string, parameters?: EffectParameters ): ChainEffect { return { id: generateId(), type, name, enabled: true, parameters: parameters || getDefaultParameters(type), }; } /** * Create a new effect chain */ export function createEffectChain(name: string = 'New Chain'): EffectChain { return { id: generateId(), name, effects: [], }; } /** * Add effect to chain */ export function addEffectToChain( chain: EffectChain, effect: ChainEffect ): EffectChain { return { ...chain, effects: [...chain.effects, effect], }; } /** * Remove effect from chain */ export function removeEffectFromChain( chain: EffectChain, effectId: string ): EffectChain { return { ...chain, effects: chain.effects.filter((e) => e.id !== effectId), }; } /** * Toggle effect enabled state */ export function toggleEffect( chain: EffectChain, effectId: string ): EffectChain { return { ...chain, effects: chain.effects.map((e) => e.id === effectId ? { ...e, enabled: !e.enabled } : e ), }; } /** * Update effect parameters */ export function updateEffectParameters( chain: EffectChain, effectId: string, parameters: EffectParameters ): EffectChain { return { ...chain, effects: chain.effects.map((e) => e.id === effectId ? { ...e, parameters } : e ), }; } /** * Reorder effects in chain */ export function reorderEffects( chain: EffectChain, fromIndex: number, toIndex: number ): EffectChain { const effects = [...chain.effects]; const [removed] = effects.splice(fromIndex, 1); effects.splice(toIndex, 0, removed); return { ...chain, effects, }; } /** * Create a preset from a chain */ export function createPreset( chain: EffectChain, name: string, description?: string ): EffectPreset { return { id: generateId(), name, description, chain: JSON.parse(JSON.stringify(chain)), // Deep clone createdAt: Date.now(), }; } /** * Load preset (returns a new chain) */ export function loadPreset(preset: EffectPreset): EffectChain { return JSON.parse(JSON.stringify(preset.chain)); // Deep clone } /** * Get default parameters for an effect type */ export function getDefaultParameters(type: EffectType): EffectParameters { switch (type) { // Filters case 'lowpass': case 'highpass': return { frequency: 1000, Q: 1 } as FilterOptions; case 'bandpass': case 'notch': return { frequency: 1000, Q: 1 } as FilterOptions; case 'lowshelf': case 'highshelf': return { frequency: 1000, Q: 1, gain: 0 } as FilterOptions; case 'peaking': return { frequency: 1000, Q: 1, gain: 0 } as FilterOptions; // Dynamics case 'compressor': return { threshold: -24, ratio: 4, attack: 0.003, release: 0.25, knee: 30, makeupGain: 0 } as CompressorParameters; case 'limiter': return { threshold: -3, attack: 0.001, release: 0.05, makeupGain: 0 } as LimiterParameters; case 'gate': return { threshold: -40, ratio: 10, attack: 0.001, release: 0.1, knee: 0 } as GateParameters; // Time-based case 'delay': return { time: 0.5, feedback: 0.3, mix: 0.5 } as DelayParameters; case 'reverb': return { roomSize: 0.5, damping: 0.5, mix: 0.3 } as ReverbParameters; case 'chorus': return { rate: 1.5, depth: 0.002, mix: 0.5 } as ChorusParameters; case 'flanger': return { rate: 0.5, depth: 0.002, feedback: 0.5, mix: 0.5 } as FlangerParameters; case 'phaser': return { rate: 0.5, depth: 0.5, stages: 4, mix: 0.5 } as PhaserParameters; // Advanced case 'distortion': return { drive: 0.5, type: 'soft', output: 0.7, mix: 1 } as DistortionParameters; case 'pitch': return { semitones: 0, cents: 0, mix: 1 } as PitchShifterParameters; case 'timestretch': return { rate: 1.0, preservePitch: false, mix: 1 } as TimeStretchParameters; case 'bitcrusher': return { bitDepth: 8, sampleRate: 8000, mix: 1 } as BitcrusherParameters; default: return {}; } } /** * Get effect display name */ export const EFFECT_NAMES: Record = { lowpass: 'Low-Pass Filter', highpass: 'High-Pass Filter', bandpass: 'Band-Pass Filter', notch: 'Notch Filter', lowshelf: 'Low Shelf', highshelf: 'High Shelf', peaking: 'Peaking EQ', compressor: 'Compressor', limiter: 'Limiter', gate: 'Gate/Expander', delay: 'Delay/Echo', reverb: 'Reverb', chorus: 'Chorus', flanger: 'Flanger', phaser: 'Phaser', pitch: 'Pitch Shifter', timestretch: 'Time Stretch', distortion: 'Distortion', bitcrusher: 'Bitcrusher', };