AV Synesthesia Experience
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 });