RoadAudio
The sound of a road war, made on the fly with Web Audio. No files to load.
What you hear
- the engine, with a pitch that climbs through three gears as you speed up, and a brighter tone under throttle;
- gun cracks, Gunner shotgun booms, missile whooshes, and the splat of oil and the puff of smoke;
- explosions: a noise rumble that dies away over a low thump;
- metal on metal for rams and crashes, and a ping off armour;
- a tyre squeal while your car slides;
- the bomber's rotor chop while one is up;
- a police siren while a patrol car chases you, louder as it gets close;
- a chime in the weapons truck.
Use it in a scene
A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.
Build it from scratch with the bjs CLI:
- 1Scaffold a project
World, renderer, and dev server — ready to run.
- 2Install dependencies
- 3Add RoadAudio
Copies its source into src/ so the scene resolves.
First time? Run bjs login once.
- 4Paste the scene into src/scenes/arcade-room.ts and run
SceneLoader builds the world from the JSON; reload to rebuild.
Starting the sound
Browsers only let a page start audio during a user gesture. The audio is set up on the first key press, click or tap, inside that gesture. Keys reach it as keyboard.keydown events, so the scene needs KeyboardInput; a click or a lifted finger is caught on the page itself, so a HUD's START button and a phone start the sound too.
M mutes and unmutes. While the world is paused, all sound fades out.
Engine in three gears
Each gear is a third of topSpeed. Inside a gear the note climbs 34 Hz. At the next gear it drops back, but each gear starts 9 Hz higher than the last. With the default top speed of 52 m/s:
| Gear | Speed (m/s) | Note (Hz) |
|---|---|---|
| 1 | 0 to 17 | 42 up to 76 |
| 2 | 17 to 35 | 51 up to 85 |
| 3 | 35 to 52 | 60 up to 94 |
The System plays that note on two saw waves, one tuned a hair sharp so the pair beats like an engine, plus a sine an octave below. They pass through a low-pass filter set to cutoff, which opens under throttle.
Sounds from noise
Every crack, boom and squeal starts from one two-second buffer of white noise, made once when the audio starts:
A filter keeps the part of the hiss that sounds like the thing, and a quick fade gives it a shape. Each burst starts at a random spot in the buffer, so no two shots sound the same. Gun cracks are rationed to about 22 a second, so a fast gun can't pile up hundreds of sounds.
| Sound | Made from |
|---|---|
| gun crack | noise through a band-pass at 2600 Hz, 0.05 s long |
| ram | noise at 1400 Hz, louder the harder the hit |
| explosion | noise through a low-pass sliding from 1800 Hz down to 120 Hz, over a sine thump falling from 110 Hz to 38 Hz |
| missile | noise through a band-pass sweeping up from 400 Hz to 3200 Hz |
| tyre squeal | a narrow band of noise at 2400 Hz |
| rotor | noise at 220 Hz, cut on and off by a square wave ROTOR_HZ (11) times a second |
| siren | a triangle tone swept around SIREN.hz (1000 Hz) by 360 Hz, 0.3 times a second |
| truck chime | three square-wave notes on docking, two on a refit |
Fading with distance
Halfway to reach, a sound plays at a quarter of its volume. Past reach it is skipped. The siren follows the nearest car chasing you. The missile whoosh and the truck chime play at full volume wherever they happen.
Settings
| Field | Default | What it does |
|---|---|---|
player | "Player" | The car whose engine you hear. Distances are measured from it. |
volume | 0.7 | Master volume, 0 to 1. |
muteKey | "KeyM" | KeyboardEvent.code that mutes and unmutes. |
reach | 140 | Metres past which a sound is not heard. 0 turns fading off. |
topSpeed | 52 | Speed in m/s at the top of third gear. Match it to CarDrive's maxSpeed. |
muted | false | Live mute state. Not read from the scene; the mute key and roadAudio.mute flip it. |
Events
| Event | Payload | Direction |
|---|---|---|
keyboard.keydown | { code } | listens: starts the audio, and the mute key |
roadAudio.mute | { muted? }, leave out muted to toggle | listens |
autoRunnerWeapons.fired | { entityId, weapon, x, z } | listens |
autoRunnerDriver.fired | { entityId, x, z, targetId } | listens |
autoRunnerProjectile.exploded | { entityId, owner, kind, x, z, radius, ground } | listens |
autoRunnerProjectile.ricochet | { entityId, targetId, owner, kind, x, z } | listens |
health.died | { entityId } | listens |
carCollision.hit | { a, b, x, z, closing, dvA, dvB } | listens, above 5 m/s closing |
carCollision.crashed | { entityId, kind, x, z, closing } | listens, above 4 m/s closing |
carCollision.spinout.started | { entityId } | listens, player only |
carCollision.spinout.ended | { entityId } | listens, player only |
autoRunnerBomber.arrived | { entityId } | listens |
autoRunnerBomber.left | { entityId } | listens |
autoRunnerDriver.pursuit | { entityId, targetId, on } | listens |
supplyTruck.docked | { entityId, truckId, offers, seconds } | listens |
supplyTruck.upgraded | { entityId, offer } | listens |
RoadAudio emits nothing.
Needs
- MeshPrimitive: where the player, each wreck and each police car is, for the distance fade.
- CarDrive: the player's speed and throttle, for the engine note.
- CarCollision: rams, crashes and spin-outs, and the ghost tag that silences a lost car's engine.















































