import type { Chapter } from '../../ui/types.ts' import driver from './driver.m?raw' import script from './room3d.m?raw' import text from './text.md?raw' import Listen, { type Clip } from '../../audio/Listen.tsx' import { Row, SeriesPlot, fmtErr, fmtMs, fmtNum } from '../common/panels.tsx' import { faded, ink, method } from '../../plot/palette.ts' interface ConvergeOut { Ns: number[] err: number[] ms: number[] cells: number[] ns: number[] orders: (number | null)[] fhi: number ppw: number[] fullband: number rate: number fullcells: number lowband: number t: number[] y: number[] ye: number[] t1: number[] y1: number[] } interface HearOut { Ns: number[] fsk: number[] clips: Clip[] clipExact: Clip } const BOX = { L: [3.2, 2.4, 2.0], lam: 0.55, sigma: 0.0005, xs: [0.8, 0.6, 0.8], xr: [2.4, 1.6, 1.2] } function duration(sec: number): string { if (sec < 120) return `${sec.toFixed(0)} s` if (sec < 7200) return `${(sec / 60).toFixed(0)} minutes` if (sec < 2 * 86400) return `${(sec / 3600).toFixed(1)} hours` if (sec < 2 * 365 * 86400) return `${(sec / 86400).toFixed(0)} days` return `${(sec / (365.25 * 86400)).toFixed(1)} years` } function ConvergePanels({ d }: { d: ConvergeOut }) { const last = d.Ns.length - 1 const tip = (i: number) => [ `N = ${d.Ns[i]}: ${d.cells[i].toLocaleString()} nodes`, `${fmtNum(d.ppw[i], 3)} points per wavelength at ${d.fhi} Hz`, `error ${fmtErr(d.err[i])}, run ${fmtMs(d.ms[i])}`, ] return ( <> ) } export const ch09: Chapter = { id: 'fdtd', nav: 'A room, by grid', part: 'Rooms', title: 'A room, by finite differences', blurb: 'The leapfrog scheme in three dimensions, checked against the exact image sum for rigid walls, and the arithmetic that confines it to low frequencies.', text, labs: [ { id: 'converge', title: 'A pulse in a rigid 3.2 × 2.4 × 2 m room', caption: 'A pulse with most of its energy below about 1.1 kHz, heard 1.8 m away for 30 ms, which is long enough for several dozen reflections. This lab waits for the run button.', driver, script, controls: [], defaults: {}, derive: () => ({ mode: 'converge', ...BOX, T: 0.03, Ns: [16, 24, 32, 48, 64] }), manual: true, editorOpen: true, render: (d: ConvergeOut) => , summary: (d: ConvergeOut) => [ { label: `error at N = ${d.Ns[d.Ns.length - 1]}`, value: fmtErr(d.err[d.err.length - 1]) }, { label: 'observed order', value: fmtNum(d.orders[d.orders.length - 1], 3) }, { label: 'ns per node per step', value: fmtNum(d.rate, 3) }, { label: 'Chapter 8’s room, 1 s, to 20 kHz', value: duration(d.fullband) }, { label: 'the same, to 2 kHz', value: duration(d.lowband) }, ], }, { id: 'hear', title: 'Three tenths of a second in the room', caption: 'The same pulse and the same rigid room, for long enough to hear it ring, exactly and on two grids. This lab waits for the run button.', driver, script, controls: [], defaults: {}, derive: () => ({ mode: 'hear', ...BOX, T: 0.3, fs: 22050, Ns: [16, 32] }), manual: true, render: (d: HearOut) => ( ({ label: `N = ${d.Ns[k]}`, clip: c })), ]} note={`the same scale; the grids run at ${d.fsk.map((f) => `${(f / 1000).toFixed(1)}`).join(' and ')} kHz`} /> ), }, ], }