Audio Grid
I do not try to sell myself out as a JavaScript programmer, or at least it has been a while, but I like doing these little experiments with AI.
audiogrid.js
// Audio analysis variables
let audioContext;
let analyser;
let microphone;
let isAudioInitialized = false;
// Canvas variables
const canvas = document.getElementById('gridCanvas');
const ctx = canvas.getContext('2d');
// Status elements
const startButton = document.getElementById('startButton');
const statusText = document.getElementById('statusText');
// Dot settings
let baseDotSpacing = 30; // Space between dots
let maxDotSize = 6; // Maximum dot size in the center
let minDotSize = 1; // Minimum dot size at the edges
// Wave effect variables
let waves = [];
const maxWaves = 5;
// Animation variables
let animationId;
// Set up canvas
function setupCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
// Initialize audio context and analyzer
async function initializeAudio() {
try {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser();
// Set up FFT size for frequency analysis
analyser.fftSize = 2048;
// Get microphone access
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
microphone = audioContext.createMediaStreamSource(stream);
microphone.connect(analyser);
isAudioInitialized = true;
statusText.textContent = "Listening to audio input";
// Start the visualization loop
startVisualization();
} catch (error) {
console.error('Error initializing audio:', error);
statusText.textContent = "Error: " + error.message;
}
}
// Convert pitch to color
function pitchToColor(frequency, waveHeight = 1) {
// Map frequency ranges to different colors with intensity based on wave height
const hue = Math.min(360, frequency * 0.3);
const saturation = 100;
// Darker colors for taller waves (higher waveHeight values)
const lightness = Math.min(100, Math.max(5, 50 + frequency * 0.1 - (waveHeight * 40)));
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
}
// Create a new wave
function createWave(strength, frequency) {
waves.push({
radius: 0,
maxRadius: Math.max(canvas.width, canvas.height) * 1.5,
strength: strength,
frequency: frequency,
speed: 5 + (strength * 3) // Wave speed based on strength
});
// Limit the number of waves
if (waves.length > maxWaves) {
waves.shift();
}
}
// Update waves
function updateWaves() {
for (let i = waves.length - 1; i >= 0; i--) {
waves[i].radius += waves[i].speed;
// Remove waves that have expanded beyond the canvas
if (waves[i].radius > waves[i].maxRadius) {
waves.splice(i, 1);
}
}
}
// Draw dots affected by waves
function drawDots() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
// Calculate grid cells based on dot spacing
const cols = Math.floor(canvas.width / baseDotSpacing) + 1;
const rows = Math.floor(canvas.height / baseDotSpacing) + 1;
// Offset grid to center it
const offsetX = (canvas.width % baseDotSpacing) / 2;
const offsetY = (canvas.height % baseDotSpacing) / 2;
// Calculate maximum possible distance from center for size scaling
const maxDistance = Math.sqrt(Math.pow(centerX, 2) + Math.pow(centerY, 2));
// Draw dots in a grid pattern
for (let i = 0; i <= cols; i++) {
for (let j = 0; j <= rows; j++) {
const x = i * baseDotSpacing + offsetX;
const y = j * baseDotSpacing + offsetY;
// Calculate distance from center for dot size
const distanceFromCenter = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
const distanceRatio = distanceFromCenter / maxDistance;
// Calculate warped position and color based on waves
const { warpedX, warpedY, color, waveHeight } = calculateWarpedPosition(x, y, centerX, centerY);
// Calculate dot size based on distance from center (larger in center, smaller at edges)
const dotSize = maxDotSize - ((maxDotSize - minDotSize) * distanceRatio);
// Draw the dot
ctx.beginPath();
ctx.arc(warpedX, warpedY, dotSize * (1 + waveHeight * 0.5), 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
}
}
}
// Calculate warped position based on waves
function calculateWarpedPosition(x, y, centerX, centerY) {
let warpedX = x;
let warpedY = y;
let dominantColor = "#333333"; // Default color
let maxWaveHeight = 0;
// Calculate distance from center
const distanceFromCenter = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
// Apply wave distortions
for (const wave of waves) {
const distanceFromWave = Math.abs(distanceFromCenter - wave.radius);
// Apply warping effect if point is near the wave radius
const warpRange = 50;
if (distanceFromWave < warpRange) {
// Calculate warp factor based on distance from wave
const warpFactor = wave.strength * (1 - distanceFromWave / warpRange);
// Calculate direction vector from center
const dirX = (x - centerX) / (distanceFromCenter || 1);
const dirY = (y - centerY) / (distanceFromCenter || 1);
// Apply warping in the direction from center
warpedX += dirX * warpFactor * 20;
warpedY += dirY * warpFactor * 20;
// Update max wave height if this wave is stronger
if (warpFactor > maxWaveHeight) {
maxWaveHeight = warpFactor;
// Use the color based on both frequency and wave height
dominantColor = pitchToColor(wave.frequency, warpFactor);
}
}
}
return {
warpedX,
warpedY,
color: dominantColor,
waveHeight: maxWaveHeight
};
}
// Analyze audio and update visualization
function analyzeAudio() {
if (!isAudioInitialized) return;
// Create array to hold frequency data
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
// Get frequency data
analyser.getByteFrequencyData(dataArray);
// Calculate volume (average of all frequencies)
let sum = 0;
for (let i = 0; i < bufferLength; i++) {
sum += dataArray[i];
}
const averageVolume = sum / bufferLength;
// Find dominant frequency
let maxValue = 0;
let maxIndex = 0;
for (let i = 0; i < bufferLength; i++) {
if (dataArray[i] > maxValue) {
maxValue = dataArray[i];
maxIndex = i;
}
}
// Calculate dominant frequency
const dominantFrequency = maxIndex * audioContext.sampleRate / analyser.fftSize;
// Create new waves based on volume threshold
if (averageVolume > 40) {
const strength = averageVolume / 255; // Normalize to 0-1 range
createWave(strength, dominantFrequency);
}
// Update and draw everything
updateWaves();
drawDots();
}
// Start visualization loop
function startVisualization() {
function loop() {
analyzeAudio();
animationId = requestAnimationFrame(loop);
}
// Start the loop
loop();
}
// Handle window resize
window.addEventListener('resize', () => {
setupCanvas();
});
// Initialize everything
function init() {
setupCanvas();
drawDots();
startButton.addEventListener('click', async () => {
if (!isAudioInitialized) {
await initializeAudio();
startButton.textContent = "Audio Running";
}
});
}
// Start the application
init();