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>
123 lines
3.2 KiB
TypeScript
123 lines
3.2 KiB
TypeScript
import { useState, useCallback, useEffect } from 'react';
|
|
import type {
|
|
EffectChain,
|
|
EffectPreset,
|
|
ChainEffect,
|
|
EffectParameters,
|
|
} from '@/lib/audio/effects/chain';
|
|
import {
|
|
createEffectChain,
|
|
addEffectToChain,
|
|
removeEffectFromChain,
|
|
toggleEffect,
|
|
updateEffectParameters,
|
|
reorderEffects,
|
|
loadPreset,
|
|
} from '@/lib/audio/effects/chain';
|
|
|
|
const STORAGE_KEY_CHAIN = 'audio-ui-effect-chain';
|
|
const STORAGE_KEY_PRESETS = 'audio-ui-effect-presets';
|
|
|
|
export function useEffectChain() {
|
|
const [chain, setChain] = useState<EffectChain>(() => {
|
|
if (typeof window === 'undefined') return createEffectChain('Main Chain');
|
|
|
|
try {
|
|
const saved = localStorage.getItem(STORAGE_KEY_CHAIN);
|
|
return saved ? JSON.parse(saved) : createEffectChain('Main Chain');
|
|
} catch {
|
|
return createEffectChain('Main Chain');
|
|
}
|
|
});
|
|
|
|
const [presets, setPresets] = useState<EffectPreset[]>(() => {
|
|
if (typeof window === 'undefined') return [];
|
|
|
|
try {
|
|
const saved = localStorage.getItem(STORAGE_KEY_PRESETS);
|
|
return saved ? JSON.parse(saved) : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
});
|
|
|
|
// Save chain to localStorage whenever it changes
|
|
useEffect(() => {
|
|
if (typeof window === 'undefined') return;
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY_CHAIN, JSON.stringify(chain));
|
|
} catch (error) {
|
|
console.error('Failed to save effect chain:', error);
|
|
}
|
|
}, [chain]);
|
|
|
|
// Save presets to localStorage whenever they change
|
|
useEffect(() => {
|
|
if (typeof window === 'undefined') return;
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY_PRESETS, JSON.stringify(presets));
|
|
} catch (error) {
|
|
console.error('Failed to save presets:', error);
|
|
}
|
|
}, [presets]);
|
|
|
|
const addEffect = useCallback((effect: ChainEffect) => {
|
|
setChain((prev) => addEffectToChain(prev, effect));
|
|
}, []);
|
|
|
|
const removeEffect = useCallback((effectId: string) => {
|
|
setChain((prev) => removeEffectFromChain(prev, effectId));
|
|
}, []);
|
|
|
|
const toggleEffectEnabled = useCallback((effectId: string) => {
|
|
setChain((prev) => toggleEffect(prev, effectId));
|
|
}, []);
|
|
|
|
const updateEffect = useCallback(
|
|
(effectId: string, parameters: EffectParameters) => {
|
|
setChain((prev) => updateEffectParameters(prev, effectId, parameters));
|
|
},
|
|
[]
|
|
);
|
|
|
|
const reorder = useCallback((fromIndex: number, toIndex: number) => {
|
|
setChain((prev) => reorderEffects(prev, fromIndex, toIndex));
|
|
}, []);
|
|
|
|
const clearChain = useCallback(() => {
|
|
setChain((prev) => ({ ...prev, effects: [] }));
|
|
}, []);
|
|
|
|
const savePreset = useCallback((preset: EffectPreset) => {
|
|
setPresets((prev) => [...prev, preset]);
|
|
}, []);
|
|
|
|
const loadPresetToChain = useCallback((preset: EffectPreset) => {
|
|
const loadedChain = loadPreset(preset);
|
|
setChain(loadedChain);
|
|
}, []);
|
|
|
|
const deletePreset = useCallback((presetId: string) => {
|
|
setPresets((prev) => prev.filter((p) => p.id !== presetId));
|
|
}, []);
|
|
|
|
const importPreset = useCallback((preset: EffectPreset) => {
|
|
setPresets((prev) => [...prev, preset]);
|
|
}, []);
|
|
|
|
return {
|
|
chain,
|
|
presets,
|
|
addEffect,
|
|
removeEffect,
|
|
toggleEffectEnabled,
|
|
updateEffect,
|
|
reorder,
|
|
clearChain,
|
|
savePreset,
|
|
loadPresetToChain,
|
|
deletePreset,
|
|
importPreset,
|
|
};
|
|
}
|