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