import type { Chapter } from '../../ui/types.ts'
import type { Params } from '../../engine/types.ts'
import driver from './driver.m?raw'
import script from './images.m?raw'
import text from './text.md?raw'
import Listen, { type Clip } from '../../audio/Listen.tsx'
import { Row, SeriesPlot, fmtMs, fmtNum } from '../common/panels.tsx'
import { faded, method } from '../../plot/palette.ts'
interface RoomOut {
ms: number
rt: number
rtbroad: number
sabine: number
eyring: number
lattice: number
ts: number[]
sch: number[]
schB: number[]
tp: number[]
sp: number[]
tmax: number
te: number[]
he: number[]
direct: number
clipDry: Clip
clipWet: Clip
clipIr: Clip
alpha: number
V: number
S: number
}
interface DecayOut {
alphas: number[]
rt: number[]
lattice: number[]
sabine: number[]
eyring: number[]
ms: number[]
}
const ROOM = { L: [6, 4.5, 3], xs: [1.5, 1.2, 1.4], xr: [4.3, 3.1, 1.6] }
const FORMULA = { sabine: method.fd2, eyring: method.waveguide, lattice: method.exact }
const s = (v: number) => `${fmtNum(v, 3)} s`
function RoomPanels({ d }: { d: RoomOut }) {
const line = (rt: number) => ({ x: [0, d.tmax * 1000], y: [0, (-60 * d.tmax) / rt] })
return (
<>
>
)
}
function DecayPanels({ d }: { d: DecayOut }) {
const tip = (i: number) => [
`α = ${d.alphas[i]}`,
`measured ${s(d.rt[i])}`,
`lattice ${s(d.lattice[i])}`,
`Sabine ${s(d.sabine[i])}, Eyring ${s(d.eyring[i])}`,
`images ${fmtMs(d.ms[i])}`,
]
const ratio = (v: number[]) => v.map((x, i) => x / d.lattice[i])
return (
)
}
export const ch08: Chapter = {
id: 'images',
nav: 'A room, by images',
part: 'Rooms',
title: 'A room, by image sources',
blurb:
'The walls replaced by mirrors, the room by a lattice of images, and the two reverberation formulas every acoustician knows, measured against both.',
text,
labs: [
{
id: 'room',
title: 'A 6 × 4.5 × 3 m room',
caption:
'Every wall absorbs the same fraction of the energy that reaches it. Change it, and listen to the same phrase played in each room.',
driver,
script,
controls: [
{
kind: 'select',
key: 'alpha',
label: 'absorption α',
options: [
{ value: 0.1, label: '0.1, hard walls' },
{ value: 0.2, label: '0.2' },
{ value: 0.35, label: '0.35' },
{ value: 0.6, label: '0.6, heavily damped' },
],
},
{
kind: 'select',
key: 'taps',
label: 'each arrival placed',
options: [
{ value: 0, label: 'on the nearest sample' },
{ value: 8, label: 'as an 8-tap windowed sinc' },
{ value: 32, label: 'as a 32-tap windowed sinc' },
],
},
],
defaults: { alpha: 0.2, taps: 8 },
derive: (p: Params) => ({
mode: 'room',
...ROOM,
fs: 22050,
alpha: p.alpha,
taps: p.taps,
tcap: 1.2,
notes: [60, 64, 67, 72, 76, 72, 67, 64, 60],
step: 0.24,
}),
editorOpen: true,
render: (d: RoomOut) => ,
summary: (d: RoomOut) => [
{ label: 'images', value: fmtMs(d.ms) },
{ label: 'RT measured', value: s(d.rt) },
{ label: 'lattice', value: s(d.lattice) },
{ label: 'Sabine', value: s(d.sabine) },
{ label: 'Eyring', value: s(d.eyring) },
],
},
{
id: 'decay',
title: 'Reverberation time against absorption',
caption: 'Six rooms, from nearly bare to heavily damped. This lab takes a few seconds, so it waits for the run button.',
driver,
script,
controls: [],
defaults: {},
derive: () => ({ mode: 'decay', ...ROOM, fs: 16000, alphas: [0.05, 0.1, 0.2, 0.3, 0.5, 0.7], tcap: 1.5, taps: 8 }),
manual: true,
render: (d: DecayOut) => ,
summary: (d: DecayOut) => {
const dev = Math.max(...d.rt.slice(0, 4).map((v, i) => Math.abs(v / d.lattice[i] - 1)))
return [{ label: 'measured vs lattice, α ≤ 0.3', value: `within ${(100 * dev).toFixed(0)} %` }]
},
},
],
}