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