1pub 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 cpal::{
16 Device, SampleRate, StreamConfig, SupportedStreamConfigRange,
17 traits::{DeviceTrait, HostTrait},
18};
19use kira::{
20 AudioManager, AudioManagerSettings, Decibels, Tween, Value,
21 backend::{
22 self,
23 cpal::{CpalBackend, CpalBackendSettings},
24 },
25 clock::{ClockHandle, ClockSpeed, ClockTime},
26 effect::filter::{FilterBuilder, FilterHandle},
27 listener::ListenerHandle,
28 track::{TrackBuilder, TrackHandle},
29};
30use music::MusicTransitionManifest;
31use sfx::{SfxEvent, SfxTriggerItem};
32use soundcache::load_ogg;
33use std::{collections::VecDeque, time::Duration};
34use strum::Display;
35use tracing::{debug, error, info, warn};
36
37use common::{
38 assets::{AssetExt, AssetHandle},
39 comp::Ori,
40};
41use vek::*;
42
43use crate::hud::Subtitle;
44
45pub fn to_decibels(amplitude: f32) -> Decibels {
66 if amplitude <= 0.001 {
67 Decibels::SILENCE
68 } else if amplitude == 1.0 {
69 Decibels::IDENTITY
70 } else {
71 Decibels(amplitude.log10() * 20.0)
72 }
73}
74
75struct Tracks {
76 music: TrackHandle,
77 ui: TrackHandle,
78 sfx: TrackHandle,
79 ambience: TrackHandle,
80}
81
82#[derive(Clone, Copy, Debug, Display)]
83pub enum SfxChannelSettings {
84 Low,
85 Medium,
86 High,
87}
88
89impl SfxChannelSettings {
90 pub fn from_str_slice(str: &str) -> Self {
91 match str {
92 "Low" => SfxChannelSettings::Low,
93 "Medium" => SfxChannelSettings::Medium,
94 "High" => SfxChannelSettings::High,
95 _ => SfxChannelSettings::Medium,
96 }
97 }
98
99 pub fn to_usize(&self) -> usize {
100 match self {
101 SfxChannelSettings::Low => 16,
102 SfxChannelSettings::Medium => 32,
103 SfxChannelSettings::High => 64,
104 }
105 }
106}
107
108struct Effects {
109 sfx: FilterHandle,
110 ambience: FilterHandle,
111}
112
113#[derive(Default)]
114struct Channels {
115 music: Vec<MusicChannel>,
116 ambience: Vec<AmbienceChannel>,
117 sfx: Vec<SfxChannel>,
118 ui: Vec<UiChannel>,
119}
120
121impl Channels {
122 fn get_music_channel(&mut self, channel_tag: MusicChannelTag) -> Option<&mut MusicChannel> {
125 self.music.iter_mut().find(|c| c.get_tag() == channel_tag)
126 }
127
128 fn get_sfx_channel(&mut self) -> Option<&mut SfxChannel> {
130 self.sfx.iter_mut().find(|c| c.is_done())
131 }
132
133 fn get_ui_channel(&mut self) -> Option<&mut UiChannel> {
135 self.ui.iter_mut().find(|c| c.is_done())
136 }
137
138 fn get_ambience_channel(
141 &mut self,
142 channel_tag: AmbienceChannelTag,
143 ) -> Option<&mut AmbienceChannel> {
144 self.ambience
145 .iter_mut()
146 .find(|channel| channel.get_tag() == channel_tag)
147 }
148
149 fn count_active(&self) -> ActiveChannels {
150 ActiveChannels {
151 music: self.music.iter().filter(|c| !c.is_done()).count(),
152 ambience: self.ambience.iter().filter(|c| c.is_active()).count(),
153 sfx: self.sfx.iter().filter(|c| !c.is_done()).count(),
154 ui: self.ui.iter().filter(|c| !c.is_done()).count(),
155 }
156 }
157}
158
159#[derive(Default)]
160pub struct ActiveChannels {
161 pub music: usize,
162 pub ambience: usize,
163 pub sfx: usize,
164 pub ui: usize,
165}
166
167#[derive(Default)]
168struct Volumes {
169 sfx: f32,
170 ambience: f32,
171 music: f32,
172 master: f32,
173}
174
175struct ListenerInstance {
176 handle: ListenerHandle,
177 pos: Vec3<f32>,
178 ori: Vec3<f32>,
179}
180
181struct AudioFrontendInner {
182 manager: AudioManager,
183 tracks: Tracks,
184 effects: Effects,
185 channels: Channels,
186 listener: ListenerInstance,
187 clock: ClockHandle,
188}
189
190enum AudioCreationError {
191 Manager(<CpalBackend as backend::Backend>::Error),
192 Clock(kira::ResourceLimitReached),
193 Track(kira::ResourceLimitReached),
194 Listener(kira::ResourceLimitReached),
195}
196
197impl AudioFrontendInner {
198 fn new(
199 num_sfx_channels: usize,
200 num_ui_channels: usize,
201 buffer_size: usize,
202 device: Option<Device>,
203 config: Option<StreamConfig>,
204 ) -> Result<Self, AudioCreationError> {
205 let backend_settings = CpalBackendSettings { device, config };
206 let manager_settings = AudioManagerSettings {
207 internal_buffer_size: buffer_size,
208 backend_settings,
209 ..Default::default()
210 };
211 let mut manager = AudioManager::<CpalBackend>::new(manager_settings)
212 .map_err(AudioCreationError::Manager)?;
213
214 let mut clock = manager
215 .add_clock(ClockSpeed::TicksPerSecond(1.0))
216 .map_err(AudioCreationError::Clock)?;
217 clock.start();
218
219 let mut sfx_track_builder = TrackBuilder::new();
220 let mut ambience_track_builder = TrackBuilder::new();
221
222 let effects = Effects {
223 sfx: sfx_track_builder.add_effect(FilterBuilder::new().cutoff(Value::Fixed(20000.0))),
224 ambience: ambience_track_builder
225 .add_effect(FilterBuilder::new().cutoff(Value::Fixed(20000.0))),
226 };
227
228 let listener_handle = manager
229 .add_listener(Vec3::zero(), Quaternion::identity())
230 .map_err(AudioCreationError::Listener)?;
231
232 let listener = ListenerInstance {
233 handle: listener_handle,
234 pos: Vec3::zero(),
235 ori: Vec3::unit_x(),
236 };
237
238 let mut tracks = Tracks {
239 music: manager
240 .add_sub_track(TrackBuilder::new())
241 .map_err(AudioCreationError::Track)?,
242 ui: manager
243 .add_sub_track(TrackBuilder::new())
244 .map_err(AudioCreationError::Track)?,
245 sfx: manager
246 .add_sub_track(sfx_track_builder)
247 .map_err(AudioCreationError::Track)?,
248 ambience: manager
249 .add_sub_track(ambience_track_builder)
250 .map_err(AudioCreationError::Track)?,
251 };
252
253 let mut channels = Channels::default();
254
255 for _ in 0..num_sfx_channels {
256 if let Ok(channel) = SfxChannel::new(&mut tracks.sfx, listener.handle.id()) {
257 channels.sfx.push(channel);
258 } else {
259 warn!("Cannot create sfx channel")
260 }
261 }
262
263 for _ in 0..num_ui_channels {
264 if let Ok(channel) = UiChannel::new(&mut tracks.ui) {
265 channels.ui.push(channel);
266 } else {
267 warn!("Cannot create ui channel")
268 }
269 }
270
271 Ok(Self {
272 manager,
273 tracks,
274 effects,
275 channels,
276 listener,
277 clock,
278 })
279 }
280
281 fn manager(&mut self) -> &mut AudioManager { &mut self.manager }
282
283 fn clock(&self) -> &ClockHandle { &self.clock }
284
285 fn create_music_channel(&mut self, channel_tag: MusicChannelTag) {
286 let channel = MusicChannel::new(&mut self.tracks.music);
287 match channel {
288 Ok(mut next_music_channel) => {
289 next_music_channel.set_volume(1.0);
290 next_music_channel.set_tag(channel_tag);
291 self.channels.music.push(next_music_channel);
292 },
293 Err(e) => error!(
294 ?e,
295 "Failed to crate new music channel, music may fail playing"
296 ),
297 }
298 }
299
300 fn new_ambience_channel(&mut self, channel_tag: AmbienceChannelTag) {
302 let channel = AmbienceChannel::new(channel_tag, 0.0, &mut self.tracks.ambience, true);
303 match channel {
304 Ok(ambience_channel) => self.channels.ambience.push(ambience_channel),
305 Err(e) => error!(
306 ?e,
307 "Failed to crate new ambience channel, sounds may fail playing"
308 ),
309 }
310 }
311}
312
313pub struct AudioFrontend {
322 inner: Option<AudioFrontendInner>,
323
324 pub subtitles_enabled: bool,
325 pub subtitles: VecDeque<Subtitle>,
326
327 volumes: Volumes,
328 music_spacing: f32,
329 pub combat_music_enabled: bool,
330
331 mtm: AssetHandle<MusicTransitionManifest>,
332}
333
334impl AudioFrontend {
335 pub fn new(
336 num_sfx_channels: usize,
337 num_ui_channels: usize,
338 subtitles: bool,
339 combat_music_enabled: bool,
340 buffer_size: usize,
341 ) -> Self {
342 let mut device = cpal::default_host().default_output_device();
343 let mut supported_config = None;
344 let mut samplerate = 44100;
345 if let Some(device) = device.as_mut() {
346 if let Ok(default_output_config) = device.default_output_config() {
347 info!(
348 "Current default samplerate: {:?}",
349 default_output_config.sample_rate().0
350 );
351 samplerate = default_output_config.sample_rate().0;
352 if samplerate > 48000 {
353 warn!(
354 "Current default samplerate is higher than 48000; attempting to lower \
355 samplerate"
356 );
357 let supported_configs = device.supported_output_configs();
358 if let Ok(supported_configs) = supported_configs {
359 let best_config = supported_configs.max_by(|x, y| {
360 SupportedStreamConfigRange::cmp_default_heuristics(x, y)
361 });
362 if let Some(best_config) = best_config {
363 warn!("Attempting to change samplerate to 48khz");
364 supported_config = best_config.try_with_sample_rate(SampleRate(48000));
365 if supported_config.is_none() {
366 warn!("Attempting to change samplerate to 44.1khz");
367 supported_config =
368 best_config.try_with_sample_rate(SampleRate(44100));
369 }
370 if supported_config.is_none() {
371 warn!("Could not change samplerate, using default")
372 }
373 }
374 }
375 }
376 }
377 }
378 let mut config = None;
379 if let Some(supported_config) = supported_config {
380 info!(
381 "Samplerate is {:?}",
382 supported_config.config().sample_rate.0
383 );
384 config = Some(supported_config.config())
385 } else {
386 info!("Samplerate is {:?}", samplerate)
387 }
388 let inner = AudioFrontendInner::new(
389 num_sfx_channels,
390 num_ui_channels,
391 buffer_size,
392 device,
393 config,
394 )
395 .inspect_err(|err| match err {
396 AudioCreationError::Manager(e) => {
397 #[cfg(unix)]
398 error!(
399 ?e,
400 "failed to construct audio frontend manager. Is `pulseaudio-alsa` installed?"
401 );
402 #[cfg(not(unix))]
403 error!(?e, "failed to construct audio frontend manager.");
404 },
405 AudioCreationError::Clock(e) => {
406 error!(?e, "Failed to construct audio frontend clock.")
407 },
408 AudioCreationError::Track(e) => {
409 error!(?e, "Failed to construct audio frontend track.")
410 },
411 AudioCreationError::Listener(e) => {
412 error!(?e, "Failed to construct audio frontend listener.")
413 },
414 })
415 .ok();
416
417 if let Some(inner) = inner {
418 Self {
419 inner: Some(inner),
420 volumes: Volumes::default(),
421 music_spacing: 1.0,
422 mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
423 subtitles: VecDeque::new(),
424 subtitles_enabled: subtitles,
425 combat_music_enabled,
426 }
427 } else {
428 Self {
429 inner: None,
430 volumes: Volumes::default(),
431 music_spacing: 1.0,
432 mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
433 subtitles: VecDeque::new(),
434 subtitles_enabled: subtitles,
435 combat_music_enabled,
436 }
437 }
438 }
439
440 pub fn no_audio() -> Self {
442 Self {
443 inner: None,
444 music_spacing: 1.0,
445 volumes: Volumes::default(),
446 mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
447 subtitles: VecDeque::new(),
448 subtitles_enabled: false,
449 combat_music_enabled: false,
450 }
451 }
452
453 pub fn maintain(&mut self) {
455 if let Some(inner) = &mut self.inner {
456 inner.channels.music.retain(|c| !c.is_done());
457 inner.channels.ambience.retain(|c| !c.is_stopped());
458 }
459 }
460
461 pub fn get_clock(&self) -> Option<&ClockHandle> { self.inner.as_ref().map(|i| i.clock()) }
462
463 pub fn get_clock_time(&self) -> Option<ClockTime> { self.get_clock().map(|clock| clock.time()) }
464
465 pub fn get_num_active_channels(&self) -> ActiveChannels {
467 self.inner
468 .as_ref()
469 .map(|i| i.channels.count_active())
470 .unwrap_or_default()
471 }
472
473 pub fn get_cpu_usage(&mut self) -> f32 {
474 if let Some(inner) = self.inner.as_mut() {
475 inner.manager.backend_mut().pop_cpu_usage().unwrap_or(0.0)
476 } else {
477 0.0
478 }
479 }
480
481 fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag, length: f32) {
484 if self.music_enabled()
485 && let Some(inner) = &mut self.inner
486 {
487 let mtm = self.mtm.read();
488
489 if let Some(current_channel) = inner.channels.music.iter_mut().find(|c| !c.is_done()) {
490 let (fade_out, _fade_in) = mtm
491 .fade_timings
492 .get(&(current_channel.get_tag(), channel_tag))
493 .unwrap_or(&(1.0, 1.0));
494 current_channel.fade_out(*fade_out, None);
495 }
496
497 let now = inner.clock().time();
498
499 let channel = match inner.channels.get_music_channel(channel_tag) {
500 Some(c) => c,
501 None => {
502 inner.create_music_channel(channel_tag);
503 inner
504 .channels
505 .music
506 .last_mut()
507 .expect("We just created this")
508 },
509 };
510
511 let (fade_out, fade_in) = mtm
512 .fade_timings
513 .get(&(channel.get_tag(), channel_tag))
514 .unwrap_or(&(1.0, 0.1));
515 let source = load_ogg(sound, true);
516 channel.stop(Some(*fade_out), None);
517 channel.set_length(length);
518 channel.set_tag(channel_tag);
519 channel.set_loop_data(false, LoopPoint::Start, LoopPoint::End);
520 channel.play(source, now, Some(*fade_in), Some(*fade_out));
521 }
522 }
523
524 pub fn set_loop(&mut self, channel_tag: MusicChannelTag, sound_loops: bool) {
526 if let Some(inner) = self.inner.as_mut() {
527 let channel = inner.channels.get_music_channel(channel_tag);
528 if let Some(channel) = channel {
529 let loop_data = channel.get_loop_data();
530 channel.set_loop_data(sound_loops, loop_data.1, loop_data.2);
531 }
532 }
533 }
534
535 pub fn set_loop_points(&mut self, channel_tag: MusicChannelTag, start: f32, end: f32) {
537 if let Some(inner) = self.inner.as_mut() {
538 let channel = inner.channels.get_music_channel(channel_tag);
539 if let Some(channel) = channel {
540 channel.set_loop_data(
541 true,
542 LoopPoint::Point(start as f64),
543 LoopPoint::Point(end as f64),
544 );
545 }
546 }
547 }
548
549 pub fn set_ambience_master_filter(&mut self, frequency: u32) {
551 if let Some(inner) = self.inner.as_mut() {
552 inner
553 .effects
554 .ambience
555 .set_cutoff(Value::Fixed(frequency as f64), Tween::default());
556 }
557 }
558
559 pub fn get_sfx_file<'a>(
564 trigger_item: Option<(&'a SfxEvent, &'a SfxTriggerItem)>,
565 ) -> Option<(&'a str, f32, Option<&'a str>)> {
566 trigger_item.map(|(event, item)| {
567 let file = match item.files.len() {
568 0 => {
569 debug!("Sfx event {:?} is missing audio file.", event);
570 "voxygen.audio.sfx.placeholder"
571 },
572 1 => item
573 .files
574 .last()
575 .expect("Failed to determine sound file for this trigger item."),
576 _ => {
577 let rand_step = rand::random::<usize>() % item.files.len();
579 &item.files[rand_step]
580 },
581 };
582
583 (file, item.threshold, item.subtitle.as_deref())
587 })
588 }
589
590 pub fn set_sfx_master_filter(&mut self, frequency: u32) {
592 if let Some(inner) = self.inner.as_mut() {
593 inner
594 .effects
595 .sfx
596 .set_cutoff(Value::Fixed(frequency as f64), Tween::default());
597 }
598 }
599
600 pub fn emit_sfx(
603 &mut self,
604 trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
605 position: Vec3<f32>,
606 volume: Option<f32>,
607 ) {
608 if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
609 self.emit_subtitle(subtitle, Some(position), dur);
610 if self.sfx_enabled()
612 && let Some(inner) = self.inner.as_mut()
613 && let Some(channel) = inner.channels.get_sfx_channel()
614 {
615 let sound = load_ogg(sfx_file, false);
616 channel.update(position);
617
618 let source = sound.volume(to_decibels(volume.unwrap_or(1.0) * 5.0));
619 channel.play(source);
620 }
621 } else {
622 warn!(
623 "Missing sfx trigger config for sfx event at position: {:?}\n{:#?}",
624 position,
625 backtrace::Backtrace::new(),
626 );
627 }
628 }
629
630 pub fn emit_ui_sfx(
633 &mut self,
634 trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
635 volume: Option<f32>,
636 ) {
637 if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
638 self.emit_subtitle(subtitle, None, dur);
639
640 if self.sfx_enabled()
642 && let Some(inner) = self.inner.as_mut()
643 && let Some(channel) = inner.channels.get_ui_channel()
644 {
645 let sound = load_ogg(sfx_file, false).volume(to_decibels(volume.unwrap_or(1.0)));
646 channel.play(sound);
647 }
648 } else {
649 warn!("Missing sfx trigger config for ui sfx event.",);
650 }
651 }
652
653 pub fn emit_subtitle(
655 &mut self,
656 subtitle: Option<&str>,
657 position: Option<Vec3<f32>>,
658 duration: f32,
659 ) {
660 if self.subtitles_enabled {
661 if let Some(subtitle) = subtitle {
662 self.subtitles.push_back(Subtitle {
663 localization: subtitle.to_string(),
664 position,
665 show_for: duration as f64,
666 });
667 if self.subtitles.len() > 10 {
668 self.subtitles.pop_front();
669 }
670 }
671 }
672 }
673
674 pub fn play_ambience_looping(&mut self, channel_tag: AmbienceChannelTag, sound: &str) {
676 if self.ambience_enabled()
677 && let Some(inner) = self.inner.as_mut()
678 && let Some(channel) = inner.channels.get_ambience_channel(channel_tag)
679 {
680 let source = load_ogg(sound, true).loop_region(0.0..);
681 channel.set_looping(true);
682 channel.play(source, Some(1.0), None);
683 }
684 }
685
686 pub fn play_ambience_oneshot(
690 &mut self,
691 channel_tag: AmbienceChannelTag,
692 trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
693 volume: Option<f32>,
694 delay: Option<f32>,
695 ) {
696 if self.ambience_enabled()
697 && trigger_item.is_some()
698 && let Some(inner) = self.inner.as_mut()
699 && let Some(channel) = inner.channels.get_ambience_channel(channel_tag)
700 {
701 let sound = AudioFrontend::get_sfx_file(trigger_item)
702 .unwrap_or(("", 0.0, Some("")))
703 .0;
704 let source = load_ogg(sound, false)
705 .loop_region(None)
706 .volume(to_decibels(volume.unwrap_or(1.0)));
707 channel.set_looping(false);
708 channel.fade_to(1.0, 0.0);
709 channel.play(source, None, delay);
710 }
711 }
712
713 pub fn set_listener_pos(&mut self, pos: Vec3<f32>, ori: Vec3<f32>) {
714 if let Some(inner) = self.inner.as_mut() {
715 let tween = Tween {
716 duration: Duration::from_secs_f32(0.01),
717 ..Default::default()
718 };
719
720 inner.listener.pos = pos;
721 inner.listener.ori = ori;
722
723 inner.listener.handle.set_position(pos, tween);
724
725 let ori_quat = Ori::from(ori).to_quat();
726 inner
727 .listener
728 .handle
729 .set_orientation(ori_quat.normalized(), tween);
730 }
731 }
732
733 pub fn get_listener(&mut self) -> Option<&mut ListenerHandle> {
734 self.inner.as_mut().map(|i| &mut i.listener.handle)
735 }
736
737 pub fn get_listener_pos(&self) -> Vec3<f32> {
738 self.inner
739 .as_ref()
740 .map(|i| i.listener.pos)
741 .unwrap_or_default()
742 }
743
744 pub fn get_listener_ori(&self) -> Vec3<f32> {
745 self.inner
746 .as_ref()
747 .map(|i| i.listener.ori)
748 .unwrap_or_else(Vec3::unit_x)
749 }
750
751 pub fn play_title_music(&mut self) {
754 if self.music_enabled() {
755 self.play_music(
756 "voxygen.audio.soundtrack.veloren_title_tune",
757 MusicChannelTag::TitleMusic,
758 43.0,
759 );
760 self.set_loop(MusicChannelTag::TitleMusic, true);
761 }
762 }
763
764 pub fn get_master_volume(&self) -> f32 { self.volumes.master }
766
767 pub fn get_music_volume(&self) -> f32 { self.volumes.music }
769
770 pub fn get_ambience_volume(&self) -> f32 { self.volumes.ambience }
772
773 pub fn get_sfx_volume(&self) -> f32 { self.volumes.sfx }
775
776 pub fn music_enabled(&self) -> bool { self.get_music_volume() > 0.0 }
778
779 pub fn ambience_enabled(&self) -> bool { self.get_ambience_volume() > 0.0 }
781
782 pub fn sfx_enabled(&self) -> bool { self.get_sfx_volume() > 0.0 }
784
785 pub fn set_music_volume(&mut self, music_volume: f32) {
786 self.volumes.music = music_volume;
787
788 if let Some(inner) = self.inner.as_mut() {
789 inner
790 .tracks
791 .music
792 .set_volume(to_decibels(music_volume), Tween::default())
793 }
794 }
795
796 pub fn set_ambience_volume(&mut self, ambience_volume: f32) {
797 self.volumes.ambience = ambience_volume;
798
799 if let Some(inner) = self.inner.as_mut() {
800 inner
801 .tracks
802 .ambience
803 .set_volume(to_decibels(ambience_volume), Tween::default())
804 }
805 }
806
807 pub fn set_sfx_volume(&mut self, sfx_volume: f32) {
810 self.volumes.sfx = sfx_volume;
811
812 if let Some(inner) = self.inner.as_mut() {
813 inner
814 .tracks
815 .sfx
816 .set_volume(to_decibels(sfx_volume), Tween::default())
817 }
818 }
819
820 pub fn set_music_spacing(&mut self, multiplier: f32) { self.music_spacing = multiplier }
821
822 pub fn set_subtitles(&mut self, enabled: bool) { self.subtitles_enabled = enabled }
823
824 pub fn set_master_volume(&mut self, master_volume: f32) {
826 self.volumes.master = master_volume;
827
828 if let Some(inner) = self.inner.as_mut() {
829 inner
830 .manager()
831 .main_track()
832 .set_volume(to_decibels(master_volume), Tween::default());
833 }
834 }
835
836 pub fn stop_all_ambience(&mut self) {
837 if let Some(inner) = self.inner.as_mut() {
838 for channel in &mut inner.channels.ambience {
839 channel.stop(None, None);
840 }
841 }
842 }
843
844 pub fn stop_all_music(&mut self) {
845 if let Some(inner) = self.inner.as_mut() {
846 for channel in &mut inner.channels.music {
847 channel.stop(None, None);
848 }
849 }
850 }
851
852 pub fn stop_all_sfx(&mut self) {
853 if let Some(inner) = self.inner.as_mut() {
854 for channel in &mut inner.channels.sfx {
855 channel.stop();
856 }
857 for channel in &mut inner.channels.ui {
858 channel.stop();
859 }
860 }
861 }
862
863 pub fn set_num_sfx_channels(&mut self, channels: usize) {
864 if let Some(inner) = self.inner.as_mut() {
865 inner.channels.sfx = Vec::new();
866 for _ in 0..channels {
867 if let Ok(channel) =
868 SfxChannel::new(&mut inner.tracks.sfx, inner.listener.handle.id())
869 {
870 inner.channels.sfx.push(channel);
871 } else {
872 warn!("Cannot create sfx channel")
873 }
874 }
875 }
876 }
877
878 pub fn get_num_music_channels(&self) -> usize {
879 self.inner
880 .as_ref()
881 .map(|i| i.channels.music.len())
882 .unwrap_or(0)
883 }
884
885 pub fn get_num_ambience_channels(&self) -> usize {
886 self.inner
887 .as_ref()
888 .map(|i| i.channels.ambience.len())
889 .unwrap_or(0)
890 }
891}