Skip to content

Commit 7749c99

Browse files
mgi388SolarLiner
andauthored
Add ability to mute audio sinks (#16813)
# Objective - Allow users to mute audio. ```rust fn mute( keyboard_input: Res<ButtonInput<KeyCode>>, mut sink: Single<&mut AudioSink, With<MyMusic>>, ) { if keyboard_input.just_pressed(KeyCode::KeyM) { sink.toggle_mute(); } } ``` - I want to be able to press, say, `M` and mute all my audio. I want this for dev, but I'm sure it's a useful player setting as well. - Muting is different to pausing—I don't want to pause my sounds, I want them to keep playing but with no volume. For example if I have background music playing which is made up of 5 tracks, I want to be able to temporarily mute my background music, and if I unmute at, say, track 4, I want to play track 4 rather than have had everything paused and still be on the first track. - I want to be able to continue to control the volume of my audio even when muted. Like in the example, if I have muted my audio but I use the volume up/down controls, I want Bevy to remember those volume changes so that when I unmute, the volume corresponds to that. ## Solution - Add methods to audio to allow muting, unmuting and toggling muting. - To preserve the user's intended volume, each sink needs to keep track of a "managed volume". - I checked `rodio` and I don't see any built in support for doing this, so I added it to `bevy_audio`. - I'm interested to hear if this is a good idea or a bad idea. To me, this API looks nice and looks usable, but I'm aware it involves some changes to the existing API and now also requires mutable access in some places compared to before. - I'm also aware of work on *Better Audio*, but I'm hoping that if this change isn't too wild it might be a useful addition considering we don't really know when we'll eventually get better audio. ## Testing - Update and run the example: `cargo run --example audio_control` - Run the example: `cargo run --example soundtrack` - Update and run the example: `cargo run --example spatial_audio_3d` - Add unit tests. --- ## Showcase See 2 changed examples that show how you can mute an audio sink and a spatial audio sink. ## Migration Guide - The `AudioSinkPlayback` trait now has 4 new methods to allow you to mute audio sinks: `is_muted`, `mute`, `unmute` and `toggle_mute`. You can use these methods on `bevy_audio`'s `AudioSink` and `SpatialAudioSink` components to manage the sink's mute state. - `AudioSinkPlayback`'s `set_volume` method now takes a mutable reference instead of an immutable one. Update your code which calls `set_volume` on `AudioSink` and `SpatialAudioSink` components to take a mutable reference. E.g.: Before: ```rust fn increase_volume(sink: Single<&AudioSink>) { sink.set_volume(sink.volume() + 0.1); } ``` After: ```rust fn increase_volume(mut sink: Single<&mut AudioSink>) { let current_volume = sink.volume(); sink.set_volume(current_volume + 0.1); } ``` - The `PlaybackSettings` component now has a `muted` field which you can use to spawn your audio in a muted state. `PlaybackSettings` also now has a helper method `muted` which you can use when building the component. E.g.: ```rust commands.spawn(( // ... AudioPlayer::new(asset_server.load("sounds/Windless Slopes.ogg")), PlaybackSettings::LOOP.with_spatial(true).muted(), )); ``` --------- Co-authored-by: Nathan Graule <solarliner@gmail.com>
1 parent 3af0b29 commit 7749c99

File tree

7 files changed

+291
-64
lines changed

7 files changed

+291
-64
lines changed

crates/bevy_audio/src/audio.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ pub struct PlaybackSettings {
6969
/// Useful for "deferred playback", if you want to prepare
7070
/// the entity, but hear the sound later.
7171
pub paused: bool,
72+
/// Whether to create the sink in muted state or not.
73+
///
74+
/// This is useful for audio that should be initially muted. You can still
75+
/// set the initial volume and it is applied when the audio is unmuted.
76+
pub muted: bool,
7277
/// Enables spatial audio for this source.
7378
///
7479
/// See also: [`SpatialListener`].
@@ -100,6 +105,7 @@ impl PlaybackSettings {
100105
volume: Volume(1.0),
101106
speed: 1.0,
102107
paused: false,
108+
muted: false,
103109
spatial: false,
104110
spatial_scale: None,
105111
};
@@ -128,6 +134,12 @@ impl PlaybackSettings {
128134
self
129135
}
130136

137+
/// Helper to start muted.
138+
pub const fn muted(mut self) -> Self {
139+
self.muted = true;
140+
self
141+
}
142+
131143
/// Helper to set the volume from start of playback.
132144
pub const fn with_volume(mut self, volume: Volume) -> Self {
133145
self.volume = volume;

crates/bevy_audio/src/audio_output.rs

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use bevy_transform::prelude::GlobalTransform;
1010
use bevy_utils::tracing::warn;
1111
use rodio::{OutputStream, OutputStreamHandle, Sink, Source, SpatialSink};
1212

13-
use crate::AudioSink;
13+
use crate::{AudioSink, AudioSinkPlayback};
1414

1515
/// Used internally to play audio on the current "audio device"
1616
///
@@ -157,6 +157,19 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
157157
}
158158
};
159159

160+
match settings.mode {
161+
PlaybackMode::Loop => sink.append(audio_source.decoder().repeat_infinite()),
162+
PlaybackMode::Once | PlaybackMode::Despawn | PlaybackMode::Remove => {
163+
sink.append(audio_source.decoder());
164+
}
165+
};
166+
167+
let mut sink = SpatialAudioSink::new(sink);
168+
169+
if settings.muted {
170+
sink.mute();
171+
}
172+
160173
sink.set_speed(settings.speed);
161174
sink.set_volume(settings.volume.0 * global_volume.volume.0);
162175

@@ -165,28 +178,15 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
165178
}
166179

167180
match settings.mode {
168-
PlaybackMode::Loop => {
169-
sink.append(audio_source.decoder().repeat_infinite());
170-
commands.entity(entity).insert(SpatialAudioSink { sink });
171-
}
172-
PlaybackMode::Once => {
173-
sink.append(audio_source.decoder());
174-
commands.entity(entity).insert(SpatialAudioSink { sink });
175-
}
176-
PlaybackMode::Despawn => {
177-
sink.append(audio_source.decoder());
178-
commands
179-
.entity(entity)
180-
// PERF: insert as bundle to reduce archetype moves
181-
.insert((SpatialAudioSink { sink }, PlaybackDespawnMarker));
182-
}
183-
PlaybackMode::Remove => {
184-
sink.append(audio_source.decoder());
185-
commands
186-
.entity(entity)
187-
// PERF: insert as bundle to reduce archetype moves
188-
.insert((SpatialAudioSink { sink }, PlaybackRemoveMarker));
189-
}
181+
PlaybackMode::Loop | PlaybackMode::Once => commands.entity(entity).insert(sink),
182+
PlaybackMode::Despawn => commands
183+
.entity(entity)
184+
// PERF: insert as bundle to reduce archetype moves
185+
.insert((sink, PlaybackDespawnMarker)),
186+
PlaybackMode::Remove => commands
187+
.entity(entity)
188+
// PERF: insert as bundle to reduce archetype moves
189+
.insert((sink, PlaybackRemoveMarker)),
190190
};
191191
} else {
192192
let sink = match Sink::try_new(stream_handle) {
@@ -197,6 +197,19 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
197197
}
198198
};
199199

200+
match settings.mode {
201+
PlaybackMode::Loop => sink.append(audio_source.decoder().repeat_infinite()),
202+
PlaybackMode::Once | PlaybackMode::Despawn | PlaybackMode::Remove => {
203+
sink.append(audio_source.decoder());
204+
}
205+
};
206+
207+
let mut sink = AudioSink::new(sink);
208+
209+
if settings.muted {
210+
sink.mute();
211+
}
212+
200213
sink.set_speed(settings.speed);
201214
sink.set_volume(settings.volume.0 * global_volume.volume.0);
202215

@@ -205,28 +218,15 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
205218
}
206219

207220
match settings.mode {
208-
PlaybackMode::Loop => {
209-
sink.append(audio_source.decoder().repeat_infinite());
210-
commands.entity(entity).insert(AudioSink { sink });
211-
}
212-
PlaybackMode::Once => {
213-
sink.append(audio_source.decoder());
214-
commands.entity(entity).insert(AudioSink { sink });
215-
}
216-
PlaybackMode::Despawn => {
217-
sink.append(audio_source.decoder());
218-
commands
219-
.entity(entity)
220-
// PERF: insert as bundle to reduce archetype moves
221-
.insert((AudioSink { sink }, PlaybackDespawnMarker));
222-
}
223-
PlaybackMode::Remove => {
224-
sink.append(audio_source.decoder());
225-
commands
226-
.entity(entity)
227-
// PERF: insert as bundle to reduce archetype moves
228-
.insert((AudioSink { sink }, PlaybackRemoveMarker));
229-
}
221+
PlaybackMode::Loop | PlaybackMode::Once => commands.entity(entity).insert(sink),
222+
PlaybackMode::Despawn => commands
223+
.entity(entity)
224+
// PERF: insert as bundle to reduce archetype moves
225+
.insert((sink, PlaybackDespawnMarker)),
226+
PlaybackMode::Remove => commands
227+
.entity(entity)
228+
// PERF: insert as bundle to reduce archetype moves
229+
.insert((sink, PlaybackRemoveMarker)),
230230
};
231231
}
232232
}

0 commit comments

Comments
 (0)