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`}
/>
),
},
],
}