Bevy Audio
Bevy's audio system has you covered on the basics. You can play sounds and control volume, even spatially.
There are 3 main components that make up audio in Bevy:
AudioSourceis an asset type that holds the audio dataAudioPlayera component that we spawn to play anAudioSourceAudioSinka device destination for the audio data
An AudioSink is the type that wraps rodio::Sink which is the library Bevy uses to manage sound playback.
Playing simple tones
The most simple way to get some audio playing is to use a Pitch asset which will play a single tone at a specific frequency.
The only things we need is an AudioPlayer
fn play_pitch(
mut pitch_assets: ResMut<Assets<Pitch>>,
mut commands: Commands,
) {
info!("playing pitch with frequency: {}", 220.0);
commands.spawn((
AudioPlayer(pitch_assets.add(Pitch::new(220.0, Duration::new(1, 0)))),
PlaybackSettings::DESPAWN,
));
}
This system will play a tone for 1 second every time its called. This is acting as a global sink and will direct this audio directly to your headphones without any kind of spatial effects.
Playing audio
We can trigger our sounds to play by spawning an AudioPlayer on any entity.
fn play_background_audio(
asset_server: Res<AssetServer>,
mut commands: Commands,
) {
let audio = asset_server.load("background_audio.ogg");
// Create an entity dedicated to playing our background music
commands.spawn((
AudioPlayer::new(audio),
PlaybackSettings::LOOP,
));
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// We only want to spawn a single player once at startup
.add_systems(Startup, play_background_audio)
.run();
}
Once the asset is loaded the music will start playing in a loop until this entity we spawned is despawned or the component is removed.
The actual playing of this audio happens in a system that was added in the AudioPlugin. When the audio playback begins, the system adds an AudioSink to the AudioPlayer we just added which we use to control the playback.
The data must be one of the file formats supported by Bevy:
wavoggflacmp3
Bevy will include ogg by default, but for additional audio formats you will need to include the feature inside your Cargo.toml:
[dependencies]
bevy = { version = "0.17", features = ["mp3"] }
There are a few different playback settings that are built in:
| Setting | Description |
|---|---|
PlaybackSettings::ONCE | Will play the associated audio only once |
PlaybackSettings::LOOP | Will loop the audio |
PlaybackSettings::DESPAWN | Will play the audio once then despawn the entity |
PlaybackSettings::REMOVE | Will play the audio once then despawn the component |
Controlling playback
To control the playback of our AudioPlayer we can use the AudioSink which was added by the AudioPlugin automatically when we spawned our entity:
fn pause(
keyboard_input: Res<ButtonInput<KeyCode>>,
music_controller: Query<&AudioSink, With<MusicBox>>,
) {
let Ok(sink) = music_controller.single() else {
return;
};
if keyboard_input.just_pressed(KeyCode::Space) {
sink.toggle_playback();
}
}
The AudioSink is our public API to:
| Method | Description |
|---|---|
play | Resumes playback |
pause | Pause playback |
stop | Stop the playback, cannot be restarted after |
mute | Mute the playback |
unmute | Unmute the playback |
toggle_playback | Toggle the playback |
toggle_mute | Toggle muting the playback |
is_paused | Returns true if the sink is paused |
is_muted | Returns true if the sink is muted |
speed | Get the speed of the sound |
set_speed | Control the speed of the playback |
empty | Returns true if the sink has no more sounds to play |
try_seek | Seek to a certain point in the source sound |
Volume
There are two separate sources of volume for our apps:
- Global volume
- Audio sink volume
To change the global volume we modify the GlobalVolume resource:
use bevy::audio::Volume;
fn change_global_volume(mut volume: ResMut<GlobalVolume>) {
volume.volume = Volume::Linear(0.5);
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(GlobalVolume::new(Volume::Linear(0.2)))
.add_systems(Startup, change_global_volume)
.run();
}
Then for the individual audio sinks we can use their public interface within our systems to modify their individual values:
fn volume_system(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut sink: Single<&mut AudioSink, With<MusicBox>>,
) {
let current_volume = sink.volume();
if keyboard_input.just_pressed(KeyCode::Equal) {
sink.set_volume(current_volume.increase_by_percentage(10.0));
} else if keyboard_input.just_pressed(KeyCode::Minus) {
sink.set_volume(current_volume.decrease_by_percentage(10.0));
}
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, volume_system)
.run();
}
Spatial audio
The example above will play a flat unmodified sound for whatever source we feed our bundle.
To change our spatial audio settings globally we can set the audio plugin settings globally by overriding them in our DefaultPlugins:
use bevy::audio::{SpatialScale, AudioPlugin};
const AUDIO_SCALE: f32 = 1. / 100.;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(AudioPlugin {
default_spatial_scale: SpatialScale::new_2d(AUDIO_SCALE),
..default()
}))
.run();
}
Then to play our sounds we can add some kind of listener. In this case a SpatialListener component that will modify the sound we hear relative to whatever entity is emitting the sound:
fn play_2d_spatial_audio(
mut commands: Commands,
asset_server: Res<AssetServer>,
) {
// Spawn our emitter
commands.spawn((
Player,
AudioPlayer::new(asset_server.load("flight_of_the_valkaries.ogg")),
PlaybackSettings::LOOP
));
// Spawn our listener
commands.spawn((
SpatialListener::new(100.), // Gap between the ears
Transform::default(),
));
}
This will spawn a player entity with the sound emitting from their position. So for example, other players around could hear it according to how far away from us they are.
Its expected you only spawn a single SpatialListener in your app.
Internals
Internally, Bevy is using rodio to decode these sources.
The AudioPlayer is made up of both a source and some settings which controls the playback:
#[derive(Component, Reflect)]
#[reflect(Component)]
#[require(PlaybackSettings)]
pub struct AudioPlayer<Source = AudioSource>(pub Handle<Source>)
where
Source: Asset + Decodable;
The Decodable trait is what allows Bevy to convert the source file into a rodio compatible rodio::Source type. Types that implement this trait will hold raw sound data that is then converted into an iterator of samples.