import { useState, useRef, useCallback, useEffect } from 'react'; export type SoundType = 'none' | 'rain' | 'cafe' | 'whitenoise' | 'forest' | 'ocean'; export function useAmbientSound() { const [currentSound, setCurrentSound] = useState('none'); const [volume, setVolume] = useState(0.5); const [isPlaying, setIsPlaying] = useState(false); const audioContextRef = useRef(null); const nodesRef = useRef<{ sources: AudioBufferSourceNode[]; gains: GainNode[]; masterGain: GainNode | null; }>({ sources: [], gains: [], masterGain: null }); const stopSound = useCallback(() => { nodesRef.current.sources.forEach(source => { try { source.stop(); } catch (e) {} }); nodesRef.current.sources = []; nodesRef.current.gains = []; setIsPlaying(false); }, []); const createNoiseBuffer = useCallback((ctx: AudioContext, type: 'white' | 'brown' | 'pink') => { const bufferSize = ctx.sampleRate * 2; const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate); const output = buffer.getChannelData(0); if (type === 'white') { for (let i = 0; i < bufferSize; i++) { output[i] = Math.random() * 2 - 1; } } else if (type === 'brown') { let lastOut = 0; for (let i = 0; i < bufferSize; i++) { const white = Math.random() * 2 - 1; output[i] = (lastOut + 0.02 * white) / 1.02; lastOut = output[i]; output[i] *= 3.5; } } else if (type === 'pink') { let b0 = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0; for (let i = 0; i < bufferSize; i++) { const white = Math.random() * 2 - 1; b0 = 0.99886 * b0 + white * 0.0555179; b1 = 0.99332 * b1 + white * 0.0750759; b2 = 0.96900 * b2 + white * 0.1538520; b3 = 0.86650 * b3 + white * 0.3104856; b4 = 0.55000 * b4 + white * 0.5329522; b5 = -0.7616 * b5 - white * 0.0168980; output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; output[i] *= 0.11; b6 = white * 0.115926; } } return buffer; }, []); const playSound = useCallback((type: SoundType) => { stopSound(); if (type === 'none') { setCurrentSound('none'); return; } if (!audioContextRef.current) { audioContextRef.current = new AudioContext(); } const ctx = audioContextRef.current; const masterGain = ctx.createGain(); masterGain.gain.value = volume; masterGain.connect(ctx.destination); nodesRef.current.masterGain = masterGain; const createLoopingSource = (buffer: AudioBuffer, gainValue: number = 1) => { const source = ctx.createBufferSource(); const gain = ctx.createGain(); source.buffer = buffer; source.loop = true; gain.gain.value = gainValue; source.connect(gain); gain.connect(masterGain); source.start(); nodesRef.current.sources.push(source); nodesRef.current.gains.push(gain); return { source, gain }; }; switch (type) { case 'rain': { // Bruit de pluie = bruit rose + bruit brun const pinkBuffer = createNoiseBuffer(ctx, 'pink'); const brownBuffer = createNoiseBuffer(ctx, 'brown'); createLoopingSource(pinkBuffer, 0.6); createLoopingSource(brownBuffer, 0.3); break; } case 'cafe': { // Ambiance café = bruit brun léger + variations const brownBuffer = createNoiseBuffer(ctx, 'brown'); const pinkBuffer = createNoiseBuffer(ctx, 'pink'); createLoopingSource(brownBuffer, 0.4); createLoopingSource(pinkBuffer, 0.15); break; } case 'whitenoise': { const whiteBuffer = createNoiseBuffer(ctx, 'white'); createLoopingSource(whiteBuffer, 0.3); break; } case 'forest': { // Forêt = bruit rose doux const pinkBuffer = createNoiseBuffer(ctx, 'pink'); const brownBuffer = createNoiseBuffer(ctx, 'brown'); createLoopingSource(pinkBuffer, 0.25); createLoopingSource(brownBuffer, 0.15); break; } case 'ocean': { // Océan = bruit brun const brownBuffer = createNoiseBuffer(ctx, 'brown'); createLoopingSource(brownBuffer, 0.7); break; } } setCurrentSound(type); setIsPlaying(true); }, [volume, stopSound, createNoiseBuffer]); const changeVolume = useCallback((newVolume: number) => { setVolume(newVolume); if (nodesRef.current.masterGain) { nodesRef.current.masterGain.gain.value = newVolume; } }, []); const toggleSound = useCallback(() => { if (isPlaying) { stopSound(); } else if (currentSound !== 'none') { playSound(currentSound); } }, [isPlaying, currentSound, playSound, stopSound]); useEffect(() => { return () => { stopSound(); if (audioContextRef.current) { audioContextRef.current.close(); } }; }, [stopSound]); return { currentSound, volume, isPlaying, playSound, stopSound, changeVolume, toggleSound, }; }