Spaces:
Sleeping
Sleeping
File size: 5,822 Bytes
5008b66 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
import { useEffect, useRef } from 'react';
interface FFTSpectrumAnalyzerProps {
isActive: boolean;
className?: string;
}
export default function FFTSpectrumAnalyzer({
isActive,
className = ""
}: FFTSpectrumAnalyzerProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const animationRef = useRef<number>();
const frequencyDataRef = useRef<Uint8Array>(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 (
<div className={`flex flex-col gap-2 ${className}`}>
<div className="flex items-center justify-between">
<label className="text-sm font-medium text-foreground">
FFT Spectrum Analyzer
</label>
<span className="text-xs text-muted-foreground">
0 - 8kHz (Speech Bandwidth)
</span>
</div>
<canvas
ref={canvasRef}
width={400}
height={100}
className={`w-full rounded-lg border ${
isActive
? 'border-cyan-500/50 bg-black/40'
: 'border-border bg-surface'
}`}
/>
<div className="flex justify-between text-xs text-muted-foreground px-1">
<span>0 Hz</span>
<span>2 kHz</span>
<span>4 kHz</span>
<span>6 kHz</span>
<span>8 kHz</span>
</div>
{/* Info Box */}
<div className="p-2 bg-surface rounded border border-border text-xs text-muted-foreground space-y-1">
<p>
<span className="text-cyan-400">●</span> Raw frequency spectrum of synthesized speech
</p>
<p>
<span className="text-cyan-400">●</span> Shows energy at different frequencies in real-time
</p>
<p>
<span className="text-cyan-400">●</span> Peaks represent vocal formants (voice characteristics)
</p>
</div>
</div>
);
}
|