import type { Chapter } from '../../ui/types.ts' import type { Num, Params } from '../../engine/types.ts' import driver from './driver.m?raw' import script from './openbox.m?raw' import text from './text.md?raw' import Listen, { type Clip } from '../../audio/Listen.tsx' import Field from '../../plot/Field.tsx' import { Row, SeriesPlot, fmtDb, fmtMs } from '../common/panels.tsx' import { method, type MethodKey } from '../../plot/palette.ts' interface SnapOut { ms: number refl: number tsnap: number ref: Num[][] num: Num[][] err: Num[][] emax: number umax: number urefmax: number t: number[] eng: number[] L: number } interface SweepOut { methods: string[] Ws: number[] refl: Num[][] ms: Num[][] } interface HearOut { methods: string[] fs: number tt: number[] traces: number[][] clips: Clip[] } const EDGE: Record = { rigid: { label: 'rigid wall', key: 'fd2', shape: 'circle' }, mur: { label: 'Mur’s condition', key: 'waveguide', shape: 'diamond' }, sponge: { label: 'damping layer', key: 'fd4', shape: 'square' }, pml: { label: 'perfectly matched layer', key: 'boundary', shape: 'cross' }, } const METHOD_CONTROL = { kind: 'select' as const, key: 'method', label: 'edge', options: Object.entries(EDGE).map(([k, v]) => ({ value: k, label: v.label })), } const REGION = { N: 100, L: 2, lam: 0.7, sigma: 0.06, xs: 0.7, ys: 1.1, T: 0.0085 } function SnapPanels({ d }: { d: SnapOut }) { const dom: [number, number] = [0, d.L] return ( <> Math.max(v, -160)), color: method.boundary, width: 1.6 }]} legend={false} /> ) } function SweepPanels({ d }: { d: SweepOut }) { const tip = (k: number) => (j: number) => [`layer ${d.Ws[j]} nodes`, `reflected ${fmtDb(d.refl[k][j])}`, `run ${fmtMs(d.ms[k][j])}`] const layered = d.methods.map((m, k) => ({ m, k })).filter(({ m }) => m === 'sponge' || m === 'pml') const flat = d.methods.map((m, k) => ({ m, k })).filter(({ m }) => m === 'rigid' || m === 'mur') return ( ({ y: d.refl[k][0] as number, color: method[EDGE[m].key], label: EDGE[m].label }))} series={layered.map(({ m, k }) => ({ x: d.Ws, y: d.refl[k], color: method[EDGE[m].key], shape: EDGE[m].shape, label: EDGE[m].label, tip: tip(k), }))} /> ({ x: d.ms[k], y: d.refl[k], color: method[EDGE[m].key], shape: EDGE[m].shape, label: EDGE[m].label, tip: tip(k), }))} /> ) } function HearPanels({ d }: { d: HearOut }) { return ( <> ({ x: d.tt, y: d.traces[k].map((v) => v + 1.2 * (d.methods.length - 1 - k)), color: method[EDGE[m].key], label: EDGE[m].label, width: 1.2, }))} yTicks={[]} /> ({ label: EDGE[m].label, clip: d.clips[k] }))} note={`the same scale, at the simulation's own rate of ${(d.fs / 1000).toFixed(1)} kHz`} /> ) } export const ch07: Chapter = { id: 'open', nav: 'Letting sound out', part: 'Rooms', title: 'Letting sound out', blurb: 'A grid has to end somewhere. Four ways of ending it, measured against a reference that is exact by causality, and heard.', text, labs: [ { id: 'snap', title: 'One pulse, four edges', caption: 'A pulse released 0.7 m from the left of a 2 m square. The snapshot is taken once it has reached every edge; whatever is in the difference panel came back from one.', driver, script, controls: [ METHOD_CONTROL, { kind: 'slider', key: 'W', label: 'layer width, nodes', min: 0, max: 0, values: [5, 10, 20, 40] }, ], defaults: { method: 'mur', W: 10 }, derive: (p: Params) => ({ mode: 'snap', ...REGION, tsnap: 0.0042, method: p.method, W: p.W }), editorOpen: true, render: (d: SnapOut) => , summary: (d: SnapOut) => [ { label: 'reflected energy', value: fmtDb(d.refl) }, { label: 'largest reflection / largest wave', value: (d.emax / d.urefmax).toExponential(1) }, ], }, { id: 'sweep', title: 'Reflection against cost', caption: 'Every edge, and the two layers at four widths. This lab takes several seconds, so it waits for the run button.', driver, script, controls: [], defaults: {}, derive: () => ({ mode: 'sweep', ...REGION, methods: ['rigid', 'mur', 'sponge', 'pml'], Ws: [5, 10, 20, 40] }), manual: true, render: (d: SweepOut) => , summary: (d: SweepOut) => { const k = d.methods.indexOf('pml') return [ { label: 'Mur', value: fmtDb(d.refl[d.methods.indexOf('mur')][0]) }, { label: 'PML, 5 nodes', value: fmtDb(d.refl[k][0]) }, { label: 'PML, 40 nodes', value: fmtDb(d.refl[k][d.Ws.length - 1]) }, ] }, }, { id: 'hear', title: 'A box, and a box that is not there', caption: 'A 3 m square, a thump at one point, a microphone at another, for 0.3 s. This lab waits for the run button.', driver, script, controls: [], defaults: {}, derive: () => ({ mode: 'hear', N: 60, L: 3, lam: 0.7, sigma: 0.1, xs: 1.0, ys: 1.3, xr: 2.1, yr: 1.7, T: 0.3, W: 10, methods: ['rigid', 'mur', 'pml'], }), manual: true, render: (d: HearOut) => , }, ], }