Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Audio Visualization Reacting to Beat</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script> | |
| <style> | |
| .visualizer-container { | |
| position: relative; | |
| width: 100%; | |
| height: 300px; | |
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); | |
| border-radius: 12px; | |
| overflow: hidden; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); | |
| } | |
| .bar { | |
| position: absolute; | |
| bottom: 0; | |
| width: 8px; | |
| background: linear-gradient(to top, #00b4db, #0083b0); | |
| border-radius: 4px 4px 0 0; | |
| transition: height 0.05s ease-out; | |
| box-shadow: 0 0 10px rgba(0, 180, 219, 0.7); | |
| } | |
| .beat-circle { | |
| position: absolute; | |
| border-radius: 50%; | |
| background: radial-gradient(circle, rgba(255,105,180,0.8) 0%, rgba(255,20,147,0.5) 70%, transparent 100%); | |
| transform: translate(-50%, -50%); | |
| opacity: 0; | |
| transition: opacity 0.3s, transform 0.3s; | |
| } | |
| .pulse { | |
| animation: pulse 0.5s ease-out; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); opacity: 0.8; } | |
| 100% { transform: scale(1.5); opacity: 0; } | |
| } | |
| .audio-wave { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100px; | |
| background: linear-gradient(to top, rgba(0, 180, 219, 0.1), transparent); | |
| clip-path: polygon(0% 100%, 100% 100%, 100% 0%, 0% 0%); | |
| } | |
| #audioPlayer { | |
| display: none; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4"> | |
| <div class="max-w-4xl w-full"> | |
| <h1 class="text-4xl font-bold mb-2 text-center bg-gradient-to-r from-cyan-400 to-pink-500 bg-clip-text text-transparent"> | |
| Audio React Visualizer | |
| </h1> | |
| <p class="text-gray-400 text-center mb-8">Visualization reacts to both frequency and beat detection</p> | |
| <div class="visualizer-container mb-8" id="visualizer"> | |
| <div class="audio-wave" id="audioWave"></div> | |
| </div> | |
| <div class="flex flex-col md:flex-row gap-4 items-center justify-center"> | |
| <div class="flex-1"> | |
| <input type="file" id="audioUpload" accept="audio/*" class="hidden" /> | |
| <label for="audioUpload" class="cursor-pointer bg-gradient-to-r from-cyan-500 to-blue-500 hover:from-cyan-600 hover:to-blue-600 text-white font-bold py-3 px-6 rounded-lg flex items-center justify-center transition-all duration-300 shadow-lg hover:shadow-xl"> | |
| <i class="fas fa-music mr-2"></i> Choose Audio File | |
| </label> | |
| </div> | |
| <div class="flex-1"> | |
| <button id="playButton" class="bg-gradient-to-r from-pink-500 to-purple-500 hover:from-pink-600 hover:to-purple-600 text-white font-bold py-3 px-6 rounded-lg w-full flex items-center justify-center transition-all duration-300 shadow-lg hover:shadow-xl"> | |
| <i class="fas fa-play mr-2"></i> Play | |
| </button> | |
| </div> | |
| </div> | |
| <audio id="audioPlayer" controls></audio> | |
| <div class="mt-8 grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div class="bg-gray-800 p-4 rounded-lg"> | |
| <h3 class="text-cyan-400 font-semibold mb-2"><i class="fas fa-sliders-h mr-2"></i>Controls</h3> | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-gray-400 mb-1">Sensitivity</label> | |
| <input type="range" id="sensitivity" min="0.1" max="1" step="0.05" value="0.5" class="w-full accent-cyan-500"> | |
| </div> | |
| <div> | |
| <label class="block text-gray-400 mb-1">Bar Count</label> | |
| <input type="range" id="barCount" min="20" max="200" step="10" value="80" class="w-full accent-pink-500"> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-800 p-4 rounded-lg"> | |
| <h3 class="text-purple-400 font-semibold mb-2"><i class="fas fa-chart-bar mr-2"></i>Audio Info</h3> | |
| <div class="space-y-2"> | |
| <p>Status: <span id="status" class="text-pink-400">Waiting for audio</span></p> | |
| <p>Beat: <span id="beatStatus" class="text-cyan-400">No beat detected</span></p> | |
| <p>Volume: <span id="volumeLevel">0</span>%</p> | |
| </div> | |
| </div> | |
| <div class="bg-gray-800 p-4 rounded-lg"> | |
| <h3 class="text-pink-400 font-semibold mb-2"><i class="fas fa-info-circle mr-2"></i>About</h3> | |
| <p class="text-gray-400 text-sm">This visualization analyzes audio frequencies and detects beats in real-time. The bars represent different frequency ranges, while circles appear when beats are detected.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Audio elements | |
| const audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| let audioSource = null; | |
| let analyser = null; | |
| let dataArray = null; | |
| let isPlaying = false; | |
| let animationId = null; | |
| // Beat detection variables | |
| let lastBeatTime = 0; | |
| let beatThreshold = 1.5; | |
| let beatHoldTime = 400; // ms | |
| let beatDecayRate = 0.98; | |
| let beatCutOff = 0; | |
| // DOM elements | |
| const visualizer = document.getElementById('visualizer'); | |
| const audioWave = document.getElementById('audioWave'); | |
| const playButton = document.getElementById('playButton'); | |
| const audioUpload = document.getElementById('audioUpload'); | |
| const audioPlayer = document.getElementById('audioPlayer'); | |
| const statusElement = document.getElementById('status'); | |
| const beatStatusElement = document.getElementById('beatStatus'); | |
| const volumeLevelElement = document.getElementById('volumeLevel'); | |
| const sensitivityInput = document.getElementById('sensitivity'); | |
| const barCountInput = document.getElementById('barCount'); | |
| let barCount = parseInt(barCountInput.value); | |
| let bars = []; | |
| let sensitivity = parseFloat(sensitivityInput.value); | |
| // Create initial bars | |
| function createBars() { | |
| // Clear existing bars | |
| visualizer.querySelectorAll('.bar').forEach(bar => bar.remove()); | |
| bars = []; | |
| const containerWidth = visualizer.clientWidth; | |
| const barWidth = containerWidth / barCount; | |
| for (let i = 0; i < barCount; i++) { | |
| const bar = document.createElement('div'); | |
| bar.className = 'bar'; | |
| bar.style.left = `${i * barWidth}px`; | |
| bar.style.height = '0px'; | |
| visualizer.appendChild(bar); | |
| bars.push(bar); | |
| } | |
| } | |
| createBars(); | |
| // Handle file upload | |
| audioUpload.addEventListener('change', (e) => { | |
| const file = e.target.files[0]; | |
| if (!file) return; | |
| const fileURL = URL.createObjectURL(file); | |
| audioPlayer.src = fileURL; | |
| statusElement.textContent = 'Ready to play'; | |
| statusElement.className = 'text-green-400'; | |
| // Reset visualization | |
| if (isPlaying) { | |
| stopAudio(); | |
| } | |
| // Setup audio when ready | |
| audioPlayer.oncanplaythrough = () => { | |
| setupAudio(); | |
| }; | |
| }); | |
| // Play button click | |
| playButton.addEventListener('click', () => { | |
| if (!audioPlayer.src) { | |
| statusElement.textContent = 'No audio selected'; | |
| statusElement.className = 'text-red-400'; | |
| return; | |
| } | |
| if (isPlaying) { | |
| stopAudio(); | |
| playButton.innerHTML = '<i class="fas fa-play mr-2"></i> Play'; | |
| } else { | |
| startAudio(); | |
| playButton.innerHTML = '<i class="fas fa-pause mr-2"></i> Pause'; | |
| } | |
| }); | |
| // Setup audio context and analyzer | |
| function setupAudio() { | |
| if (audioSource) { | |
| audioSource.disconnect(); | |
| } | |
| analyser = audioContext.createAnalyser(); | |
| analyser.fftSize = 2048; | |
| audioSource = audioContext.createMediaElementSource(audioPlayer); | |
| audioSource.connect(analyser); | |
| analyser.connect(audioContext.destination); | |
| const bufferLength = analyser.frequencyBinCount; | |
| dataArray = new Uint8Array(bufferLength); | |
| } | |
| // Start audio playback and visualization | |
| function startAudio() { | |
| if (audioContext.state === 'suspended') { | |
| audioContext.resume(); | |
| } | |
| audioPlayer.play(); | |
| isPlaying = true; | |
| statusElement.textContent = 'Playing'; | |
| statusElement.className = 'text-green-400'; | |
| // Start visualization | |
| visualize(); | |
| } | |
| // Stop audio playback and visualization | |
| function stopAudio() { | |
| audioPlayer.pause(); | |
| isPlaying = false; | |
| statusElement.textContent = 'Paused'; | |
| statusElement.className = 'text-yellow-400'; | |
| // Stop visualization | |
| cancelAnimationFrame(animationId); | |
| // Reset bars | |
| bars.forEach(bar => { | |
| bar.style.height = '0px'; | |
| }); | |
| } | |
| // Beat detection function | |
| function detectBeat(level) { | |
| if (level > beatCutOff && level > beatThreshold) { | |
| // Beat detected | |
| beatCutOff = level * 1.1; | |
| lastBeatTime = Date.now(); | |
| // Create beat circle | |
| createBeatCircle(); | |
| return true; | |
| } else { | |
| // Decay beat cutoff | |
| if (Date.now() - lastBeatTime > beatHoldTime) { | |
| beatCutOff *= beatDecayRate; | |
| beatCutOff = Math.max(beatCutOff, beatThreshold); | |
| } | |
| return false; | |
| } | |
| } | |
| // Create a beat circle at random position | |
| function createBeatCircle() { | |
| const circle = document.createElement('div'); | |
| circle.className = 'beat-circle pulse'; | |
| // Random position | |
| const x = Math.random() * visualizer.clientWidth; | |
| const y = Math.random() * visualizer.clientHeight * 0.7; | |
| const size = 30 + Math.random() * 70; | |
| circle.style.left = `${x}px`; | |
| circle.style.top = `${y}px`; | |
| circle.style.width = `${size}px`; | |
| circle.style.height = `${size}px`; | |
| visualizer.appendChild(circle); | |
| // Remove after animation | |
| setTimeout(() => { | |
| circle.remove(); | |
| }, 500); | |
| } | |
| // Main visualization function | |
| function visualize() { | |
| animationId = requestAnimationFrame(visualize); | |
| analyser.getByteFrequencyData(dataArray); | |
| // Calculate average volume | |
| let sum = 0; | |
| for (let i = 0; i < dataArray.length; i++) { | |
| sum += dataArray[i]; | |
| } | |
| const average = sum / dataArray.length; | |
| const volumePercent = Math.min(Math.round((average / 255) * 100), 100); | |
| volumeLevelElement.textContent = volumePercent; | |
| // Check for beat | |
| if (detectBeat(average * sensitivity)) { | |
| beatStatusElement.textContent = 'Beat detected!'; | |
| beatStatusElement.className = 'text-pink-400 animate-pulse'; | |
| setTimeout(() => { | |
| beatStatusElement.textContent = 'Listening...'; | |
| beatStatusElement.className = 'text-cyan-400'; | |
| }, 200); | |
| } | |
| // Update bars | |
| const barGroupSize = Math.floor(dataArray.length / barCount); | |
| for (let i = 0; i < barCount; i++) { | |
| const start = i * barGroupSize; | |
| let sum = 0; | |
| for (let j = start; j < start + barGroupSize; j++) { | |
| sum += dataArray[j]; | |
| } | |
| const average = sum / barGroupSize; | |
| const height = (average / 255) * visualizer.clientHeight * 1.2; | |
| bars[i].style.height = `${height}px`; | |
| bars[i].style.opacity = `${0.2 + (height / visualizer.clientHeight) * 0.8}`; | |
| } | |
| // Update audio wave | |
| analyser.getByteTimeDomainData(dataArray); | |
| let wavePath = 'path(\'M0 ' + (visualizer.clientHeight / 2) + ' '; | |
| for (let i = 0; i < dataArray.length; i++) { | |
| const x = (i / dataArray.length) * visualizer.clientWidth; | |
| const y = (dataArray[i] / 255) * visualizer.clientHeight; | |
| wavePath += 'L' + x + ' ' + y + ' '; | |
| } | |
| wavePath += 'L' + visualizer.clientWidth + ' ' + (visualizer.clientHeight / 2) + ' Z\')'; | |
| audioWave.style.clipPath = wavePath; | |
| } | |
| // Handle sensitivity change | |
| sensitivityInput.addEventListener('input', () => { | |
| sensitivity = parseFloat(sensitivityInput.value); | |
| }); | |
| // Handle bar count change | |
| barCountInput.addEventListener('input', () => { | |
| barCount = parseInt(barCountInput.value); | |
| createBars(); | |
| }); | |
| // Handle window resize | |
| window.addEventListener('resize', () => { | |
| createBars(); | |
| }); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=omar1232/audio-react-visualizer" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |