Skip to main content

bevy_kira_components/sources/audio_file/
mod.rs

1//! Audio source implementation for audio files.
2//!
3//! This implementation wraps both `StaticSound` and `StreamingSound`, to allow for better defaults around streaming
4//! like having a size threshold, beyond which the file is kept on disk instead of loaded into memory in its entirety.
5//! (note that this is not yet implemented).
6//!
7//! Specifying if the asset is streamed or not is part of the loader settings, which can be changed in `.meta` files,
8//! or specified with [`AssetServer::load_with_settings`].
9
10use bevy::prelude::*;
11use bevy::utils::error;
12use kira::sound::static_sound::{StaticSoundData, StaticSoundHandle, StaticSoundSettings};
13use std::io::Cursor;
14
15use std::path::PathBuf;
16use std::sync::Arc;
17
18use crate::backend::AudioBackend;
19use kira::manager::error::PlaySoundError;
20use kira::manager::AudioManager;
21use kira::sound::streaming::{StreamingSoundData, StreamingSoundHandle, StreamingSoundSettings};
22use kira::sound::{FromFileError, PlaybackRate, PlaybackState, Region};
23
24use crate::sources::audio_file::loader::AudioFileLoader;
25use crate::AudioPlaybackSet;
26use kira::tween::{Tween, Value};
27use kira::{CommandError, OutputDestination};
28use serde::{Deserialize, Serialize};
29use thiserror::Error;
30
31use super::{AudioBundle, AudioHandle, AudioSource, AudioSourcePlugin};
32
33pub mod loader;
34
35#[doc(hidden)]
36#[allow(missing_docs)]
37pub mod prelude {
38    pub use super::loader::*;
39    pub use super::{
40        AudioFile, AudioFileBundle, AudioFileError, AudioFileHandle, AudioFileSettings,
41    };
42}
43
44/// Specialization of [`AudioBundle`] for the [`AudioFile`] asset.
45pub type AudioFileBundle = AudioBundle<AudioFile>;
46
47/// Implementation of an audio source using the Static and Streaming file data from [`kira`].
48pub struct AudioFilePlugin;
49
50impl Plugin for AudioFilePlugin {
51    fn build(&self, app: &mut App) {
52        app.init_asset_loader::<AudioFileLoader>()
53            .add_plugins(AudioSourcePlugin::<AudioFile>::default())
54            .add_systems(PostUpdate, audio_finished.in_set(AudioPlaybackSet::Cleanup));
55    }
56}
57
58fn audio_finished(
59    mut commands: Commands,
60    q_sources: Query<(Entity, &AudioHandle<AudioFileHandle>)>,
61) {
62    for (entity, AudioHandle(handle)) in &q_sources {
63        if matches!(handle.playback_state(), PlaybackState::Stopped) {
64            commands
65                .entity(entity)
66                .remove::<AudioHandle<AudioFileHandle>>();
67        }
68    }
69}
70
71/// Bevy [`Asset`] implementation that wraps audio data for [`kira`].
72///
73/// Streaming audio data is currently not possible over the internet, so when targeting the web,
74/// all audio sources need to be [`Static`](Self::Static).
75#[derive(Asset, Clone, TypePath)]
76pub enum AudioFile {
77    /// Static audio data, fully loaded in memory.
78    Static(Arc<[u8]>, StaticSoundSettings),
79    /// Streaming audio data, pointing to a path on disk and loaded on demand.
80    Streaming {
81        /// Path to the audio file being read
82        path: PathBuf,
83        /// Settings for the streaming audio file
84        settings: StreamingSoundSettings,
85    },
86}
87
88/// Enumeration of possible errors when loading an audio file.
89#[derive(Debug, Error)]
90pub enum AudioFileError {
91    /// Error comes from trying to load the file for streaming
92    #[error(transparent)]
93    FromFileError(#[from] FromFileError),
94}
95
96/// Settings available to the user when instantiating an audio file.
97#[derive(Debug, Component, Deserialize, Serialize)]
98pub struct AudioFileSettings {
99    /// By default, sounds will start playing right away when inserted. Setting this to `true`
100    /// prevents that.
101    pub start_paused: bool,
102    /// Volume at which the audio will play at.
103    pub volume: f64,
104    /// Panning (in 0..=1) for the sound, where 0 is hard left, and 1 is hard right.
105    pub panning: f64,
106    /// Optionally loop a region of the sound (given in seconds)
107    pub loop_region: Option<Region>,
108    /// Only play a specific region of the file
109    pub play_region: Region,
110    /// Play the file in reverse (not available for streaming sound files)
111    pub reverse: bool,
112    // pub start_time: StartTime, // TODO: Implement with serializable types
113}
114
115impl Default for AudioFileSettings {
116    fn default() -> Self {
117        Self {
118            start_paused: false,
119            volume: 1.0,
120            panning: 0.5,
121            loop_region: None,
122            play_region: Region::from(..),
123            reverse: false,
124        }
125    }
126}
127
128fn play_sound_error_transmute<Out>(err: PlaySoundError<()>) -> PlaySoundError<Out> {
129    match err {
130        PlaySoundError::CommandError(cmd) => PlaySoundError::CommandError(cmd),
131        PlaySoundError::SoundLimitReached => PlaySoundError::SoundLimitReached,
132        _ => unreachable!(),
133    }
134}
135
136fn play_sound_error_cast<In, Out: From<In>>(err: PlaySoundError<In>) -> PlaySoundError<Out> {
137    match err {
138        PlaySoundError::CommandError(cmd) => PlaySoundError::CommandError(cmd),
139        PlaySoundError::SoundLimitReached => PlaySoundError::SoundLimitReached,
140        PlaySoundError::IntoSoundError(input) => PlaySoundError::IntoSoundError(input.into()),
141        _ => unreachable!(),
142    }
143}
144
145impl AudioSource for AudioFile {
146    type Error = PlaySoundError<AudioFileError>;
147    type Handle = AudioFileHandle;
148    type Settings = AudioFileSettings;
149
150    fn create_handle(
151        &self,
152        manager: &mut AudioManager<AudioBackend>,
153        settings: &Self::Settings,
154        output_destination: OutputDestination,
155    ) -> Result<Self::Handle, Self::Error> {
156        let start_paused = settings.start_paused;
157        match self {
158            Self::Static(data, kira_settings) => {
159                let settings = (*kira_settings)
160                    .output_destination(output_destination)
161                    .volume(settings.volume)
162                    .panning(settings.panning)
163                    .loop_region(settings.loop_region)
164                    .reverse(settings.reverse)
165                    .playback_region(settings.play_region);
166                let static_data = StaticSoundData::from_cursor(Cursor::new(data.clone()), settings)
167                    .map_err(|err| {
168                        PlaySoundError::IntoSoundError(AudioFileError::FromFileError(err))
169                    })?;
170                manager
171                    .play(static_data)
172                    .map_err(play_sound_error_transmute)
173                    .map(|mut handle| {
174                        if start_paused {
175                            error(handle.pause(Tween::default()));
176                        }
177                        handle
178                    })
179                    .map(RawAudioHandleImpl::Static)
180                    .map(AudioFileHandle)
181            }
182            Self::Streaming {
183                path,
184                settings: kira_settings,
185            } => {
186                let settings = (*kira_settings)
187                    .output_destination(output_destination)
188                    .volume(settings.volume)
189                    .panning(settings.panning)
190                    .loop_region(settings.loop_region)
191                    .playback_region(settings.play_region);
192                let streaming_sound_data =
193                    StreamingSoundData::from_file(path, settings).map_err(|err| {
194                        PlaySoundError::IntoSoundError(AudioFileError::FromFileError(err))
195                    })?;
196                manager
197                    .play(streaming_sound_data)
198                    .map_err(play_sound_error_cast)
199                    .map(|mut handle| {
200                        if start_paused {
201                            error(handle.pause(Tween::default()));
202                        }
203                        handle
204                    })
205                    .map(RawAudioHandleImpl::Streaming)
206                    .map(AudioFileHandle)
207            }
208        }
209    }
210}
211
212/// Enum of the possible sound handles that [`kira`] returns
213enum RawAudioHandleImpl {
214    Static(StaticSoundHandle),
215    Streaming(StreamingSoundHandle<FromFileError>),
216}
217
218/// Handle to an existing audio file. Access this component in your systems to manipulate the
219/// audio in real time (see the `spatial` example to see how to do so).
220pub struct AudioFileHandle(RawAudioHandleImpl);
221
222macro_rules! defer_call {
223    (fn $name:ident(&self $(, $argname:ident: $argtype:ty)*) -> $ret:ty) => {
224        defer_call!(fn $name :: $name(&self $(, $argname: $argtype)*) -> $ret);
225    };
226    // Don't know how to parametrize the `mut` and be able to factor these two into one variant
227    (fn $name:ident :: $fnname:ident(&self $(, $argname:ident: $argtype:ty)*) -> $ret:ty) => {
228       /// Forward a call to [`StaticSoundHandle::$name`] or [`StreamingSoundHandle::$name`] .
229       pub fn $fnname(&self, $($argname: $argtype),*) -> $ret {
230            match self {
231                Self(RawAudioHandleImpl::Static(handle)) => handle.$name($($argname),*),
232                Self(RawAudioHandleImpl::Streaming(handle)) => handle.$name($($argname),*),
233            }
234        }
235    };
236    (fn $name:ident(&mut self $(, $argname:ident: $argtype:ty)*) -> $ret:ty) => {
237        /// Forward a call to [`StaticSoundHandle::$name`] or [`StreamingSoundHandle::$name`] .
238        pub fn $name(&mut self, $($argname: $argtype),*) -> $ret {
239            match self {
240                Self(RawAudioHandleImpl::Static(handle)) => handle.$name($($argname),*),
241                Self(RawAudioHandleImpl::Streaming(handle)) => handle.$name($($argname),*),
242            }
243        }
244    };
245}
246
247impl AudioFileHandle {
248    defer_call!(fn state :: playback_state(&self) -> PlaybackState);
249    defer_call!(fn position(&self) -> f64);
250    defer_call!(fn set_playback_rate(&mut self, rate: impl Into<Value<PlaybackRate>>, tween: Tween) -> Result<(), CommandError>);
251    defer_call!(fn set_panning(&mut self, panning: impl Into<Value<f64>>, tween: Tween) ->Result<(), CommandError>);
252    defer_call!(fn set_playback_region(&mut self, region: impl Into<Region>) -> Result<(), CommandError>);
253    defer_call!(fn set_loop_region(&mut self, region: impl Into<Region>) -> Result<(), CommandError>);
254    defer_call!(fn pause(&mut self, tween: Tween) -> Result<(), CommandError>);
255    defer_call!(fn resume(&mut self, tween: Tween) -> Result<(), CommandError>);
256    defer_call!(fn stop(&mut self, tween: Tween) -> Result<(), CommandError>);
257    defer_call!(fn seek_to(&mut self, position: f64) -> Result<(), CommandError>);
258    defer_call!(fn seek_by(&mut self, amount: f64) -> Result<(), CommandError>);
259}