veloren_voxygen/audio/
mod.rs

1//! Handles audio device detection and playback of sound effects and music
2
3pub mod ambience;
4pub mod channel;
5pub mod fader;
6pub mod music;
7pub mod sfx;
8pub mod soundcache;
9
10use anim::vek::Quaternion;
11use channel::{
12    AmbienceChannel, AmbienceChannelTag, LoopPoint, MusicChannel, MusicChannelTag, SfxChannel,
13    UiChannel,
14};
15use kira::{
16    AudioManager, AudioManagerSettings, Decibels, Tween, Value,
17    backend::{self, DefaultBackend},
18    clock::{ClockHandle, ClockSpeed, ClockTime},
19    effect::filter::{FilterBuilder, FilterHandle},
20    listener::ListenerHandle,
21    track::{TrackBuilder, TrackHandle},
22};
23use music::MusicTransitionManifest;
24use sfx::{SfxEvent, SfxTriggerItem};
25use soundcache::load_ogg;
26use std::{collections::VecDeque, time::Duration};
27use tracing::{debug, error, warn};
28
29use common::{
30    assets::{AssetExt, AssetHandle},
31    comp::Ori,
32};
33use vek::*;
34
35use crate::hud::Subtitle;
36
37// #[derive(Clone)]
38// pub struct Listener {
39//     pub pos: Vec3<f32>,
40//     pub ori: Vec3<f32>,
41
42//     ear_left_rpos: Vec3<f32>,
43//     ear_right_rpos: Vec3<f32>,
44// }
45
46// impl Default for Listener {
47//     fn default() -> Self {
48//         Self {
49//             pos: Default::default(),
50//             ori: Default::default(),
51//             ear_left_rpos: Vec3::unit_x(),
52//             ear_right_rpos: -Vec3::unit_x(),
53//         }
54//     }
55// }
56
57pub fn to_decibels(amplitude: f32) -> Decibels {
58    if amplitude <= 0.001 {
59        Decibels::SILENCE
60    } else if amplitude == 1.0 {
61        Decibels::IDENTITY
62    } else {
63        Decibels(amplitude.log10() * 20.0)
64    }
65}
66
67struct Tracks {
68    music: TrackHandle,
69    ui: TrackHandle,
70    sfx: TrackHandle,
71    ambience: TrackHandle,
72}
73
74struct Effects {
75    sfx: FilterHandle,
76    ambience: FilterHandle,
77}
78
79#[derive(Default)]
80struct Channels {
81    music: Vec<MusicChannel>,
82    ambience: Vec<AmbienceChannel>,
83    sfx: Vec<SfxChannel>,
84    ui: Vec<UiChannel>,
85}
86
87impl Channels {
88    /// Gets the music channel matching the given tag, of which there should be
89    /// only one, if any.
90    fn get_music_channel(&mut self, channel_tag: MusicChannelTag) -> Option<&mut MusicChannel> {
91        self.music.iter_mut().find(|c| c.get_tag() == channel_tag)
92    }
93
94    /// Retrive an empty sfx channel from the list
95    fn get_sfx_channel(&mut self) -> Option<&mut SfxChannel> {
96        self.sfx.iter_mut().find(|c| c.is_done())
97    }
98
99    /// Retrive an empty UI channel from the list
100    fn get_ui_channel(&mut self) -> Option<&mut UiChannel> {
101        self.ui.iter_mut().find(|c| c.is_done())
102    }
103
104    /// Retrieves the channel currently having the given tag
105    /// If no channel with the given tag is found, returns None
106    fn get_ambience_channel(
107        &mut self,
108        channel_tag: AmbienceChannelTag,
109    ) -> Option<&mut AmbienceChannel> {
110        self.ambience
111            .iter_mut()
112            .find(|channel| channel.get_tag() == channel_tag)
113    }
114
115    fn count_active(&self) -> ActiveChannels {
116        ActiveChannels {
117            music: self.music.iter().filter(|c| !c.is_done()).count(),
118            ambience: self.ambience.iter().filter(|c| c.is_active()).count(),
119            sfx: self.sfx.iter().filter(|c| !c.is_done()).count(),
120            ui: self.ui.iter().filter(|c| !c.is_done()).count(),
121        }
122    }
123}
124
125#[derive(Default)]
126pub struct ActiveChannels {
127    pub music: usize,
128    pub ambience: usize,
129    pub sfx: usize,
130    pub ui: usize,
131}
132
133#[derive(Default)]
134struct Volumes {
135    sfx: f32,
136    ambience: f32,
137    music: f32,
138    master: f32,
139}
140
141struct ListenerInstance {
142    handle: ListenerHandle,
143    pos: Vec3<f32>,
144    ori: Vec3<f32>,
145}
146
147struct AudioFrontendInner {
148    manager: AudioManager,
149    tracks: Tracks,
150    effects: Effects,
151    channels: Channels,
152    listener: ListenerInstance,
153    clock: ClockHandle,
154}
155
156enum AudioCreationError {
157    Manager(<DefaultBackend as backend::Backend>::Error),
158    Clock(kira::ResourceLimitReached),
159    Track(kira::ResourceLimitReached),
160    Listener(kira::ResourceLimitReached),
161}
162
163impl AudioFrontendInner {
164    fn new(
165        num_sfx_channels: usize,
166        num_ui_channels: usize,
167        buffer_size: usize,
168    ) -> Result<Self, AudioCreationError> {
169        let manager_settings = AudioManagerSettings {
170            internal_buffer_size: buffer_size,
171            ..Default::default()
172        };
173        let mut manager = AudioManager::<DefaultBackend>::new(manager_settings)
174            .map_err(AudioCreationError::Manager)?;
175
176        let mut clock = manager
177            .add_clock(ClockSpeed::TicksPerSecond(1.0))
178            .map_err(AudioCreationError::Clock)?;
179        clock.start();
180
181        let mut sfx_track_builder = TrackBuilder::new();
182        let mut ambience_track_builder = TrackBuilder::new();
183
184        let effects = Effects {
185            sfx: sfx_track_builder.add_effect(FilterBuilder::new().cutoff(Value::Fixed(20000.0))),
186            ambience: ambience_track_builder
187                .add_effect(FilterBuilder::new().cutoff(Value::Fixed(20000.0))),
188        };
189
190        let listener_handle = manager
191            .add_listener(Vec3::zero(), Quaternion::identity())
192            .map_err(AudioCreationError::Listener)?;
193
194        let listener = ListenerInstance {
195            handle: listener_handle,
196            pos: Vec3::zero(),
197            ori: Vec3::unit_x(),
198        };
199
200        let mut tracks = Tracks {
201            music: manager
202                .add_sub_track(TrackBuilder::new())
203                .map_err(AudioCreationError::Track)?,
204            ui: manager
205                .add_sub_track(TrackBuilder::new())
206                .map_err(AudioCreationError::Track)?,
207            sfx: manager
208                .add_sub_track(sfx_track_builder)
209                .map_err(AudioCreationError::Track)?,
210            ambience: manager
211                .add_sub_track(ambience_track_builder)
212                .map_err(AudioCreationError::Track)?,
213        };
214
215        let mut channels = Channels::default();
216
217        for _ in 0..num_sfx_channels {
218            if let Ok(channel) = SfxChannel::new(&mut tracks.sfx, listener.handle.id()) {
219                channels.sfx.push(channel);
220            } else {
221                warn!("Cannot create sfx channel")
222            }
223        }
224
225        for _ in 0..num_ui_channels {
226            if let Ok(channel) = UiChannel::new(&mut tracks.ui) {
227                channels.ui.push(channel);
228            } else {
229                warn!("Cannot create ui channel")
230            }
231        }
232
233        Ok(Self {
234            manager,
235            tracks,
236            effects,
237            channels,
238            listener,
239            clock,
240        })
241    }
242
243    fn manager(&mut self) -> &mut AudioManager { &mut self.manager }
244
245    fn clock(&self) -> &ClockHandle { &self.clock }
246
247    fn create_music_channel(&mut self, channel_tag: MusicChannelTag) {
248        let channel = MusicChannel::new(&mut self.tracks.music);
249        match channel {
250            Ok(mut next_music_channel) => {
251                next_music_channel.set_volume(1.0);
252                next_music_channel.set_tag(channel_tag);
253                self.channels.music.push(next_music_channel);
254            },
255            Err(e) => error!(
256                ?e,
257                "Failed to crate new music channel, music may fail playing"
258            ),
259        }
260    }
261
262    /// Adds a new ambience channel of the given tag at zero volume
263    fn new_ambience_channel(&mut self, channel_tag: AmbienceChannelTag) {
264        let channel = AmbienceChannel::new(channel_tag, 0.0, &mut self.tracks.ambience, true);
265        match channel {
266            Ok(ambience_channel) => self.channels.ambience.push(ambience_channel),
267            Err(e) => error!(
268                ?e,
269                "Failed to crate new ambience channel, sounds may fail playing"
270            ),
271        }
272    }
273}
274
275/// Holds information about the system audio devices and internal channels used
276/// for sfx and music playback. An instance of `AudioFrontend` is used by
277/// Voxygen's [`GlobalState`](../struct.GlobalState.html#structfield.audio) to
278/// provide access to devices and playback control in-game
279///
280/// TODO: Use a listener struct (like the one commented out above) instead of
281/// keeping all listener data in the AudioFrontend struct. Will be helpful when
282/// we do more with spatial audio.
283pub struct AudioFrontend {
284    inner: Option<AudioFrontendInner>,
285
286    pub subtitles_enabled: bool,
287    pub subtitles: VecDeque<Subtitle>,
288
289    volumes: Volumes,
290    music_spacing: f32,
291    pub combat_music_enabled: bool,
292
293    mtm: AssetHandle<MusicTransitionManifest>,
294}
295
296impl AudioFrontend {
297    pub fn new(
298        num_sfx_channels: usize,
299        num_ui_channels: usize,
300        subtitles: bool,
301        combat_music_enabled: bool,
302        buffer_size: usize,
303    ) -> Self {
304        let inner = AudioFrontendInner::new(num_sfx_channels, num_ui_channels, buffer_size)
305            .inspect_err(|err| match err {
306                AudioCreationError::Manager(e) => {
307                    #[cfg(unix)]
308                    error!(
309                        ?e,
310                        "failed to construct audio frontend manager. Is `pulseaudio-alsa` \
311                         installed?"
312                    );
313                    #[cfg(not(unix))]
314                    error!(?e, "failed to construct audio frontend manager.");
315                },
316                AudioCreationError::Clock(e) => {
317                    error!(?e, "Failed to construct audio frontend clock.")
318                },
319                AudioCreationError::Track(e) => {
320                    error!(?e, "Failed to construct audio frontend track.")
321                },
322                AudioCreationError::Listener(e) => {
323                    error!(?e, "Failed to construct audio frontend listener.")
324                },
325            })
326            .ok();
327
328        Self {
329            inner,
330            volumes: Volumes::default(),
331            music_spacing: 1.0,
332            mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
333            subtitles: VecDeque::new(),
334            subtitles_enabled: subtitles,
335            combat_music_enabled,
336        }
337    }
338
339    /// Construct in `no-audio` mode for debugging
340    pub fn no_audio() -> Self {
341        Self {
342            inner: None,
343            music_spacing: 1.0,
344            volumes: Volumes::default(),
345            mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
346            subtitles: VecDeque::new(),
347            subtitles_enabled: false,
348            combat_music_enabled: false,
349        }
350    }
351
352    /// Drop any unused music channels
353    pub fn maintain(&mut self) {
354        if let Some(inner) = &mut self.inner {
355            inner.channels.music.retain(|c| !c.is_done());
356            inner.channels.ambience.retain(|c| !c.is_stopped());
357        }
358    }
359
360    pub fn get_clock(&self) -> Option<&ClockHandle> { self.inner.as_ref().map(|i| i.clock()) }
361
362    pub fn get_clock_time(&self) -> Option<ClockTime> { self.get_clock().map(|clock| clock.time()) }
363
364    /// Returns [music channels, ambience channels, sfx channels, ui channels]
365    pub fn get_num_active_channels(&self) -> ActiveChannels {
366        self.inner
367            .as_ref()
368            .map(|i| i.channels.count_active())
369            .unwrap_or_default()
370    }
371
372    /// Play a music file with the given tag. Pass in the length of the track in
373    /// seconds.
374    fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag, length: f32) {
375        if self.music_enabled()
376            && let Some(inner) = &mut self.inner
377        {
378            let mtm = self.mtm.read();
379
380            if let Some(current_channel) = inner.channels.music.iter_mut().find(|c| !c.is_done()) {
381                let (fade_out, _fade_in) = mtm
382                    .fade_timings
383                    .get(&(current_channel.get_tag(), channel_tag))
384                    .unwrap_or(&(1.0, 1.0));
385                current_channel.fade_out(*fade_out, None);
386            }
387
388            let now = inner.clock().time();
389
390            let channel = match inner.channels.get_music_channel(channel_tag) {
391                Some(c) => c,
392                None => {
393                    inner.create_music_channel(channel_tag);
394                    inner
395                        .channels
396                        .music
397                        .last_mut()
398                        .expect("We just created this")
399                },
400            };
401
402            let (fade_out, fade_in) = mtm
403                .fade_timings
404                .get(&(channel.get_tag(), channel_tag))
405                .unwrap_or(&(1.0, 0.1));
406            let source = load_ogg(sound, true);
407            channel.stop(Some(*fade_out), None);
408            channel.set_length(length);
409            channel.set_tag(channel_tag);
410            channel.set_loop_data(false, LoopPoint::Start, LoopPoint::End);
411            channel.play(source, now, Some(*fade_in), Some(*fade_out));
412        }
413    }
414
415    /// Turn on or off looping
416    pub fn set_loop(&mut self, channel_tag: MusicChannelTag, sound_loops: bool) {
417        if let Some(inner) = self.inner.as_mut() {
418            let channel = inner.channels.get_music_channel(channel_tag);
419            if let Some(channel) = channel {
420                let loop_data = channel.get_loop_data();
421                channel.set_loop_data(sound_loops, loop_data.1, loop_data.2);
422            }
423        }
424    }
425
426    /// Loops music from start point to end point in seconds
427    pub fn set_loop_points(&mut self, channel_tag: MusicChannelTag, start: f32, end: f32) {
428        if let Some(inner) = self.inner.as_mut() {
429            let channel = inner.channels.get_music_channel(channel_tag);
430            if let Some(channel) = channel {
431                channel.set_loop_data(
432                    true,
433                    LoopPoint::Point(start as f64),
434                    LoopPoint::Point(end as f64),
435                );
436            }
437        }
438    }
439
440    /// Set the cutoff of the filter affecting all ambience
441    pub fn set_ambience_master_filter(&mut self, frequency: u32) {
442        if let Some(inner) = self.inner.as_mut() {
443            inner
444                .effects
445                .ambience
446                .set_cutoff(Value::Fixed(frequency as f64), Tween::default());
447        }
448    }
449
450    /// Find sound based on given trigger_item.
451    /// Randomizes if multiple sounds are found.
452    /// Errors if no sounds are found.
453    /// Returns (file, threshold, subtitle)
454    pub fn get_sfx_file<'a>(
455        trigger_item: Option<(&'a SfxEvent, &'a SfxTriggerItem)>,
456    ) -> Option<(&'a str, f32, Option<&'a str>)> {
457        trigger_item.map(|(event, item)| {
458            let file = match item.files.len() {
459                0 => {
460                    debug!("Sfx event {:?} is missing audio file.", event);
461                    "voxygen.audio.sfx.placeholder"
462                },
463                1 => item
464                    .files
465                    .last()
466                    .expect("Failed to determine sound file for this trigger item."),
467                _ => {
468                    // If more than one file is listed, choose one at random
469                    let rand_step = rand::random::<usize>() % item.files.len();
470                    &item.files[rand_step]
471                },
472            };
473
474            // NOTE: Threshold here is meant to give subtitles some idea of the duration of
475            // the audio, it doesn't have to be perfect but in the future, if possible we
476            // might want to switch it out for the actual duration.
477            (file, item.threshold, item.subtitle.as_deref())
478        })
479    }
480
481    /// Set the cutoff of the filter affecting all spatial sfx
482    pub fn set_sfx_master_filter(&mut self, frequency: u32) {
483        if let Some(inner) = self.inner.as_mut() {
484            inner
485                .effects
486                .sfx
487                .set_cutoff(Value::Fixed(frequency as f64), Tween::default());
488        }
489    }
490
491    /// Play an sfx file given the position and SfxEvent at the given volume
492    /// (default 1.0)
493    pub fn emit_sfx(
494        &mut self,
495        trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
496        position: Vec3<f32>,
497        volume: Option<f32>,
498    ) {
499        if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
500            self.emit_subtitle(subtitle, Some(position), dur);
501            // Play sound in empty channel at given position
502            if self.sfx_enabled()
503                && let Some(inner) = self.inner.as_mut()
504                && let Some(channel) = inner.channels.get_sfx_channel()
505            {
506                let sound = load_ogg(sfx_file, false);
507                channel.update(position);
508
509                let source = sound.volume(to_decibels(volume.unwrap_or(1.0) * 5.0));
510                channel.play(source);
511            }
512        } else {
513            warn!(
514                "Missing sfx trigger config for sfx event at position: {:?}",
515                position
516            );
517        }
518    }
519
520    /// Plays a sfx non-spatially at the given volume (default 1.0); doesn't
521    /// need a position
522    pub fn emit_ui_sfx(
523        &mut self,
524        trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
525        volume: Option<f32>,
526    ) {
527        if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
528            self.emit_subtitle(subtitle, None, dur);
529
530            // Play sound in empty channel
531            if self.sfx_enabled()
532                && let Some(inner) = self.inner.as_mut()
533                && let Some(channel) = inner.channels.get_ui_channel()
534            {
535                let sound = load_ogg(sfx_file, false).volume(to_decibels(volume.unwrap_or(1.0)));
536                channel.play(sound);
537            }
538        } else {
539            warn!("Missing sfx trigger config for ui sfx event.",);
540        }
541    }
542
543    /// Push a subtitle to the subtitle queue
544    pub fn emit_subtitle(
545        &mut self,
546        subtitle: Option<&str>,
547        position: Option<Vec3<f32>>,
548        duration: f32,
549    ) {
550        if self.subtitles_enabled {
551            if let Some(subtitle) = subtitle {
552                self.subtitles.push_back(Subtitle {
553                    localization: subtitle.to_string(),
554                    position,
555                    show_for: duration as f64,
556                });
557                if self.subtitles.len() > 10 {
558                    self.subtitles.pop_front();
559                }
560            }
561        }
562    }
563
564    /// Plays an ambience sound that loops in the channel with a given tag
565    pub fn play_ambience_looping(&mut self, channel_tag: AmbienceChannelTag, sound: &str) {
566        if self.ambience_enabled()
567            && let Some(inner) = self.inner.as_mut()
568            && let Some(channel) = inner.channels.get_ambience_channel(channel_tag)
569        {
570            let source = load_ogg(sound, true).loop_region(0.0..);
571            channel.set_looping(true);
572            channel.play(source, Some(1.0), None);
573        }
574    }
575
576    /// Plays an ambience sound once at the given volume after the given delay.
577    /// Make sure it uses a channel tag that does not change the volume of its
578    /// channel. Currently, ambience oneshots use the Sfx file system
579    pub fn play_ambience_oneshot(
580        &mut self,
581        channel_tag: AmbienceChannelTag,
582        trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
583        volume: Option<f32>,
584        delay: Option<f32>,
585    ) {
586        if self.ambience_enabled()
587            && trigger_item.is_some()
588            && let Some(inner) = self.inner.as_mut()
589            && let Some(channel) = inner.channels.get_ambience_channel(channel_tag)
590        {
591            let sound = AudioFrontend::get_sfx_file(trigger_item)
592                .unwrap_or(("", 0.0, Some("")))
593                .0;
594            let source = load_ogg(sound, false)
595                .loop_region(None)
596                .volume(to_decibels(volume.unwrap_or(1.0)));
597            channel.set_looping(false);
598            channel.fade_to(1.0, 0.0);
599            channel.play(source, None, delay);
600        }
601    }
602
603    pub fn set_listener_pos(&mut self, pos: Vec3<f32>, ori: Vec3<f32>) {
604        if let Some(inner) = self.inner.as_mut() {
605            let tween = Tween {
606                duration: Duration::from_secs_f32(0.01),
607                ..Default::default()
608            };
609
610            inner.listener.pos = pos;
611            inner.listener.ori = ori;
612
613            inner.listener.handle.set_position(pos, tween);
614
615            let ori_quat = Ori::from(ori).to_quat();
616            inner
617                .listener
618                .handle
619                .set_orientation(ori_quat.normalized(), tween);
620        }
621    }
622
623    pub fn get_listener(&mut self) -> Option<&mut ListenerHandle> {
624        self.inner.as_mut().map(|i| &mut i.listener.handle)
625    }
626
627    pub fn get_listener_pos(&self) -> Vec3<f32> {
628        self.inner
629            .as_ref()
630            .map(|i| i.listener.pos)
631            .unwrap_or_default()
632    }
633
634    pub fn get_listener_ori(&self) -> Vec3<f32> {
635        self.inner
636            .as_ref()
637            .map(|i| i.listener.ori)
638            .unwrap_or_else(Vec3::unit_x)
639    }
640
641    /// Switches the playing music to the title music, which is pinned to a
642    /// specific sound file (veloren_title_tune.ogg)
643    pub fn play_title_music(&mut self) {
644        if self.music_enabled() {
645            self.play_music(
646                "voxygen.audio.soundtrack.veloren_title_tune",
647                MusicChannelTag::TitleMusic,
648                43.0,
649            );
650            self.set_loop(MusicChannelTag::TitleMusic, true);
651        }
652    }
653
654    /// Retrieves the current setting for music volume
655    pub fn get_music_volume(&self) -> f32 { self.volumes.music }
656
657    /// Retrieves the current setting for ambience volume
658    pub fn get_ambience_volume(&self) -> f32 { self.volumes.ambience }
659
660    /// Retrieves the current setting for sfx volume
661    pub fn get_sfx_volume(&self) -> f32 { self.volumes.sfx }
662
663    /// Returns false if volume is 0 or the mute is on
664    pub fn music_enabled(&self) -> bool { self.get_music_volume() > 0.0 }
665
666    /// Returns false if volume is 0 or the mute is on
667    pub fn ambience_enabled(&self) -> bool { self.get_ambience_volume() > 0.0 }
668
669    /// Returns false if volume is 0 or the mute is on
670    pub fn sfx_enabled(&self) -> bool { self.get_sfx_volume() > 0.0 }
671
672    pub fn set_music_volume(&mut self, music_volume: f32) {
673        self.volumes.music = music_volume;
674
675        if let Some(inner) = self.inner.as_mut() {
676            inner
677                .tracks
678                .music
679                .set_volume(to_decibels(music_volume), Tween::default())
680        }
681    }
682
683    pub fn set_ambience_volume(&mut self, ambience_volume: f32) {
684        self.volumes.ambience = ambience_volume;
685
686        if let Some(inner) = self.inner.as_mut() {
687            inner
688                .tracks
689                .ambience
690                .set_volume(to_decibels(ambience_volume), Tween::default())
691        }
692    }
693
694    /// Sets the volume for both spatial sfx and UI (might separate these
695    /// controls later)
696    pub fn set_sfx_volume(&mut self, sfx_volume: f32) {
697        self.volumes.sfx = sfx_volume;
698
699        if let Some(inner) = self.inner.as_mut() {
700            inner
701                .tracks
702                .sfx
703                .set_volume(to_decibels(sfx_volume), Tween::default())
704        }
705    }
706
707    pub fn set_music_spacing(&mut self, multiplier: f32) { self.music_spacing = multiplier }
708
709    pub fn set_subtitles(&mut self, enabled: bool) { self.subtitles_enabled = enabled }
710
711    /// Updates volume of the master track
712    pub fn set_master_volume(&mut self, master_volume: f32) {
713        self.volumes.master = master_volume;
714
715        if let Some(inner) = self.inner.as_mut() {
716            inner
717                .manager()
718                .main_track()
719                .set_volume(to_decibels(master_volume), Tween::default());
720        }
721    }
722
723    pub fn stop_all_ambience(&mut self) {
724        if let Some(inner) = self.inner.as_mut() {
725            for channel in &mut inner.channels.ambience {
726                channel.stop(None, None);
727            }
728        }
729    }
730
731    pub fn stop_all_music(&mut self) {
732        if let Some(inner) = self.inner.as_mut() {
733            for channel in &mut inner.channels.music {
734                channel.stop(None, None);
735            }
736        }
737    }
738
739    pub fn stop_all_sfx(&mut self) {
740        if let Some(inner) = self.inner.as_mut() {
741            for channel in &mut inner.channels.sfx {
742                channel.stop();
743            }
744            for channel in &mut inner.channels.ui {
745                channel.stop();
746            }
747        }
748    }
749
750    pub fn get_num_music_channels(&self) -> usize {
751        self.inner
752            .as_ref()
753            .map(|i| i.channels.music.len())
754            .unwrap_or(0)
755    }
756
757    pub fn get_num_ambience_channels(&self) -> usize {
758        self.inner
759            .as_ref()
760            .map(|i| i.channels.ambience.len())
761            .unwrap_or(0)
762    }
763}