interface Props {
data: number[]; // envelope values, typically in [0, 1]
height?: number;
color?: "cyber" | "danger";
}
/** Static envelope-style waveform on SVG. Lightweight; no Web Audio decoding. */
export default function WaveformViewer({
data,
height = 96,
color = "cyber",
}: Props) {
if (!data.length) {
return (
waiting for audio…
);
}
const stroke = color === "danger" ? "rgb(var(--danger))" : "rgb(var(--cyber))";
const fill =
color === "danger" ? "rgb(var(--danger) / 0.13)" : "rgb(var(--cyber) / 0.13)";
const W = 1000;
const H = height;
const max = Math.max(...data, 0.001);
const stepX = W / Math.max(data.length - 1, 1);
// Build a mirrored waveform path for the classic oscilloscope look.
let topPath = `M 0 ${H / 2}`;
let botPath = `M ${W} ${H / 2}`;
data.forEach((v, i) => {
const norm = v / max;
const x = i * stepX;
const yT = H / 2 - (norm * H * 0.45);
topPath += ` L ${x.toFixed(2)} ${yT.toFixed(2)}`;
});
for (let i = data.length - 1; i >= 0; i -= 1) {
const norm = data[i] / max;
const x = i * stepX;
const yB = H / 2 + (norm * H * 0.45);
botPath += ` L ${x.toFixed(2)} ${yB.toFixed(2)}`;
}
const closed = `${topPath} L ${W} ${H / 2} ${botPath} Z`;
return (
);
}