// index.html
Visualizer Pro
// styles.css
body {
margin: 0;
font-family: sans-serif;
background-color: #111;
color: white;
display: flex;
flex-direction: column;
align-items: center;
height: 100vh;
overflow: hidden;
}
#controls {
display: flex;
gap: 1rem;
padding: 1rem;
flex-wrap: wrap;
justify-content: center;
}
canvas {
flex-grow: 1;
width: 100%;
height: 100%;
}
#drop-zone {
padding: 1rem;
border: 2px dashed #888;
cursor: pointer;
text-align: center;
color: #ccc;
}
body.neon {
background-color: #000;
color: #0ff;
}
body.light {
background-color: #fefefe;
color: #333;
}
@media (max-width: 600px) {
#controls {
flex-direction: column;
align-items: center;
}
}
// app.js
const canvas = document.getElementById('visualizer');
const ctx = canvas.getContext('2d');
const audioUpload = document.getElementById('audio-upload');
const dropZone = document.getElementById('drop-zone');
const visualizerStyle = document.getElementById('visualizer-style');
const colorTheme = document.getElementById('color-theme');
const fullscreenToggle = document.getElementById('fullscreen-toggle');
let audioCtx, source, analyser, dataArray, bufferLength;
let animationId, audioBuffer, currentVisualizer = 'bars';
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
function applyTheme(theme) {
document.body.className = theme;
}
colorTheme.addEventListener('change', e => applyTheme(e.target.value));
visualizerStyle.addEventListener('change', e => currentVisualizer = e.target.value);
fullscreenToggle.addEventListener('click', () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
});
dropZone.addEventListener('click', () => audioUpload.click());
dropZone.addEventListener('dragover', e => {
e.preventDefault();
dropZone.style.borderColor = 'lime';
});
dropZone.addEventListener('dragleave', () => {
dropZone.style.borderColor = '#888';
});
dropZone.addEventListener('drop', e => {
e.preventDefault();
dropZone.style.borderColor = '#888';
handleFile(e.dataTransfer.files[0]);
});
audioUpload.addEventListener('change', e => handleFile(e.target.files[0]));
function handleFile(file) {
if (!file) return;
const reader = new FileReader();
reader.onload = function (e) {
initAudio(e.target.result);
};
reader.readAsArrayBuffer(file);
}
function initAudio(audioData) {
if (audioCtx) {
audioCtx.close();
cancelAnimationFrame(animationId);
}
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
audioCtx.decodeAudioData(audioData, buffer => {
audioBuffer = buffer;
playAudio();
});
}
function playAudio() {
source = audioCtx.createBufferSource();
source.buffer = audioBuffer;
analyser = audioCtx.createAnalyser();
source.connect(analyser);
analyser.connect(audioCtx.destination);
analyser.fftSize = 256;
bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
source.start();
visualize();
}
function visualize() {
animationId = requestAnimationFrame(visualize);
analyser.getByteFrequencyData(dataArray);
ctx.clearRect(0, 0, canvas.width, canvas.height);
switch (currentVisualizer) {
case 'bars':
drawBars();
break;
case 'waveform':
drawWaveform();
break;
case 'radial':
drawRadial();
break;
}
}
function drawBars() {
const barWidth = (canvas.width / bufferLength) * 2.5;
let x = 0;
for (let i = 0; i < bufferLength; i++) {
const barHeight = dataArray[i];
ctx.fillStyle = 'rgba(0,255,255,0.8)';
ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight);
x += barWidth + 1;
}
}
function drawWaveform() {
analyser.getByteTimeDomainData(dataArray);
ctx.beginPath();
const sliceWidth = canvas.width / bufferLength;
let x = 0;
for (let i = 0; i < bufferLength; i++) {
const v = dataArray[i] / 128.0;
const y = v * canvas.height / 2;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
x += sliceWidth;
}
ctx.strokeStyle = 'rgba(255,255,0,0.8)';
ctx.lineWidth = 2;
ctx.stroke();
}
function drawRadial() {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = 100;
const step = (Math.PI * 2) / bufferLength;
ctx.beginPath();
for (let i = 0; i < bufferLength; i++) {
const angle = i * step;
const length = dataArray[i];
const x = centerX + Math.cos(angle) * (radius + length);
const y = centerY + Math.sin(angle) * (radius + length);
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.strokeStyle = 'rgba(255,0,255,0.8)';
ctx.lineWidth = 2;
ctx.stroke();
}
// Phase 2 & 3 Enhancements
// PHASE 2: Audio & Playback Features
// Add metadata display (title, duration, etc.)
function displayMetadata(file) {
const metadataDiv = document.createElement('div');
metadataDiv.id = 'metadata';
metadataDiv.style.textAlign = 'center';
metadataDiv.style.marginTop = '10px';
metadataDiv.innerText = `File: ${file.name} | Size: ${(file.size / 1024 / 1024).toFixed(2)} MB`;
document.getElementById('controls').appendChild(metadataDiv);
}
// Add playback controls
let isPlaying = false;
const playBtn = document.createElement('button');
playBtn.textContent = 'Play/Pause';
playBtn.onclick = () => {
if (!audioCtx || !source) return;
if (audioCtx.state === 'running') {
audioCtx.suspend();
isPlaying = false;
} else {
audioCtx.resume();
isPlaying = true;
}
};
document.getElementById('controls').appendChild(playBtn);
// PHASE 3: Recording Visualizer + Audio with Export
let mediaRecorder, recordedChunks = [];
function startRecording() {
const canvasStream = canvas.captureStream(30); // 30 FPS
const audioStream = audioCtx.createMediaStreamDestination();
source.connect(audioStream);
const combinedStream = new MediaStream([...canvasStream.getVideoTracks(), ...audioStream.stream.getAudioTracks()]);
mediaRecorder = new MediaRecorder(combinedStream);
recordedChunks = [];
mediaRecorder.ondataavailable = event => {
if (event.data.size > 0) recordedChunks.push(event.data);
};
mediaRecorder.onstop = () => {
const blob = new Blob(recordedChunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'visualizer_recording.webm';
a.click();
URL.revokeObjectURL(url);
};
mediaRecorder.start();
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
}
}
// Add record + stop buttons
const recordBtn = document.createElement('button');
recordBtn.textContent = 'Start Recording';
recordBtn.onclick = startRecording;
document.getElementById('controls').appendChild(recordBtn);
const stopBtn = document.createElement('button');
stopBtn.textContent = 'Stop Recording';
stopBtn.onclick = stopRecording;
document.getElementById('controls').appendChild(stopBtn);
Drop your audio file here or click to upload