feat: complete Phase 7.4 - real-time track effects system
Implemented comprehensive real-time effect processing for multi-track audio: Core Features: - Per-track effect chains with drag-and-drop reordering - Effect bypass/enable toggle per effect - Real-time parameter updates (filters, dynamics, time-based, distortion, bitcrusher, pitch, timestretch) - Add/remove effects during playback without interruption - Effect chain persistence via localStorage - Automatic playback stop when tracks are deleted Technical Implementation: - Effect processor with dry/wet routing for bypass functionality - Real-time effect parameter updates using AudioParam setValueAtTime - Structure change detection for add/remove/reorder operations - Stale closure fix using refs for latest track state - ScriptProcessorNode for bitcrusher, pitch shifter, and time stretch - Dual-tap delay line for pitch shifting - Overlap-add synthesis for time stretching UI Components: - EffectBrowser dialog with categorized effects - EffectDevice component with parameter controls - EffectParameters for all 19 real-time effect types - Device rack with horizontal scrolling (Ableton-style) Removed offline-only effects (normalize, fadeIn, fadeOut, reverse) as they don't fit the real-time processing model. Completed all items in Phase 7.4: - [x] Per-track effect chain - [x] Effect rack UI - [x] Effect bypass per track - [x] Real-time effect processing during playback - [x] Add/remove effects during playback - [x] Real-time parameter updates - [x] Effect chain persistence 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -25,11 +25,6 @@ import type { FilterOptions } from './filters';
|
||||
|
||||
// Effect type identifier
|
||||
export type EffectType =
|
||||
// Basic
|
||||
| 'normalize'
|
||||
| 'fadeIn'
|
||||
| 'fadeOut'
|
||||
| 'reverse'
|
||||
// Filters
|
||||
| 'lowpass'
|
||||
| 'highpass'
|
||||
@@ -116,7 +111,7 @@ export function createEffect(
|
||||
type,
|
||||
name,
|
||||
enabled: true,
|
||||
parameters,
|
||||
parameters: parameters || getDefaultParameters(type),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -230,14 +225,63 @@ 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<EffectType, string> = {
|
||||
normalize: 'Normalize',
|
||||
fadeIn: 'Fade In',
|
||||
fadeOut: 'Fade Out',
|
||||
reverse: 'Reverse',
|
||||
lowpass: 'Low-Pass Filter',
|
||||
highpass: 'High-Pass Filter',
|
||||
bandpass: 'Band-Pass Filter',
|
||||
|
||||
1059
lib/audio/effects/processor.ts
Normal file
1059
lib/audio/effects/processor.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user