/** * Low-frequency room model shared by the subwoofer simulator and the rules-of-thumb study. * Rectangular room, rigid walls, uniform modal damping (Kuttruff, Room Acoustics, ch. 3 and 5): * H(ω) = Σsubs [ 1/(iω) + iω Σn 2^m φn(seat) φn(sub) / (ωn² − ω² + 2iβω) ], φn = Π cos(nπx/L), β = 3 ln10 / T60. */ export const C = 343; export interface Mode { nx: number; ny: number; nz: number; w2: number; m: number } export function modes(lx: number, ly: number, lz: number, fmax = 320): Mode[] { const out: Mode[] = []; for (let nx = 0; nx < 24; nx++) for (let ny = 0; ny < 24; ny++) for (let nz = 0; nz < 10; nz++) { if (!nx && !ny && !nz) continue; const f = (C / 2) * Math.hypot(nx / lx, ny / ly, nz / lz); if (f > fmax) continue; out.push({ nx, ny, nz, w2: (2 * Math.PI * f) ** 2, m: (nx ? 1 : 0) + (ny ? 1 : 0) + (nz ? 1 : 0) }); } return out; } export const phi = (md: Mode, x: number, y: number, z: number, lx: number, ly: number, lz: number) => Math.cos((md.nx * Math.PI * x) / lx) * Math.cos((md.ny * Math.PI * y) / ly) * Math.cos((md.nz * Math.PI * z) / lz); export const FREQS = Array.from({ length: 48 }, (_, i) => 25 * Math.pow(120 / 25, i / 47)); export type P3 = [number, number, number]; /** dB magnitude at the seat for one or more subs (meters), over FREQS. */ export function response(ms: Mode[], seat: P3, subs: P3[], lx: number, ly: number, lz: number, beta: number, freqs = FREQS) { const pr = ms.map((md) => phi(md, seat[0], seat[1], seat[2], lx, ly, lz) * 2 ** md.m); const ps = ms.map((md, k) => subs.reduce((a, s) => a + phi(md, s[0], s[1], s[2], lx, ly, lz), 0) * pr[k]); return freqs.map((f) => { const w = 2 * Math.PI * f; let re = 0, im = -subs.length / w; for (let k = 0; k < ms.length; k++) { const a = ms[k].w2 - w * w, b = 2 * beta * w, d = a * a + b * b; re += (w * ps[k] * b) / d; im += (w * ps[k] * a) / d; } return 10 * Math.log10(re * re + im * im + 1e-30); }); } /** Standard deviation of a dB response: how uneven the bass is. */ export function spread(db: number[]) { const mean = db.reduce((a, b) => a + b, 0) / db.length; return Math.sqrt(db.reduce((a, b) => a + (b - mean) ** 2, 0) / db.length); } export const betaFor = (t60: number) => (3 * Math.log(10)) / t60;