/ simulated_instruments / triangle
Sign in
simulated_instruments / triangle
triangle / webapp / src / components / plots / ModeMap.tsx
97 lines · 3.3 KBBlameHistoryRaw
1// The retained rod modes: frequency against ring time T60 = 6.91 / (zeta om),
2// colored by family. In-plane and out-of-plane modes interleave across the
3// whole band; the marker area shows how much of each mode's damping is
4// radiation (a large marker is a mode the air takes energy from, a small one
5// rings on and is heard mostly by its persistence).
7import { useCanvas, drawFrame, PLOT_COLORS } from './canvasUtils';
9interface Props {
10 modeF: Float32Array;
11 modeZeta: Float32Array;
12 modeZetaRad: Float32Array;
13 modeFamily: Float32Array;
16const F_LO = 100;
17const F_HI = 24000;
19export default function ModeMap({ modeF, modeZeta, modeZetaRad, modeFamily }: Props) {
20 const ref = useCanvas(
21 (ctx, w, h) => {
22 drawFrame(ctx, w, h);
23 const padL = 40;
24 const padB = 20;
25 const pw = w - padL - 10;
26 const ph = h - padB - 14;
27 const top = 12;
28 const n = modeF.length;
29 if (n === 0) return;
31 const t60 = new Float64Array(n);
32 let tMin = Infinity;
33 let tMax = 0;
34 for (let i = 0; i < n; i++) {
35 t60[i] = 6.91 / (Math.max(1e-9, modeZeta[i]) * 2 * Math.PI * modeF[i]);
36 tMin = Math.min(tMin, t60[i]);
37 tMax = Math.max(tMax, t60[i]);
38 }
39 tMin = Math.max(1e-3, tMin / 1.5);
40 tMax = tMax * 1.5;
42 const fx = (f: number) =>
43 padL + ((Math.log10(f) - Math.log10(F_LO)) / (Math.log10(F_HI) - Math.log10(F_LO))) * pw;
44 const ty = (t: number) =>
45 top + ph - ((Math.log10(t) - Math.log10(tMin)) / (Math.log10(tMax) - Math.log10(tMin))) * ph;
47 ctx.font = '10px system-ui';
48 ctx.textAlign = 'center';
49 for (const f of [100, 200, 500, 1000, 2000, 5000, 10000, 20000]) {
50 const x = fx(f);
51 ctx.strokeStyle = PLOT_COLORS.grid;
52 ctx.beginPath();
53 ctx.moveTo(x, top);
54 ctx.lineTo(x, top + ph);
55 ctx.stroke();
56 ctx.fillStyle = PLOT_COLORS.axis;
57 ctx.fillText(f >= 1000 ? `${f / 1000}k` : `${f}`, x, h - 7);
58 }
59 ctx.textAlign = 'right';
60 for (let e = Math.ceil(Math.log10(tMin)); e <= Math.floor(Math.log10(tMax)); e++) {
61 const t = 10 ** e;
62 const y = ty(t);
63 ctx.strokeStyle = PLOT_COLORS.grid;
64 ctx.beginPath();
65 ctx.moveTo(padL, y);
66 ctx.lineTo(padL + pw, y);
67 ctx.stroke();
68 ctx.fillStyle = PLOT_COLORS.axis;
69 ctx.fillText(t >= 1 ? `${t}s` : `${t * 1000}ms`, padL - 4, y + 3);
70 }
72 for (let i = 0; i < n; i++) {
73 const x = fx(Math.min(F_HI, Math.max(F_LO, modeF[i])));
74 const y = ty(t60[i]);
75 const radShare = Math.min(1, modeZetaRad[i] / Math.max(1e-12, modeZeta[i]));
76 const r = 2 + 5 * Math.sqrt(radShare);
77 ctx.fillStyle = modeFamily[i] === 0 ? PLOT_COLORS.trace : PLOT_COLORS.accent;
78 ctx.globalAlpha = 0.85;
79 ctx.beginPath();
80 ctx.arc(x, y, r, 0, 2 * Math.PI);
81 ctx.fill();
82 ctx.globalAlpha = 1;
83 }
85 ctx.fillStyle = PLOT_COLORS.trace;
86 ctx.textAlign = 'left';
87 ctx.fillText('in-plane', padL + 6, top + 2);
88 ctx.fillStyle = PLOT_COLORS.accent;
89 ctx.fillText('out-of-plane', padL + 56, top + 2);
90 ctx.fillStyle = PLOT_COLORS.axis;
91 ctx.fillText(`· ${n} modes · marker = radiation share of damping`, padL + 128, top + 2);
92 },
93 [modeF, modeZeta, modeZetaRad, modeFamily],
94 210,
95 );
96 return <canvas ref={ref} />;
↑↓ move↵openescclose