aa03146Maraca shake simulator: MATLAB model and interactive web appmagland 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 // time gridlines every 0.1 s
25 const durS = y.length / sampleRate;
26 ctx.fillStyle = PLOT_COLORS.axis;
27 ctx.font = '10px system-ui';
28 ctx.textAlign = 'center';
29 for (let t = 0; t <= durS + 1e-9; t += 0.1) {
30 const x = padL + (t / durS) * pw;
31 ctx.strokeStyle = PLOT_COLORS.grid;
32 ctx.beginPath();
33 ctx.moveTo(x, 6);
34 ctx.lineTo(x, 6 + ph);
35 ctx.stroke();
36 ctx.fillText(`${t.toFixed(1)}s`, x, h - 4);
37 }
38 ctx.textAlign = 'right';
39 ctx.fillText('+1', padL - 4, 12);
40 ctx.fillText('-1', padL - 4, 6 + ph);
42 // min/max envelope per pixel column
43 ctx.strokeStyle = PLOT_COLORS.trace;
44 ctx.beginPath();
45 const perPx = y.length / pw;
46 for (let px = 0; px < pw; px++) {
47 const i0 = Math.floor(px * perPx);
48 const i1 = Math.min(y.length, Math.max(i0 + 1, Math.floor((px + 1) * perPx)));
49 let mn = Infinity, mx = -Infinity;
50 for (let i = i0; i < i1; i++) {
51 if (y[i] < mn) mn = y[i];
52 if (y[i] > mx) mx = y[i];
53 }
54 if (!Number.isFinite(mn)) continue;
55 ctx.moveTo(padL + px + 0.5, midY - mx * (ph / 2));
56 ctx.lineTo(padL + px + 0.5, midY - mn * (ph / 2) + 0.5);
57 }
58 ctx.stroke();
59 },
60 [y, sampleRate],
61 170,
62 );
63 return <canvas ref={ref} />;
64}