vipan-kumar's picture
Initial commit: Audio Deepfake Detector with 8 detectors trained on jay15k
e6a1f55
Raw
History Blame Contribute Delete
2.01 kB
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 (
<div className="panel-alt flex h-24 items-center justify-center font-mono text-xs text-ink-dim">
waiting for audio…
</div>
);
}
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 (
<div className="panel-alt overflow-hidden">
<svg
viewBox={`0 0 ${W} ${H}`}
preserveAspectRatio="none"
className="block w-full"
style={{ height }}
aria-label="audio waveform"
>
{/* center line */}
<line
x1="0"
y1={H / 2}
x2={W}
y2={H / 2}
stroke="rgb(var(--line))"
strokeDasharray="4 4"
strokeWidth="0.5"
/>
<path d={closed} fill={fill} />
<path d={topPath} stroke={stroke} strokeWidth="1.2" fill="none" />
</svg>
</div>
);
}