class AudioVisualizer extends HTMLElement {
constructor() {
super();
this.analyser = null;
this.animationId = null;
this.dataArray = null;
this.type = 'bars';
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
}
static get observedAttributes() {
return ['type', 'static'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'type') {
this.type = newValue;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.startVisualization();
}
}
}
connectedCallback() {
this.attachShadow({ mode: 'open' });
// Set canvas dimensions to match the component
this.canvas.width = this.clientWidth;
this.canvas.height = this.clientHeight;
this.shadowRoot.appendChild(this.canvas);
// Initialize the data array with the correct size
if (this.analyser) {
this.dataArray = new Uint8Array(this.analyser.frequencyBinCount);
}
// If static mode, generate demo data
if (this.hasAttribute('static')) {
this.dataArray = new Uint8Array(128);
this.generateDemoData();
this.startVisualization();
}
}
generateDemoData() {
const now = Date.now() / 1000;
for (let i = 0; i < this.dataArray.length; i++) {
// Generate some synthetic frequency data for demo purposes
const pos = i / this.dataArray.length;
const timeFactor = Math.sin(now + pos * Math.PI * 2) * 0.5 + 0.5;
const freqFactor = Math.pow(1 - pos, 1.5) + 0.1 * Math.random();
this.dataArray[i] = 64 + Math.round(150 * timeFactor * freqFactor);
}
return this.dataArray;
}
setAnalyser(analyser) {
this.analyser = analyser;
if (this.analyser) {
this.dataArray = new Uint8Array(this.analyser.frequencyBinCount);
}
}
startVisualization() {
if (!this.analyser) return;
const draw = () => {
this.analyser.getByteFrequencyData(this.dataArray);
this.clearCanvas();
switch(this.type) {
case 'bars':
this.drawBars();
break;
case 'wave':
this.drawWave();
break;
case 'circle':
this.drawCircle();
break;
case 'matrix':
this.drawMatrix();
break;
case 'particles':
this.drawParticles();
break;
case 'lissajous':
this.drawLissajous();
break;
case 'radial':
this.drawRadial();
break;
case 'polar':
this.drawPolar();
break;
case 'vortex':
this.drawVortex();
break;
default:
this.drawBars();
}
this.animationId = requestAnimationFrame(draw);
};
draw();
}
stopVisualization() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
this.clearCanvas();
}
}
clearCanvas() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
drawBars() {
const width = this.canvas.width;
const height = this.canvas.height;
const barWidth = width / this.dataArray.length;
const gradient = this.ctx.createLinearGradient(0, 0, 0, height);
gradient.addColorStop(0, '#4f46e5');
gradient.addColorStop(1, '#c026d3');
this.ctx.fillStyle = gradient;
for (let i = 0; i < this.dataArray.length; i++) {
const barHeight = (this.dataArray[i] / 255) * height;
const x = i * barWidth;
const y = height - barHeight;
// Add some rounded corners to the bars
this.ctx.beginPath();
this.ctx.moveTo(x + barWidth / 2, y);
this.ctx.arcTo(x + barWidth, y, x + barWidth, y + barHeight, 2);
this.ctx.arcTo(x + barWidth, y + barHeight, x, y + barHeight, 2);
this.ctx.arcTo(x, y + barHeight, x, y, 2);
this.ctx.arcTo(x, y, x + barWidth / 2, y, 2);
this.ctx.closePath();
this.ctx.fill();
}
}
drawWave() {
const width = this.canvas.width;
const height = this.canvas.height;
const sliceWidth = width / this.dataArray.length;
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = '#818cf8';
this.ctx.beginPath();
let x = 0;
for (let i = 0; i < this.dataArray.length; i++) {
const v = this.dataArray[i] / 255;
const y = v * height / 2;
if (i === 0) {
this.ctx.moveTo(x, y);
} else {
this.ctx.lineTo(x, y);
}
x += sliceWidth;
}
this.ctx.lineTo(width, height / 2);
this.ctx.stroke();
}
drawCircle() {
const centerX = this.canvas.width / 2;
const centerY = this.canvas.height / 2;
const radius = Math.min(this.canvas.width, this.canvas.height) * 0.4;
this.ctx.lineWidth = 2;
for (let i = 0; i < this.dataArray.length; i++) {
const amplitude = this.dataArray[i] / 255;
const angle = (i / this.dataArray.length) * Math.PI * 2;
const pointRadius = radius + (amplitude * radius * 0.5);
const x = centerX + Math.cos(angle) * pointRadius;
const y = centerY + Math.sin(angle) * pointRadius;
// Gradient color based on frequency
const gradient = this.ctx.createRadialGradient(x, y, 0, x, y, 5);
gradient.addColorStop(0, '#8b5cf6');
gradient.addColorStop(1, '#3b82f6');
this.ctx.beginPath();
this.ctx.arc(x, y, 3, 0, Math.PI * 2);
this.ctx.fillStyle = gradient;
this.ctx.fill();
// Draw connecting lines for a more dynamic effect
if (i > 0) {
const prevAngle = ((i - 1) / this.dataArray.length) * Math.PI * 2;
const prevAmplitude = this.dataArray[i - 1] / 255;
const prevPointRadius = radius + (prevAmplitude * radius * 0.5);
const prevX = centerX + Math.cos(prevAngle) * prevPointRadius;
const prevY = centerY + Math.sin(prevAngle) * prevPointRadius;
this.ctx.beginPath();
this.ctx.moveTo(prevX, prevY);
this.ctx.lineTo(x, y);
this.ctx.strokeStyle = `rgba(139, 92, 246, ${0.5 + amplitude * 0.5})`;
this.ctx.stroke();
}
}
}
}
customElements.define('audio-visualizer', AudioVisualizer);