1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! Implementations of different audio sources.
use std::fmt;
use std::marker::PhantomData;

use bevy::prelude::*;
use kira::manager::AudioManager;

use crate::backend::AudioBackend;
use crate::spatial::SpatialEmitterHandle;
use crate::{AudioPlaybackSet, AudioSourceSetup, AudioWorld, InternalAudioMarker};

pub mod audio_file;

#[doc(hidden)]
pub mod prelude {
    pub use super::audio_file::prelude::*;
    pub use super::{
        AudioBundle, AudioHandle, AudioSource, AudioSourcePlugin, NoAudioSettings,
        OutputDestination,
    };
}

/// Trait for implementing an audio source to play in the audio engine.
///
/// The audio source needs to provide two things:
///
/// 1. An implementation of [`kira::sound::Sound`] which is going to be sent to the audio engine to
///    generate audio samples
/// 2. A handle which sets up communication between the aforementioned sound and the rest of the
/// world.
///
/// The trait supports a `Settings` struct, which allows users to customize the sound that will
/// be sent before its creation.
pub trait AudioSource: Asset {
    /// Error type that encompasses possible errors that can happen when creating the audio source
    type Error: fmt::Display;
    /// Handle to the audio source, which allows control over the source from a non-audio thread.
    ///
    /// This handle will be stored in a component, which you can get by querying for `AudioHandle<Self::Handle>`.
    type Handle: 'static + Send + Sync;
    /// Settings associated with this audio source, and passed in to the source for its creation.
    type Settings: Send + Sync + Default + Component;

    /// Create an audio handle by calling the manager to play the sound data.
    fn create_handle(
        &self,
        manager: &mut AudioManager<AudioBackend>,
        settings: &Self::Settings,
        output_destination: kira::OutputDestination,
    ) -> Result<Self::Handle, Self::Error>;
}

/// Dummy struct for cases where the audio source has no settings.
#[derive(Debug, Default, Component)]
pub struct NoAudioSettings;

/// Component holding a handle to an [`AudioSource`]. Access this component from your systems to
/// control the parameters of the sound from Bevy.
#[derive(Debug, Deref, DerefMut, Component)]
pub struct AudioHandle<T>(pub T);

/// Audio source plugin, which should be added for each type of [`AudioSource`] you want to use
/// in your game.
#[derive(Debug)]
pub struct AudioSourcePlugin<T>(PhantomData<T>);

impl<T> Default for AudioSourcePlugin<T> {
    fn default() -> Self {
        Self(PhantomData)
    }
}

impl<T: AudioSource> Plugin for AudioSourcePlugin<T> {
    fn build(&self, app: &mut App) {
        app.init_asset::<T>().add_systems(
            PostUpdate,
            Self::audio_added
                .in_set(AudioPlaybackSet::Update)
                .in_set(AudioSourceSetup),
        );
    }
}

/// Possible output destinations for the sound. By default, it will be sent directly to the main
/// track, but you can send it to custom tracks with optional processing on them instead.
#[derive(Debug, Default, Component)]
pub enum OutputDestination {
    /// Send the audio data to the main track (default)
    #[default]
    MainOutput,
}

/// [`Bundle`] for easy creation of audio sources.
#[derive(Bundle)]
pub struct AudioBundle<T: AudioSource> {
    /// Handle to the [`AudioSource`] asset to be played.
    pub source: Handle<T>,
    /// Settings related to the sound to play.
    pub settings: T::Settings,
    /// Destination for the audio.
    pub output: OutputDestination,
    /// This is an internal marker for use in internal systems, and needs to be public to be able
    /// to be used properly. You can use it as `With<InternalAudioMarker>` if you want a way to
    /// discriminate entities with audio attached to them.
    pub marker: InternalAudioMarker,
}

impl<T: AudioSource> Default for AudioBundle<T> {
    fn default() -> Self {
        Self {
            source: Handle::default(),
            settings: T::Settings::default(),
            output: OutputDestination::MainOutput,
            marker: InternalAudioMarker,
        }
    }
}

impl<T: AudioSource> AudioSourcePlugin<T> {
    #[allow(clippy::type_complexity)]
    fn audio_added(
        mut commands: Commands,
        mut audio_world: ResMut<AudioWorld>,
        asset_server: Res<AssetServer>,
        assets: Res<Assets<T>>,
        q_added: Query<
            (
                Entity,
                &Handle<T>,
                &T::Settings,
                Option<&SpatialEmitterHandle>,
                &OutputDestination,
            ),
            Without<AudioHandle<T::Handle>>,
        >,
    ) {
        for (entity, source, settings, spatial_emitter, output_destination) in &q_added {
            let output_destination = if let Some(emitter) = spatial_emitter {
                kira::OutputDestination::Emitter(emitter.0.id())
            } else {
                let output_handle = match output_destination {
                    OutputDestination::MainOutput => &*audio_world.audio_manager.main_track(),
                };
                kira::OutputDestination::Track(output_handle.id())
            };
            let result = match assets.get(source) {
                Some(asset)
                    if asset_server.is_loaded_with_dependencies(source)
                        || !asset_server.is_managed(source) =>
                {
                    asset.create_handle(
                        &mut audio_world.audio_manager,
                        settings,
                        output_destination,
                    )
                }
                _ => {
                    debug!("Asset not ready");
                    continue;
                } // Asset not ready, wait
            };
            let handle = match result {
                Ok(handle) => handle,
                Err(err) => {
                    error!("Cannot create handle: {err}");
                    return;
                }
            };
            debug!("Added sound for {} in {entity:?}", T::type_path());
            commands.entity(entity).insert(AudioHandle(handle));
        }
    }
}