use crate::Volume;1use bevy_ecs::component::Component;2use bevy_math::Vec3;3use bevy_transform::prelude::Transform;4use core::time::Duration;5pub use rodio::source::SeekError;6use rodio::{Sink, SpatialSink};78/// Common interactions with an audio sink.9pub trait AudioSinkPlayback {10/// Gets the volume of the sound as a [`Volume`].11///12/// If the sink is muted, this returns the managed volume rather than the13/// sink's actual volume. This allows you to use the returned volume as if14/// the sink were not muted, because a muted sink has a physical volume of15/// 0.16fn volume(&self) -> Volume;1718/// Changes the volume of the sound to the given [`Volume`].19///20/// If the sink is muted, changing the volume won't unmute it, i.e. the21/// sink's volume will remain "off" / "muted". However, the sink will22/// remember the volume change and it will be used when23/// [`unmute`](Self::unmute) is called. This allows you to control the24/// volume even when the sink is muted.25fn set_volume(&mut self, volume: Volume);2627/// Gets the speed of the sound.28///29/// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0`30/// will change the play speed of the sound.31fn speed(&self) -> f32;3233/// Changes the speed of the sound.34///35/// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0`36/// will change the play speed of the sound.37fn set_speed(&self, speed: f32);3839/// Resumes playback of a paused sink.40///41/// No effect if not paused.42fn play(&self);4344/// Returns the position of the sound that's being played.45///46/// This takes into account any speedup or delay applied.47///48/// Example: if you [`set_speed(2.0)`](Self::set_speed) and [`position()`](Self::position) returns *5s*,49/// then the position in the recording is *10s* from its start.50fn position(&self) -> Duration;5152/// Attempts to seek to a given position in the current source.53///54/// This blocks between 0 and ~5 milliseconds.55///56/// As long as the duration of the source is known, seek is guaranteed to saturate57/// at the end of the source. For example given a source that reports a total duration58/// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to59/// 42 seconds.60///61/// # Errors62/// This function will return [`SeekError::NotSupported`] if one of the underlying63/// sources does not support seeking.64///65/// It will return an error if an implementation ran66/// into one during the seek.67///68/// When seeking beyond the end of a source, this69/// function might return an error if the duration of the source is not known.70fn try_seek(&self, pos: Duration) -> Result<(), SeekError>;7172/// Pauses playback of this sink.73///74/// No effect if already paused.75/// A paused sink can be resumed with [`play`](Self::play).76fn pause(&self);7778/// Toggles playback of the sink.79///80/// If the sink is paused, toggling playback resumes it. If the sink is81/// playing, toggling playback pauses it.82fn toggle_playback(&self) {83if self.is_paused() {84self.play();85} else {86self.pause();87}88}8990/// Returns true if the sink is paused.91///92/// Sinks can be paused and resumed using [`pause`](Self::pause) and [`play`](Self::play).93fn is_paused(&self) -> bool;9495/// Stops the sink.96///97/// It won't be possible to restart it afterwards.98fn stop(&self);99100/// Returns true if this sink has no more sounds to play.101fn empty(&self) -> bool;102103/// Returns true if the sink is muted.104fn is_muted(&self) -> bool;105106/// Mutes the sink.107///108/// Muting a sink sets the volume to 0. Use [`unmute`](Self::unmute) to109/// unmute the sink and restore the original volume.110fn mute(&mut self);111112/// Unmutes the sink.113///114/// Restores the volume to the value it was before it was muted.115fn unmute(&mut self);116117/// Toggles whether the sink is muted or not.118fn toggle_mute(&mut self) {119if self.is_muted() {120self.unmute();121} else {122self.mute();123}124}125}126127/// Used to control audio during playback.128///129/// Bevy inserts this component onto your entities when it begins playing an audio source.130/// Use [`AudioPlayer`][crate::AudioPlayer] to trigger that to happen.131///132/// You can use this component to modify the playback settings while the audio is playing.133///134/// If this component is removed from an entity, and an [`AudioSource`][crate::AudioSource] is135/// attached to that entity, that [`AudioSource`][crate::AudioSource] will start playing. If136/// that source is unchanged, that translates to the audio restarting.137#[derive(Component)]138pub struct AudioSink {139pub(crate) sink: Sink,140141/// Managed volume allows the sink to be muted without losing the user's142/// intended volume setting.143///144/// This is used to restore the volume when [`unmute`](Self::unmute) is145/// called.146///147/// If the sink is not muted, this is `None`.148///149/// If the sink is muted, this is `Some(volume)` where `volume` is the150/// user's intended volume setting, even if the underlying sink's volume is151/// 0.152pub(crate) managed_volume: Option<Volume>,153}154155impl AudioSink {156/// Create a new audio sink.157pub fn new(sink: Sink) -> Self {158Self {159sink,160managed_volume: None,161}162}163}164165impl AudioSinkPlayback for AudioSink {166fn volume(&self) -> Volume {167self.managed_volume168.unwrap_or_else(|| Volume::Linear(self.sink.volume()))169}170171fn set_volume(&mut self, volume: Volume) {172if self.is_muted() {173self.managed_volume = Some(volume);174} else {175self.sink.set_volume(volume.to_linear());176}177}178179fn speed(&self) -> f32 {180self.sink.speed()181}182183fn set_speed(&self, speed: f32) {184self.sink.set_speed(speed);185}186187fn play(&self) {188self.sink.play();189}190191fn position(&self) -> Duration {192self.sink.get_pos()193}194195fn try_seek(&self, pos: Duration) -> Result<(), SeekError> {196self.sink.try_seek(pos)197}198199fn pause(&self) {200self.sink.pause();201}202203fn is_paused(&self) -> bool {204self.sink.is_paused()205}206207fn stop(&self) {208self.sink.stop();209}210211fn empty(&self) -> bool {212self.sink.empty()213}214215fn is_muted(&self) -> bool {216self.managed_volume.is_some()217}218219fn mute(&mut self) {220self.managed_volume = Some(self.volume());221self.sink.set_volume(0.0);222}223224fn unmute(&mut self) {225if let Some(volume) = self.managed_volume.take() {226self.sink.set_volume(volume.to_linear());227}228}229}230231/// Used to control spatial audio during playback.232///233/// Bevy inserts this component onto your entities when it begins playing an audio source234/// that's configured to use spatial audio.235///236/// You can use this component to modify the playback settings while the audio is playing.237///238/// If this component is removed from an entity, and a [`AudioSource`][crate::AudioSource] is239/// attached to that entity, that [`AudioSource`][crate::AudioSource] will start playing. If240/// that source is unchanged, that translates to the audio restarting.241#[derive(Component)]242pub struct SpatialAudioSink {243pub(crate) sink: SpatialSink,244245/// Managed volume allows the sink to be muted without losing the user's246/// intended volume setting.247///248/// This is used to restore the volume when [`unmute`](Self::unmute) is249/// called.250///251/// If the sink is not muted, this is `None`.252///253/// If the sink is muted, this is `Some(volume)` where `volume` is the254/// user's intended volume setting, even if the underlying sink's volume is255/// 0.256pub(crate) managed_volume: Option<Volume>,257}258259impl SpatialAudioSink {260/// Create a new spatial audio sink.261pub fn new(sink: SpatialSink) -> Self {262Self {263sink,264managed_volume: None,265}266}267}268269impl AudioSinkPlayback for SpatialAudioSink {270fn volume(&self) -> Volume {271self.managed_volume272.unwrap_or_else(|| Volume::Linear(self.sink.volume()))273}274275fn set_volume(&mut self, volume: Volume) {276if self.is_muted() {277self.managed_volume = Some(volume);278} else {279self.sink.set_volume(volume.to_linear());280}281}282283fn speed(&self) -> f32 {284self.sink.speed()285}286287fn set_speed(&self, speed: f32) {288self.sink.set_speed(speed);289}290291fn play(&self) {292self.sink.play();293}294295fn position(&self) -> Duration {296self.sink.get_pos()297}298299fn try_seek(&self, pos: Duration) -> Result<(), SeekError> {300self.sink.try_seek(pos)301}302303fn pause(&self) {304self.sink.pause();305}306307fn is_paused(&self) -> bool {308self.sink.is_paused()309}310311fn stop(&self) {312self.sink.stop();313}314315fn empty(&self) -> bool {316self.sink.empty()317}318319fn is_muted(&self) -> bool {320self.managed_volume.is_some()321}322323fn mute(&mut self) {324self.managed_volume = Some(self.volume());325self.sink.set_volume(0.0);326}327328fn unmute(&mut self) {329if let Some(volume) = self.managed_volume.take() {330self.sink.set_volume(volume.to_linear());331}332}333}334335impl SpatialAudioSink {336/// Set the two ears position.337pub fn set_ears_position(&self, left_position: Vec3, right_position: Vec3) {338self.sink.set_left_ear_position(left_position.to_array());339self.sink.set_right_ear_position(right_position.to_array());340}341342/// Set the listener position, with an ear on each side separated by `gap`.343pub fn set_listener_position(&self, position: Transform, gap: f32) {344self.set_ears_position(345position.translation + position.left() * gap / 2.0,346position.translation + position.right() * gap / 2.0,347);348}349350/// Set the emitter position.351pub fn set_emitter_position(&self, position: Vec3) {352self.sink.set_emitter_position(position.to_array());353}354}355356#[cfg(test)]357mod tests {358use rodio::Sink;359360use super::*;361362fn test_audio_sink_playback<T: AudioSinkPlayback>(mut audio_sink: T) {363// Test volume364assert_eq!(audio_sink.volume(), Volume::Linear(1.0)); // default volume365audio_sink.set_volume(Volume::Linear(0.5));366assert_eq!(audio_sink.volume(), Volume::Linear(0.5));367audio_sink.set_volume(Volume::Linear(1.0));368assert_eq!(audio_sink.volume(), Volume::Linear(1.0));369370// Test speed371assert_eq!(audio_sink.speed(), 1.0); // default speed372audio_sink.set_speed(0.5);373assert_eq!(audio_sink.speed(), 0.5);374audio_sink.set_speed(1.0);375assert_eq!(audio_sink.speed(), 1.0);376377// Test playback378assert!(!audio_sink.is_paused()); // default pause state379audio_sink.pause();380assert!(audio_sink.is_paused());381audio_sink.play();382assert!(!audio_sink.is_paused());383384// Test toggle playback385audio_sink.pause(); // start paused386audio_sink.toggle_playback();387assert!(!audio_sink.is_paused());388audio_sink.toggle_playback();389assert!(audio_sink.is_paused());390391// Test mute392assert!(!audio_sink.is_muted()); // default mute state393audio_sink.mute();394assert!(audio_sink.is_muted());395audio_sink.unmute();396assert!(!audio_sink.is_muted());397398// Test volume with mute399audio_sink.set_volume(Volume::Linear(0.5));400audio_sink.mute();401assert_eq!(audio_sink.volume(), Volume::Linear(0.5)); // returns managed volume even though sink volume is 0402audio_sink.unmute();403assert_eq!(audio_sink.volume(), Volume::Linear(0.5)); // managed volume is restored404405// Test toggle mute406audio_sink.toggle_mute();407assert!(audio_sink.is_muted());408audio_sink.toggle_mute();409assert!(!audio_sink.is_muted());410}411412#[test]413fn test_audio_sink() {414let (sink, _queue_rx) = Sink::new_idle();415let audio_sink = AudioSink::new(sink);416test_audio_sink_playback(audio_sink);417}418}419420421