Skip to content

RFC: Hybrid geometric acoustics (IS+UTD early, MC middle, diffusion late) #148

Description

@rdmiller

RFC: a hybrid geometric-acoustics solver (kind: "hybrid-ga"). Not a fifth tracer.

CRAM already has image source, Monte Carlo rays, beam trace (+ UTD), and ART. The missing product is what ODEON/CATT ship: deterministic early + stochastic middle + diffuse late, with one energy unit and a defined crossover. Today that is a splice inside the raytracer (#129) plus three other solvers the user must run by hand.

3D FDTD (#147) covers low-frequency wave physics. This solver covers high-frequency geometric physics. They share Surface / Source / Receiver; they do not share a grid.

Decision: orchestrate, don’t clone

Option Verdict
Cone / frustum / pyramid tracing No. Beam trace already does volumetric specular.
Phonon / sound-particle No. That’s the raytracer with a different name.
Deterministic radiosity matrix Later. ART is the MC version; a form-factor matrix is an ART upgrade, not a new solver.
Fix raytracer hybrid in place Necessary (#129) but not a product. The flag still lives inside one MC solver, still first-receiver-only (#131), still peak-normalizes (#133).
Hybrid-GA: IS (+ UTD) early, MC rays optional middle, diffusion-equation late v1. One tab, one IR, one LTP.

New tree src/compute/hybrid-ga/. It calls image-source / shared diffraction / ray energy histogram / a new ADE module. It does not copy arrivalPressure.

Three regimes

t = 0 ──────── t_early ──────── t_mix ──────── t_end
   [ IS + UTD ]    [ optional MC ]    [ ADE tail ]
         deterministic     stochastic      diffuse field

Early (order ≤ N, default N = 2). Image-source paths, 1/r² spreading (#122 / #99), energy from reflectionFunction, air on energy (10^(−α r / 10)), no Math.random sign (#124). Direct sound once. UTD from shared/diffraction with inbound arrivalDirection (#134). Skip IS across the same face twice; isInFrontOf must actually run (#127).

Middle (optional). Existing raytracer, but:

  • Drop paths with chainLength−1 ≤ N (filter, do not splice this.paths).
  • Each ray carries 1/N_rays of source energy (Raytracer IR: no 1/N, peak-normalize, 10 cm mesh detector — N-dependent shape #133).
  • Detection along the segment vs a sphere about the receiver center, t = dist_center / c. Receiver mesh out of the BVH.
  • Same initialSPL / spreading convention as IS so a first-order IS arrival and a lucky first-order ray would match — they must not both be deposited.

Late. Acoustic diffusion equation (Picaut / Valeau / Billon), not peak-normalized noise and not W-only HOA (#106 / #132).

∂w/∂t = D ∇²w − σ c w
D = λ c / 3,   λ = 4V / S,   σ = −ln(1 − ᾱ) / λ   (or ᾱ / λ)

w is energy density (J/m³) per octave band. Voxelize the room coarse (cell ~ 0.5–1 m, not FDTD Δx). Neumann at walls with loss σ; coupling through apertures = shared faces. CPU is enough (10³–10⁴ cells). Source for ADE is the residual energy at t_mix from the histogram, not a new 500-unit blast (ART #116).

Crossfade: energy envelopes, 20–50 ms. Pressure IR for the tail = band-filtered noise × √(w(t) at receiver cell), independent per HOA channel if ambisonic.

Energy units (non-negotiable)

One convention for the whole solver:

If IS, rays, and ADE disagree on a 10 m cube, α = 0.2, the solver is wrong — don’t ship the tab.

Module layout

src/compute/hybrid-ga/
  index.ts                 # HybridGASolver extends Solver
  early.ts                 # wrap ImageSourceSolver + UTD, apply spreading
  middle.ts                # run/read RayTracer paths with order filter + 1/N
  diffusion/
    ade.ts                 # CPU ADE, per-band w[cell]
    voxelize-coarse.ts     # occupancy at ~1 m (can share ideas with #147, not the grid)
  assemble.ts              # IR / LTP / optional FOA
  __tests__/
    spreading.spec.ts      # 1 m vs 2 m vs 4 m direct
    crossover.spec.ts      # no double-count order ≤ N
    ade-decay.spec.ts      # cube T ≈ Eyring

Registry: registerSolverFactory("hybrid-ga", ...). Tab: N, t_mix (auto = mean free time 4V/(S c) or manual), enable MC middle, enable ADE, bands, assigned S/R/one room.

save() persists those IDs. No rooms[0], no findIDs() of the whole project (#123 / #135 / #143).

UI / results

One LTP chart with series: IS, UTD, MC (if on), ADE (if on), total. One IR play/download. Ambisonic: encode IS/UTD/MC by arrival direction; ADE tail all HOA channels, equal power, uncorrelated (#132).

Energy Decay (#139) should be able to take this IR from the store.

Validation

  1. Free-field / empty-ish: direct SPL −6 dB per doubling of distance (air off).
  2. Hard shoebox, N=1, MC off, ADE off: arrivals match analytic image sources in time and in 6 dB law; count = 1 + 6 first-order.
  3. No double count: N=2, MC on → no stochastic path with order ≤ 2 in the IR; this.paths of the inner raytracer unchanged.
  4. ADE cube: uniform α, T60 from w(t) at center within ~15 % of Eyring (air off).
  5. Crossover energy: ∫ E_early + E_middle + E_late ≈ source energy × (1 − absorbed), order of magnitude, not 7× (ART direct sound is added to every octave band, then summed — 7× too loud #116).

Physics tests, not greps.

Phases

Phase Ship
0 IS-only early with spreading + air-energy + one receiver IR/LTP. No MC, no ADE. Proves units. Unblocks #122.
1 UTD inject, inbound direction, LTP series.
2 MC middle, order filter without mutate, 1/N, sphere-along-segment. Inner raytracer can stay; this solver owns assembly.
3 ADE late, coarse voxels, Eyring test, energy crossfade.
4 FOA/HOA with diffuse tail on all channels; multi-receiver without mixing (#103).

Phase 0 is already more correct than today’s image-source tab. Phase 3 is the product.

Explicit non-goals (v1)

  • New beam / cone / phonon tracer
  • Touching 2D/3D FDTD grids
  • Frequency-dependent ADE diffusion constant (use per-band ᾱ, constant D)
  • Replacing ART (keep it; hybrid-GA does not shoot surface-to-surface form factors)
  • Random polarity on specular paths

Relationship to open bugs

Hybrid-GA consumes fixes, it does not wait for every geometric issue to close. Phase 0 should implement spreading in a shared arrivalIntensity(path, r) used by IS and beam-trace (#99 / #122). Phase 2 must not copy raytracer splice. ADE is new code.

Acceptance (phase 3)

  • kind: "hybrid-ga" in the registry
  • Direct 1 m / 2 m / 4 m: −6 dB / −12 dB
  • Hard box N=1: 7 arrivals, no extras, no missing
  • MC on: stored ray paths unmodified; IR has each early path once
  • ADE T60 on a uniform cube ≈ Eyring
  • Exported WAV is not peak-normalized; two receivers differ

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions