Skip to content

Commit

Permalink
Add ability to mute audio sinks
Browse files Browse the repository at this point in the history
  • Loading branch information
mgi388 committed Dec 14, 2024
1 parent 30bd641 commit fc22fa2
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 24 deletions.
16 changes: 8 additions & 8 deletions crates/bevy_audio/src/audio_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,25 +167,25 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
match settings.mode {
PlaybackMode::Loop => {
sink.append(audio_source.decoder().repeat_infinite());
commands.entity(entity).insert(SpatialAudioSink { sink });
commands.entity(entity).insert(SpatialAudioSink::new(sink));
}
PlaybackMode::Once => {
sink.append(audio_source.decoder());
commands.entity(entity).insert(SpatialAudioSink { sink });
commands.entity(entity).insert(SpatialAudioSink::new(sink));
}
PlaybackMode::Despawn => {
sink.append(audio_source.decoder());
commands
.entity(entity)
// PERF: insert as bundle to reduce archetype moves
.insert((SpatialAudioSink { sink }, PlaybackDespawnMarker));
.insert((SpatialAudioSink::new(sink), PlaybackDespawnMarker));
}
PlaybackMode::Remove => {
sink.append(audio_source.decoder());
commands
.entity(entity)
// PERF: insert as bundle to reduce archetype moves
.insert((SpatialAudioSink { sink }, PlaybackRemoveMarker));
.insert((SpatialAudioSink::new(sink), PlaybackRemoveMarker));
}
};
} else {
Expand All @@ -207,25 +207,25 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
match settings.mode {
PlaybackMode::Loop => {
sink.append(audio_source.decoder().repeat_infinite());
commands.entity(entity).insert(AudioSink { sink });
commands.entity(entity).insert(AudioSink::new(sink));
}
PlaybackMode::Once => {
sink.append(audio_source.decoder());
commands.entity(entity).insert(AudioSink { sink });
commands.entity(entity).insert(AudioSink::new(sink));
}
PlaybackMode::Despawn => {
sink.append(audio_source.decoder());
commands
.entity(entity)
// PERF: insert as bundle to reduce archetype moves
.insert((AudioSink { sink }, PlaybackDespawnMarker));
.insert((AudioSink::new(sink), PlaybackDespawnMarker));
}
PlaybackMode::Remove => {
sink.append(audio_source.decoder());
commands
.entity(entity)
// PERF: insert as bundle to reduce archetype moves
.insert((AudioSink { sink }, PlaybackRemoveMarker));
.insert((AudioSink::new(sink), PlaybackRemoveMarker));
}
};
}
Expand Down
133 changes: 125 additions & 8 deletions crates/bevy_audio/src/sinks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,32 @@ use rodio::{Sink, SpatialSink};
pub trait AudioSinkPlayback {
/// Gets the volume of the sound.
///
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0`
/// will multiply each sample by this value.
/// The value `1.0` is the "normal" volume (unfiltered input). Any value
/// other than `1.0` will multiply each sample by this value.
///
/// If the sink is muted, this returns the managed volume rather than the
/// sink's actual volume. This allows you to use the volume as if the sink
/// were not muted, because a muted sink has a volume of 0.
fn volume(&self) -> f32;

/// Changes the volume of the sound.
///
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0`
/// will multiply each sample by this value.
///
/// If the sink is muted, this sets the managed volume rather than the
/// sink's actual volume. This allows you to control the volume even when
/// the sink is muted, so that when it is unmuted, the volume is restored
/// and all calls to this function are respected.
///
/// # Note on Audio Volume
///
/// An increase of 10 decibels (dB) roughly corresponds to the perceived volume doubling in intensity.
/// As this function scales not the volume but the amplitude, a conversion might be necessary.
/// For example, to halve the perceived volume you need to decrease the volume by 10 dB.
/// This corresponds to 20log(x) = -10dB, solving x = 10^(-10/20) = 0.316.
/// Multiply the current volume by 0.316 to halve the perceived volume.
fn set_volume(&self, volume: f32);
fn set_volume(&mut self, volume: f32);

/// Gets the speed of the sound.
///
Expand Down Expand Up @@ -71,6 +80,24 @@ pub trait AudioSinkPlayback {

/// Returns true if this sink has no more sounds to play.
fn empty(&self) -> bool;

/// Returns true if the sink is muted.
fn is_muted(&self) -> bool;

/// Mutes the sink.
fn mute(&mut self);

/// Unmutes the sink.
fn unmute(&mut self);

/// Toggles whether the sink is muted or not.
fn toggle_mute(&mut self) {
if self.is_muted() {
self.unmute();
} else {
self.mute();
}
}
}

/// Used to control audio during playback.
Expand All @@ -86,15 +113,46 @@ pub trait AudioSinkPlayback {
#[derive(Component)]
pub struct AudioSink {
pub(crate) sink: Sink,

/// Managed volume allows the sink to be muted without losing the user's
/// intended volume setting.
///
/// This is used to restore the volume when [`unmute`](Self::unmute) is
/// called.
///
/// If the sink is not muted, this is `None`.
///
/// If the sink is muted, this is `Some(volume)` where `volume` is the
/// user's intended volume setting, even if the underlying sink's volume is
/// 0.
pub(crate) managed_volume: Option<f32>,
}

impl AudioSink {
/// Create a new audio sink.
pub fn new(sink: Sink) -> Self {
Self {
sink,
managed_volume: None,
}
}
}

impl AudioSinkPlayback for AudioSink {
fn volume(&self) -> f32 {
self.sink.volume()
if let Some(volume) = self.managed_volume {
volume
} else {
self.sink.volume()
}
}

fn set_volume(&self, volume: f32) {
self.sink.set_volume(volume);
fn set_volume(&mut self, volume: f32) {
if self.is_muted() {
self.managed_volume = Some(volume);
} else {
self.sink.set_volume(volume);
}
}

fn speed(&self) -> f32 {
Expand Down Expand Up @@ -124,6 +182,22 @@ impl AudioSinkPlayback for AudioSink {
fn empty(&self) -> bool {
self.sink.empty()
}

fn is_muted(&self) -> bool {
self.managed_volume.is_some()
}

fn mute(&mut self) {
self.managed_volume = Some(self.volume());
self.sink.set_volume(0.0);
}

fn unmute(&mut self) {
if let Some(volume) = self.managed_volume {
self.sink.set_volume(volume);
self.managed_volume = None;
}
}
}

/// Used to control spatial audio during playback.
Expand All @@ -139,15 +213,42 @@ impl AudioSinkPlayback for AudioSink {
#[derive(Component)]
pub struct SpatialAudioSink {
pub(crate) sink: SpatialSink,

/// Managed volume allows the sink to be muted without losing the user's
/// intended volume setting.
///
/// This is used to restore the volume when [`unmute`](Self::unmute) is
/// called.
///
/// If the sink is not muted, this is `None`.
///
/// If the sink is muted, this is `Some(volume)` where `volume` is the
/// user's intended volume setting, even if the underlying sink's volume is
/// 0.
pub(crate) managed_volume: Option<f32>,
}

impl SpatialAudioSink {
/// Create a new spatial audio sink.
pub fn new(sink: SpatialSink) -> Self {
Self {
sink,
managed_volume: None,
}
}
}

impl AudioSinkPlayback for SpatialAudioSink {
fn volume(&self) -> f32 {
self.sink.volume()
}

fn set_volume(&self, volume: f32) {
self.sink.set_volume(volume);
fn set_volume(&mut self, volume: f32) {
if self.is_muted() {
self.managed_volume = Some(volume);
} else {
self.sink.set_volume(volume);
}
}

fn speed(&self) -> f32 {
Expand Down Expand Up @@ -177,6 +278,22 @@ impl AudioSinkPlayback for SpatialAudioSink {
fn empty(&self) -> bool {
self.sink.empty()
}

fn is_muted(&self) -> bool {
self.managed_volume.is_some()
}

fn mute(&mut self) {
self.managed_volume = Some(self.volume());
self.sink.set_volume(0.0);
}

fn unmute(&mut self) {
if let Some(volume) = self.managed_volume {
self.sink.set_volume(volume);
self.managed_volume = None;
}
}
}

impl SpatialAudioSink {
Expand Down
22 changes: 18 additions & 4 deletions examples/audio/audio_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, (update_speed, pause, volume))
.add_systems(Update, (update_speed, pause, mute, volume))
.run();
}

Expand All @@ -30,10 +30,24 @@ fn pause(keyboard_input: Res<ButtonInput<KeyCode>>, sink: Single<&AudioSink, Wit
}
}

fn volume(keyboard_input: Res<ButtonInput<KeyCode>>, sink: Single<&AudioSink, With<MyMusic>>) {
fn mute(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut sink: Single<&mut AudioSink, With<MyMusic>>,
) {
if keyboard_input.just_pressed(KeyCode::KeyM) {
sink.toggle_mute();
}
}

fn volume(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut sink: Single<&mut AudioSink, With<MyMusic>>,
) {
if keyboard_input.just_pressed(KeyCode::Equal) {
sink.set_volume(sink.volume() + 0.1);
let current_volume = sink.volume();
sink.set_volume(current_volume + 0.1);
} else if keyboard_input.just_pressed(KeyCode::Minus) {
sink.set_volume(sink.volume() - 0.1);
let current_volume = sink.volume();
sink.set_volume(current_volume - 0.1);
}
}
10 changes: 6 additions & 4 deletions examples/audio/soundtrack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,9 @@ fn fade_in(
mut audio_sink: Query<(&mut AudioSink, Entity), With<FadeIn>>,
time: Res<Time>,
) {
for (audio, entity) in audio_sink.iter_mut() {
audio.set_volume(audio.volume() + time.delta_secs() / FADE_TIME);
for (mut audio, entity) in audio_sink.iter_mut() {
let current_volume = audio.volume();
audio.set_volume(current_volume + time.delta_secs() / FADE_TIME);
if audio.volume() >= 1.0 {
audio.set_volume(1.0);
commands.entity(entity).remove::<FadeIn>();
Expand All @@ -129,8 +130,9 @@ fn fade_out(
mut audio_sink: Query<(&mut AudioSink, Entity), With<FadeOut>>,
time: Res<Time>,
) {
for (audio, entity) in audio_sink.iter_mut() {
audio.set_volume(audio.volume() - time.delta_secs() / FADE_TIME);
for (mut audio, entity) in audio_sink.iter_mut() {
let current_volume = audio.volume();
audio.set_volume(current_volume - time.delta_secs() / FADE_TIME);
if audio.volume() <= 0.0 {
commands.entity(entity).despawn_recursive();
}
Expand Down

0 comments on commit fc22fa2

Please sign in to comment.