Technical System · Independent Build

Ambient Audio System

A custom FMOD Core audio engine for Unity
Current module · AmbientWind

The system is designed as a family of runtime modules sitting on top of the direct FMOD Core API. The first shipped module — AmbientWind — is a wind engine built from scratch: 13 scripts, ~5500 lines of C#, directional EQ, M/S stereo width, distance reverb, gust fronts, variable-length grains, listener-rotation smoothing. Further modules — surf, ambience beds, crowd — are in development against the same core.

Built independently as a technical exploration. Everything below describes the AmbientWind module.

video coming soon
Problem

Off-the-shelf spatial audio puts a static emitter in a room. Real wind moves, swells, wraps around you. A single looping stereo WAV can't do that without sounding like a loop.

Solution

A grid of emitters baked from a spline. Each one plays crossfading grain voices that drift along the wind axis, with per-voice DSP driven by your position and head rotation.

Diagram A

Signal flow

Recording is baked into a manifest of variable-length grains at authoring time. At runtime, WindGrainPlayer manages a pool of crossfading voices that route through a shared reverb before hitting FMOD Core.

Wind Recordingraw WAVGrain Slicervariable-lengthmanifest.json + WAVsbaked grain poolWINDGRAINPLAYERVoice 1EQ · Width · Reverb sendVoice 2EQ · Width · Reverb sendVoice NEQ · Width · Reverb sendShared ReverbFMOD_DSP_REVERBFMOD Corespeakers
Diagram B · Centrepiece

Direction factor decomposition

Every voice's tonal balance, stereo spread, and reverb send are decomposed from three scalars — dotFwd, tSide, tBehind — computed each frame from the listener's forward vector. The radial below shows the decomposition at eight canonical zones. Hover a wedge to inspect.

FRONTFRONT-RIGHTRIGHTBACK-RIGHTBACKBACK-LEFTLEFTFRONT-LEFTLISTENER
zone
tap or hover to inspect
Front · 0°
Forward projectiondotFwd
1.000
Side weighttSide
0.00
Rear weighttBehind
0.00
Per-voice EQ
flat
Stereo spread
Reverb send
distance-only
Diagram C

Voice lifecycle & gust fronts

Two emitter tracks crossfade continuously with variable-length grains. The gust front (around 60% of this timeline) sweeps left-to-right; subsequent grains are routed from the gust pool until the front passes.

t = 0 slistener moves forward ↓t ≈ 30 s
EMITTER A
EMITTER B
3.2s
2.1s
4.1s
2.7s
2.4s
2.6s
3.0s
2.9s
2.3s
1.5s
← GUST FRONT
ambientgust
Decisions

Key technical decisions

Spawn-time seek desync

Emitters activated on the same frame would sum into comb-filter flanging. We seek each new voice to a random position in its grain, masked by a 60 ms fade-in.

Per-voice DSP, not bus DSP

Voices drift through space. A bus EQ would average out directional information. Each voice gets its own MULTIBAND_EQ instance updated per frame.

Variable-length grains

Fixed 3 s grains lock emitters into a 0.33 Hz crossfade rhythm the ear learns to expect. Randomising grain length between 2–4.5 s breaks that periodicity permanently.

Listener-rotation smoothing

Fast head turns make EQ and width pop audibly. Exponential decay (τ = 0.25 s) on the forward vector gives the tonal field inertia — but only for DSP, never for the FMOD panner.

Execution order 32000

Guarantees WindGrainPlayer’s LateUpdate wins the last listener write over FMOD’s StudioListener.

One-way data flow

Authoring → Bake → Playback. The runtime never writes back to the author. Clean, testable, Unity-idiomatic.

by the numbers
0
scripts
0+ lines of C#
code volume
0+ voices
simultaneous voices
0-band EQ per voice
DSP granularity
0 FMOD Studio events
pure Core API
Back to portfolio