/ simulated_instruments / bass
Sign in
simulated_instruments / bass
bass / webapp / src / components / plots / WaveformPlot.tsx
65 lines · 1.9 KBBlameHistoryRaw
1import { useCanvas, drawFrame, PLOT_COLORS } from './canvasUtils';
3interface Props {
4 y: Float32Array;
5 sampleRate: number;
6}
8export default function WaveformPlot({ y, sampleRate }: Props) {
9 const ref = useCanvas(
10 (ctx, w, h) => {
11 drawFrame(ctx, w, h);
12 const padL = 34;
13 const padB = 16;
14 const pw = w - padL - 6;
15 const ph = h - padB - 6;
16 const midY = 6 + ph / 2;
18 ctx.strokeStyle = PLOT_COLORS.grid;
19 ctx.beginPath();
20 ctx.moveTo(padL, midY);
21 ctx.lineTo(padL + pw, midY);
22 ctx.stroke();
24 // adaptive time gridlines
25 const durS = y.length / sampleRate;
26 const tStep = durS > 8 ? 2.0 : durS > 4 ? 1.0 : durS > 1.5 ? 0.5 : 0.1;
27 ctx.fillStyle = PLOT_COLORS.axis;
28 ctx.font = '10px system-ui';
29 ctx.textAlign = 'center';
30 for (let t = 0; t <= durS + 1e-9; t += tStep) {
31 const x = padL + (t / durS) * pw;
32 ctx.strokeStyle = PLOT_COLORS.grid;
33 ctx.beginPath();
34 ctx.moveTo(x, 6);
35 ctx.lineTo(x, 6 + ph);
36 ctx.stroke();
37 ctx.fillText(`${t.toFixed(1)}s`, x, h - 4);
38 }
39 ctx.textAlign = 'right';
40 ctx.fillText('+1', padL - 4, 12);
41 ctx.fillText('-1', padL - 4, 6 + ph);
43 // min/max envelope per pixel column
44 ctx.strokeStyle = PLOT_COLORS.trace;
45 ctx.beginPath();
46 const perPx = y.length / pw;
47 for (let px = 0; px < pw; px++) {
48 const i0 = Math.floor(px * perPx);
49 const i1 = Math.min(y.length, Math.max(i0 + 1, Math.floor((px + 1) * perPx)));
50 let mn = Infinity, mx = -Infinity;
51 for (let i = i0; i < i1; i++) {
52 if (y[i] < mn) mn = y[i];
53 if (y[i] > mx) mx = y[i];
54 }
55 if (!Number.isFinite(mn)) continue;
56 ctx.moveTo(padL + px + 0.5, midY - mx * (ph / 2));
57 ctx.lineTo(padL + px + 0.5, midY - mn * (ph / 2) + 0.5);
58 }
59 ctx.stroke();
60 },
61 [y, sampleRate],
62 170,
63 );
64 return <canvas ref={ref} />;
↑↓ move↵openescclose