feat: add Phase 6.6 Effect Chain Management system
Effect Chain System: - Create comprehensive effect chain types and state management - Implement EffectRack component with drag-and-drop reordering - Add enable/disable toggle for individual effects - Build PresetManager for save/load/import/export functionality - Create useEffectChain hook with localStorage persistence UI Integration: - Add Chain tab to SidePanel with effect rack - Integrate preset manager dialog - Add chain management controls (clear, presets) - Update SidePanel with chain props and handlers Features: - Drag-and-drop effect reordering with visual feedback - Effect bypass/enable toggle with power icons - Save effect chains as presets with descriptions - Import/export presets as JSON files - localStorage persistence for chains and presets - Visual status indicators for enabled/disabled effects - Preset timestamp and effect count display Components Created: - /lib/audio/effects/chain.ts - Effect chain types and utilities - /components/effects/EffectRack.tsx - Visual effect chain component - /components/effects/PresetManager.tsx - Preset management dialog - /lib/hooks/useEffectChain.ts - Effect chain state hook Updated PLAN.md: - Mark Phase 6.6 as complete - Update current status to Phase 6.6 Complete - Add effect chain features to working features list - Update Next Steps to show Phase 6 complete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
260
lib/audio/effects/chain.ts
Normal file
260
lib/audio/effects/chain.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* 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 { FilterParameters } from './filters';
|
||||
|
||||
// Effect type identifier
|
||||
export type EffectType =
|
||||
// Basic
|
||||
| 'normalize'
|
||||
| 'fadeIn'
|
||||
| 'fadeOut'
|
||||
| 'reverse'
|
||||
// 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 =
|
||||
| FilterParameters
|
||||
| CompressorParameters
|
||||
| LimiterParameters
|
||||
| GateParameters
|
||||
| DelayParameters
|
||||
| ReverbParameters
|
||||
| ChorusParameters
|
||||
| FlangerParameters
|
||||
| PhaserParameters
|
||||
| PitchShifterParameters
|
||||
| TimeStretchParameters
|
||||
| DistortionParameters
|
||||
| BitcrusherParameters
|
||||
| Record<string, never>; // For effects without parameters
|
||||
|
||||
// Effect instance in a chain
|
||||
export interface ChainEffect {
|
||||
id: string;
|
||||
type: EffectType;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 effect display name
|
||||
*/
|
||||
export const EFFECT_NAMES: Record<EffectType, string> = {
|
||||
normalize: 'Normalize',
|
||||
fadeIn: 'Fade In',
|
||||
fadeOut: 'Fade Out',
|
||||
reverse: 'Reverse',
|
||||
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',
|
||||
};
|
||||
Reference in New Issue
Block a user