Lofi_Generator/hooks/useAudioEngine.ts
Avery Felts 0f17775f3f Add multi-genre support and expanded instrument options
- Add 4 genres: Hip Hop, Classical, Trap, Pop with unique patterns
- Add new instrument layers: Bass, Brass, Piano
- Each layer now has 4 instrument variations to choose from
- Add genre-specific drum patterns, chord progressions, and melodies
- Add duration control (1-10 minutes)
- Rename app to "Beat Generator" with modern gradient header
- Redesign UI with 2-column instrument grid layout
- Add color-coded accent for each instrument section

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 17:57:12 -07:00

196 lines
4.5 KiB
TypeScript

'use client';
import { useState, useEffect, useCallback, useRef } from 'react';
import { EngineState, LayerName, Genre } from '@/types/audio';
const defaultState: EngineState = {
isPlaying: false,
isInitialized: false,
bpm: 90,
swing: 0.15,
currentStep: 0,
genre: 'hiphop',
duration: 3,
instruments: {
drums: 'acoustic',
bass: 'synth',
brass: 'trumpet',
piano: 'grand',
chords: 'pad',
ambient: 'rain',
},
volumes: {
master: 0.8,
drums: 0.8,
bass: 0.6,
brass: 0.4,
piano: 0.5,
chords: 0.6,
ambient: 0.4,
},
muted: {
drums: false,
bass: false,
brass: true,
piano: true,
chords: false,
ambient: false,
},
};
export function useAudioEngine() {
const [state, setState] = useState<EngineState>(defaultState);
const [currentStep, setCurrentStep] = useState(0);
const engineRef = useRef<typeof import('@/lib/audio/audioEngine').default | null>(null);
const isInitializingRef = useRef(false);
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;
}, []);
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]);
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]);
const stop = useCallback(async () => {
const engine = await getEngine();
if (!engine) return;
engine.stop();
setCurrentStep(0);
}, [getEngine]);
const generateNewBeat = useCallback(async () => {
const engine = await getEngine();
if (!engine) return;
if (!state.isInitialized) {
await initialize();
}
engine.generateNewBeat();
}, [getEngine, state.isInitialized, initialize]);
const setGenre = useCallback(async (genre: Genre) => {
const engine = await getEngine();
if (!engine) return;
if (!state.isInitialized) {
await initialize();
}
engine.setGenre(genre);
}, [getEngine, state.isInitialized, initialize]);
const setDuration = useCallback(async (minutes: number) => {
const engine = await getEngine();
if (!engine) return;
engine.setDuration(minutes);
}, [getEngine]);
const setInstrument = useCallback(async (layer: LayerName, instrument: string) => {
const engine = await getEngine();
if (!engine) return;
if (!state.isInitialized) {
await initialize();
}
engine.setInstrument(layer, instrument);
}, [getEngine, state.isInitialized, initialize]);
const setBpm = useCallback(async (bpm: number) => {
const engine = await getEngine();
if (!engine) return;
engine.setBpm(bpm);
}, [getEngine]);
const setSwing = useCallback(async (swing: number) => {
const engine = await getEngine();
if (!engine) return;
engine.setSwing(swing);
}, [getEngine]);
const setMasterVolume = useCallback(async (volume: number) => {
const engine = await getEngine();
if (!engine) return;
engine.setMasterVolume(volume);
}, [getEngine]);
const setLayerVolume = useCallback(async (layer: LayerName, volume: number) => {
const engine = await getEngine();
if (!engine) return;
engine.setLayerVolume(layer, volume);
}, [getEngine]);
const toggleMute = useCallback(async (layer: LayerName) => {
const engine = await getEngine();
if (!engine) return;
engine.toggleMute(layer);
}, [getEngine]);
useEffect(() => {
return () => {
// Don't dispose on unmount
};
}, []);
return {
state,
currentStep,
initialize,
togglePlayback,
stop,
generateNewBeat,
setGenre,
setDuration,
setInstrument,
setBpm,
setSwing,
setMasterVolume,
setLayerVolume,
toggleMute,
};
}