Audio_Deepfake_Detection / frontend /src /components /SpectrogramViewer.tsx
vipan-kumar's picture
Initial commit: Audio Deepfake Detector with 8 detectors trained on jay15k
e6a1f55
Raw
History Blame Contribute Delete
2.64 kB
import { useEffect, useRef } from "react";
interface Props {
/** mel spectrogram values normalised to [0,1], shape [n_mels][T]. */
data: number[][];
height?: number;
}
/** Canvas mel-spectrogram with a cyan→amber→red colour ramp. */
export default function SpectrogramViewer({ data, height = 160 }: Props) {
const ref = useRef<HTMLCanvasElement | null>(null);
useEffect(() => {
const cv = ref.current;
if (!cv) return;
if (!data.length) return;
const T = data[0]?.length ?? 0;
const F = data.length;
if (T === 0) return;
cv.width = T;
cv.height = F;
const ctx = cv.getContext("2d");
if (!ctx) return;
const img = ctx.createImageData(T, F);
// Colour ramp: cyber (cool) → amber → danger (hot)
const ramp = (v: number): [number, number, number] => {
const x = Math.max(0, Math.min(1, v));
// Three-stop interpolation
const stops: [number, [number, number, number]][] = [
[0.0, [10, 18, 32]], // near-black bg
[0.35, [0, 110, 145]], // dim cyan
[0.6, [0, 212, 255]], // cyan
[0.8, [245, 158, 11]], // amber
[1.0, [255, 61, 90]], // danger red
];
for (let i = 0; i < stops.length - 1; i += 1) {
const [a, ca] = stops[i];
const [b, cb] = stops[i + 1];
if (x >= a && x <= b) {
const t = (x - a) / Math.max(1e-9, b - a);
return [
ca[0] + t * (cb[0] - ca[0]),
ca[1] + t * (cb[1] - ca[1]),
ca[2] + t * (cb[2] - ca[2]),
];
}
}
return stops[stops.length - 1][1];
};
// mel rows are low → high frequency; canvas y=0 is top so flip vertically.
for (let f = 0; f < F; f += 1) {
const row = data[F - 1 - f];
for (let t = 0; t < T; t += 1) {
const v = row[t] ?? 0;
const [r, g, b] = ramp(v);
const idx = (f * T + t) * 4;
img.data[idx] = r | 0;
img.data[idx + 1] = g | 0;
img.data[idx + 2] = b | 0;
img.data[idx + 3] = 255;
}
}
ctx.putImageData(img, 0, 0);
}, [data]);
if (!data.length) {
return (
<div className="panel-alt flex items-center justify-center font-mono text-xs text-ink-dim" style={{ height }}>
spectrogram will appear after analysis
</div>
);
}
return (
<div className="panel-alt overflow-hidden" style={{ height }}>
<canvas
ref={ref}
className="block h-full w-full"
style={{ imageRendering: "pixelated" }}
aria-label="mel spectrogram"
/>
</div>
);
}