Real-time path-traced sound propagation engine in Rust — binaural HRTF rendering, wall transmission, diffraction, coupled reverb. Native + wasm, live demo on Pages.
Translation pending
Developer toolsRust · JavaScriptGitHub @JustGoschaConnection unstableOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Live demo: https://justgoscha.github.io/omg-audio/web/ — headphones on. Desktop: WASD + mouse, Space throws a whistling, exploding ball. Android Chrome: on-screen joystick, turn your body to look around (device orientation drives the binaural rendering).
The engine today is v5.5 — rays from the listener, every connection reaching each source where it was when it emitted, the traced response rendered as traced: ENGINE_V3.md (the tracer), ENGINE_V4.md (the response rendered as traced), ENGINE_V5_5.md (what v5.5 changed), measured in docs/runs/, tested per TESTS.md, open items in OPEN.md. Parts of the overview below describe the earlier engine (portals, image sources) that the offline renderer still uses.
A real-time path-traced sound propagation engine in Rust, compiled to both native (reference build) and wasm (Web Worker + AudioWorklet). One scene — a small city square with a house playing music, a great hall with a narrator, a club with four synchronized speakers behind an entrance vestibule, a two-storey old house, a colonnade and a kiosk — and the sound field is computed, not authored:
muffled N% while it holds.Every engine version re-renders the same scripted 98 s walk (living room → corridor → great hall → outside → entrance → club) as a top-down schematic video with binaural audio — the whole history is on the walkthroughs release, headphones on:
| version | what it demonstrates |
|---|---|
| v12 – huygens-doorways | apertures re-radiate as Huygens sources; doorway wet pays Lambert obliquity — a real on-axis peak walking past an open door |
| v11 – mesh-dome-cathedral | ambient dome, mesh-emergent knife-edge occlusion, cathedral + bunker |
| v10 – club-transmission | mass-law wall transmission — oomp-oomp from the street |
| v8 – point-hrtf | per-path nearest-HRIR convolution for the strongest paths |
| v7 – hrtf-binaural | measured KEMAR HRIR binaural decode |
| v6 / v5 – coupled reverb | coupled rooms carry their own reverb through doorways |
| two-sources / voice / music | the earliest renders |
New versions join with
gh release upload walkthroughs walkthrough-vN-<name>.mp4 after the
render recipe below.
Web (what the live demo runs):
sh tools/build_web.sh # cargo build wasm (+simd128) + stage into web/
python3 tools/serve.py # http://localhost:8000/web/
node tools/web_smoke.mjs # headless pipeline test
node tools/env_probe.mjs # ambience/rain continuity through doors + swings
node tools/bench_web.mjs # realtime-factor benchmark (point budgets)
Native:
cargo run --release # live playback, Ctrl+C to quit
cargo run --release -- --render demo.wav --secs 12 # offline render + level report
cargo run --release -- --input assets/aria48.wav --render out.wav
# scripted walkthrough with fixed sources + 2D schematic video
cargo run --release -- --walkthrough --render walk.wav --json walk.json \
[--music m.wav --voice v.wav --club c.wav]
python3 tools/render_viz.py walk.json frames 30
ffmpeg -framerate 30 -i frames/%05d.png -i walk.wav \
-c:v libx264 -pix_fmt yuv420p -c:a aac -shortest walkthrough.mp4
Env knobs (native): OMG_POINT_TAPS=n point-render budget,
OMG_MUTE_TAPS / OMG_MUTE_OWN / OMG_MUTE_REMOTE isolate signal paths.
Godot 4 (GDExtension, tested against 4.7):
sh tools/build_godot.sh # cargo build the extension + stage into godot/bin/
/Applications/Godot.app/Contents/MacOS/Godot --headless --path godot -s test.gd # smoke test
/Applications/Godot.app/Contents/MacOS/Godot --path godot # playable demo
The extension is omg-audio (crates/omg-audio) as Godot nodes — nothing
demo-specific:
OmgWorld — the engine: omg-audio's threaded::Engine (simulation
thread; v3 on the GPU with use_gpu, CPU otherwise). rebuild_geometry()
collects the level's triangles (MeshInstance3D surfaces) into an
omg-audio Geometry; an output thread renders binaural audio ahead
into a ring that Godot's audio thread pulls through OmgOutputStream —
the game thread never runs DSP or waits on the engine. Also:
load_clip(path, options) (Ogg Vorbis/FLAC/Opus, FLAC, WAV decoded in
Rust in the background, with the web demo's shaping options),
whisper() (near field at one ear), ambience bed + rain routed by the
frame's sky (ambience_file, rain_bank_file, rain_intensity),
ground_plane, quality, stats(), and the web worklet's load
governor over omg-audio's mix-wide budgets.OmgGeometry — tags everything below it with an acoustic material
(material = "brick", see OmgWorld.material_presets(); "none" opts
out); as_boxes makes its meshes solid boxes. Simplest of all: give a
visual Material an omg_material meta once and every mesh using it is
acoustic — the texture brings its sound.OmgSource — a looping file and/or one-shots (play_clip), at a real
SPL (level_db at 1 m).OmgListener — the ears (falls back to the current camera).OmgDoor — a leaf or pane filling an opening (an omg-audio object box),
priced by openness.godot/demo.gd + godot/demo/ is the web demo's town at night, generated
in GDScript (same rooms, doors, windows and landmarks as web/main.js /
the walkthrough scene; furniture from assets/demo/layout.json when
present), dressed with CC0 textures (assets/textures, Poly Haven) whose
materials declare their acoustics (--audit prints every
texture → acoustic pairing), and the web demo's sound design (room loops,
footsteps, owls, bells, whispers, cars, balls, rain). It renders a cheap
pixel look by default (3D at 360 px high, width from the window's aspect,
nearest upscale, no anti-aliasing); G switches to the fine look (full
resolution, SSAO/SSIL, volumetric fog), [ and ] change the height. Controls: click to
capture / throw, WASD, Shift run, Space jump, C crouch, F fly, E door,
B bells, V whisper, R rain, L labels, Q sound quality, H hud.
-- --print-fps measures ~10 s of frame rate, -- --shots=<dir> renders
a tour of viewpoints to PNGs, -- --gpu traces on the GPU.
The load-bearing decision: the simulation clock and the audio clock
never share state except through ParamBlock.
┌─────────────────────────────────┐ ParamBlock ┌──────────────────────────────────┐
│ SIMULATION (20 Hz) │ ───────────▶ │ AUDIO (per-sample) │
│ Web Worker / native thread │ │ AudioWorklet / cpal callback │
│ │ taps: key, │ │
│ omg-core + omg-scene │ delay, dir, │ omg-dsp │
│ · image sources (ISM ≤3) │ band gains │ · fractional delays (Doppler) │
│ · stochastic tracer → RT60 │ │ · per-tap shelf EQ + wall LP │
│ · portals, transmission, │ reverb: │ · point HRIR convolution │
│ apertures, diffraction │ rt60[3], │ · order-2 ambisonic bus │
│ · EMA temporal accumulation │ level[3], │ · 8-line FDN (traced RT60) ×2 │
│ (kills MC flutter) │ remote wet │ · 20-speaker KEMAR decode + AGC │
└─────────────────────────────────┘ └──────────────────────────────────┘
ParamBlocks over postMessage (~4 KB @ 20 Hz) —
no SharedArrayBuffer, no COOP/COEP headers, so it hosts on GitHub Pages.recenter
re-zeros on your current sitting pose.Two tiers, chosen per tap by measured loudness:
N is not hardcoded: the AudioWorklet measures its own render load and walks
the budget between 8 and 32 with hysteresis — a throttled CPU (Low Power
Mode, phones) settles low, a desktop climbs to the cap. The HUD shows the
current value (hrtf ×N). Measured on an M1 at the worst-case scene
position (club/entrance doorway blend, ~446 live taps, SIMD kernel):
| point budget / source | realtime factor |
|---|---|
| 0 (bus only) | 3.0× |
| 8 | 2.5× |
| 24 | 2.1× |
| 32 | 1.9× |
| 64 | 1.3× |
Rendering every tap this way (up to 160/source × 6 sources) would be ~10× over budget — that, and only that, is why the bus tier exists. The convolution kernel is written to vectorize (NEON / wasm simd128): pre-reversed HRIRs turn the ring convolution into contiguous dot products with explicit 4-lane accumulation.
| crate | role | wasm? |
|---|---|---|
omg-core | vec/rng, materials, shoebox + triangle-mesh geometry (binned-SAH BVH, budgeted diffraction-edge extraction), ISM, path tracer, ParamBlock. Zero deps. | yes |
omg-dsp | delays, smoothers, EQ, FDN, ambisonics, HRTF (bus + point), renderer, output stage. Alloc-free hot path. | yes |
omg-scene | the world: rooms, doors, windows, materials/thickness, portal graph, transmission/aperture/diffraction routing, WorldSim | yes |
omg-web | wasm C-ABI exports (sim_* for the Worker, eng_* for the Worklet), no wasm-bindgen | is the wasm |
omg-app | native binary: cpal live output, offline render, walkthrough scripting, JSON export for the video tool | native |
omg-godot | GDExtension (godot-rust): omg-audio as Godot 4 nodes (OmgWorld, OmgGeometry, OmgSource, OmgListener, OmgDoor), KEMAR HRIRs baked in | native |
Everything here is a measured trade against the real-time budget, not a
guess — tools/bench_web.mjs is the receipt:
omg_core::mesh is an arbitrary triangle mesh with a
binned-SAH BVH — the same stochastic tracer runs over it (unit-tested
to match the analytic shoebox on a box), segments query per-surface
transmission crossings, and diffraction edges are auto-extracted with
an explicit importance budget (dense meshes degrade by dropping short/
shallow creases first, never by breaking). Measured on M1
(mesh_bench, 2 520-tri city block): BVH build 2 ms, 2 M rays/s,
full trace 79 ms at the 4096-ray desktop tier / 19 ms at the 1024-ray
mobile tier — variance, not bias, is what degrades on cheaper tiers
(EMA accumulation smooths it). On top of it, omg_core::paths finds
propagation paths on a raw mesh with ZERO authoring — no rooms, no
doors, no portals: wall thickness is emergent (entry/exit crossings
paired for the mass law), and door jambs, building corners and roof
lines are all just auto-extracted edges searched for single- and
double-bend paths (166 µs per source-listener query at the default
budget). The demo's OCCLUSION now runs on this: the old hand-built
corner list, corner-visibility matrix and multi-roof rubber band are
deleted — occlusion floors come from AutoPaths over the same world
mesh the ambient dome traces (bound-ranked single bends, branch-and-
bound double bends, an over-the-silhouette hull path for any number
of buildings, bent energy averaged over each aperture's real extent,
and per-cell caching with a fixed refresh budget). In-room acoustics
remain per-room shoeboxes (which these rooms are); the directional
late field and the wgpu compute port are the remaining milestone.Code: Apache-2.0. Acoustic data and demo media:
tools/make_motors.py.tools/make_drops.py).assets/*.wav are gitignored; regenerate via tools/ or use the shipped
.ogg versions (what the web demo loads).
tools/env_probe.mjs renders a walk through a doorway and
a full door swing and asserts the level trajectory has no step.debug button): what is actually playing
right now, and why — per-source level, live/point tap slots, own- and
remote-reverb sends, distance, ambience inlet occupancy, plus ~45 s
scrolling graphs of per-source levels, total tap count, sim tick time
and render load. Slot leaks, level pile-ups and perf spikes are
measurable on sight instead of reconstructed from ear-memory.