| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Advanced 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); |
| filter: drop-shadow(0 0 5px currentColor); |
| } |
| |
| .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; |
| } |
| |
| .particle { |
| position: absolute; |
| background-color: currentColor; |
| border-radius: 50%; |
| pointer-events: none; |
| } |
| |
| .progress-container { |
| height: 4px; |
| background-color: rgba(255, 255, 255, 0.1); |
| border-radius: 2px; |
| overflow: hidden; |
| } |
| |
| .progress-bar { |
| height: 100%; |
| background: linear-gradient(to right, #00b4db, #0083b0); |
| width: 0%; |
| transition: width 0.1s linear; |
| } |
| |
| .loading-spinner { |
| border: 3px solid rgba(255, 255, 255, 0.1); |
| border-radius: 50%; |
| border-top: 3px solid #00b4db; |
| width: 30px; |
| height: 30px; |
| animation: spin 1s linear infinite; |
| } |
| |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| .circular-visualizer { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| width: 200px; |
| height: 200px; |
| border-radius: 50%; |
| } |
| |
| .circular-bar { |
| position: absolute; |
| width: 4px; |
| background: linear-gradient(to top, #00b4db, #0083b0); |
| transform-origin: bottom center; |
| border-radius: 2px; |
| box-shadow: 0 0 5px rgba(0, 180, 219, 0.7); |
| } |
| |
| .theme-neon .bar { |
| background: linear-gradient(to top, #00ff9d, #00f7ff); |
| box-shadow: 0 0 10px rgba(0, 255, 157, 0.7); |
| } |
| |
| .theme-sunset .bar { |
| background: linear-gradient(to top, #ff7b00, #ff00aa); |
| box-shadow: 0 0 10px rgba(255, 0, 170, 0.7); |
| } |
| |
| .theme-ocean .bar { |
| background: linear-gradient(to top, #0061ff, #00ffea); |
| box-shadow: 0 0 10px rgba(0, 97, 255, 0.7); |
| } |
| |
| .theme-neon .beat-circle { |
| background: radial-gradient(circle, rgba(0, 255, 157, 0.8) 0%, rgba(0, 247, 255, 0.5) 70%, transparent 100%); |
| } |
| |
| .theme-sunset .beat-circle { |
| background: radial-gradient(circle, rgba(255, 123, 0, 0.8) 0%, rgba(255, 0, 170, 0.5) 70%, transparent 100%); |
| } |
| |
| .theme-ocean .beat-circle { |
| background: radial-gradient(circle, rgba(0, 97, 255, 0.8) 0%, rgba(0, 255, 234, 0.5) 70%, transparent 100%); |
| } |
| |
| .light-mode { |
| background-color: #f8f9fa; |
| color: #212529; |
| } |
| |
| .light-mode .bg-gray-800 { |
| background-color: #e9ecef !important; |
| } |
| |
| .light-mode .text-gray-400 { |
| color: #6c757d !important; |
| } |
| |
| .light-mode .visualizer-container { |
| background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%); |
| } |
| |
| .light-mode .progress-container { |
| background-color: rgba(0, 0, 0, 0.1); |
| } |
| |
| .tooltip { |
| position: relative; |
| display: inline-block; |
| } |
| |
| .tooltip .tooltip-text { |
| visibility: hidden; |
| width: 120px; |
| background-color: #333; |
| color: #fff; |
| text-align: center; |
| border-radius: 6px; |
| padding: 5px; |
| position: absolute; |
| z-index: 1; |
| bottom: 125%; |
| left: 50%; |
| margin-left: -60px; |
| opacity: 0; |
| transition: opacity 0.3s; |
| } |
| |
| .tooltip:hover .tooltip-text { |
| visibility: visible; |
| opacity: 1; |
| } |
| |
| .light-mode .tooltip .tooltip-text { |
| background-color: #555; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4 transition-colors duration-300"> |
| <div class="max-w-4xl w-full"> |
| <div class="flex justify-between items-center mb-4"> |
| <h1 class="text-4xl font-bold bg-gradient-to-r from-cyan-400 to-pink-500 bg-clip-text text-transparent"> |
| Advanced Audio Visualizer |
| </h1> |
| <div class="flex items-center space-x-4"> |
| <button id="fullscreenBtn" class="bg-gray-800 hover:bg-gray-700 p-2 rounded-lg transition-all duration-300"> |
| <i class="fas fa-expand"></i> |
| </button> |
| <button id="themeToggle" class="bg-gray-800 hover:bg-gray-700 p-2 rounded-lg transition-all duration-300"> |
| <i class="fas fa-moon"></i> |
| </button> |
| </div> |
| </div> |
| |
| <p class="text-gray-400 text-center mb-8">Visualization reacts to frequency, beat detection, and includes advanced effects</p> |
| |
| <div class="visualizer-container mb-4" id="visualizer"> |
| <div class="audio-wave" id="audioWave"></div> |
| <div class="circular-visualizer hidden" id="circularVisualizer"></div> |
| </div> |
| |
| <div class="progress-container mb-4"> |
| <div class="progress-bar" id="progressBar"></div> |
| </div> |
| |
| <div class="flex items-center justify-between mb-4"> |
| <span id="currentTime" class="text-sm text-gray-400">0:00</span> |
| <span id="duration" class="text-sm text-gray-400">0:00</span> |
| </div> |
| |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8"> |
| <div class="flex flex-col md:flex-row gap-4 items-center justify-center md:col-span-2"> |
| <div class="flex-1 w-full"> |
| <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 w-full"> |
| <i class="fas fa-music mr-2"></i> |
| <span id="uploadText">Choose Audio File</span> |
| <div id="loadingSpinner" class="loading-spinner ml-2 hidden"></div> |
| </label> |
| </div> |
| <div class="flex-1 w-full"> |
| <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> |
| |
| <div class="flex items-center"> |
| <i class="fas fa-volume-up mr-2 text-gray-400"></i> |
| <input type="range" id="volumeControl" min="0" max="1" step="0.01" value="0.7" class="w-full accent-cyan-500"> |
| </div> |
| </div> |
| |
| <audio id="audioPlayer" controls></audio> |
| |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8"> |
| <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 flex justify-between"> |
| <span>Sensitivity <span class="tooltip"><i class="fas fa-info-circle"></i><span class="tooltip-text">Adjust beat detection sensitivity</span></span></span> |
| <span id="sensitivityValue">0.5</span> |
| </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 flex justify-between"> |
| <span>Bar Count <span class="tooltip"><i class="fas fa-info-circle"></i><span class="tooltip-text">Number of frequency bars</span></span></span> |
| <span id="barCountValue">80</span> |
| </label> |
| <input type="range" id="barCount" min="20" max="200" step="10" value="80" class="w-full accent-cyan-500"> |
| </div> |
| <div> |
| <label class="block text-gray-400 mb-1 flex justify-between"> |
| <span>Bar Spacing <span class="tooltip"><i class="fas fa-info-circle"></i><span class="tooltip-text">Space between bars</span></span></span> |
| <span id="barSpacingValue">1</span> |
| </label> |
| <input type="range" id="barSpacing" min="0" max="2" step="0.1" value="1" class="w-full accent-cyan-500"> |
| </div> |
| <div> |
| <label class="block text-gray-400 mb-1 flex justify-between"> |
| <span>Reverb <span class="tooltip"><i class="fas fa-info-circle"></i><span class="tooltip-text">Add space effect to audio</span></span></span> |
| <span id="reverbValue">0</span> |
| </label> |
| <input type="range" id="reverb" min="0" max="1" step="0.1" value="0" class="w-full accent-cyan-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">70</span>%</p> |
| <p>Duration: <span id="durationInfo">0:00</span></p> |
| <p>File: <span id="fileName" class="truncate">None</span></p> |
| </div> |
| |
| <h3 class="text-purple-400 font-semibold mt-4 mb-2"><i class="fas fa-palette mr-2"></i>Theme</h3> |
| <select id="themeSelect" class="w-full bg-gray-700 text-white rounded p-2"> |
| <option value="default">Default</option> |
| <option value="neon">Neon</option> |
| <option value="sunset">Sunset</option> |
| <option value="ocean">Ocean</option> |
| </select> |
| |
| <h3 class="text-purple-400 font-semibold mt-4 mb-2"><i class="fas fa-eye mr-2"></i>Visual Mode</h3> |
| <select id="visualMode" class="w-full bg-gray-700 text-white rounded p-2"> |
| <option value="bars">Bars</option> |
| <option value="circular">Circular</option> |
| <option value="hybrid">Hybrid</option> |
| </select> |
| |
| <h3 class="text-purple-400 font-semibold mt-4 mb-2"><i class="fas fa-equalizer mr-2"></i>EQ Preset</h3> |
| <select id="eqPreset" class="w-full bg-gray-700 text-white rounded p-2"> |
| <option value="flat">Flat</option> |
| <option value="bass">Bass Boost</option> |
| <option value="treble">Treble Boost</option> |
| <option value="vocal">Vocal Boost</option> |
| </select> |
| </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 mb-4">This advanced visualization analyzes audio frequencies and detects beats in real-time with multiple visual effects and customization options.</p> |
| |
| <div class="flex space-x-2 mb-4"> |
| <button id="captureBtn" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded flex items-center transition-all duration-300"> |
| <i class="fas fa-camera mr-2"></i> Capture |
| </button> |
| <button id="shareBtn" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded flex items-center transition-all duration-300"> |
| <i class="fas fa-share-alt mr-2"></i> Share |
| </button> |
| </div> |
| |
| <div id="errorMessage" class="hidden bg-red-900 text-white p-2 rounded text-sm mb-4"></div> |
| |
| <div class="text-xs text-gray-500 mt-4"> |
| <p>Keyboard shortcuts:</p> |
| <p>Space: Play/Pause</p> |
| <p>↑/↓: Adjust sensitivity</p> |
| <p>←/→: Seek audio</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', () => { |
| |
| const audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
| let audioSource = null; |
| let analyser = null; |
| let dataArray = null; |
| let isPlaying = false; |
| let animationId = null; |
| let reverbNode = null; |
| let eqNodes = []; |
| |
| |
| let lastBeatTime = 0; |
| let beatThreshold = 1.5; |
| let beatHoldTime = 400; |
| let beatDecayRate = 0.98; |
| let beatCutOff = 0; |
| |
| |
| const visualizer = document.getElementById('visualizer'); |
| const audioWave = document.getElementById('audioWave'); |
| const circularVisualizer = document.getElementById('circularVisualizer'); |
| 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'); |
| const barSpacingInput = document.getElementById('barSpacing'); |
| const reverbInput = document.getElementById('reverb'); |
| const volumeControl = document.getElementById('volumeControl'); |
| const progressBar = document.getElementById('progressBar'); |
| const currentTimeElement = document.getElementById('currentTime'); |
| const durationElement = document.getElementById('duration'); |
| const durationInfoElement = document.getElementById('durationInfo'); |
| const fileNameElement = document.getElementById('fileName'); |
| const loadingSpinner = document.getElementById('loadingSpinner'); |
| const uploadText = document.getElementById('uploadText'); |
| const errorMessage = document.getElementById('errorMessage'); |
| const themeSelect = document.getElementById('themeSelect'); |
| const visualMode = document.getElementById('visualMode'); |
| const eqPreset = document.getElementById('eqPreset'); |
| const fullscreenBtn = document.getElementById('fullscreenBtn'); |
| const themeToggle = document.getElementById('themeToggle'); |
| const captureBtn = document.getElementById('captureBtn'); |
| const shareBtn = document.getElementById('shareBtn'); |
| const sensitivityValue = document.getElementById('sensitivityValue'); |
| const barCountValue = document.getElementById('barCountValue'); |
| const barSpacingValue = document.getElementById('barSpacingValue'); |
| const reverbValue = document.getElementById('reverbValue'); |
| |
| let barCount = parseInt(barCountInput.value); |
| let bars = []; |
| let circularBars = []; |
| let sensitivity = parseFloat(sensitivityInput.value); |
| let barSpacing = parseFloat(barSpacingInput.value); |
| let reverbAmount = parseFloat(reverbInput.value); |
| let particles = []; |
| let currentTheme = 'default'; |
| let isDarkMode = true; |
| |
| |
| function createBars() { |
| |
| visualizer.querySelectorAll('.bar').forEach(bar => bar.remove()); |
| circularVisualizer.querySelectorAll('.circular-bar').forEach(bar => bar.remove()); |
| bars = []; |
| circularBars = []; |
| |
| const containerWidth = visualizer.clientWidth; |
| const barWidth = Math.max(2, (containerWidth / barCount) - barSpacing); |
| |
| |
| for (let i = 0; i < barCount; i++) { |
| const bar = document.createElement('div'); |
| bar.className = 'bar'; |
| bar.style.left = `${i * (barWidth + barSpacing)}px`; |
| bar.style.width = `${barWidth}px`; |
| bar.style.height = '0px'; |
| visualizer.appendChild(bar); |
| bars.push(bar); |
| } |
| |
| |
| const radius = Math.min(circularVisualizer.clientWidth, circularVisualizer.clientHeight) / 2 - 10; |
| for (let i = 0; i < barCount; i++) { |
| const angle = (i * 2 * Math.PI) / barCount; |
| const bar = document.createElement('div'); |
| bar.className = 'circular-bar'; |
| bar.style.left = `${radius + radius * Math.cos(angle)}px`; |
| bar.style.top = `${radius + radius * Math.sin(angle)}px`; |
| bar.style.height = '0px'; |
| bar.style.transform = `rotate(${angle}rad) translateY(-${radius}px)`; |
| circularVisualizer.appendChild(bar); |
| circularBars.push(bar); |
| } |
| } |
| |
| createBars(); |
| |
| |
| function setupReverb() { |
| if (reverbNode) { |
| reverbNode.disconnect(); |
| } |
| |
| reverbNode = audioContext.createConvolver(); |
| |
| |
| const length = audioContext.sampleRate * reverbAmount * 2; |
| const impulse = audioContext.createBuffer(2, length, audioContext.sampleRate); |
| const leftChannel = impulse.getChannelData(0); |
| const rightChannel = impulse.getChannelData(1); |
| |
| for (let i = 0; i < length; i++) { |
| const n = length - i; |
| leftChannel[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, 10); |
| rightChannel[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, 10); |
| } |
| |
| reverbNode.buffer = impulse; |
| |
| if (audioSource && analyser) { |
| audioSource.disconnect(); |
| audioSource.connect(reverbNode); |
| reverbNode.connect(analyser); |
| analyser.connect(audioContext.destination); |
| } |
| } |
| |
| |
| function setupEQ() { |
| |
| eqNodes.forEach(node => node.disconnect()); |
| eqNodes = []; |
| |
| const preset = eqPreset.value; |
| |
| if (preset === 'flat') { |
| |
| return; |
| } |
| |
| |
| if (preset === 'bass') { |
| |
| const lowPass = audioContext.createBiquadFilter(); |
| lowPass.type = "lowshelf"; |
| lowPass.frequency.value = 250; |
| lowPass.gain.value = 8; |
| eqNodes.push(lowPass); |
| } else if (preset === 'treble') { |
| |
| const highPass = audioContext.createBiquadFilter(); |
| highPass.type = "highshelf"; |
| highPass.frequency.value = 4000; |
| highPass.gain.value = 8; |
| eqNodes.push(highPass); |
| } else if (preset === 'vocal') { |
| |
| const bandPass = audioContext.createBiquadFilter(); |
| bandPass.type = "peaking"; |
| bandPass.frequency.value = 1500; |
| bandPass.Q.value = 1; |
| bandPass.gain.value = 10; |
| eqNodes.push(bandPass); |
| } |
| |
| |
| if (audioSource && analyser) { |
| audioSource.disconnect(); |
| |
| let lastNode = audioSource; |
| eqNodes.forEach(node => { |
| lastNode.connect(node); |
| lastNode = node; |
| }); |
| |
| lastNode.connect(reverbNode || analyser); |
| if (reverbNode) { |
| reverbNode.connect(analyser); |
| } |
| analyser.connect(audioContext.destination); |
| } |
| } |
| |
| |
| audioUpload.addEventListener('change', (e) => { |
| const file = e.target.files[0]; |
| if (!file) return; |
| |
| |
| uploadText.textContent = 'Processing...'; |
| loadingSpinner.classList.remove('hidden'); |
| errorMessage.classList.add('hidden'); |
| |
| |
| if (!file.type.match('audio.*')) { |
| showError('Please select an audio file'); |
| return; |
| } |
| |
| |
| if (file.size > 10 * 1024 * 1024) { |
| showError('File too large (max 10MB)'); |
| return; |
| } |
| |
| const fileURL = URL.createObjectURL(file); |
| audioPlayer.src = fileURL; |
| fileNameElement.textContent = file.name; |
| |
| |
| if (isPlaying) { |
| stopAudio(); |
| } |
| |
| |
| audioPlayer.oncanplaythrough = () => { |
| uploadText.textContent = 'Choose Audio File'; |
| loadingSpinner.classList.add('hidden'); |
| statusElement.textContent = 'Ready to play'; |
| statusElement.className = 'text-green-400'; |
| |
| |
| durationElement.textContent = formatTime(audioPlayer.duration); |
| durationInfoElement.textContent = formatTime(audioPlayer.duration); |
| |
| setupAudio(); |
| }; |
| |
| audioPlayer.onerror = () => { |
| showError('Error loading audio file'); |
| }; |
| }); |
| |
| |
| function showError(message) { |
| errorMessage.textContent = message; |
| errorMessage.classList.remove('hidden'); |
| uploadText.textContent = 'Choose Audio File'; |
| loadingSpinner.classList.add('hidden'); |
| statusElement.textContent = 'Error'; |
| statusElement.className = 'text-red-400'; |
| } |
| |
| |
| function formatTime(seconds) { |
| if (isNaN(seconds)) return '0:00'; |
| |
| const minutes = Math.floor(seconds / 60); |
| const secs = Math.floor(seconds % 60); |
| return `${minutes}:${secs < 10 ? '0' : ''}${secs}`; |
| } |
| |
| |
| 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'; |
| } |
| }); |
| |
| |
| function setupAudio() { |
| if (audioSource) { |
| audioSource.disconnect(); |
| } |
| |
| analyser = audioContext.createAnalyser(); |
| analyser.fftSize = 2048; |
| |
| audioSource = audioContext.createMediaElementSource(audioPlayer); |
| |
| |
| if (eqNodes.length > 0) { |
| let lastNode = audioSource; |
| eqNodes.forEach(node => { |
| lastNode.connect(node); |
| lastNode = node; |
| }); |
| lastNode.connect(reverbNode || analyser); |
| } else { |
| audioSource.connect(reverbNode || analyser); |
| } |
| |
| if (reverbNode) { |
| reverbNode.connect(analyser); |
| } |
| analyser.connect(audioContext.destination); |
| |
| const bufferLength = analyser.frequencyBinCount; |
| dataArray = new Uint8Array(bufferLength); |
| } |
| |
| |
| function startAudio() { |
| if (audioContext.state === 'suspended') { |
| audioContext.resume(); |
| } |
| |
| audioPlayer.play(); |
| isPlaying = true; |
| statusElement.textContent = 'Playing'; |
| statusElement.className = 'text-green-400'; |
| |
| |
| visualize(); |
| } |
| |
| |
| function stopAudio() { |
| audioPlayer.pause(); |
| isPlaying = false; |
| statusElement.textContent = 'Paused'; |
| statusElement.className = 'text-yellow-400'; |
| |
| |
| cancelAnimationFrame(animationId); |
| |
| |
| bars.forEach(bar => { |
| bar.style.height = '0px'; |
| }); |
| |
| circularBars.forEach(bar => { |
| bar.style.height = '0px'; |
| }); |
| } |
| |
| |
| function detectBeat(level) { |
| if (level > beatCutOff && level > beatThreshold) { |
| |
| beatCutOff = level * 1.1; |
| lastBeatTime = Date.now(); |
| |
| |
| createBeatCircle(); |
| |
| |
| createParticles(level); |
| |
| return true; |
| } else { |
| |
| if (Date.now() - lastBeatTime > beatHoldTime) { |
| beatCutOff *= beatDecayRate; |
| beatCutOff = Math.max(beatCutOff, beatThreshold); |
| } |
| return false; |
| } |
| } |
| |
| |
| function createBeatCircle() { |
| const circle = document.createElement('div'); |
| circle.className = 'beat-circle pulse'; |
| |
| |
| 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`; |
| |
| |
| if (currentTheme === 'neon') { |
| circle.style.color = '#00ff9d'; |
| } else if (currentTheme === 'sunset') { |
| circle.style.color = '#ff7b00'; |
| } else if (currentTheme === 'ocean') { |
| circle.style.color = '#0061ff'; |
| } else { |
| circle.style.color = '#ff1493'; |
| } |
| |
| visualizer.appendChild(circle); |
| |
| |
| setTimeout(() => { |
| circle.remove(); |
| }, 500); |
| } |
| |
| |
| function createParticles(level) { |
| const particleCount = Math.floor(level / 2); |
| |
| for (let i = 0; i < particleCount; i++) { |
| const particle = document.createElement('div'); |
| particle.className = 'particle'; |
| |
| |
| const x = visualizer.clientWidth / 2 + (Math.random() - 0.5) * 100; |
| const y = visualizer.clientHeight / 2 + (Math.random() - 0.5) * 100; |
| const size = 2 + Math.random() * 4; |
| const angle = Math.random() * Math.PI * 2; |
| const velocity = 1 + Math.random() * 3; |
| |
| |
| if (currentTheme === 'neon') { |
| particle.style.backgroundColor = '#00ff9d'; |
| } else if (currentTheme === 'sunset') { |
| particle.style.backgroundColor = '#ff7b00'; |
| } else if (currentTheme === 'ocean') { |
| particle.style.backgroundColor = '#0061ff'; |
| } else { |
| particle.style.backgroundColor = '#ff1493'; |
| } |
| |
| particle.style.width = `${size}px`; |
| particle.style.height = `${size}px`; |
| particle.style.left = `${x}px`; |
| particle.style.top = `${y}px`; |
| |
| visualizer.appendChild(particle); |
| particles.push({ |
| element: particle, |
| x: x, |
| y: y, |
| vx: Math.cos(angle) * velocity, |
| vy: Math.sin(angle) * velocity, |
| life: 50 + Math.random() * 50 |
| }); |
| } |
| } |
| |
| |
| function updateParticles() { |
| for (let i = particles.length - 1; i >= 0; i--) { |
| const p = particles[i]; |
| p.x += p.vx; |
| p.y += p.vy; |
| p.life--; |
| |
| p.element.style.left = `${p.x}px`; |
| p.element.style.top = `${p.y}px`; |
| p.element.style.opacity = p.life / 100; |
| |
| if (p.life <= 0) { |
| p.element.remove(); |
| particles.splice(i, 1); |
| } |
| } |
| } |
| |
| |
| function visualize() { |
| animationId = requestAnimationFrame(visualize); |
| |
| analyser.getByteFrequencyData(dataArray); |
| |
| |
| 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; |
| |
| |
| if (audioPlayer.duration) { |
| const progress = (audioPlayer.currentTime / audioPlayer.duration) * 100; |
| progressBar.style.width = `${progress}%`; |
| currentTimeElement.textContent = formatTime(audioPlayer.currentTime); |
| } |
| |
| |
| 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); |
| } |
| |
| |
| updateParticles(); |
| |
| |
| const visualModeValue = visualMode.value; |
| |
| if (visualModeValue === 'bars' || visualModeValue === 'hybrid') { |
| |
| visualizer.querySelectorAll('.bar').forEach(bar => bar.style.display = 'block'); |
| circularVisualizer.style.display = 'none'; |
| |
| 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}`; |
| } |
| } |
| |
| if (visualModeValue === 'circular' || visualModeValue === 'hybrid') { |
| |
| if (visualModeValue === 'hybrid') { |
| circularVisualizer.style.display = 'block'; |
| } else { |
| visualizer.querySelectorAll('.bar').forEach(bar => bar.style.display = 'none'); |
| circularVisualizer.style.display = 'block'; |
| } |
| |
| 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) * 100; |
| |
| circularBars[i].style.height = `${height}px`; |
| circularBars[i].style.opacity = `${0.2 + (height / 100) * 0.8}`; |
| } |
| } |
| |
| |
| 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; |
| } |
| |
| |
| sensitivityInput.addEventListener('input', () => { |
| sensitivity = parseFloat(sensitivityInput.value); |
| sensitivityValue.textContent = sensitivity.toFixed(2); |
| }); |
| |
| |
| barCountInput.addEventListener('input', () => { |
| barCount = parseInt(barCountInput.value); |
| barCountValue.textContent = barCount; |
| createBars(); |
| }); |
| |
| |
| barSpacingInput.addEventListener('input', () => { |
| barSpacing = parseFloat(barSpacingInput.value); |
| barSpacingValue.textContent = barSpacing.toFixed(1); |
| createBars(); |
| }); |
| |
| |
| reverbInput.addEventListener('input', () => { |
| reverbAmount = parseFloat(reverbInput.value); |
| reverbValue.textContent = reverbAmount.toFixed(1); |
| setupReverb(); |
| }); |
| |
| |
| volumeControl.addEventListener('input', () => { |
| audioPlayer.volume = parseFloat(volumeControl.value); |
| volumeLevelElement.textContent = Math.round(volumeControl.value * 100); |
| }); |
| |
| |
| themeSelect.addEventListener('change', () => { |
| currentTheme = themeSelect.value; |
| visualizer.className = 'visualizer-container mb-4'; |
| if (currentTheme !== 'default') { |
| visualizer.classList.add(`theme-${currentTheme}`); |
| } |
| }); |
| |
| |
| visualMode.addEventListener('change', () => { |
| createBars(); |
| }); |
| |
| |
| eqPreset.addEventListener('change', () => { |
| setupEQ(); |
| }); |
| |
| |
| window.addEventListener('resize', () => { |
| createBars(); |
| }); |
| |
| |
| document.addEventListener('keydown', (e) => { |
| if (e.code === 'Space') { |
| e.preventDefault(); |
| playButton.click(); |
| } else if (e.code === 'ArrowUp') { |
| e.preventDefault(); |
| sensitivityInput.value = Math.min(1, parseFloat(sensitivityInput.value) + 0.05); |
| sensitivityInput.dispatchEvent(new Event('input')); |
| } else if (e.code === 'ArrowDown') { |
| e.preventDefault(); |
| sensitivityInput.value = Math.max(0.1, parseFloat(sensitivityInput.value) - 0.05); |
| sensitivityInput.dispatchEvent(new Event('input')); |
| } else if (e.code === 'ArrowRight' && audioPlayer.duration) { |
| e.preventDefault(); |
| audioPlayer.currentTime = Math.min(audioPlayer.duration, audioPlayer.currentTime + 5); |
| } else if (e.code === 'ArrowLeft' && audioPlayer.duration) { |
| e.preventDefault(); |
| audioPlayer.currentTime = Math.max(0, audioPlayer.currentTime - 5); |
| } |
| }); |
| |
| |
| fullscreenBtn.addEventListener('click', () => { |
| if (!document.fullscreenElement) { |
| document.documentElement.requestFullscreen(); |
| fullscreenBtn.innerHTML = '<i class="fas fa-compress"></i>'; |
| } else { |
| if (document.exitFullscreen) { |
| document.exitFullscreen(); |
| fullscreenBtn.innerHTML = '<i class="fas fa-expand"></i>'; |
| } |
| } |
| }); |
| |
| |
| themeToggle.addEventListener('click', () => { |
| isDarkMode = !isDarkMode; |
| if (isDarkMode) { |
| document.body.classList.remove('light-mode'); |
| themeToggle.innerHTML = '<i class="fas fa-moon"></i>'; |
| } else { |
| document.body.classList.add('light-mode'); |
| themeToggle.innerHTML = '<i class="fas fa-sun"></i>'; |
| } |
| }); |
| |
| |
| captureBtn.addEventListener('click', () => { |
| if (!isPlaying) { |
| alert('Play some audio first to capture the visualization'); |
| return; |
| } |
| |
| |
| alert('Visualization captured! (In a real implementation, this would save the image)'); |
| }); |
| |
| |
| shareBtn.addEventListener('click', () => { |
| if (navigator.share) { |
| navigator.share({ |
| title: 'Audio Visualizer', |
| text: 'Check out this cool audio visualization!', |
| url: window.location.href |
| }).catch(err => { |
| console.log('Error sharing:', err); |
| }); |
| } else { |
| alert('Web Share API not supported in your browser'); |
| } |
| }); |
| |
| |
| function autoCalibrate() { |
| |
| if (dataArray && dataArray.length > 0) { |
| let sum = 0; |
| for (let i = 0; i < dataArray.length; i++) { |
| sum += dataArray[i]; |
| } |
| const average = sum / dataArray.length; |
| beatThreshold = average / 255 * 1.5; |
| } |
| } |
| |
| |
| setupReverb(); |
| setupEQ(); |
| }); |
| </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/advanced-audio-visualizer" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |