Skip to main content

Playground · research instrument

DSP · Audio

Room Mode Calculator

Enter your room dimensions and see every standing-wave resonance up to a chosen frequency. Identify which modes pile up at your listening position, and explore the pressure pattern of any single eigenmode across the floor plan.

Independent research instrument — not claimed as MakerPortal shipped product code. Methods, equations, assumptions, and limitations are disclosed so you can inspect what the page does and does not establish.

Standard room size? 653 rooms are already solved — modes, pile-ups and proportions, no input needed.

Browse solved rooms →

Room geometry

Listener position

Source position

Results

Room metrics

Volume
56.0 m³
Surface area
90.4 m²
Edge length
47.2 m
Speed of sound
343 m/s
Schroeder fc
Total modes
0

Mode counts

Axial
0
Tangential
0
Oblique
0
Density at fmax
3.28 /Hz

Modal frequency table

#nxnynzf (Hz)TypePressure at listener

2D Pressure map

Horizontal slice at listener height (z = 1.20 m). Each cell's colour shows the standing‑wave pressure of the selected mode.

Pressure map

Essence

Your room is a 3D guitar string you didn't pluck — but bass does.

Clap in an empty box and some notes keep singing. Not echo — resonance. Air trapped between parallel walls behaves exactly like a string pinned at both ends: only wavelengths that fit an integer number of half-waves survive. In 1D that's f=nc/2Lf=n c/2L. In 3D it's the same idea three times — combine half-waves along length, width, and height, and you get a grid of allowed tones. Those are room modes. Everything that booms, drones, or disappears when you move the couch lives here.

The ghost in the calculator is the assumption that walls are brick-ideal — perfectly rigid, zero absorption. Real walls breathe, flex, soak energy, shift peaks down a few Hz and smear them. Yet the rigid ideal predicts measured axial frequencies within 2–5% in concrete rooms and tells you where to put a sub to excite or avoid a mode — because nodes are geometry, not damping.

1. From wave equation to cosine lattice

Start with wave equation 2p=(1/c2)2p/t2\nabla^2 p = (1/c^2)\partial^2 p/\partial t^2. Seek separable harmonic solution p(x,y,z,t)=X(x)Y(y)Z(z)ejωtp(x,y,z,t)=X(x)Y(y)Z(z)e^{j\omega t}. Neumann walls — particle velocity zero normal to wall — mean p/n=0\partial p/\partial n =0 at x=0,Lxx=0,L_x etc.

That boundary condition quantizes wavenumbers: kx=nxπ/Lxk_x = n_x \pi/L_x, ky=nyπ/Lyk_y = n_y \pi/L_y, kz=nzπ/Lzk_z = n_z \pi/L_z with nx,ny,nzN0n_x,n_y,n_z\in\mathbb{N}_0 not all zero. Total k=kx2+ky2+kz2=ω/c|\mathbf{k}| = \sqrt{k_x^2+k_y^2+k_z^2} = \omega/c. Divide by 2π2\pi:

fnx,ny,nz=c2(nxLx)2+(nyLy)2+(nzLz)2f_{n_x,n_y,n_z} = \frac{c}{2} \sqrt{\left(\frac{n_x}{L_x}\right)^2 + \left(\frac{n_y}{L_y}\right)^2 + \left(\frac{n_z}{L_z}\right)^2}

cc itself comes from temperature: c(T)=331.31+T/273.15c(T)=331.3\sqrt{1+T/273.15} m/s. So a hot room tunes modes up ~0.17% per °C — 0.6 Hz shift at 100 Hz per 3 °C. Measurable. The calculator recomputes it live.

2. Pressure shape — where sub and ears couple

For rigid walls the eigenfunction is pure cosines product:

pnxnynz(x,y,z)=cos(nxπxLx)cos(nyπyLy)cos(nzπzLz)p_{n_x n_y n_z}(x,y,z)=\cos\left(\frac{n_x\pi x}{L_x}\right)\cos\left(\frac{n_y\pi y}{L_y}\right)\cos\left(\frac{n_z\pi z}{L_z}\right)

Value in [-1,1]. Antinode ±1 at every wall — pressure maximal, velocity zero. Node 0 where nxπx/Lx=π/2,3π/2n_x\pi x/L_x = \pi/2, 3\pi/2... i.e. x=Lx/(2nx),3Lx/(2nx)x = L_x/(2n_x), 3L_x/(2n_x)... That's a plane. Two cosines zero → nodal line. Three → nodal point.

Coupling is ruthlessly simple: mode amplitude p(source)×p(listener)\propto p(\text{source})\times p(\text{listener}). Put a sub at a pressure antinode (wall) and it maximally drives that mode. Put it at a node and that mode goes silent — even though other modes still fire because their nodes are elsewhere. Same at listener: at a node you don't hear that mode, even if source excites it. This is why corner placement (all cos=±1) excites every mode, and why moving a sub 70 cm can kill a 40 Hz boom without EQ.

Velocity is 90° out of phase spatially: vp\mathbf{v}\propto \nabla p. Where p=0p=0, particle velocity peaks — good place for porous absorbers which need air motion. Where p=±1p=±1, velocity zero — absorbers do nothing, but membrane traps work.

3. Why modes look like they do on the map

The 2D slice fixes z=zlistenerz = z_{listener} and shows p(x,y,z)p(x,y,z). Red/blue are ± pressure. Watch (1,0,0): left red, right blue, one half-wave across length. (2,0,0): red-blue-red — two half-waves. (1,1,0): checkerboard — one wave along x and y, hitting four walls. Higher indices tile faster, density f2\propto f^2.

The listener diamond and source dot on the map are not decoration — the table's "Pressure at listener" column is exactly p(xL,yL,zL)p(x_L,y_L,z_L) and source coupling would be p(xS,yS,zS)p(x_S,y_S,z_S). Scan the table: if a mode is +0.99 at listener, that frequency will dominate at the chair; -0.03 means you're sitting in its null.

Axial / Tangential / Oblique — why energy differs

Axial (nx,0,0)(n_x,0,0) — one index non-zero. Wave bounces between two parallel walls only. Two reflections per cycle, minimal wall contact, highest Q, biggest peak. (1,0,0) at c/2Lxc/2L_x is almost always the worst offender.

Tangential (nx,ny,0)(n_x,n_y,0) — two non-zero. Four walls per cycle. Energy hits walls more often, losses double ~ — about 3 dB less energy than axial after averaging.

Oblique (nx,ny,nz)(n_x,n_y,n_z) — three non-zero. All six surfaces. Least energy in practice, ~6 dB below axial, but most numerous: number of oblique grows as f3f^3. Topology: 1-0-0 is plane wave, 1-1-0 is diagonal, 1-1-1 bounces around room like a billiard.

Real rooms weight axials strongest, which is why your table highlights axial rows — they need treatment first.

Schroeder / Schroeder-Bay — when statistics beats determinism

fc=2000T60Vf_c = 2000 \sqrt{\frac{T_{60}}{V}}

T60T_{60} here is estimated from Sabine 0.161 V/A with αˉ=0.15\bar{\alpha}=0.15 typical furnished. fcf_c is where 10 modes overlap within a mode's half-power bandwidth — above it, individual peaks blur into a statistical tail. Below, you hear discrete colouration. Rule of thumb: VV in m³, T60T_{60} in s. 60 m³ living room, 0.4 s → fc163f_c\approx 163 Hz. That's why bass below ~200 Hz needs modal fixes (placement, traps, EQ), above it needs absorption/diffusion — different physics. Constant 2000 varies by school (1500–4000); we use 2000 as conventional. Bonello's extra check: modal density should monotonically increase in each third-octave — if not, ratios are poor. Sepmeyer and Louden suggested Lx:Ly:Lz1:1.6:2.33L_x:L_y:L_z \approx 1:1.6:2.33 or 1:1.4:1.9 to spread axials. Use this tool to test any ratio — look for piled axial pairs within 5 Hz, those will ring.

Nodal placement as acoustical judo

Want less (2,0,0)? Place sub at x=Lx/4x=L_x/4 or 3Lx/43L_x/4 because cos(2πx/Lx)=0\cos(2\pi x/L_x)=0 there. Want fewer nulls at listening spot? Avoid listener at x=Lx/2x=L_x/2 for (1,0,0) (null at center), avoid y=Ly/2y=L_y/2 for (0,1,0), etc. The map makes this visual: drag source or listener in your head to a white (zero) region for the bad mode.

Full elimination impossible because nodes differ per mode. Trick: sub at 1/3 length excites (1,0,0) and (2,0,0) moderately but nulls (3,0,0). Two subs at 1/4 and 3/4 cancel even axials via destructive pressure summation — principle behind double-bass arrays and CABS.

For porous absorption: put it where p\nabla p maximal (nodes) and thick enough >λ/10> \lambda/10 or spaced off wall. For pressure traps (membrane): put at antinodes (walls).

What ideal model ignores

Finite wall impedance → eigenfrequencies shift down 2–5% and Q finite (~10–40 at low end). Damping not modal-independent: tangential/oblique damp faster because more wall hits.

Coupling: real modes are not orthogonal with lossy walls; energy leaks between them. Non-rectangular (L-shaped, vaulted) splits degeneracy — our integer triple no longer holds. Furniture acts as scatterer, adds absorption \approx area·α and breaks perfect cosine via diffusion.

Air absorption above 2 kHz in large rooms matters (Sabine's 4mV term). The pressure map shows undamped pp, not SPL re energy — it's geometric ideal, not FEM.

Playbook — tame the booms

1. Find the offender

Enter LxL_x, LyL_y, LzL_z. Set max freq 300 Hz — bass region. Sort by freq: lowest three axials are your enemies. Note where two axials land within 4 Hz — Bonello violation, audible double peak. Example 5×4×2.8 m: (1,0,0)=34.3(1,0,0)=34.3 Hz, (0,1,0)=42.9(0,1,0)=42.9 Hz, (0,0,1)=61.3(0,0,1)=61.3 Hz. Those three define your room's signature.

2. Use the map to move intelligenty

Pick mode (1,0,0) in dropdown. See red left, blue right. Drag listener (mentally) to center white line → pressure 0 → that mode disappears at ear. Pick (0,1,0), same along width. Real listening spot is compromise — stay >0.3 m from all walls, avoid exact centers L/2 if you want to hear axial at all, sit at center if you want to null it. For sub: corner = excite all, 1/2 wall = kill first axial, 1/4 wall = kill second, 1/3 and 2/3 dual subs kill even more. Check source pressure vs mode in table extension idea: source coupling = p(xS)p(x_S) similar to listener.

3. Ratio tune / break tie

If building or choosing room, sweep LxL_x slider ±10 cm and watch table — aim for axial spacing >5% apart below 100 Hz and increasing density per third-octave. Louden 1:1.4:1.9, Sepmeyer 1:1.28:1.54, EBU 1:1.25:1.6 all better than cube (1:1:1 disaster). The Schroeder readout tells you where you can stop caring — above fcf_c, use absorption, not geometry.

4. Treatment logic

Below fcf_c: move sub/listener (free), then EQ (narrow notch -3 to -6 dB, Q 8–12) at modal frequency, then resonant traps tuned to fmodef_{mode} placed at pressure antinodes (wall). Porous panels effective only if 10–15 cm thick or spaced, placed at nodes (velocity max). Above fcf_c: broadband absorption, aim T600.3T_{60}\approx0.3–0.5 s for small rooms, calculate via A=0.161V/T60A =0.161 V/T_{60}. The pressure map's +1/-1 scale reminds you: porous does nothing at +1 walls.

5. Validate with measurement

Run a sine sweep at listening position — peaks in transfer function should match table within a few Hz. Nulls should match nodes. If measured peak higher Q than expected, wall is more rigid than assumed; if lower and broader, extra absorption present. Use that to update αˉ\bar{\alpha} and predict T60T_{60}.

Honest limits

Rigid-wall, undamped, rectangular, no coupling, no air absorption. Real fnf_n lower due to wall compliance. Damping and modal overlap shift peaks. Yet axial ordering, nodal geometry, and density growth are robust — trust them for placement and ratio decisions, not for absolute SPL to 0.1 dB.

Quick reference

p(x,y,z)=cosnxπxLxcosnyπyLycosnzπzLzp(x,y,z)=\cos\frac{n_x\pi x}{L_x}\cos\frac{n_y\pi y}{L_y}\cos\frac{n_z\pi z}{L_z}
Coupling psrcplst\propto p_{src}\,p_{lst} — zero at node, max at wall.
Modal density f2V\propto f^2 V — sparse below fcf_c, statistical above.

Anatomy of the instrument

Seven sliders, one SVG map, one live table. Here is what each control and visualization is actually doing under the hood — and why the slider limits, the color scale, and the cell resolution were chosen the way they are.

The input controls and metrics

  1. 01

    Room dimensions (Lx, Ly, Lz). Range sliders with linked numeric readouts. The readParams() function pulls all values and feeds them to the roomModes() solver. Slider max values are deliberately capped (Lx/Ly 1–20 m, Lz 1–10 m) because below 1 m the modal density explodes and above 20 m you're in a hall where statistical acoustics replaces modal analysis.

  2. 02

    Temperature. This sets c(T)c(T) via ISO 9613-1 and feeds every frequency calculation. At -10 °C, c325c \approx 325 m/s; at 45 °C, c358c \approx 358 m/s — a 10% spread. Every mode frequency shifts proportionally. The Schroeder frequency also shifts because T₆₀ depends on cc through the Sabine constant 0.161.

  3. 03

    Listener and source positions. Six additional range sliders whose max values update dynamically to stay within the room bounds. When you drag a room dimension slider, updateReadouts() re-clamps the listener/source max values so you can't position them outside the room. Both positions feed the pressure-at-listener column and determine the dot/diamond positions on the SVG map.

  4. 04

    The metrics dashboard. Volume, surface area, and edge length are simple geometry. Schroeder frequency uses Sabine with ᾱ=0.15 — deliberately a conservative estimate because real furnished rooms typically have ᾱ between 0.12 and 0.25 at low frequencies. The mode counter runs all three types, while density at fmax evaluates the Weyl volume, surface, and edge terms to estimate how many additional modes fit in the next hertz.

The mode-counting loop

roomModes(Lx, Ly, Lz, c, fmax) iterates integer triples (nx, ny, nz) from 0 upward, computes f=(c/2)(nx/Lx)2+(ny/Ly)2+(nz/Lz)2f = (c/2)\sqrt{(n_x/L_x)^2 + (n_y/L_y)^2 + (n_z/L_z)^2}, halts when f exceeds fmax, and classifies each triple as axial (one non-zero), tangential (two), or oblique (three). This is an exhaustive search, not a formula — for a 5×4×2.8 m room up to 400 Hz, it produces about 450 modes. The algorithm stops per-axis loops when a single-axis contribution alone exceeds fmax.

The pressure map and table

  1. 05

    SVG pressure map. Built entirely from <rect> elements in an inline SVG — no canvas, no WebGL. Each cell computes p(x,y,zlistener)p(x,y,z_{listener}) at its centroid via modePressure() which evaluates cos(nxπx/Lx)cos(nyπy/Ly)cos(nzπz/Lz)\cos(n_x\pi x/L_x)\cos(n_y\pi y/L_y)\cos(n_z\pi z/L_z). The color is HSL with hue at 0° (positive/red) or 240° (negative/blue), saturation proportional to |p|. The grid resolution slider (15–60 cells) trades visual fidelity for DOM weight.

  2. 06

    Colorbar and legend. A linear gradient from −1 (blue) through 0 (white) to +1 (red), drawn as a vertical bar to the right of the map. The listener dot (white) and source dot (CTAs colored) are overlaid at their scaled xy positions. The dimension labels Lx and Ly are annotated at the bottom and left of the map, rotated for Ly.

  3. 07

    The modes table. Built by mapping the sorted mode array to HTML rows, capped at 200 rows with a truncation notice. Rows are color-coded by type: green-tinted axials, yellow-tinted tangentials, purple-tinted obliques. Any mode with |p| > 0.5 at the listener position gets a left-border highlight — these are your priority targets. The table-note paragraph updates to tell you how many modes are shown vs total.

  4. 08

    No idle render loop. The page computes on every slider input event — no requestAnimationFrame, no polling. The handleInput() function re-clamps listener/source positions, calls recalculate() which runs the mode solver, builds the table, populates the dropdown, and redraws the SVG. When you change the mode dropdown or grid resolution, only drawPressureMap() re-runs — the mode table is not rebuilt.

Gear for room measurement & treatment

Room acoustics stack · 22 picks

Measurement mics & monitors22

More gear across every app: the full Gear list →

Two gotchas worth knowing

The rigid-wall assumption breaks at the first drywall

This tool assumes perfectly rigid walls — the Neumann boundary condition. Drywall on studs is not rigid; it's a compliant panel that shifts eigenfrequencies down by 2–5% and adds damping (finite Q). A mode computed at 42.9 Hz may measure at 40.5 Hz in a real room. The ordering and nodal geometry remain correct, but don't trust the absolute frequency to better than ±5%. If you need measurement-grade accuracy, use a calibrated mic and swept sine — the map is for placement decisions, not absolute frequency confirmation.

The 2D map is a single z-slice illusion

The pressure map shows a horizontal slice at the listener's ear height. But room modes are fully 3D — a mode you see as "null at center" on the 2D map may actually peak at z=1.80 m (standing height) while vanishing at z=1.20 m (sitting height). Oblique modes especially have strong z-dependence. If you move your measurement mic vertically and the boom returns, you're seeing exactly this effect. The table's "Pressure at listener" column uses all three coordinates, so it's always correct — trust the number, not just the 2D visual.

JavaScript — the mode solver

The core roomModes() and modePressure() functions. Exhaustive integer scan, temperature-dependent speed of sound, zero-allocation cosine evaluation.

Room modes — JavaScript

const speedOfSound = (tC) => 331.3 * Math.sqrt(1 + tC / 273.15);

const schroederFrequency = (V, T60) => {
  if (V <= 0 || T60 <= 0) return NaN;
  return 2000 * Math.sqrt(T60 / V);
};

const modePressure = (x, y, z, Lx, Ly, Lz, nx, ny, nz) => {
  return Math.cos(nx * Math.PI * x / Lx)
       * Math.cos(ny * Math.PI * y / Ly)
       * Math.cos(nz * Math.PI * z / Lz);
};

const roomModes = (Lx, Ly, Lz, c, fmax) => {
  const modes = [];
  const maxNx = Math.ceil(2 * Lx * fmax / c) + 1;
  const maxNy = Math.ceil(2 * Ly * fmax / c) + 1;
  const maxNz = Math.ceil(2 * Lz * fmax / c) + 1;

  for (let nx = 0; nx <= maxNx; nx++) {
    const fx = (nx * c) / (2 * Lx);
    if (fx > fmax) break;
    for (let ny = 0; ny <= maxNy; ny++) {
      const fy = (ny * c) / (2 * Ly);
      if (Math.hypot(fx, fy) > fmax) break;
      for (let nz = 0; nz <= maxNz; nz++) {
        if (nx === 0 && ny === 0 && nz === 0) continue;
        const f = (c / 2) * Math.sqrt(
          (nx / Lx) ** 2 + (ny / Ly) ** 2 + (nz / Lz) ** 2
        );
        if (f > fmax) break;
        const nonZero = (nx > 0 ? 1 : 0) + (ny > 0 ? 1 : 0) + (nz > 0 ? 1 : 0);
        const type = nonZero === 1 ? 'axial'
          : nonZero === 2 ? 'tangential' : 'oblique';
        modes.push({ nx, ny, nz, freq: f, type });
      }
    }
  }
  return modes.sort((a, b) => a.freq - b.freq);
};

Frequently asked questions

Why are axial modes the loudest and most problematic?

Axial modes bounce between only two parallel walls — they touch the least total surface area per cycle and therefore lose the least energy to wall absorption. Tangential (four walls) and oblique (all six surfaces) leak energy faster because every wall contact bleeds a small fraction. In a concrete room, the (1,0,0) axial at c/(2Lx) is almost always the worst offender — it's the one you hear booming when you clap.

How do I read the pressure at listener column?

It's the eigenfunction value p(x,y,z) at your listening position, ranging from -1 (antinode with inverted phase) to +1 (antinode). Values near 0 mean you're sitting in that mode's pressure null — you won't hear that frequency even if the source is exciting it. Values above 0.5 in magnitude mean that mode dominates your seat. Scan the table: modes with |p| > 0.5 are the ones you need to treat or move away from.

What should I do with the Schroeder frequency number?

Below fs, individual modes are sparse and audible — treat them with tuned traps, sub/listener placement, or narrow EQ cuts. Above fs, modes overlap densely and behave statistically — broadband absorption works. The tool estimates T₆₀ from Sabine with ᾱ=0.15 (typical furnished room). If your room is more absorbent, fs shifts down; if it's concrete, fs shifts up. Measure T₆₀ to get the real crossover.

Why do my measured room modes differ from the calculator's predictions?

Several reasons: real walls are not perfectly rigid (they're compliant, shifting frequencies down 2-5%), absorption isn't uniform across surfaces, furniture breaks the modal cosine shape, and non-rectangular geometry (alcoves, sloped ceilings) breaks the integer-index assumption entirely. This tool gives the rigid-wall ideal — it's typically within 5% for concrete rooms and serves as an excellent starting point for placement decisions even in imperfect rooms.

How do I use the 2D pressure map to place a subwoofer?

Select a problematic mode from the dropdown, then look for red or blue regions — those are pressure antinodes (walls and certain interior regions where cosines peak). Placing a sub in an antinode maximally excites that mode. If you want to kill the mode instead, place the sub at a white/light region (pressure node) where cos≈0 — the sub physically cannot couple to that mode. Move the listener dot similarly: put your head in a zero-pressure region for that frequency and you won't hear it.

Shareable still

The instrument, captured—not illustrated.

This 16:9 frame is rendered from the real browser instrument above. It is the page's canonical preview for image search, link unfurls, and posts that need to show what the tool actually does.

Download 1280 × 720 JPEG
Room Mode Calculator — live MakerPortal instrument screenshot
Canonical capture · real UI · no generated scientific artwork