document.addEventListener('DOMContentLoaded', () => { // DOM Elements const uploadBtn = document.getElementById('upload-btn'); const audioUpload = document.getElementById('audio-upload'); const audioControls = document.getElementById('audio-controls'); const songTitle = document.getElementById('song-title'); const songDuration = document.getElementById('song-duration'); const currentTimeEl = document.getElementById('current-time'); const totalTimeEl = document.getElementById('total-time'); const playBtn = document.getElementById('play-btn'); const visualPreview = document.getElementById('visual-preview'); const styleOptions = document.getElementById('style-options'); const renderSection = document.getElementById('render-section'); const generateBtn = document.getElementById('generate-btn'); const progressContainer = document.getElementById('progress-container'); const progressBar = document.getElementById('progress-bar'); const progressPercent = document.getElementById('progress-percent'); const downloadSection = document.getElementById('download-section'); const downloadBtn = document.getElementById('download-btn'); const visualizerCanvas = document.getElementById('visualizer'); const videoCanvas = document.getElementById('video-canvas'); // Audio context and variables let audioContext; let analyser; let audioBuffer; let audioElement; let wavesurfer; let isPlaying = false; let selectedStyle = 'abstract'; let animationId; // Initialize Wavesurfer function initWavesurfer() { wavesurfer = WaveSurfer.create({ container: '#waveform', waveColor: '#8B5CF6', progressColor: '#EC4899', cursorColor: '#EC4899', barWidth: 2, barRadius: 3, cursorWidth: 1, height: 60, barGap: 2, responsive: true, }); wavesurfer.on('ready', () => { audioElement = wavesurfer.backend.getAudioElement(); setupAudioAnalysis(); updateDuration(); playBtn.innerHTML = ''; feather.replace(); }); wavesurfer.on('audioprocess', updateCurrentTime); wavesurfer.on('seek', updateCurrentTime); wavesurfer.on('finish', () => { isPlaying = false; playBtn.innerHTML = ''; feather.replace(); }); } // Setup audio analysis function setupAudioAnalysis() { audioContext = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioContext.createAnalyser(); analyser.fftSize = 256; const source = audioContext.createMediaElementSource(audioElement); source.connect(analyser); analyser.connect(audioContext.destination); startVisualizer(); } // Audio visualizer function startVisualizer() { const canvasCtx = visualizerCanvas.getContext('2d'); const WIDTH = visualizerCanvas.width = visualizerCanvas.offsetWidth; const HEIGHT = visualizerCanvas.height = visualizerCanvas.offsetHeight; const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); function draw() { animationId = requestAnimationFrame(draw); analyser.getByteFrequencyData(dataArray); canvasCtx.fillStyle = 'rgba(0, 0, 0, 0.1)'; canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); const barWidth = (WIDTH / bufferLength) * 2.5; let x = 0; for (let i = 0; i < bufferLength; i++) { const barHeight = (dataArray[i] / 255) * HEIGHT; const hue = i / bufferLength * 360; canvasCtx.fillStyle = `hsla(${hue}, 100%, 50%, 0.8)`; canvasCtx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight); x += barWidth + 1; } } draw(); } // Update current time display function updateCurrentTime() { if (audioElement) { const currentTime = formatTime(audioElement.currentTime); currentTimeEl.textContent = currentTime; } } // Update duration display function updateDuration() { if (audioElement) { const duration = formatTime(audioElement.duration); totalTimeEl.textContent = duration; songDuration.textContent = duration; } } // Format time (seconds to MM:SS) function formatTime(seconds) { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs < 10 ? '0' : ''}${secs}`; } // Generate background image based on audio analysis function generateBackgroundImage(style) { // In a real app, this would use the audio analysis to generate an image // For demo purposes, we'll use placeholder gradients based on style let gradient; switch (style) { case 'nature': gradient = 'linear-gradient(135deg, #10B981 0%, #3B82F6 100%)'; break; case 'cosmic': gradient = 'linear-gradient(135deg, #8B5CF6 0%, #EC4899 100%)'; break; default: // abstract gradient = 'linear-gradient(135deg, #6366F1 0%, #A78BFA 50%, #F472B6 100%)'; } visualPreview.style.backgroundImage = gradient; } // Event listeners uploadBtn.addEventListener('click', () => audioUpload.click()); audioUpload.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; songTitle.textContent = file.name.replace('.mp3', ''); // Initialize wavesurfer if not already done if (!wavesurfer) { initWavesurfer(); } const blobUrl = URL.createObjectURL(file); wavesurfer.load(blobUrl); audioControls.classList.remove('hidden'); styleOptions.classList.remove('hidden'); renderSection.classList.remove('hidden'); downloadSection.classList.add('hidden'); // Generate initial background generateBackgroundImage(selectedStyle); }); playBtn.addEventListener('click', () => { if (!wavesurfer) return; if (isPlaying) { wavesurfer.pause(); isPlaying = false; playBtn.innerHTML = ''; } else { wavesurfer.play(); isPlaying = true; playBtn.innerHTML = ''; } feather.replace(); }); // Style selection document.querySelectorAll('.style-option').forEach(btn => { btn.addEventListener('click', () => { selectedStyle = btn.dataset.style; document.querySelectorAll('.style-option').forEach(b => { b.classList.remove('border-purple-500', 'border-2'); }); btn.classList.add('border-purple-500', 'border-2'); generateBackgroundImage(selectedStyle); }); }); // Generate video generateBtn.addEventListener('click', async () => { if (!wavesurfer || wavesurfer.isLoading()) { alert('Please wait for the audio to finish loading first'); return; } // Reset visualizer if already running if (animationId) { cancelAnimationFrame(animationId); } generateBtn.disabled = true; generateBtn.innerHTML = 'Generating...'; progressContainer.classList.remove('hidden'); // Setup video canvas videoCanvas.width = 1280; videoCanvas.height = 720; const videoCtx = videoCanvas.getContext('2d'); videoCanvas.classList.remove('hidden'); // Create a MediaRecorder to capture the visualization const stream = videoCanvas.captureStream(30); const mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm;codecs=vp9' }); const chunks = []; mediaRecorder.ondataavailable = (event) => { chunks.push(event.data); }; mediaRecorder.onstop = () => { const blob = new Blob(chunks, { type: 'video/webm' }); const url = URL.createObjectURL(blob); // Convert to MP4 (simulated) setTimeout(() => { progressBar.style.width = '100%'; progressPercent.textContent = '100%'; progressPercent.previousElementSibling.textContent = 'Finalizing...'; setTimeout(() => { generateBtn.disabled = false; generateBtn.innerHTML = 'Generate Video'; progressContainer.classList.add('hidden'); downloadSection.classList.remove('hidden'); downloadBtn.href = url; downloadBtn.download = `${songTitle.textContent || 'audio-visualization'}.mp4`; }, 500); }, 1000); }; // Start recording mediaRecorder.start(100); // Collect data every 100ms // Animation frame for video generation let startTime = Date.now(); const duration = wavesurfer.getDuration() * 1000; let lastFrameTime = 0; const frameRate = 30; const frameInterval = 1000 / frameRate; function renderVideoFrame() { const currentTime = Date.now() - startTime; const progress = Math.min(100, (currentTime / duration) * 100); progressBar.style.width = `${progress}%`; progressPercent.textContent = `${Math.round(progress)}%`; progressPercent.previousElementSibling.textContent = 'Rendering...'; // Update visualization const analyserData = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(analyserData); // Draw visualization videoCtx.fillStyle = 'rgba(0, 0, 0, 1)'; videoCtx.fillRect(0, 0, videoCanvas.width, videoCanvas.height); // Create gradient based on selected style let gradient; switch(selectedStyle) { case 'nature': gradient = videoCtx.createLinearGradient(0, 0, videoCanvas.width, videoCanvas.height); gradient.addColorStop(0, '#10B981'); gradient.addColorStop(1, '#3B82F6'); break; case 'cosmic': gradient = videoCtx.createLinearGradient(0, 0, videoCanvas.width, videoCanvas.height); gradient.addColorStop(0, '#8B5CF6'); gradient.addColorStop(1, '#EC4899'); break; default: // abstract gradient = videoCtx.createLinearGradient(0, 0, videoCanvas.width, videoCanvas.height); gradient.addColorStop(0, '#6366F1'); gradient.addColorStop(0.5, '#A78BFA'); gradient.addColorStop(1, '#F472B6'); } // Draw bars based on audio data const barWidth = videoCanvas.width / analyserData.length; for (let i = 0; i < analyserData.length; i++) { const barHeight = (analyserData[i] / 255) * videoCanvas.height; videoCtx.fillStyle = gradient; videoCtx.fillRect(i * barWidth, videoCanvas.height - barHeight, barWidth, barHeight); } // Add song title videoCtx.fillStyle = 'white'; videoCtx.font = 'bold 48px Poppins'; videoCtx.textAlign = 'center'; videoCtx.fillText(songTitle.textContent || 'Audio Visualization', videoCanvas.width/2, videoCanvas.height/2); if (currentTime < duration) { animationId = requestAnimationFrame(renderVideoFrame); } else { mediaRecorder.stop(); } } // Start the visualization renderVideoFrame(); }); // Clean up on unmount window.addEventListener('beforeunload', () => { if (animationId) { cancelAnimationFrame(animationId); } if (audioContext && audioContext.state !== 'closed') { audioContext.close(); } // Clean up any media streams const streams = videoCanvas.captureStream().getTracks(); streams.forEach(stream => stream.stop()); }); });