InnerVoice / frontend /components /WaveformVisualizer.tsx
E5K7's picture
Initial commit: InnerVoice MVP
bf04727
"use client";
import { useEffect, useRef } from "react";
interface WaveformVisualizerProps {
analyserNode: AnalyserNode | null;
isActive: boolean;
height?: number;
color?: string;
}
export default function WaveformVisualizer({
analyserNode,
isActive,
height = 80,
color = "#8B5CF6",
}: WaveformVisualizerProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const animFrameRef = useRef<number>(0);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const draw = () => {
animFrameRef.current = requestAnimationFrame(draw);
const W = canvas.width;
const H = canvas.height;
ctx.clearRect(0, 0, W, H);
if (!analyserNode || !isActive) {
// Idle animation — gentle sine wave
ctx.beginPath();
const t = Date.now() / 800;
for (let x = 0; x < W; x++) {
const y = H / 2 + Math.sin(x * 0.03 + t) * 8 * Math.sin(x * 0.008 + t * 0.5);
x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
}
ctx.strokeStyle = color + "60";
ctx.lineWidth = 2;
ctx.stroke();
return;
}
const bufLen = analyserNode.frequencyBinCount;
const dataArray = new Uint8Array(bufLen);
analyserNode.getByteTimeDomainData(dataArray);
// Draw gradient waveform
const gradient = ctx.createLinearGradient(0, 0, W, 0);
gradient.addColorStop(0, "#8B5CF6");
gradient.addColorStop(0.5, "#14b8a6");
gradient.addColorStop(1, "#8B5CF6");
ctx.beginPath();
const sliceW = W / bufLen;
let x = 0;
for (let i = 0; i < bufLen; i++) {
const v = dataArray[i] / 128.0;
const y = (v * H) / 2;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
x += sliceW;
}
ctx.lineTo(W, H / 2);
ctx.strokeStyle = gradient;
ctx.lineWidth = 2.5;
ctx.shadowBlur = 12;
ctx.shadowColor = color;
ctx.stroke();
ctx.shadowBlur = 0;
};
draw();
return () => cancelAnimationFrame(animFrameRef.current);
}, [analyserNode, isActive, color]);
return (
<canvas
ref={canvasRef}
width={600}
height={height}
className="w-full rounded-xl"
style={{ height }}
/>
);
}