Game audio
A real time mixer with buses, loops and crossfades, varied effects, seamless loops and audio sprites.
On this page
A game needs to play many sounds at once, change them while they play, loop music without a click, and not repeat the same jump sound twenty times. The game audio example runs a whole scripted session; this page explains the pieces.
The mixer
audioMixer opens a mixer. Each sound you play has its own volume, pan and pitch, belongs to a bus (music, effects, voice) with its own volume, and a master volume sits on top.
mixer = audioMixer( output="memory", buses={ music: 0.7, sfx: 1.0 } );
jump = audioGain( audioSweep( 260, 760, 0.18 ), -8 );
song = audioGain( audioOsc( "triangle", audioFreq( "A2" ), 2 ), -10 );
theme = mixer.play( song, { bus: "music", loop: true, fadeIn: 0.3 } );
mixer.render( 0.5 ); // let half a second pass
mixer.play( jump, { bus: "sfx", pan: -0.4, pitch: 1.1 } );
mixer.setVolume( "music", 0.3 ); // duck the music
session = mixer.render( 1.0 );
theme.stop( 0.5 ); // fade out and stop
mixer.close();
Outputs. output decides where the sound goes:
| Output | Does |
|---|---|
speakers (default) | Plays to the sound card. Needs a sound card and the JVM's java.desktop module |
null | Runs in real time with no sound. For servers and tests that need time to pass |
memory | Nothing runs by itself. You call mixer.render( seconds ) and get the audio back as a buffer: render a whole scripted session to a file |
Play options for mixer.play( sound, options ):
| Option | Does |
|---|---|
bus | The bus name, default main |
volume, pan, pitch | Level, -1 left to 1 right, and a speed factor (2 is an octave up) |
loop | Repeat until stopped. loopStart and loopEnd (seconds in the sound) loop only a part: an intro, then a loop |
start | Begin this many seconds into the sound |
fadeIn | Fade in over seconds |
play returns a playback you can change while it plays: setVolume, setPan, setPitch, fadeTo( level, seconds ), pause(), resume(), stop() and stop( fadeSeconds ), with isPlaying(), isFinished() and getPosition(). Changes are applied smoothly across the next block, so there are no clicks. On the mixer, crossfade( playback, newSound, seconds, options ) swaps music, setVolume( bus, v ) and setVolume( v ) set bus and master volume, stopAll() and stopAll( bus, fade ) stop sounds, and waitUntilSilent( seconds ) waits.
A sound is converted to the mixer's sample rate once and remembered, so playing it again is cheap.
Play a sound now
audioPlay( audioLoad( "fanfare.wav" ) ); // waits until it is done
audioPlay( audio=sound, wait=false, volume=0.5 ); // returns a playback at once
bx-audio play fanfare.wav
Seamless loops
A loop clicks when its end does not meet its start. audioLoopify folds the end into the start with an equal power crossfade, so the join is smooth, and returns a buffer shorter by the crossfade.
pad = audioOsc( "saw", 437, 1.1 ); // 437 Hz does not fit a whole number of periods in 1.1 s
loop = audioLoopify( pad, 0.25 ); // 0.85 s, ends exactly where it starts
It works best on content that continues across the join: pads, ambience, music with a reverb tail.
Effects that do not repeat
audioVariations makes slightly different copies of a sound: a random pitch (the speed moves with it, like a tape) and level, repeatable with a seed. Pick one at random each time the sound plays.
step = audioEnvelope( audioNoise( seconds=0.15, seed=4, color="brown" ), 0.002, 0.05 );
steps = audioVariations( audio=step, count=5, pitchRange=1.5, gainRange=2, seed=1 );
println( steps.len() & " footsteps" );
Audio sprites
Browser and many engine games load all the effects as one file and a map of where each starts, because it is one download. audioSprite builds both, and writes the map in the Howler.js format:
sprite = audioSprite(
sounds = [ jump: audioSweep( 260, 760, 0.18 ), coin: audioOsc( "sine", 988, 0.25 ), boom: audioNoise( seconds=1, color="brown" ) ],
gap = 0.1,
path = "sfx/sprite.wav"
);
println( sprite.sprites );
This writes sfx/sprite.wav and sfx/sprite.json:
{ "src": [ "sprite.wav" ], "sprite": { "jump": [ 0, 180 ], "coin": [ 280, 250 ], "boom": [ 630, 1000 ] } }
The times are [ start, duration ] in milliseconds. Use an ordered struct, written with square brackets, when the order matters. All sounds must have the same channels and sample rate.
Command line
bx-audio loopify pad.wav pad-loop.wav --crossfade=0.25
bx-audio sprite sfx/sprite.wav jump.wav coin.wav boom.wav --gap=0.1
Not included
MIDI files are not rendered: the JDK's software synthesizer can only be driven offline through a package the JDK does not export. Sounds are synthesized or loaded from WAV, MP3, FLAC and, with FFmpeg, OGG, Opus and AAC.