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());
});
});