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