/** * Everything in SI: metres, seconds, hertz, metres per second. * * The app used to be dimensionless — a domain two units across, a background * speed of one — which is tidy but leaves every number needing a translation * before it means anything, and leaves the microphone's recording with no * honest playback rate. In physical units all of that falls out: a wavelength * is a length you can compare to the room, a timestep is a real duration, and * the recorded trace plays back in real time at the pitch a microphone there * would have heard. * * It also makes the method's limits visible rather than hidden. A grid solver * resolves a wavelength with some number of cells, so a fixed grid over a * fixed domain is a low-frequency method: at 512 points across ten metres, * 700 Hz is 25 cells per wavelength and 2 kHz is nine. That ratio is now on * screen, because it is the number that decides whether what you are watching * is physics or grid dispersion. */ /** Speed of sound in air at about 20 °C, m/s. */ export const C_AIR = 343; /** Side of the square domain, metres. Hall-sized: big enough for a room with * air around it, small enough that a wavefront crosses it in 30 ms. */ export const DOMAIN = 10; /** Cells per wavelength below which what is on screen is as much grid * dispersion as it is sound. */ export const POOR_RESOLUTION = 8; /** A length in metres, written the way a person would say it. */ export const fmtLength = (m: number): string => Math.abs(m) < 1 ? `${(1000 * m).toPrecision(3)} mm` : `${m.toPrecision(3)} m`; /** A duration in seconds, likewise. */ export const fmtTime = (s: number): string => { const a = Math.abs(s); if (a > 0 && a < 1e-3) return `${(1e6 * s).toPrecision(3)} µs`; if (a < 1) return `${(1e3 * s).toPrecision(3)} ms`; return `${s.toPrecision(3)} s`; };