'use client'; import { useState, useEffect, useCallback, useRef } from 'react'; import { EngineState, LayerName } from '@/types/audio'; const defaultState: EngineState = { isPlaying: false, isInitialized: false, bpm: 78, swing: 0.12, currentStep: 0, volumes: { master: 0.8, drums: 0.8, chords: 0.6, ambient: 0.4, }, muted: { drums: false, chords: false, ambient: false, }, }; export function useAudioEngine() { const [state, setState] = useState(defaultState); const [currentStep, setCurrentStep] = useState(0); const engineRef = useRef(null); const isInitializingRef = useRef(false); // Dynamically import the audio engine (client-side only) const getEngine = useCallback(async () => { if (typeof window === 'undefined') return null; if (!engineRef.current) { const { default: audioEngine } = await import('@/lib/audio/audioEngine'); engineRef.current = audioEngine; } return engineRef.current; }, []); // Initialize engine and set up callbacks const initialize = useCallback(async () => { if (isInitializingRef.current) return; isInitializingRef.current = true; try { const engine = await getEngine(); if (!engine) return; engine.setCallbacks({ onStepChange: (step) => { setCurrentStep(step); }, onStateChange: (newState) => { setState(newState); }, }); await engine.initialize(); setState(engine.getState()); } finally { isInitializingRef.current = false; } }, [getEngine]); // Play/pause toggle const togglePlayback = useCallback(async () => { const engine = await getEngine(); if (!engine) return; if (!state.isInitialized) { await initialize(); } const currentState = engine.getState(); if (currentState.isPlaying) { engine.pause(); } else { await engine.play(); } }, [getEngine, state.isInitialized, initialize]); // Stop playback const stop = useCallback(async () => { const engine = await getEngine(); if (!engine) return; engine.stop(); setCurrentStep(0); }, [getEngine]); // Generate new beat const generateNewBeat = useCallback(async () => { const engine = await getEngine(); if (!engine) return; if (!state.isInitialized) { await initialize(); } engine.generateNewBeat(); }, [getEngine, state.isInitialized, initialize]); // Set BPM const setBpm = useCallback(async (bpm: number) => { const engine = await getEngine(); if (!engine) return; engine.setBpm(bpm); }, [getEngine]); // Set swing const setSwing = useCallback(async (swing: number) => { const engine = await getEngine(); if (!engine) return; engine.setSwing(swing); }, [getEngine]); // Set master volume const setMasterVolume = useCallback(async (volume: number) => { const engine = await getEngine(); if (!engine) return; engine.setMasterVolume(volume); }, [getEngine]); // Set layer volume const setLayerVolume = useCallback(async (layer: LayerName, volume: number) => { const engine = await getEngine(); if (!engine) return; engine.setLayerVolume(layer, volume); }, [getEngine]); // Toggle layer mute const toggleMute = useCallback(async (layer: LayerName) => { const engine = await getEngine(); if (!engine) return; engine.toggleMute(layer); }, [getEngine]); // Cleanup on unmount useEffect(() => { return () => { // Don't dispose on unmount to allow seamless navigation // The engine is a singleton that persists }; }, []); return { state, currentStep, initialize, togglePlayback, stop, generateNewBeat, setBpm, setSwing, setMasterVolume, setLayerVolume, toggleMute, }; }