RasmahRasmah

Room Acoustics (Helmholtz Cavity Modes)

using Rasmah
using LinearAlgebra

Overview

Every enclosed air volume rings at its own set of natural frequencies — the room modes. They are why the bass sounds boomy in one corner of a living room and dead in another, why an HVAC duct hums at one particular fan speed, and the first thing an acoustics engineer computes for an auditorium, a loudspeaker cabinet, or a car cabin.

The physics is the scalar Helmholtz eigenproblem: acoustic pressure modes solve K p = (ω²/c²) M p with K = ∫ ∇p·∇q and M = ∫ (1/c²) p q, where c is the material’s sound speed. This tutorial computes the modes of a 4 × 3 × 2.5 m room — a typical living room — and checks them against the standing-wave formula f = c/(2L), the textbook result for a rigid-walled rectangular cavity.

shape → boundary sets → mesh → boundary conditions → solve

The physics

In a rigid-walled rectangular room the acoustic modes are standing waves with frequencies

\[ f_{mnk} = \frac{c}{2}\sqrt{\Big(\frac{m}{L_x}\Big)^2 + \Big(\frac{n}{L_y}\Big)^2 + \Big(\frac{k}{L_z}\Big)^2}, \]

for integers (m, n, k). The lowest axial mode is f = c/(2L) along the longest dimension; air at c ≈ 343 m/s in a 4 m room gives ≈ 42.9 Hz — a frequency almost every loudspeaker can excite, which is why small rooms have a “one-note bass” problem.

The room

A box, drawn and meshed the canonical way (an axis-aligned box carries its BRep face sets onto the mesh, so face selection happens before meshing):

Lx, Ly, Lz = 4.0, 3.0, 2.5                    # the living room, in metres

room = box(Lx, Ly, Lz)
b = brep(room)
mesh = tetrahedralize(b, 6)
TetMesh{Matrix{Float64}, Matrix{Int64}, Rasmah.BRepFaceLabels}([-2.0 -1.3333333333333335 … 1.3333333333333335 2.0; -1.5 -1.5 … 1.5 1.5; -1.25 -1.25 … 1.25 1.25], [1 1 … 132 132; 2 9 … 167 168; 9 8 … 168 133; 44 44 … 175 175], Rasmah.BRepFaceLabels(FaceSet[FaceSet([19 2 … 23 8; 20 9 … 24 9; 27 10 … 31 16]), FaceSet([142 152 … 157 160; 143 159 … 164 161; 150 160 … 165 168]), FaceSet([75 109 … 39 36; 76 144 … 74 37; 111 145 … 75 72]), FaceSet([101 136 … 32 99; 136 171 … 33 100; 137 172 … 68 135]), FaceSet([71 120 … 78 113; 78 127 … 113 148; 113 162 … 120 155]), FaceSet([77 126 … 42 126; 112 161 … 77 133; 119 168 … 84 168])]))
vtk(b)

The rigid-walled modes

With all walls rigid, the first “mode” is the constant-pressure mode (ω = 0, no spatial variation) and the first non-trivial mode is the (1,0,0) standing wave along the length:

res = solve(Acoustics(), mesh, air; nev = 6)

println("natural frequencies (Hz): ", round.(res.frequencies; digits = 2))
println("wavenumbers (1/m):        ", round.(res.wavenumbers; digits = 2))

c = sound_speed(air)
println("first axial mode ≈ c/(2Lx) = ", round(c / (2 * Lx); digits = 2), " Hz")
natural frequencies (Hz): Quantity{Float64}[0.0 Hz, 43.41 Hz, 58.69 Hz, 70.39 Hz, 75.23 Hz, 85.0 Hz]
wavenumbers (1/m):        Quantity{Float64}[0.0 1/m, 0.79 1/m, 1.07 1/m, 1.29 1/m, 1.38 1/m, 1.56 1/m]
first axial mode ≈ c/(2Lx) = 42.93 Hz

The second and third non-trivial modes land at c/(2Ly) ≈ 57.2 Hz and c/(2Lz) ≈ 68.6 Hz; combined (m, n) modes appear higher up.

vtk(mesh; field = res.modes[2], fieldname = "pressure mode (1,0,0)")

The pressure-release wall

One open window changes everything: the wall becomes a pressure-release surface (p = 0 — a node of the standing wave), which is why a room’s character changes with its furniture, curtains, and open doors:

soft = faces_in_box(b, [-Lx / 2 - 0.1, -1.0, -1.0], [-Lx / 2 + 0.1, 1.0, 1.0])
res2 = solve(Acoustics(), mesh, air; fixed = soft => 0.0, nev = 3)
println("pressure-release frequencies: ", round.(res2.frequencies; digits = 2), " Hz")
pressure-release frequencies: Quantity{Float64}[21.53 Hz, 63.17 Hz, 66.08 Hz] Hz

Notes

  • Acoustics() uses sound_speed(mat); for a scalar wave-optics problem replace c by the phase velocity c₀ / refractive_index.
  • The field is :p (field_names(Acoustics()) == (:p,)).
  • For a rigid-wall cavity leave fixed empty and skip the ω = 0 constant mode; eigen returns it first.

Next steps