bevy_kira_components/sources/
mod.rs

1//! Implementations of different audio sources.
2pub mod audio_file;
3
4use crate::backend::AudioBackend;
5use crate::spatial::SpatialEmitterHandle;
6
7use crate::{AudioPlaybackSet, AudioSourceSetup, AudioWorld, InternalAudioMarker};
8use bevy::prelude::*;
9use kira::manager::AudioManager;
10
11use std::fmt;
12use std::marker::PhantomData;
13
14#[doc(hidden)]
15pub mod prelude {
16    pub use super::audio_file::prelude::*;
17    pub use super::{AudioBundle, AudioHandle, AudioSource, AudioSourcePlugin, OutputDestination};
18}
19
20/// Trait for implementing an audio source to play in the audio engine.
21///
22/// The audio source needs to provide two things:
23///
24/// 1. An implementation of [`kira::sound::Sound`] which is going to be sent to the audio engine to
25///    generate audio samples
26/// 2. A handle which sets up communication between the aforementioned sound and the rest of the
27/// world.
28///
29/// The trait supports a `Settings` struct, which allows users to customize the sound that will
30/// be sent before its creation.
31pub trait AudioSource: Asset {
32    /// Error type that encompasses possible errors that can happen when creating the audio source
33    type Error: fmt::Display;
34    /// Handle to the audio source, which allows control over the source from a non-audio thread.
35    ///
36    /// This handle will be stored in a component, which you can get by querying for `AudioHandle<Self::Handle>`.
37    type Handle: 'static + Send + Sync;
38    /// Settings associated with this audio source, and passed in to the source for its creation.
39    type Settings: Send + Sync + Default + Component;
40
41    /// Create an audio handle by calling the manager to play the sound data.
42    fn create_handle(
43        &self,
44        manager: &mut AudioManager<AudioBackend>,
45        settings: &Self::Settings,
46        output_destination: kira::OutputDestination,
47    ) -> Result<Self::Handle, Self::Error>;
48}
49
50/// Component holding a handle to an [`AudioSource`]. Access this component from your systems to
51/// control the parameters of the sound from Bevy.
52#[derive(Debug, Deref, DerefMut, Component)]
53pub struct AudioHandle<T>(pub T);
54
55/// Audio source plugin, which should be added for each type of [`AudioSource`] you want to use
56/// in your game.
57#[derive(Debug)]
58pub struct AudioSourcePlugin<T>(PhantomData<T>);
59
60impl<T> Default for AudioSourcePlugin<T> {
61    fn default() -> Self {
62        Self(PhantomData)
63    }
64}
65
66impl<T: AudioSource> Plugin for AudioSourcePlugin<T> {
67    fn build(&self, app: &mut App) {
68        app.init_asset::<T>().add_systems(
69            PostUpdate,
70            Self::audio_added
71                .in_set(AudioPlaybackSet::Update)
72                .in_set(AudioSourceSetup),
73        );
74    }
75}
76
77/// Possible output destinations for the sound. By default, it will be sent directly to the main
78/// track, but you can send it to custom tracks with optional processing on them instead.
79#[derive(Debug, Default, Component)]
80pub enum OutputDestination {
81    /// Send the audio data to the main track (default)
82    #[default]
83    MainOutput,
84}
85
86/// [`Bundle`] for easy creation of audio sources.
87#[derive(Bundle)]
88pub struct AudioBundle<T: AudioSource> {
89    /// Handle to the [`AudioSource`] asset to be played.
90    pub source: Handle<T>,
91    /// Settings related to the sound to play.
92    pub settings: T::Settings,
93    /// Destination for the audio.
94    pub output: OutputDestination,
95    /// This is an internal marker for use in internal systems, and needs to be public to be able
96    /// to be used properly. You can use it as `With<InternalAudioMarker>` if you want a way to
97    /// discriminate entities with audio attached to them.
98    pub marker: InternalAudioMarker,
99}
100
101impl<T: AudioSource> Default for AudioBundle<T> {
102    fn default() -> Self {
103        Self {
104            source: Handle::default(),
105            settings: T::Settings::default(),
106            output: OutputDestination::MainOutput,
107            marker: InternalAudioMarker,
108        }
109    }
110}
111
112impl<T: AudioSource> AudioSourcePlugin<T> {
113    #[allow(clippy::type_complexity)]
114    fn audio_added(
115        mut commands: Commands,
116        mut audio_world: ResMut<AudioWorld>,
117        asset_server: Res<AssetServer>,
118        assets: Res<Assets<T>>,
119        q_added: Query<
120            (
121                Entity,
122                &Handle<T>,
123                &T::Settings,
124                Option<&SpatialEmitterHandle>,
125                &OutputDestination,
126            ),
127            Without<AudioHandle<T::Handle>>,
128        >,
129    ) {
130        let main_track_handle = audio_world.audio_manager.main_track();
131        for (entity, source, settings, spatial_emitter, output_destination) in &q_added {
132            let output_destination = if let Some(emitter) = spatial_emitter {
133                kira::OutputDestination::Emitter(emitter.0.id())
134            } else {
135                let output_handle = match output_destination {
136                    OutputDestination::MainOutput => &main_track_handle,
137                };
138                kira::OutputDestination::Track(output_handle.id())
139            };
140            let result = match assets.get(source) {
141                Some(asset)
142                    if asset_server.is_loaded_with_dependencies(source)
143                        || !asset_server.is_managed(source) =>
144                {
145                    asset.create_handle(
146                        &mut audio_world.audio_manager,
147                        settings,
148                        output_destination,
149                    )
150                }
151                _ => {
152                    debug!("Asset not ready");
153                    continue;
154                } // Asset not ready, wait
155            };
156            let handle = match result {
157                Ok(handle) => handle,
158                Err(err) => {
159                    error!("Cannot create handle: {err}");
160                    return;
161                }
162            };
163            debug!("Added sound for {} in {entity:?}", T::type_path());
164            commands.entity(entity).insert(AudioHandle(handle));
165        }
166    }
167}