diff --git a/src/App.js b/src/App.js index ee887f6..d8223c9 100644 --- a/src/App.js +++ b/src/App.js @@ -20,6 +20,7 @@ import CreateContractPage from './pages/CreateContractPage'; import SkydivingGame from './components/SkydivingGame'; import StrangerThings from './components/stranger-things/StrangerThings'; import DotChaser from './components/dot-chaser/DotChaser'; +import AudioVisualizer from './components/equalizer/AudioVisualizer'; library.add(faArrowDown, faCloud); @@ -41,8 +42,11 @@ const App = () => - } - /> + + } /> + + } /> diff --git a/src/components/equalizer/AudioVisualizer.js b/src/components/equalizer/AudioVisualizer.js new file mode 100644 index 0000000..b8c9c8d --- /dev/null +++ b/src/components/equalizer/AudioVisualizer.js @@ -0,0 +1,155 @@ +import React, { useState, useRef, useEffect } from 'react'; + +const numRings = 15; +const segmentsPerRing = 25; + +const AudioVisualizer = () => { + const [rings, setRings] = useState([]); + const audioContextRef = useRef(null); + const analyserRef = useRef(null); + const animationFrameIdRef = useRef(null); + const rotationRef = useRef(0); + const [viewportSize, setViewportSize] = useState({ width: window.innerWidth, height: window.innerHeight }); + + useEffect(() => { + const handleResize = () => { + setViewportSize({ width: window.innerWidth, height: window.innerHeight }); + }; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + useEffect(() => { + const maxRadius = Math.min(viewportSize.width, viewportSize.height) / 2 - 50; + const initialRings = Array.from({ length: numRings }, (_, i) => { + const baseRadius = (i / numRings) * maxRadius + 20; + const segments = Array.from({ length: segmentsPerRing }, (_, j) => ({ + id: j, + angle: (j / segmentsPerRing) * 2 * Math.PI, + radius: baseRadius, + })); + return { + id: i, + color: `hsl(${i * (360 / numRings)}, 80%, 50%)`, + baseRadius: baseRadius, + segments: segments, + amplitudeFactor: 0, + }; + }); + setRings(initialRings); + + const setupAudio = async () => { + try { + const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); + audioContextRef.current = new (window.AudioContext || ('webkitAudioContext' in window && window.webkitAudioContext))(); + analyserRef.current = audioContextRef.current.createAnalyser(); + analyserRef.current.fftSize = 256; + + const source = audioContextRef.current.createMediaStreamSource(stream); + source.connect(analyserRef.current); + + const bufferLength = analyserRef.current.frequencyBinCount; + const dataArray = new Uint8Array(bufferLength); + + const draw = () => { + animationFrameIdRef.current = requestAnimationFrame(draw); + analyserRef.current?.getByteFrequencyData(dataArray); + + const sum = dataArray.reduce((acc, val) => acc + val, 0); + const avgAmplitude = sum / bufferLength; + rotationRef.current += avgAmplitude ** 1.6 / 1000; + + setRings(prevRings => + prevRings.map(ring => { + const firstNBinsToRender = 40; + const binIndex = Math.floor(ring.id * (firstNBinsToRender / numRings)); + const frequencyAmplitude = dataArray[binIndex] || 0; + const amplitudeFactor = frequencyAmplitude / 50; + + const newSegments = ring.segments.map(segment => + ({ ...segment, radius: ring.baseRadius + amplitudeFactor * (viewportSize.width / 150) })); + + return { + ...ring, + segments: newSegments, + amplitudeFactor + }; + }) + ); + }; + draw(); + } catch (err) { + console.error('Error accessing the microphone:', err); + } + }; + + if (navigator.mediaDevices?.getUserMedia !== undefined) { + setupAudio(); + } else { + console.error('getUserMedia not supported on your browser!'); + } + + return () => { + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current); + } + if (audioContextRef.current) { + audioContextRef.current.close(); + } + }; + }, [viewportSize]); + + const containerStyle = { + position: 'fixed', + top: 0, + left: 0, + width: '100vw', + height: '100vh', + backgroundColor: '#000', + overflow: 'hidden', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }; + + const visualizerWrapperStyle = { + position: 'relative', + width: '100%', + height: '100%', + transition: 'transform 0.1s', + transform: `rotate(${rotationRef.current}deg)`, + }; + + return ( +
+
+ {rings.map(ring => { + const ringDotSize = Math.round(3 + ring.amplitudeFactor * (viewportSize.width / 2000)); + return ( +
+ {ring.segments.map(segment => ( +
+ ))} +
+ ); + })} +
+
+ ); +}; + +export default AudioVisualizer;