Game sound without sound files.

Brusverk is a procedural audio library for games, written in Rust. Engines, weather, weapons and interface sounds are computed while the game runs, from the game's own state: throttle, speed, damage, distance. It plugs into Godot 4 as an audio stream, and it is running in this tab as WebAssembly.

  • 43 built-in generators
  • about 1 MB of WebAssembly
  • nothing is played from a recording
  • MIT or Apache-2.0
Loading the engine…
10 kHz1 kHz100 Hz
throttlerpm
A heavy truck for 14 seconds. The picture is its spectrogram; the two lines under it are all the game has to supply.

Pick something to hear.

Each picture is the spectrogram of the sound under it, computed in this tab when the page loaded. Press play and it is computed again, so the event sounds come out a little different each time. Nothing is downloaded except the engine.

Every sound in the library opens in the Sound Lab, with its inputs as sliders and all of its parameters:

Your game already has the inputs.

A recorded engine loop has one speed. A generated one follows the game: the throttle, revs, slip and damage your physics already computes become the sound's inputs, every frame. In Godot 4 a generator is an AudioStream, so players, buses, 3D position and effects work the way they do for a sample.

  1. Build the extension.cargo build -p brusverk-godot --release, then point a .gdextension file at the library it produces.
  2. Give a player a generator.Pick a built-in one and a preset, or load a model file of your own.
  3. Feed it your game state.Set the inputs every physics frame. They all run from 0 to 1.
  4. Tune it by ear.Parameters show up in the Godot inspector. Or shape the sound in the Sound Lab and copy the values back with set_params_json().

Tested with Godot 4.7 on macOS. Linux and Windows build from the same source but have not been tested here. Not using Godot? brusverk-core is plain Rust with no engine dependency, and the WebAssembly build has a C interface.

GDScript: an engine that follows the game, and an explosion
# once: an engine on any AudioStreamPlayer, 2D or 3D
var gen := SoundGenerator.create("piston")
gen.preset = "Heavy truck"
gen.set_param("engine/external_rpm", 1)  # the game owns the revs
player.stream = gen
player.play()

# every frame: hand it what the physics already knows
func _physics_process(_dt):
    var pb := player.get_stream_playback() as SoundGeneratorPlayback
    pb.set_inputs({"throttle": throttle, "rpm": rpm, "load": load})

# an event: fired with what the game knows at that moment
var boom := SoundGenerator.create("explosion")
boom.preset = "Ship destroyed"
boom_player.stream = boom

func explode(power: float, distance: float) -> void:
    # power: glancing hit to full blast. distance: 0 point blank, 1 far off
    boom.set_start_input("power", power)
    boom.set_start_input("distance", distance)
    boom_player.play()  # `finished` fires when the tail has rung out

Need a sound that isn't there? Write it as a text file.

A model file is a graph of nodes (noise, filters, resonators, delays, a reverb) plus formulas that turn the inputs into control signals. It loads while the game runs, shows its parameters in the Godot inspector like a built-in generator, and costs about three times as much to compute.

This is campfire.toml, the file behind the campfire demo above. Sparks are random impulses, shaped into short bursts of noise and coloured by a filter that moves a little each time.

Edit this file in the Sound Lab

models/campfire.toml
The file appears here once the engine has loaded.

What's inside.

One Rust crate does the synthesis. Around it are a Godot 4 extension, the WebAssembly build this page runs, and the tools used to check every sound.

Engines

Piston
Each cylinder fires in order into one of two exhaust banks. The header and exhaust pipes are resonators with lengths in metres. Turbo, engine brake and diesel clatter for the heavy ones.
Layouts
Cross-plane and flat-plane V8, inline four and six, boxer four, V-twin, single, V12.
Fitted to
A recording of a Rover 3.5 V8: third-octave levels within about 4 dB at idle and at 3000 rpm.
Jet
A turbine with spool inertia, afterburner, wind and damage layers.

Event sounds

Built from
Up to eight layers each: cracks, swept bodies, sub drops, debris and a reverb tail.
At trigger
The game passes power and distance. Every trigger varies slightly, so the tenth explosion is not the first one again.
Stereo
Sub and crack stay centred, noise and tail are spread. Width 0 gives exactly the mono sound.
Ending
The player emits finished when the tail has rung out, as it would for a sample.

Synth voice

Voice
Three oscillators, a state-variable filter, two envelopes and an LFO. 32 voices.
Patches
59 parameters, stored as JSON or as a Godot resource.
Chip effects
11 presets in the sfxr tradition (pickup, laser, jump and so on), each reproducible from a seed.
Try it
The Instrument and Sound FX tabs of the Sound Lab.

Under the hood

Audio thread
No allocation, locks or blocking while rendering. Parameter changes arrive over lock-free queues.
Speed
Every built-in generator running at once renders 26 times faster than real time on one core of an Apple-silicon laptop.
Checked
More than 200 automated tests. Every sound is reviewed by spectrogram for dropouts, clipping and ringing, and the audio of this site is measured in a real browser.
Size
The core crate has no engine dependencies. The web build is about 1 MB.