AV Synesthesia Experience
🎵 AV Synesthesia Experience
🎧 Move mouse • Load audio file • Click anywhere for explosions
📱 Works best with headphones or speakers
🎵 Controls ▼
Mode: Particles
Palette: Ocean
, ‘%’, ‘&’, “‘”, ‘(‘, ‘)’, ‘*’, ‘+’, ‘,’, ‘-‘, ‘.’, ‘/’, ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘:’, ‘;’, ‘<', '=', '>‘, ‘?’, ‘@’, ‘A’, ‘B’, ‘C’, ‘D’, ‘E’ ]; // Color palettes const palettes = [ // Ocean [‘#1e3a8a’, ‘#3b82f6’, ‘#06b6d4’, ‘#64ffda’, ‘#00ffd5’], // Sunset [‘#7c3aed’, ‘#a855f7’, ‘#ec4899’, ‘#f97316’, ‘#fbbf24’], // Forest [‘#065f46’, ‘#059669’, ‘#10b981’, ‘#6ee7b7’, ‘#a7f3d0’], // Galaxy [‘#1e1b4b’, ‘#4c1d95’, ‘#7c3aed’, ‘#c084fc’, ‘#f3e8ff’], // Neon [‘#ff0080’, ‘#00ff80’, ‘#8000ff’, ‘#ff8000’, ‘#00ffff’] ]; function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } function initAudio() { audioContext = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioContext.createAnalyser(); analyser.fftSize = 256; dataArray = new Uint8Array(analyser.frequencyBinCount); } async function toggleAudio() { try { if (!audioContext) { initAudio(); } // Resume audio context if suspended if (audioContext.state === ‘suspended’) { await audioContext.resume(); console.log(‘Audio context resumed’); } if (!isPlaying) { // Create multiple oscillators for richer ambient sound const oscillator1 = audioContext.createOscillator(); const oscillator2 = audioContext.createOscillator(); const oscillator3 = audioContext.createOscillator(); const gainNode1 = audioContext.createGain(); const gainNode2 = audioContext.createGain(); const gainNode3 = audioContext.createGain(); const masterGain = audioContext.createGain(); // Connect oscillators oscillator1.connect(gainNode1); oscillator2.connect(gainNode2); oscillator3.connect(gainNode3); gainNode1.connect(masterGain); gainNode2.connect(masterGain); gainNode3.connect(masterGain); masterGain.connect(analyser); analyser.connect(audioContext.destination); // Set frequencies and gains oscillator1.frequency.setValueAtTime(220, audioContext.currentTime); oscillator2.frequency.setValueAtTime(330, audioContext.currentTime); oscillator3.frequency.setValueAtTime(110, audioContext.currentTime); gainNode1.gain.setValueAtTime(0.15, audioContext.currentTime); gainNode2.gain.setValueAtTime(0.1, audioContext.currentTime); gainNode3.gain.setValueAtTime(0.05, audioContext.currentTime); masterGain.gain.setValueAtTime(0.3, audioContext.currentTime); // Set waveforms oscillator1.type = ‘sine’; oscillator2.type = ‘triangle’; oscillator3.type = ‘sawtooth’; oscillator1.start(); oscillator2.start(); oscillator3.start(); isPlaying = true; currentAudio = { osc1: oscillator1, osc2: oscillator2, osc3: oscillator3 }; console.log(‘Audio started successfully’); // Modulate frequencies for ambient effect const modulateFreq = () => { if (isPlaying && currentAudio) { const baseTime = audioContext.currentTime; const t = time * 0.001; oscillator1.frequency.setValueAtTime( 220 + Math.sin(t * 2) * 50, baseTime ); oscillator2.frequency.setValueAtTime( 330 + Math.cos(t * 1.5) * 30, baseTime ); oscillator3.frequency.setValueAtTime( 110 + Math.sin(t * 0.5) * 20, baseTime ); } }; setInterval(modulateFreq, 100); } else { // Stop audio if (currentAudio && currentAudio.osc1) { currentAudio.osc1.stop(); currentAudio.osc2.stop(); currentAudio.osc3.stop(); currentAudio = null; } isPlaying = false; console.log(‘Audio stopped’); } } catch (error) { console.error(‘Audio error:’, error); alert(‘Audio failed to start. Try clicking the Start Audio button again.’); } } function loadAudio(event) { const file = event.target.files[0]; if (file) { console.log(‘Loading audio file:’, file.name); const reader = new FileReader(); reader.onload = function(e) { if (!audioContext) { initAudio(); } audioContext.decodeAudioData(e.target.result) .then(function(buffer) { console.log(‘Audio decoded successfully’); // Stop current audio if playing if (currentAudio && currentAudio.stop) { currentAudio.stop(); } else if (currentAudio && currentAudio.osc1) { currentAudio.osc1.stop(); currentAudio.osc2.stop(); currentAudio.osc3.stop(); } // Create new audio source const source = audioContext.createBufferSource(); const gainNode = audioContext.createGain(); source.buffer = buffer; source.connect(gainNode); gainNode.connect(analyser); analyser.connect(audioContext.destination); gainNode.gain.setValueAtTime(0.5, audioContext.currentTime); source.loop = true; source.start(); currentAudio = source; isPlaying = true; console.log(‘Audio file playing’); }) .catch(function(error) { console.error(‘Error decoding audio:’, error); alert(‘Could not decode audio file. Please try a different file.’); }); }; reader.readAsArrayBuffer(file); } } function toggleMode() { mode = (mode + 1) % 4; const modes = [‘Particles’, ‘Waves’, ‘ASCII’, ‘Fractals’]; document.getElementById(‘modeDisplay’).textContent = modes[mode]; } function toggleColors() { colorPalette = (colorPalette + 1) % palettes.length; const paletteNames = [‘Ocean’, ‘Sunset’, ‘Forest’, ‘Galaxy’, ‘Neon’]; document.getElementById(‘paletteDisplay’).textContent = paletteNames[colorPalette]; } function getFrequencyData() { if (analyser && dataArray) { analyser.getByteFrequencyData(dataArray); return dataArray; } return new Uint8Array(128).fill(50 + Math.sin(time * 0.01) * 30); } function drawParticles() { const freqData = getFrequencyData(); const palette = palettes[colorPalette]; // Add new particles for (let i = 0; i < 3; i++) { if (particles.length < 200) { particles.push({ x: mouseX + (Math.random() - 0.5) * 100, y: mouseY + (Math.random() - 0.5) * 100, vx: (Math.random() - 0.5) * 4, vy: (Math.random() - 0.5) * 4, life: 1, decay: 0.005 + Math.random() * 0.01, size: 2 + Math.random() * 8, color: palette[Math.floor(Math.random() * palette.length)], freq: freqData[Math.floor(Math.random() * freqData.length)] }); } } // Update and draw particles particles = particles.filter(p => { p.x += p.vx + Math.sin(time * 0.01 + p.x * 0.01) * 2; p.y += p.vy + Math.cos(time * 0.01 + p.y * 0.01) * 2; p.life -= p.decay; const alpha = p.life * (p.freq / 255); const size = p.size * (1 + p.freq / 128); ctx.globalAlpha = alpha; ctx.fillStyle = p.color; ctx.beginPath(); ctx.arc(p.x, p.y, size, 0, Math.PI * 2); ctx.fill(); return p.life > 0; }); } function drawWaves() { const freqData = getFrequencyData(); const palette = palettes[colorPalette]; ctx.globalAlpha = 0.8; for (let i = 0; i < 5; i++) { ctx.beginPath(); ctx.strokeStyle = palette[i % palette.length]; ctx.lineWidth = 3; for (let x = 0; x < canvas.width; x += 10) { const freq = freqData[Math.floor((x / canvas.width) * freqData.length)]; const y = canvas.height / 2 + Math.sin(x * 0.01 + time * 0.02 + i) * (freq / 4) + Math.sin(x * 0.005 + time * 0.01) * 50; if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.stroke(); } } function drawASCII() { const freqData = getFrequencyData(); const palette = palettes[colorPalette]; ctx.font = '16px Courier New'; ctx.globalAlpha = 0.9; const cols = Math.floor(canvas.width / 16); const rows = Math.floor(canvas.height / 20); for (let x = 0; x < cols; x++) { for (let y = 0; y < rows; y++) { const freq = freqData[Math.floor((x / cols) * freqData.length)]; const charIndex = Math.floor((freq / 255) * asciiChars.length); const char = asciiChars[charIndex] || asciiChars[0]; const colorIndex = Math.floor((freq + time * 10) % palette.length); ctx.fillStyle = palette[colorIndex]; const offsetX = Math.sin(time * 0.01 + x * 0.1) * 5; const offsetY = Math.cos(time * 0.01 + y * 0.1) * 5; ctx.fillText(char, x * 16 + offsetX, y * 20 + offsetY); } } } function drawFractals() { const freqData = getFrequencyData(); const palette = palettes[colorPalette]; ctx.globalAlpha = 0.7; function drawBranch(x, y, angle, length, depth) { if (depth > 6 || length < 2) return; const freq = freqData[depth % freqData.length]; const colorIndex = Math.floor((freq + depth * 50) % palette.length); ctx.strokeStyle = palette[colorIndex]; ctx.lineWidth = Math.max(1, depth / 2); const endX = x + Math.cos(angle) * length; const endY = y + Math.sin(angle) * length; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(endX, endY); ctx.stroke(); const newLength = length * (0.7 + freq / 1000); const angleOffset = (freq / 255) * 0.5 + 0.3; drawBranch(endX, endY, angle - angleOffset, newLength, depth + 1); drawBranch(endX, endY, angle + angleOffset, newLength, depth + 1); } const centerX = canvas.width / 2; const centerY = canvas.height; const mainFreq = freqData[0] || 100; for (let i = 0; i < 5; i++) { const startAngle = -Math.PI/2 + (i - 2) * 0.3; drawBranch(centerX + i * 100 - 200, centerY, startAngle, mainFreq / 2, 0); } } function animate() { time++; // Clear with trailing effect ctx.globalAlpha = 0.1; ctx.fillStyle = '#0a0a0f'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.globalAlpha = 1; // Draw based on current mode switch(mode) { case 0: drawParticles(); break; case 1: drawWaves(); break; case 2: drawASCII(); break; case 3: drawFractals(); break; } animationId = requestAnimationFrame(animate); } // Event listeners window.addEventListener('resize', resizeCanvas); window.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; }); window.addEventListener(‘click’, () => { // Add explosion effect const palette = palettes[colorPalette]; for (let i = 0; i < 20; i++) { particles.push({ x: mouseX, y: mouseY, vx: (Math.random() - 0.5) * 15, vy: (Math.random() - 0.5) * 15, life: 1, decay: 0.02, size: 5 + Math.random() * 10, color: palette[Math.floor(Math.random() * palette.length)], freq: 200 + Math.random() * 55 }); } }); // Initialize resizeCanvas(); animate(); // Auto-start with ambient audio after user interaction let audioStarted = false; document.addEventListener('click', async () => { if (!audioStarted) { console.log(‘Attempting to start audio after user interaction’); await toggleAudio(); audioStarted = true; } }, { once: false });