Files
pomodoro/src/hooks/useAmbientSound.ts
T
Puechberty Arthur b933c6040c feat: implement TodoList component with filtering and priority management
feat: add useAmbientSound hook for ambient sound management

feat: create useLocalStorage hook for persistent state management

feat: develop useTheme hook for theme switching functionality

feat: implement useTimer hook for Pomodoro timer logic

feat: create useTodos hook for managing todo list functionality

style: add global styles and custom scrollbar for better UI experience

chore: set up main entry point for the application

feat: define types for Timer, Todo, and Statistics

feat: create utility function for class name merging

chore: configure Tailwind CSS for styling

chore: set up TypeScript configuration for the project

chore: configure Vite for development and build process
2026-03-30 19:27:27 +02:00

170 lines
5.0 KiB
TypeScript

import { useState, useRef, useCallback, useEffect } from 'react';
export type SoundType = 'none' | 'rain' | 'cafe' | 'whitenoise' | 'forest' | 'ocean';
export function useAmbientSound() {
const [currentSound, setCurrentSound] = useState<SoundType>('none');
const [volume, setVolume] = useState(0.5);
const [isPlaying, setIsPlaying] = useState(false);
const audioContextRef = useRef<AudioContext | null>(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,
};
}