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,12 +25,12 @@ export function useMultiTrack() {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Note: AudioBuffers and EffectChains can't be serialized, so we only restore track metadata
|
||||
// Note: AudioBuffers can't be serialized, but EffectChains can
|
||||
return parsed.map((t: any) => ({
|
||||
...t,
|
||||
name: String(t.name || 'Untitled Track'), // Ensure name is always a string
|
||||
audioBuffer: null, // Will need to be reloaded
|
||||
effectChain: createEffectChain(`${t.name} Effects`), // Recreate effect chain
|
||||
effectChain: t.effectChain || createEffectChain(`${t.name} Effects`), // Restore effect chain or create new
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -47,7 +47,7 @@ export function useMultiTrack() {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
try {
|
||||
// Only save serializable fields, excluding audioBuffer, effectChain, and any DOM references
|
||||
// Only save serializable fields, excluding audioBuffer and any DOM references
|
||||
const trackData = tracks.map((track) => ({
|
||||
id: track.id,
|
||||
name: String(track.name || 'Untitled Track'),
|
||||
@@ -60,7 +60,7 @@ export function useMultiTrack() {
|
||||
recordEnabled: track.recordEnabled,
|
||||
collapsed: track.collapsed,
|
||||
selected: track.selected,
|
||||
// Note: effectChain is excluded - will be recreated on load
|
||||
effectChain: track.effectChain, // Save effect chain
|
||||
}));
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(trackData));
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user