// index.html Visualizer Pro
Drop your audio file here or click to upload
// 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);