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