// Small multiples of the pressure standing wave |p(x)| along the bore for // the lowest resonances of the first note's fingering. The window is at the // left, the foot at the right; an open tone hole shows up as the profile // being pinned near zero beyond it, which is how a fingering shortens the // instrument without shortening the tube. import { useCanvas, drawFrame, PLOT_COLORS } from './canvasUtils'; interface Props { profX: Float32Array; profP: Float32Array; // nsta x n, column-major modeF: Float32Array; } const SHOWN = 8; const CELL_H = 100; export default function BoreProfile({ profX, profP, modeF }: Props) { const nShown = Math.min(SHOWN, modeF.length); const rows = Math.ceil(nShown / 4); const ref = useCanvas( (ctx, w, h) => { drawFrame(ctx, w, h); const nsta = profX.length; if (nsta === 0 || nShown === 0) return; const cols = 4; const cw = w / cols; const ch = h / rows; const xMax = profX[nsta - 1]; for (let m = 0; m < nShown; m++) { const x0 = (m % cols) * cw + 10; const y0 = Math.floor(m / cols) * ch + 12; const iw = cw - 20; const ih = ch - 34; const off = m * nsta; let pMax = 1e-12; for (let i = 0; i < nsta; i++) pMax = Math.max(pMax, profP[off + i]); // bore axis ctx.strokeStyle = 'rgba(139, 147, 161, 0.3)'; ctx.beginPath(); ctx.moveTo(x0, y0 + ih); ctx.lineTo(x0 + iw, y0 + ih); ctx.stroke(); ctx.strokeStyle = PLOT_COLORS.trace; ctx.lineWidth = 1.6; ctx.beginPath(); for (let i = 0; i < nsta; i++) { const x = x0 + (profX[i] / xMax) * iw; const y = y0 + ih - (profP[off + i] / pMax) * ih; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.stroke(); ctx.lineWidth = 1; ctx.font = '10px system-ui'; ctx.textAlign = 'center'; ctx.fillStyle = PLOT_COLORS.accent; ctx.fillText(`${modeF[m].toFixed(0)} Hz`, x0 + iw / 2, y0 + ih + 14); } ctx.textAlign = 'left'; ctx.fillStyle = PLOT_COLORS.axis; ctx.font = '10px system-ui'; ctx.fillText('window → foot', 10, 9); }, [profX, profP, modeF, nShown, rows], rows * CELL_H, ); return ; }