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