Skip to content

RandomStudio/positional-audio-tool

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

positional-audio-tool

A desktop tool for debugging positional audio in a space. Place audio emitters and walls (with material types) on a top-down 2D canvas, drag a listener around, and hear a physically-modelled spatial mix of what you'd hear standing at that point.

Built in Rust with egui for the interface, Valve's Steam Audio (via audionimbus) for acoustics, and cpal for real-time output.

What it simulates

The point of the tool is to approximate how sound really behaves in a room. Each emitter's audio reaches the listener through a chain of physical models:

  • Distance falloff — sources get quieter with distance by the inverse-distance law (−6 dB every time the distance doubles), as a point source does in open air.
  • Propagation delay — sound travels at ~343 m/s, so each source is delayed by its real time-of-flight. This produces the precedence (Haas) effect and comb filtering between overlapping sources, plus a gentle Doppler glide as the listener moves.
  • Air absorption — high frequencies fade faster than lows over distance, so far-away sources sound duller, not just quieter.
  • Binaural spatialization (HRTF) — you hear each source from its actual direction via head-related filtering (for headphones); a speaker-panning mode is also available.
  • Source directivity — emitters can be aimed: each has a facing direction and a beam width (angle of emission), so a speaker pointed away from the listener sounds dimmer, like a real loudspeaker rather than an omnidirectional point.
  • Occlusion & transmission — walls block the direct path and muffle whatever passes through, depending on material, frequency, and thickness (glass leaks more highs than concrete; a thicker wall transmits less).
  • Directional reflections / reverb — ray-traced reverb that tracks room size and surface materials and arrives from the right direction (a hard wall on your left reflects from your left).
  • Diffraction — low frequencies bend around walls instead of stopping at a hard shadow edge, found by tracing sound paths around the geometry.
  • Material acoustics — six wall materials (concrete, brick, glass, wood, carpet, curtain), each with its own absorption, scattering, and transmission.

Everything is in real units: emitter levels in dB, wall lengths in millimetres, a metric canvas. See How the simulation is wired for the details.

Running

cargo run

Headphones recommended. Spatialization defaults to HRTF binaural rendering, which assumes headphone playback. A speaker/panning output mode is available in the side panel for loudspeaker listening.

Steam Audio native library. build.rs embeds an rpath to the auto-installed libphonon.dylib in the build tree, so ./target/debug/positional-audio-tool runs directly. A relocatable .app bundle (dylib copied in, @executable_path rpath, codesigning) is still future work.

Two seamless demo tones are in assets/ (400 Hz and 600 Hz) for loading onto emitters.

Using the tool

  • Place emitter — click on the canvas to drop an emitter, then Load audio (WAV or MP3) in the side panel to give it a looping clip and a dB level slider. Each emitter also has facing and emission (beam width) sliders; the canvas draws the resulting cone, and at 360° emission it is omnidirectional.
  • Draw wall — pick a material, then click two points to lay a wall segment. Each wall's length is shown in millimetres on the canvas and in the side panel, where it can be typed to an exact value and given a thickness (drawn to scale, and affecting how much sound transmits through).
  • Move listener — the listener (ear icon) follows your cursor over the canvas; click to toggle following on/off.
  • Room dimensions — set the room's width × depth in millimetres; the canvas is sized to show that room (drawn as a boundary rectangle).

Architecture

Four concerns, split as the code is:

  • core/ — pure, no IO or threads: the 2D scene model, the 2D→3D geometry mapping (walls extruded to vertical quads), material acoustics, and the per-source mix computation (compute_mix). Unit-tested.
  • debug/ — the egui window (owns the macOS main thread) and the 2D canvas.
  • sim/ — the background simulation thread that ray-traces reflections and bakes / runs pathing on the same Steam Audio Simulator, shipping params to the audio thread.
  • audio/ — the cpal output stream and the real-time mixer. Per voice it runs a propagation DelayLineDirectEffectBinauralEffect, plus a ReflectionEffect and a PathEffect, each decoded to stereo through an AmbisonicsDecodeEffect.

The UI computes a SimSnapshot (per-source direction, distance, attenuation, air absorption, gain) each frame and publishes it through an ArcSwap. The audio callback reads the latest snapshot lock-free and never blocks or allocates on the hot path; new clip samples arrive over a single-producer rtrb ring, and reflection / pathing params arrive over two more rtrb rings from the simulation thread.

Status

Working today: the 2D editor (place emitters, draw walls with per-material editing, drag the listener, settable canvas scale), looping WAV/MP3 emitters with dB levels, scene save/load, headphone and speaker output modes, and the full real-time acoustic chain from What it simulates. Runs as a standalone binary.

How the simulation is wired

The heavier pieces of the chain run off the audio thread and feed it lock-free.

Propagation delay

Each voice runs through a variable fractional DelayLine sized to its true time-of-flight (distance / 343 m·s⁻¹) before the direct effect. Because every source arrives at its real time, the precedence (Haas) effect and comb filtering between overlapping sources fall out for free; moving the listener glides the delay, giving mild Doppler. The delay is applied only to the direct path — reflections and paths carry their own propagation times from the simulation.

Reflections

A background thread (sim/thread.rs) runs Steam Audio's ray-traced reflection simulation (Convolution, ambisonic order 1) at ~15 Hz: it builds a Scene/StaticMesh from the walls (plus an optional floor/ceiling enclosure), traces rays from the listener, and computes a directional (4-channel ambisonic) impulse response per source from each surface's absorption/scattering. The ReflectionEffectParams are shipped to the audio thread over a lock-free rtrb ring (params are Send and retain their source, keeping the IR valid), where each voice convolves through a ReflectionEffect and decodes the ambisonic reverb to stereo with an AmbisonicsDecodeEffect (binaural for headphones, panning for speakers). Because the reverb is directional you can hear where the room is — a hard wall on your left reflects from your left. Its presence is determined entirely by geometry and materials, not a manual control. Draw walls, toggle Floor & ceiling, and the reverb appears; swap a wall from concrete to curtain and the room goes from live to dead.

Diffraction (pathing)

The same simulator also runs Steam Audio's pathing simulation. On each geometry change it generates a grid of probes at ear height (sim/pathing.rs) and bakes the shortest sound paths between them around the walls. At runtime it finds the path from each source to the listener — including paths that bend around obstacles — and ships an owned PathEffectParams (3-band EQ + ambisonic directionality) to the audio thread, where a PathEffect + AmbisonicsDecodeEffect render it. This restores the low-frequency energy that leaks around a wall, instead of treating occlusion as a hard shadow. Pathing needs a floor to place probes on, so it is most meaningful with the Floor & ceiling enclosure on.

A headless test (enclosed_room_simulates_and_renders_reverb) exercises the whole chain end to end: scene → run_reflections/run_pathing → params → ReflectionEffect/PathEffect apply → AmbisonicsDecodeEffect decode.

Future upgrades: higher ambisonic orders for sharper directionality, individualised HRTFs, and a relocatable macOS .app bundle.

Development

cargo fmt
cargo clippy --all-targets -- -D warnings
cargo test

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors