Skip to main content

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