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::{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().0
348            );
349            samplerate = default_output_config.sample_rate().0;
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(SampleRate(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(SampleRate(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 =
379                            best_config.try_with_sample_rate(SampleRate(set_samplerate));
380                        if supported_config.is_none() {
381                            error!(
382                                "Could not set samplerate to {:?}, falling back to default.",
383                                set_samplerate
384                            );
385                        }
386                    }
387                }
388            }
389        }
390        let mut config = None;
391        if let Some(supported_config) = supported_config {
392            info!(
393                "Samplerate is {:?}",
394                supported_config.config().sample_rate.0
395            );
396            config = Some(supported_config.config())
397        } else {
398            info!("Samplerate is {:?}", samplerate)
399        }
400        let inner = AudioFrontendInner::new(
401            num_sfx_channels,
402            num_ui_channels,
403            buffer_size,
404            device,
405            config,
406        )
407        .inspect_err(|err| match err {
408            AudioCreationError::Manager(e) => {
409                #[cfg(unix)]
410                error!(
411                    ?e,
412                    "failed to construct audio frontend manager. Is `pulseaudio-alsa` installed?"
413                );
414                #[cfg(not(unix))]
415                error!(?e, "failed to construct audio frontend manager.");
416            },
417            AudioCreationError::Clock(e) => {
418                error!(?e, "Failed to construct audio frontend clock.")
419            },
420            AudioCreationError::Track(e) => {
421                error!(?e, "Failed to construct audio frontend track.")
422            },
423            AudioCreationError::Listener(e) => {
424                error!(?e, "Failed to construct audio frontend listener.")
425            },
426        })
427        .ok();
428
429        if let Some(inner) = inner {
430            Self {
431                inner: Some(inner),
432                volumes: Volumes::default(),
433                music_spacing: 1.0,
434                mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
435                subtitles: VecDeque::new(),
436                subtitles_enabled: subtitles,
437                combat_music_enabled,
438            }
439        } else {
440            Self {
441                inner: None,
442                volumes: Volumes::default(),
443                music_spacing: 1.0,
444                mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
445                subtitles: VecDeque::new(),
446                subtitles_enabled: subtitles,
447                combat_music_enabled,
448            }
449        }
450    }
451
452    fn channels_mut(&mut self) -> Option<&mut Channels> { Some(&mut self.inner.as_mut()?.channels) }
453
454    /// Construct in `no-audio` mode for debugging
455    pub fn no_audio() -> Self {
456        Self {
457            inner: None,
458            music_spacing: 1.0,
459            volumes: Volumes::default(),
460            mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
461            subtitles: VecDeque::new(),
462            subtitles_enabled: false,
463            combat_music_enabled: false,
464        }
465    }
466
467    /// Drop any unused music channels, ambience channels, and reset the tags of
468    /// unused UI channels.
469    pub fn maintain(&mut self) {
470        if let Some(inner) = &mut self.inner {
471            inner.channels.music.retain(|c| !c.is_done());
472            inner.channels.ambience.retain(|c| !c.is_stopped());
473            inner.channels.sfx.iter_mut().for_each(|c| {
474                if c.is_done() {
475                    c.drop_track();
476                }
477            });
478            inner.channels.ui.iter_mut().for_each(|c| {
479                if c.is_done() {
480                    c.tag = None
481                }
482            });
483        }
484    }
485
486    pub fn get_clock(&self) -> Option<&ClockHandle> { self.inner.as_ref().map(|i| i.clock()) }
487
488    pub fn get_clock_time(&self) -> Option<ClockTime> { self.get_clock().map(|clock| clock.time()) }
489
490    /// Returns [music channels, ambience channels, sfx channels, ui channels]
491    pub fn get_num_active_channels(&self) -> ActiveChannels {
492        self.inner
493            .as_ref()
494            .map(|i| i.channels.count_active())
495            .unwrap_or_default()
496    }
497
498    pub fn get_cpu_usage(&mut self) -> f32 {
499        if let Some(inner) = self.inner.as_mut() {
500            inner.manager.backend_mut().pop_cpu_usage().unwrap_or(0.0)
501        } else {
502            0.0
503        }
504    }
505
506    /// Play a music file with the given tag. Pass in the length of the track in
507    /// seconds.
508    fn play_music(&mut self, sound: &str, channel_tag: MusicChannelTag, length: f32) {
509        if self.music_enabled()
510            && let Some(inner) = &mut self.inner
511        {
512            let mtm = self.mtm.read();
513
514            if let Some(current_channel) = inner.channels.music.iter_mut().find(|c| !c.is_done()) {
515                let (fade_out, _fade_in) = mtm
516                    .0
517                    .fade_timings
518                    .get(&(current_channel.get_tag(), channel_tag))
519                    .unwrap_or(&(1.0, 1.0));
520                current_channel.fade_out(*fade_out, None);
521            }
522
523            let now = inner.clock().time();
524
525            let channel = match inner.channels.get_music_channel(channel_tag) {
526                Some(c) => c,
527                None => {
528                    inner.create_music_channel(channel_tag);
529                    inner
530                        .channels
531                        .music
532                        .last_mut()
533                        .expect("We just created this")
534                },
535            };
536
537            let (fade_out, fade_in) = mtm
538                .0
539                .fade_timings
540                .get(&(channel.get_tag(), channel_tag))
541                .unwrap_or(&(1.0, 0.1));
542            let source = load_ogg(sound, true);
543            channel.stop(Some(*fade_out), None);
544            channel.set_length(length);
545            channel.set_tag(channel_tag);
546            channel.set_loop_data(false, LoopPoint::Start, LoopPoint::End);
547            channel.play(source, now, Some(*fade_in), Some(*fade_out));
548        }
549    }
550
551    /// Turn on or off looping
552    pub fn set_loop(&mut self, channel_tag: MusicChannelTag, sound_loops: bool) {
553        if let Some(inner) = self.inner.as_mut() {
554            let channel = inner.channels.get_music_channel(channel_tag);
555            if let Some(channel) = channel {
556                let loop_data = channel.get_loop_data();
557                channel.set_loop_data(sound_loops, loop_data.1, loop_data.2);
558            }
559        }
560    }
561
562    /// Loops music from start point to end point in seconds
563    pub fn set_loop_points(&mut self, channel_tag: MusicChannelTag, start: f32, end: f32) {
564        if let Some(inner) = self.inner.as_mut() {
565            let channel = inner.channels.get_music_channel(channel_tag);
566            if let Some(channel) = channel {
567                channel.set_loop_data(
568                    true,
569                    LoopPoint::Point(start as f64),
570                    LoopPoint::Point(end as f64),
571                );
572            }
573        }
574    }
575
576    /// Find sound based on given trigger_item.
577    /// Randomizes if multiple sounds are found.
578    /// Errors if no sounds are found.
579    /// Returns (file, threshold, subtitle)
580    pub fn get_sfx_file<'a>(
581        trigger_item: Option<(&'a SfxEvent, &'a SfxTriggerItem)>,
582    ) -> Option<(&'a str, f32, Option<&'a str>)> {
583        trigger_item.map(|(event, item)| {
584            let file = match item.files.len() {
585                0 => {
586                    debug!("Sfx event {:?} is missing audio file.", event);
587                    "voxygen.audio.sfx.placeholder"
588                },
589                1 => item
590                    .files
591                    .last()
592                    .expect("Failed to determine sound file for this trigger item."),
593                _ => {
594                    // If more than one file is listed, choose one at random
595                    let rand_step = (rand::random::<u64>() as usize) % item.files.len();
596                    &item.files[rand_step]
597                },
598            };
599
600            // NOTE: Threshold here is meant to give subtitles some idea of the duration of
601            // the audio, it doesn't have to be perfect but in the future, if possible we
602            // might want to switch it out for the actual duration.
603            (file, item.threshold, item.subtitle.as_deref())
604        })
605    }
606
607    /// Set the cutoff of the filter affecting all spatial sfx
608    pub fn set_sfx_master_filter(&mut self, frequency: u32) {
609        if let Some(inner) = self.inner.as_mut() {
610            inner
611                .effects
612                .sfx
613                .set_cutoff(Value::Fixed(frequency as f64), Tween::default());
614        }
615    }
616
617    /// Play an sfx file given the position and SfxEvent at the given volume
618    /// (default 1.0)
619    pub fn emit_sfx(
620        &mut self,
621        trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
622        emitter_pos: Vec3<f32>,
623        volume: Option<f32>,
624    ) -> Option<SfxHandle> {
625        if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
626            self.emit_subtitle(subtitle, Some(emitter_pos), dur);
627            // Play sound in empty channel at given position
628            if self.sfx_enabled()
629                && let Some(inner) = self.inner.as_mut()
630                && let Some((channel_idx, channel)) = inner.channels.get_empty_sfx_channel()
631            {
632                let listener_id = inner.listener.handle.id();
633                let sound = load_ogg(sfx_file, false);
634                channel.set_pos(emitter_pos);
635
636                // Initial calculation of player position attenuation to avoid popping
637                let ratio = calculate_player_attenuation(inner.player_pos, emitter_pos);
638
639                let source_volume = volume.unwrap_or(1.0);
640                let source = sound.volume(to_decibels(source_volume * 5.0 * ratio));
641
642                // We build new tracks here because we have to set the emitter position
643                // initially, which isn't possible to synchronize with the start of a new sound.
644                let sfx_track_builder = SpatialTrackBuilder::new()
645                    .distances((1.0, SFX_DIST_LIMIT))
646                    .attenuation_function(Some(kira::Easing::OutPowf(0.66)));
647                if let Ok(track) = inner.tracks.sfx.add_spatial_sub_track(
648                    listener_id,
649                    emitter_pos,
650                    sfx_track_builder,
651                ) {
652                    Some(SfxHandle {
653                        channel_idx,
654                        play_id: channel.play(source, source_volume, track),
655                    })
656                } else {
657                    debug!("Could not add SpacialTrack to play sfx");
658                    None
659                }
660            } else {
661                None
662            }
663        } else {
664            warn!(
665                "Missing sfx trigger config for sfx event: {:?}; {:?}",
666                trigger_item,
667                backtrace::Backtrace::new(),
668            );
669            None
670        }
671    }
672
673    /// Plays a sfx non-spatially at the given volume (default 1.0); doesn't
674    /// need a position
675    pub fn emit_ui_sfx(
676        &mut self,
677        trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
678        volume: Option<f32>,
679        tag: Option<channel::UiChannelTag>,
680    ) {
681        if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
682            self.emit_subtitle(subtitle, None, dur);
683
684            // Play sound in empty channel
685            if self.sfx_enabled()
686                && let Some(inner) = self.inner.as_mut()
687                && !inner
688                    .channels
689                    .ui
690                    .iter()
691                    .any(|c| tag.is_some() && c.tag == tag)
692                && let Some(channel) = inner.channels.get_ui_channel()
693            {
694                let sound = load_ogg(sfx_file, false).volume(to_decibels(volume.unwrap_or(1.0)));
695                channel.play(sound, tag);
696            }
697        } else {
698            warn!("Missing sfx trigger config for ui sfx event.",);
699        }
700    }
701
702    /// Push a subtitle to the subtitle queue
703    pub fn emit_subtitle(
704        &mut self,
705        subtitle: Option<&str>,
706        position: Option<Vec3<f32>>,
707        duration: f32,
708    ) {
709        if self.subtitles_enabled
710            && let Some(subtitle) = subtitle
711        {
712            self.subtitles.push_back(Subtitle {
713                localization: subtitle.to_string(),
714                position,
715                show_for: duration as f64,
716            });
717            if self.subtitles.len() > 10 {
718                self.subtitles.pop_front();
719            }
720        }
721    }
722
723    /// Set the cutoff of the filter affecting all ambience
724    pub fn set_ambience_master_filter(&mut self, frequency: u32) {
725        if let Some(inner) = self.inner.as_mut() {
726            inner
727                .effects
728                .ambience
729                .set_cutoff(Value::Fixed(frequency as f64), Tween::default());
730        }
731    }
732
733    /// Plays an ambience sound that loops in the channel with a given tag
734    pub fn play_ambience_looping(
735        &mut self,
736        channel_tag: AmbienceChannelTag,
737        sound: &str,
738        start: usize,
739        end: usize,
740    ) {
741        if self.ambience_enabled()
742            && let Some(inner) = self.inner.as_mut()
743            && let Some(channel) = inner.channels.get_ambience_channel(channel_tag)
744        {
745            let source = load_ogg(sound, true).loop_region(
746                kira::sound::PlaybackPosition::Samples(start)
747                    ..kira::sound::PlaybackPosition::Samples(end),
748            );
749            channel.play(source, Some(1.0), None);
750        }
751    }
752
753    /// Plays an ambience sound once at the given volume after the given delay.
754    /// Make sure it uses a channel tag that does not change the volume of its
755    /// channel. Currently, ambience oneshots use the Sfx file system
756    pub fn play_ambience_oneshot(
757        &mut self,
758        channel_tag: AmbienceChannelTag,
759        trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
760        volume: Option<f32>,
761        delay: Option<f32>,
762    ) {
763        if self.ambience_enabled()
764            && trigger_item.is_some()
765            && let Some(inner) = self.inner.as_mut()
766            && let Some(channel) = inner.channels.get_ambience_channel(channel_tag)
767        {
768            let sound = AudioFrontend::get_sfx_file(trigger_item)
769                .unwrap_or(("", 0.0, Some("")))
770                .0;
771            let source = load_ogg(sound, false)
772                .loop_region(None)
773                .volume(to_decibels(volume.unwrap_or(1.0)));
774            channel.fade_to(1.0, 0.0);
775            channel.play(source, None, delay);
776        }
777    }
778
779    pub fn set_listener_pos(&mut self, pos: Vec3<f32>, ori: Vec3<f32>) {
780        if let Some(inner) = self.inner.as_mut() {
781            let tween = Tween {
782                duration: Duration::from_secs_f32(0.01),
783                ..Default::default()
784            };
785
786            inner.listener.pos = pos;
787            inner.listener.ori = ori;
788
789            inner.listener.handle.set_position(pos, tween);
790
791            let ori_quat = Ori::from(ori).to_quat();
792            inner
793                .listener
794                .handle
795                .set_orientation(ori_quat.normalized(), tween);
796        }
797    }
798
799    pub fn get_listener(&mut self) -> Option<&mut ListenerHandle> {
800        self.inner.as_mut().map(|i| &mut i.listener.handle)
801    }
802
803    pub fn get_listener_pos(&self) -> Vec3<f32> {
804        self.inner
805            .as_ref()
806            .map(|i| i.listener.pos)
807            .unwrap_or_default()
808    }
809
810    pub fn get_listener_ori(&self) -> Vec3<f32> {
811        self.inner
812            .as_ref()
813            .map(|i| i.listener.ori)
814            .unwrap_or_else(Vec3::unit_x)
815    }
816
817    /// Switches the playing music to the title music, which is pinned to a
818    /// specific sound file (veloren_title_tune.ogg)
819    pub fn play_title_music(&mut self) {
820        if self.music_enabled() {
821            self.play_music(
822                "voxygen.audio.soundtrack.veloren_title_tune",
823                MusicChannelTag::TitleMusic,
824                43.0,
825            );
826            self.set_loop(MusicChannelTag::TitleMusic, true);
827        }
828    }
829
830    /// Retrieves the current setting for master volume
831    pub fn get_master_volume(&self) -> f32 { self.volumes.master }
832
833    /// Retrieves the current setting for music volume
834    pub fn get_music_volume(&self) -> f32 { self.volumes.music }
835
836    /// Retrieves the current setting for ambience volume
837    pub fn get_ambience_volume(&self) -> f32 { self.volumes.ambience }
838
839    /// Retrieves the current setting for sfx volume
840    pub fn get_sfx_volume(&self) -> f32 { self.volumes.sfx }
841
842    /// Returns false if volume is 0 or the mute is on
843    pub fn music_enabled(&self) -> bool { self.get_music_volume() > 0.0 }
844
845    /// Returns false if volume is 0 or the mute is on
846    pub fn ambience_enabled(&self) -> bool { self.get_ambience_volume() > 0.0 }
847
848    /// Returns false if volume is 0 or the mute is on
849    pub fn sfx_enabled(&self) -> bool { self.get_sfx_volume() > 0.0 }
850
851    pub fn set_music_volume(&mut self, music_volume: f32) {
852        self.volumes.music = music_volume;
853
854        if let Some(inner) = self.inner.as_mut() {
855            inner
856                .tracks
857                .music
858                .set_volume(to_decibels(music_volume), Tween::default())
859        }
860    }
861
862    pub fn set_ambience_volume(&mut self, ambience_volume: f32) {
863        self.volumes.ambience = ambience_volume;
864
865        if let Some(inner) = self.inner.as_mut() {
866            inner
867                .tracks
868                .ambience
869                .set_volume(to_decibels(ambience_volume), Tween::default())
870        }
871    }
872
873    /// Sets the volume for both spatial sfx and UI (might separate these
874    /// controls later)
875    pub fn set_sfx_volume(&mut self, sfx_volume: f32) {
876        self.volumes.sfx = sfx_volume;
877
878        if let Some(inner) = self.inner.as_mut() {
879            inner
880                .tracks
881                .sfx
882                .set_volume(to_decibels(sfx_volume), Tween::default())
883        }
884    }
885
886    pub fn set_music_spacing(&mut self, multiplier: f32) { self.music_spacing = multiplier }
887
888    pub fn set_subtitles(&mut self, enabled: bool) { self.subtitles_enabled = enabled }
889
890    /// Updates volume of the master track
891    pub fn set_master_volume(&mut self, master_volume: f32) {
892        self.volumes.master = master_volume;
893
894        if let Some(inner) = self.inner.as_mut() {
895            inner
896                .manager()
897                .main_track()
898                .set_volume(to_decibels(master_volume), Tween::default());
899        }
900    }
901
902    pub fn stop_all_ambience(&mut self) {
903        if let Some(inner) = self.inner.as_mut() {
904            for channel in &mut inner.channels.ambience {
905                channel.stop(None, None);
906            }
907        }
908    }
909
910    pub fn stop_all_music(&mut self) {
911        if let Some(inner) = self.inner.as_mut() {
912            for channel in &mut inner.channels.music {
913                channel.stop(None, None);
914            }
915        }
916    }
917
918    pub fn stop_all_sfx(&mut self) {
919        if let Some(inner) = self.inner.as_mut() {
920            for channel in &mut inner.channels.sfx {
921                channel.stop();
922            }
923            for channel in &mut inner.channels.ui {
924                channel.stop();
925            }
926        }
927    }
928
929    pub fn set_num_sfx_channels(&mut self, channels: usize) {
930        if let Some(inner) = self.inner.as_mut() {
931            inner.channels.sfx = Vec::new();
932            for _ in 0..channels {
933                inner.channels.sfx.push(SfxChannel::new());
934            }
935        }
936    }
937
938    pub fn get_num_music_channels(&self) -> usize {
939        self.inner
940            .as_ref()
941            .map(|i| i.channels.music.len())
942            .unwrap_or(0)
943    }
944
945    pub fn get_num_ambience_channels(&self) -> usize {
946        self.inner
947            .as_ref()
948            .map(|i| i.channels.ambience.len())
949            .unwrap_or(0)
950    }
951}