'use client'; import { useState, useEffect, useRef } from 'react'; import { Howl } from 'howler'; interface Sound { id: number; name: string; file: string; category: string; } interface ActiveSound { id: number; volume: number; howl?: Howl; playing: boolean; } export default function Mixer() { const [sounds, setSounds] = useState([]); const [activeSounds, setActiveSounds] = useState([]); const [isLoading, setIsLoading] = useState(true); const [masterPlaying, setMasterPlaying] = useState(false); const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:5000'; // Fetch sounds from the API useEffect(() => { const fetchSounds = async () => { try { // In a real app, fetch from API // const response = await fetch(`${backendUrl}/api/sounds`); // const data = await response.json(); // For MVP, use hardcoded data const data = [ { id: 1, name: 'Rain', file: '/sounds/rain.mp3', category: 'Nature' }, { id: 2, name: 'Thunder', file: '/sounds/thunder.mp3', category: 'Nature' }, { id: 3, name: 'Forest', file: '/sounds/forest.mp3', category: 'Nature' }, { id: 4, name: 'Ocean Waves', file: '/sounds/ocean.mp3', category: 'Water' }, { id: 5, name: 'Fireplace', file: '/sounds/fire.mp3', category: 'Ambience' }, { id: 6, name: 'White Noise', file: '/sounds/white-noise.mp3', category: 'Noise' }, { id: 7, name: 'Coffee Shop', file: '/sounds/coffee-shop.mp3', category: 'Ambience' }, { id: 8, name: 'Birds', file: '/sounds/birds.mp3', category: 'Nature' } ]; setSounds(data); setIsLoading(false); } catch (error) { console.error('Error fetching sounds:', error); setIsLoading(false); } }; fetchSounds(); }, [backendUrl]); // Handle adding a sound to the active mix const addSound = (sound: Sound) => { // Check if sound is already in the mix if (activeSounds.some(as => as.id === sound.id)) { return; } // Create a new Howl instance for the sound const howl = new Howl({ src: [sound.file], loop: true, volume: 0.5, html5: true, }); setActiveSounds(prev => [ ...prev, { id: sound.id, volume: 0.5, howl, playing: false } ]); }; // Handle removing a sound from the active mix const removeSound = (id: number) => { setActiveSounds(prev => { const updatedSounds = prev.filter(sound => sound.id !== id); const soundToRemove = prev.find(sound => sound.id === id); // Stop the sound if it's playing if (soundToRemove?.howl && soundToRemove.playing) { soundToRemove.howl.stop(); } return updatedSounds; }); }; // Handle volume change for a specific sound const handleVolumeChange = (id: number, volume: number) => { setActiveSounds(prev => prev.map(sound => { if (sound.id === id) { if (sound.howl) { sound.howl.volume(volume); } return { ...sound, volume }; } return sound; }) ); }; // Play or pause all active sounds const toggleMasterPlayback = () => { const newPlayingState = !masterPlaying; setMasterPlaying(newPlayingState); setActiveSounds(prev => prev.map(sound => { if (sound.howl) { if (newPlayingState) { sound.howl.play(); } else { sound.howl.pause(); } } return { ...sound, playing: newPlayingState }; }) ); }; // Group sounds by category const soundsByCategory = sounds.reduce((acc, sound) => { if (!acc[sound.category]) { acc[sound.category] = []; } acc[sound.category].push(sound); return acc; }, {} as Record); // Cleanup on unmount useEffect(() => { return () => { activeSounds.forEach(sound => { if (sound.howl) { sound.howl.stop(); } }); }; }, [activeSounds]); if (isLoading) { return (
); } return (

Sound Mixer

{/* Sound Library */}

Sound Library

{Object.entries(soundsByCategory).map(([category, categorySounds]) => (

{category}

{categorySounds.map(sound => (
addSound(sound)} > {sound.name}
))}
))}
{/* Sound Mixer */}

Your Mix

{activeSounds.length === 0 ? (

Add sounds from the library to start mixing

) : (
{activeSounds.map(sound => { const soundDetails = sounds.find(s => s.id === sound.id); return (
{soundDetails?.name}
0% handleVolumeChange(sound.id, parseFloat(e.target.value))} className="w-full accent-emerald-500" /> 100%
); })}
)}
); }