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