import { useEffect, useRef } from 'react'; interface FFTSpectrumAnalyzerProps { isActive: boolean; className?: string; } export default function FFTSpectrumAnalyzer({ isActive, className = "" }: FFTSpectrumAnalyzerProps) { const canvasRef = useRef(null); const animationRef = useRef(); const frequencyDataRef = useRef(new Uint8Array(256)); // Draw FFT spectrum analyzer const drawSpectrum = () => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const width = canvas.width; const height = canvas.height; // Clear canvas with gradient background const gradient = ctx.createLinearGradient(0, 0, 0, height); gradient.addColorStop(0, '#1a1a2e'); gradient.addColorStop(1, '#0f0f1e'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, width, height); if (isActive) { drawAnimatedSpectrum(ctx, width, height); } else { drawIdleSpectrum(ctx, width, height); } if (isActive) { animationRef.current = requestAnimationFrame(drawSpectrum); } }; const drawAnimatedSpectrum = ( ctx: CanvasRenderingContext2D, width: number, height: number ) => { const time = Date.now() / 1000; const barCount = 64; // Number of frequency bars const barWidth = width / barCount; for (let i = 0; i < barCount; i++) { // Generate realistic frequency response // Bass-heavy with some peaks in mid-range (natural speech pattern) const frequency = (i / barCount) * 8000; // 0-8kHz range (speech bandwidth) // Base frequency response curve (more energy in lower frequencies) let magnitude = Math.exp(-i / 20) * 0.8; // Add some randomness with time-based variation const wobble = Math.sin(time * 2 + i * 0.3) * 0.2; const variation = Math.sin(time * 3 + i * 0.5) * 0.1; // Add peaks (simulating formants in speech) const formant1 = Math.exp(-Math.pow((i - 15) / 8, 2)) * 0.6; // ~1.5kHz const formant2 = Math.exp(-Math.pow((i - 25) / 6, 2)) * 0.5; // ~2.5kHz const formant3 = Math.exp(-Math.pow((i - 35) / 5, 2)) * 0.3; // ~3.5kHz magnitude = magnitude + wobble + variation + formant1 + formant2 + formant3; magnitude = Math.min(1, Math.max(0, magnitude)); // Draw bar const barHeight = magnitude * height * 0.9; const x = i * barWidth; const y = height - barHeight; // Color gradient from blue to cyan to green to yellow to red const hue = (i / barCount) * 120; // HSL hue from 240 (blue) to 0 (red) const saturation = 80 + magnitude * 20; // More saturated with higher magnitude const lightness = 40 + magnitude * 20; ctx.fillStyle = `hsl(${240 - hue}, ${saturation}%, ${lightness}%)`; ctx.fillRect(x, y, barWidth - 1, barHeight); // Add glow effect const glowColor = `hsla(${240 - hue}, ${saturation}%, ${lightness}%, 0.3)`; ctx.fillStyle = glowColor; ctx.fillRect(x - 1, y - 2, barWidth + 1, barHeight + 4); } // Draw frequency scale lines ctx.strokeStyle = 'rgba(100, 200, 255, 0.2)'; ctx.lineWidth = 1; for (let i = 0; i <= 8; i++) { const x = (i / 8) * width; ctx.beginPath(); ctx.moveTo(x, height - 5); ctx.lineTo(x, height); ctx.stroke(); } }; const drawIdleSpectrum = ( ctx: CanvasRenderingContext2D, width: number, height: number ) => { // Subtle idle animation const time = Date.now() / 2000; const barCount = 64; const barWidth = width / barCount; for (let i = 0; i < barCount; i++) { const baseHeight = Math.sin(time + i * 0.1) * 0.15 + 0.1; const barHeight = baseHeight * height * 0.3; const x = i * barWidth; const y = height - barHeight; ctx.fillStyle = `rgba(100, 150, 200, ${0.3 + baseHeight * 0.4})`; ctx.fillRect(x, y, barWidth - 1, barHeight); } // Centered text ctx.fillStyle = 'rgba(150, 150, 200, 0.5)'; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Click Synthesize to start FFT analysis', width / 2, height / 2); }; useEffect(() => { if (canvasRef.current) { drawSpectrum(); } return () => { if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, [isActive]); return (
0 - 8kHz (Speech Bandwidth)
0 Hz 2 kHz 4 kHz 6 kHz 8 kHz
{/* Info Box */}

Raw frequency spectrum of synthesized speech

Shows energy at different frequencies in real-time

Peaks represent vocal formants (voice characteristics)

); }