omar1232's picture
Add 3 files
2182874 verified
Raw
History Blame Contribute Delete
47.2 kB
<!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', () => {
// Audio elements
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 = [];
// 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 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;
// Create initial bars
function createBars() {
// Clear existing bars
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);
// Create linear bars
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);
}
// Create circular bars
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();
// Setup reverb effect
function setupReverb() {
if (reverbNode) {
reverbNode.disconnect();
}
reverbNode = audioContext.createConvolver();
// Create impulse response for reverb
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);
}
}
// Setup EQ filters
function setupEQ() {
// Clear existing EQ nodes
eqNodes.forEach(node => node.disconnect());
eqNodes = [];
const preset = eqPreset.value;
if (preset === 'flat') {
// No EQ applied
return;
}
// Create EQ nodes based on preset
if (preset === 'bass') {
// Bass boost
const lowPass = audioContext.createBiquadFilter();
lowPass.type = "lowshelf";
lowPass.frequency.value = 250;
lowPass.gain.value = 8;
eqNodes.push(lowPass);
} else if (preset === 'treble') {
// Treble boost
const highPass = audioContext.createBiquadFilter();
highPass.type = "highshelf";
highPass.frequency.value = 4000;
highPass.gain.value = 8;
eqNodes.push(highPass);
} else if (preset === 'vocal') {
// Vocal boost (mid range)
const bandPass = audioContext.createBiquadFilter();
bandPass.type = "peaking";
bandPass.frequency.value = 1500;
bandPass.Q.value = 1;
bandPass.gain.value = 10;
eqNodes.push(bandPass);
}
// Connect EQ nodes if audio is playing
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);
}
}
// Handle file upload
audioUpload.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
// Show loading state
uploadText.textContent = 'Processing...';
loadingSpinner.classList.remove('hidden');
errorMessage.classList.add('hidden');
// Check file type
if (!file.type.match('audio.*')) {
showError('Please select an audio file');
return;
}
// Check file size (limit to 10MB)
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;
// Reset visualization
if (isPlaying) {
stopAudio();
}
// Setup audio when ready
audioPlayer.oncanplaythrough = () => {
uploadText.textContent = 'Choose Audio File';
loadingSpinner.classList.add('hidden');
statusElement.textContent = 'Ready to play';
statusElement.className = 'text-green-400';
// Update duration info
durationElement.textContent = formatTime(audioPlayer.duration);
durationInfoElement.textContent = formatTime(audioPlayer.duration);
setupAudio();
};
audioPlayer.onerror = () => {
showError('Error loading audio file');
};
});
// Show error message
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';
}
// Format time (seconds to MM:SS)
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}`;
}
// 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);
// Apply EQ if needed
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);
}
// 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';
});
circularBars.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();
// Create particles
createParticles(level);
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`;
// Apply theme color
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);
// Remove after animation
setTimeout(() => {
circle.remove();
}, 500);
}
// Create particles on beat
function createParticles(level) {
const particleCount = Math.floor(level / 2);
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
// Random position near center
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;
// Apply theme color
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
});
}
}
// Update particles
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);
}
}
}
// 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;
// Update progress bar
if (audioPlayer.duration) {
const progress = (audioPlayer.currentTime / audioPlayer.duration) * 100;
progressBar.style.width = `${progress}%`;
currentTimeElement.textContent = formatTime(audioPlayer.currentTime);
}
// 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 particles
updateParticles();
// Update bars based on visual mode
const visualModeValue = visualMode.value;
if (visualModeValue === 'bars' || visualModeValue === 'hybrid') {
// Show linear bars
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') {
// Show circular bars
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}`;
}
}
// 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);
sensitivityValue.textContent = sensitivity.toFixed(2);
});
// Handle bar count change
barCountInput.addEventListener('input', () => {
barCount = parseInt(barCountInput.value);
barCountValue.textContent = barCount;
createBars();
});
// Handle bar spacing change
barSpacingInput.addEventListener('input', () => {
barSpacing = parseFloat(barSpacingInput.value);
barSpacingValue.textContent = barSpacing.toFixed(1);
createBars();
});
// Handle reverb change
reverbInput.addEventListener('input', () => {
reverbAmount = parseFloat(reverbInput.value);
reverbValue.textContent = reverbAmount.toFixed(1);
setupReverb();
});
// Handle volume change
volumeControl.addEventListener('input', () => {
audioPlayer.volume = parseFloat(volumeControl.value);
volumeLevelElement.textContent = Math.round(volumeControl.value * 100);
});
// Handle theme change
themeSelect.addEventListener('change', () => {
currentTheme = themeSelect.value;
visualizer.className = 'visualizer-container mb-4';
if (currentTheme !== 'default') {
visualizer.classList.add(`theme-${currentTheme}`);
}
});
// Handle visual mode change
visualMode.addEventListener('change', () => {
createBars();
});
// Handle EQ preset change
eqPreset.addEventListener('change', () => {
setupEQ();
});
// Handle window resize
window.addEventListener('resize', () => {
createBars();
});
// Handle keyboard controls
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);
}
});
// Handle fullscreen toggle
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>';
}
}
});
// Handle theme toggle (dark/light)
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>';
}
});
// Handle capture button
captureBtn.addEventListener('click', () => {
if (!isPlaying) {
alert('Play some audio first to capture the visualization');
return;
}
// Use html2canvas library would be better here, but for simplicity we'll just alert
alert('Visualization captured! (In a real implementation, this would save the image)');
});
// Handle share button
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');
}
});
// Auto-calibrate beat detection
function autoCalibrate() {
// Simple auto-calibration based on initial audio energy
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;
}
}
// Initialize with some default settings
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>