// The washer as a boundary condition. The shell rests on a felt sleeve at // its mounting circle; modeled as a spring per unit length, it gives the // whole shell two rigid-body ways to move that a clamp forbids: a bounce // (m = 0) and a tilt (m = 1). The figure animates either one, slowed far // down, with the closed-form rigid-shell frequencies for the stiffness set // by the slider. import { useEffect, useRef, useState } from 'react'; import { drawFrame, PLOT_COLORS } from '../../components/plots/canvasUtils'; import { Figure, FigControls, FigSlider } from '../figparts'; const HEIGHT = 230; const A = 0.23; const C = 0.029; const RISE = 0.04; const RHOH = 8.86; // kg/m^2 const SLOW = 8; // animation runs this many times slower than real export default function WasherFig() { const [kw, setKw] = useState(1.65e5); const [mode, setMode] = useState<'bounce' | 'tilt'>('bounce'); const canvasRef = useRef(null); const fBounce = Math.sqrt((kw * 2 * Math.PI * C) / (RHOH * Math.PI * (A * A - C * C))) / (2 * Math.PI); const fTilt = Math.sqrt((4 * kw * C * C * C) / (RHOH * (A ** 4 - C ** 4))) / (2 * Math.PI); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; let raf = 0; const t0 = performance.now(); const draw = () => { const dpr = window.devicePixelRatio || 1; const w = canvas.clientWidth; const bw = Math.max(1, Math.round(w * dpr)); const bh = Math.max(1, Math.round(HEIGHT * dpr)); if (canvas.width !== bw || canvas.height !== bh) { canvas.width = bw; canvas.height = bh; canvas.style.height = `${HEIGHT}px`; } const ctx = canvas.getContext('2d')!; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); drawFrame(ctx, w, HEIGHT); const f = mode === 'bounce' ? fBounce : fTilt; const tSec = (performance.now() - t0) / 1000; const phase = Math.sin(2 * Math.PI * (f / SLOW) * tSec); const cx = w / 2; const baseY = HEIGHT - 56; const scale = (w - 80) / (2 * A); const zScale = (HEIGHT - 130) / RISE; const ampPx = 13; const dz = (r: number, side: number) => { // vertical offset of the shell at signed radius, in px if (mode === 'bounce') return -phase * ampPx; return -phase * ampPx * ((side * r) / A); }; const px = (r: number, side: number) => cx + side * r * scale; const py = (z: number) => baseY - z * zScale; // ground ctx.strokeStyle = PLOT_COLORS.grid; ctx.beginPath(); ctx.moveTo(20, baseY + 26); ctx.lineTo(w - 20, baseY + 26); ctx.stroke(); // springs at +-c const zc = RISE * (1 - (C * C) / (A * A)); for (const side of [-1, 1]) { const x = px(C, side); const topY = py(zc) + dz(C, side) + 4; const botY = baseY + 26; ctx.strokeStyle = PLOT_COLORS.accent; ctx.beginPath(); ctx.moveTo(x, topY); const nzig = 6; for (let i = 1; i <= nzig; i++) { const yy = topY + ((botY - topY) * i) / (nzig + 1); ctx.lineTo(x + (i % 2 === 0 ? 7 : -7), yy); } ctx.lineTo(x, botY); ctx.stroke(); } // shell cross-section, displaced rigidly ctx.strokeStyle = '#c9a227'; ctx.lineWidth = 3; ctx.beginPath(); const N = 80; for (let i = 0; i <= N; i++) { const rr = -A + (2 * A * i) / N; const side = rr < 0 ? -1 : 1; const r = Math.abs(rr); const z = RISE * (1 - (r * r) / (A * A)); const x = px(r, side); const y = py(z) + dz(r, side); if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.stroke(); ctx.lineWidth = 1; ctx.fillStyle = PLOT_COLORS.axis; ctx.font = '11px system-ui'; ctx.textAlign = 'left'; ctx.fillText( `${mode === 'bounce' ? 'bounce (m = 0)' : 'tilt (m = 1)'} at ${f.toFixed(1)} Hz, shown ${SLOW}× slower`, 14, 20, ); ctx.fillText( `rigid-shell closed forms: bounce ${fBounce.toFixed(1)} Hz, tilt ${fTilt.toFixed(1)} Hz`, 14, 38, ); raf = requestAnimationFrame(draw); }; raf = requestAnimationFrame(draw); return () => cancelAnimationFrame(raf); }, [mode, fBounce, fTilt]); return (
The whole shell riding on its felt washer, with the mounting circle drawn as the springs it is. Treating the shell as rigid gives closed forms for the two rigid-body modes; at the paper's measured stiffness (1.65×105{' '} N/m per meter of circumference) they land at 22.9 Hz and 4.1 Hz, and letting the shell flex lowers the bounce to about 13 Hz. Both are below hearing, but they set the boundary the audible modes see, and under a hard stroke the shell swings away from the stick by centimeters, which changes what a follow-through stroke does. The Rigid clamp preset in the simulator removes all of this, as the earlier literature did, and is worth an A/B listen. } > `${v.toExponential(2)} N/m/m`} onChange={setKw} />
); }