| | document.addEventListener('DOMContentLoaded', () => { |
| | |
| | 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'); |
| | |
| | |
| | let audioContext; |
| | let analyser; |
| | let audioBuffer; |
| | let audioElement; |
| | let wavesurfer; |
| | let isPlaying = false; |
| | let selectedStyle = 'abstract'; |
| | let animationId; |
| | |
| | |
| | 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 = '<i data-feather="play" class="w-5 h-5"></i>'; |
| | feather.replace(); |
| | }); |
| | |
| | wavesurfer.on('audioprocess', updateCurrentTime); |
| | wavesurfer.on('seek', updateCurrentTime); |
| | wavesurfer.on('finish', () => { |
| | isPlaying = false; |
| | playBtn.innerHTML = '<i data-feather="play" class="w-5 h-5"></i>'; |
| | feather.replace(); |
| | }); |
| | } |
| | |
| | |
| | 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(); |
| | } |
| | |
| | |
| | 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(); |
| | } |
| | |
| | |
| | function updateCurrentTime() { |
| | if (audioElement) { |
| | const currentTime = formatTime(audioElement.currentTime); |
| | currentTimeEl.textContent = currentTime; |
| | } |
| | } |
| | |
| | |
| | function updateDuration() { |
| | if (audioElement) { |
| | const duration = formatTime(audioElement.duration); |
| | totalTimeEl.textContent = duration; |
| | songDuration.textContent = duration; |
| | } |
| | } |
| | |
| | |
| | function formatTime(seconds) { |
| | const mins = Math.floor(seconds / 60); |
| | const secs = Math.floor(seconds % 60); |
| | return `${mins}:${secs < 10 ? '0' : ''}${secs}`; |
| | } |
| | |
| | |
| | function generateBackgroundImage(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: |
| | gradient = 'linear-gradient(135deg, #6366F1 0%, #A78BFA 50%, #F472B6 100%)'; |
| | } |
| | |
| | visualPreview.style.backgroundImage = gradient; |
| | } |
| | |
| | |
| | uploadBtn.addEventListener('click', () => audioUpload.click()); |
| | |
| | audioUpload.addEventListener('change', (e) => { |
| | const file = e.target.files[0]; |
| | if (!file) return; |
| | |
| | songTitle.textContent = file.name.replace('.mp3', ''); |
| | |
| | |
| | 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'); |
| | |
| | |
| | generateBackgroundImage(selectedStyle); |
| | }); |
| | |
| | playBtn.addEventListener('click', () => { |
| | if (!wavesurfer) return; |
| | |
| | if (isPlaying) { |
| | wavesurfer.pause(); |
| | isPlaying = false; |
| | playBtn.innerHTML = '<i data-feather="play" class="w-5 h-5"></i>'; |
| | } else { |
| | wavesurfer.play(); |
| | isPlaying = true; |
| | playBtn.innerHTML = '<i data-feather="pause" class="w-5 h-5"></i>'; |
| | } |
| | feather.replace(); |
| | }); |
| | |
| | |
| | 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); |
| | }); |
| | }); |
| | |
| | generateBtn.addEventListener('click', async () => { |
| | if (!wavesurfer || wavesurfer.isLoading()) { |
| | alert('Please wait for the audio to finish loading first'); |
| | return; |
| | } |
| |
|
| | |
| | if (animationId) { |
| | cancelAnimationFrame(animationId); |
| | } |
| |
|
| | generateBtn.disabled = true; |
| | generateBtn.innerHTML = '<span class="animate-pulse">Generating...</span>'; |
| | progressContainer.classList.remove('hidden'); |
| | |
| | |
| | videoCanvas.width = 1280; |
| | videoCanvas.height = 720; |
| | const videoCtx = videoCanvas.getContext('2d'); |
| | videoCanvas.classList.remove('hidden'); |
| |
|
| | |
| | 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); |
| | |
| | |
| | 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); |
| | }; |
| |
|
| | |
| | mediaRecorder.start(100); |
| |
|
| | |
| | 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...'; |
| |
|
| | |
| | const analyserData = new Uint8Array(analyser.frequencyBinCount); |
| | analyser.getByteFrequencyData(analyserData); |
| |
|
| | |
| | videoCtx.fillStyle = 'rgba(0, 0, 0, 1)'; |
| | videoCtx.fillRect(0, 0, videoCanvas.width, videoCanvas.height); |
| |
|
| | |
| | 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: |
| | gradient = videoCtx.createLinearGradient(0, 0, videoCanvas.width, videoCanvas.height); |
| | gradient.addColorStop(0, '#6366F1'); |
| | gradient.addColorStop(0.5, '#A78BFA'); |
| | gradient.addColorStop(1, '#F472B6'); |
| | } |
| |
|
| | |
| | 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); |
| | } |
| |
|
| | |
| | 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(); |
| | } |
| | } |
| |
|
| | |
| | renderVideoFrame(); |
| | }); |
| | |
| | window.addEventListener('beforeunload', () => { |
| | if (animationId) { |
| | cancelAnimationFrame(animationId); |
| | } |
| | if (audioContext && audioContext.state !== 'closed') { |
| | audioContext.close(); |
| | } |
| | |
| | const streams = videoCanvas.captureStream().getTracks(); |
| | streams.forEach(stream => stream.stop()); |
| | }); |
| | }); |