/** * Everything in SI: metres, seconds, hertz, metres per second. * * Physical units are what make the numbers here mean something. The string's * fundamental is a real pitch, the box is a real size you could build, the * timestep is a real duration, and the microphone's trace plays back at the * pitch a microphone there would have heard. They also keep the method's * limits visible: a grid solver resolves a wavelength with some number of * cells, so a fixed grid is a low-frequency method, and how low is a number * the app can show rather than hide. */ /** Speed of sound in air at about 20 °C, m/s. */ export const C_AIR = 343; /** The domain, metres: a box around the instrument, twice as long along the * string (x) as across (y) and up (z), centred on the origin. The top plate * of the dulcimer body sits at z = 0 and the string runs along x just above * it. */ export const DOMAIN_X = 1.0; export const DOMAIN_YZ = 0.5; /** Fraction of the stability limit the timestep is taken at. Fixed rather * than a control: everything downstream (the string grid, the recording's * sample rate) follows from dt, and one good value is worth more here than a * slider. */ export const CFL = 0.5; /** Cells per wavelength below which what is on screen (and in the recording) * 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`; };