veloren_voxygen/audio/
channel.rs

1//! Distinct audio playback channels for music and sound effects
2//!
3//! Voxygen's audio system uses a limited number of channels to play multiple
4//! sounds simultaneously. Each additional channel used decreases performance
5//! in-game, so the amount of channels utilized should be kept to a minimum.
6//!
7//! When constructing a new [`AudioFrontend`](../struct.AudioFrontend.html), the
8//! number of sfx channels are determined by the `num_sfx_channels` value
9//! defined in the client
10//! [`AudioSettings`](../../settings/struct.AudioSettings.html)
11
12use kira::{
13    Easing, StartTime, Tween,
14    clock::ClockTime,
15    listener::ListenerId,
16    sound::PlaybackState,
17    track::{SpatialTrackBuilder, SpatialTrackHandle, TrackBuilder, TrackHandle},
18};
19use serde::Deserialize;
20use std::time::Duration;
21use strum::EnumIter;
22use tracing::warn;
23use vek::*;
24
25use crate::audio;
26
27use super::soundcache::{AnySoundData, AnySoundHandle};
28
29/// Each `MusicChannel` has a `MusicChannelTag` which help us determine when we
30/// should transition between two types of in-game music. For example, we
31/// transition between `TitleMusic` and `Exploration` when a player enters the
32/// world by crossfading over a slow duration. In the future, transitions in the
33/// world such as `Exploration` -> `BossBattle` would transition more rapidly.
34#[derive(PartialEq, Clone, Copy, Hash, Eq, Deserialize, Debug)]
35pub enum MusicChannelTag {
36    TitleMusic,
37    Exploration,
38    Combat,
39}
40
41/// A MusicChannel is designed to play music which
42/// is always heard at the player's position.
43pub struct MusicChannel {
44    tag: MusicChannelTag,
45    track: TrackHandle,
46    source: Option<AnySoundHandle>,
47    length: f32,
48    loop_data: (bool, LoopPoint, LoopPoint), // Loops?, Start, End
49}
50
51#[derive(Clone, Copy, Debug, PartialEq)]
52pub enum LoopPoint {
53    Start,
54    End,
55    Point(f64),
56}
57
58impl MusicChannel {
59    pub fn new(route_to: &mut TrackHandle) -> Result<Self, kira::ResourceLimitReached> {
60        let track = route_to.add_sub_track(TrackBuilder::new().volume(audio::to_decibels(0.0)))?;
61        Ok(Self {
62            tag: MusicChannelTag::TitleMusic,
63            track,
64            source: None,
65            length: 0.0,
66            loop_data: (false, LoopPoint::Start, LoopPoint::End),
67        })
68    }
69
70    pub fn set_tag(&mut self, tag: MusicChannelTag) { self.tag = tag; }
71
72    pub fn set_source(&mut self, source_handle: Option<AnySoundHandle>) {
73        self.source = source_handle;
74    }
75
76    pub fn set_length(&mut self, length: f32) { self.length = length; }
77
78    // Gets the currently set loop data
79    pub fn get_loop_data(&self) -> (bool, LoopPoint, LoopPoint) { self.loop_data }
80
81    /// Sets whether the sound loops, and the start and end points of the loop
82    pub fn set_loop_data(&mut self, loops: bool, start: LoopPoint, end: LoopPoint) {
83        if let Some(source) = self.source.as_mut() {
84            self.loop_data = (loops, start, end);
85            if loops {
86                match (start, end) {
87                    (LoopPoint::Start, LoopPoint::End) => {
88                        source.set_loop_region(0.0..);
89                    },
90                    (LoopPoint::Start, LoopPoint::Point(end)) => {
91                        source.set_loop_region(..end);
92                    },
93                    (LoopPoint::Point(start), LoopPoint::End) => {
94                        source.set_loop_region(start..);
95                    },
96                    (LoopPoint::Point(start), LoopPoint::Point(end)) => {
97                        source.set_loop_region(start..end);
98                    },
99                    _ => {
100                        warn!("Invalid loop points given")
101                    },
102                }
103            } else {
104                source.set_loop_region(None);
105            }
106        }
107    }
108
109    pub fn play(
110        &mut self,
111        mut source: AnySoundData,
112        now: ClockTime,
113        fade_in: Option<f32>,
114        delay: Option<f32>,
115    ) {
116        if let Some(fade_in) = fade_in {
117            let fade_in_tween = Tween {
118                duration: Duration::from_secs_f32(fade_in),
119                ..Default::default()
120            };
121            source = source.fade_in_tween(fade_in_tween);
122        }
123
124        if let Some(delay) = delay {
125            source = source.start_time(now + delay as f64);
126        }
127
128        match self.track.play(source) {
129            Ok(handle) => self.source = Some(handle),
130            Err(e) => {
131                warn!(?e, "Cannot play music")
132            },
133        }
134    }
135
136    /// Stop whatever is playing on this channel with an optional fadeout and
137    /// delay
138    pub fn stop(&mut self, duration: Option<f32>, delay: Option<f32>) {
139        if let Some(source) = self.source.as_mut() {
140            let tween = Tween {
141                duration: Duration::from_secs_f32(duration.unwrap_or(0.1)),
142                start_time: StartTime::Delayed(Duration::from_secs_f32(delay.unwrap_or(0.0))),
143                ..Default::default()
144            };
145            source.stop(tween)
146        };
147    }
148
149    /// Set the volume of the current channel.
150    pub fn set_volume(&mut self, volume: f32) {
151        self.track
152            .set_volume(audio::to_decibels(volume), Tween::default());
153    }
154
155    /// Fade to a given amplitude over a given duration, optionally after a
156    /// delay
157    pub fn fade_to(&mut self, volume: f32, duration: f32, delay: Option<f32>) {
158        let mut start_time = StartTime::Immediate;
159        if let Some(delay) = delay {
160            start_time = StartTime::Delayed(Duration::from_secs_f32(delay))
161        }
162        let tween = Tween {
163            start_time,
164            duration: Duration::from_secs_f32(duration),
165            easing: Easing::Linear,
166        };
167        self.track.set_volume(audio::to_decibels(volume), tween);
168    }
169
170    /// Fade to silence over a given duration and stop, optionally after a delay
171    /// Use fade_to() if this fade is temporary
172    pub fn fade_out(&mut self, duration: f32, delay: Option<f32>) {
173        self.stop(Some(duration), delay);
174    }
175
176    /// Returns true if the sound has stopped playing (whether by fading out or
177    /// by finishing)
178    pub fn is_done(&self) -> bool {
179        self.source
180            .as_ref()
181            .is_none_or(|source| source.state() == PlaybackState::Stopped)
182    }
183
184    pub fn get_tag(&self) -> MusicChannelTag { self.tag }
185
186    /// Get a mutable reference to the channel's track
187    pub fn get_track(&mut self) -> &mut TrackHandle { &mut self.track }
188
189    pub fn get_source(&mut self) -> Option<&mut AnySoundHandle> { self.source.as_mut() }
190
191    pub fn get_length(&self) -> f32 { self.length }
192}
193
194/// AmbienceChannelTags are used for non-positional sfx. Currently the only use
195/// is for wind.
196#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, EnumIter)]
197pub enum AmbienceChannelTag {
198    Wind,
199    Rain,
200    ThunderRumbling,
201    Leaves,
202    Cave,
203    Thunder,
204}
205
206/// An AmbienceChannel uses a non-positional audio sink designed to play sounds
207/// which are always heard at the camera's position.
208#[derive(Debug)]
209pub struct AmbienceChannel {
210    tag: AmbienceChannelTag,
211    target_volume: f32,
212    track: TrackHandle,
213    source: Option<AnySoundHandle>,
214    pub looping: bool,
215}
216
217impl AmbienceChannel {
218    pub fn new(
219        tag: AmbienceChannelTag,
220        init_volume: f32,
221        route_to: &mut TrackHandle,
222        looping: bool,
223    ) -> Result<Self, kira::ResourceLimitReached> {
224        let ambience_track_builder = TrackBuilder::new();
225        let track =
226            route_to.add_sub_track(ambience_track_builder.volume(audio::to_decibels(0.0)))?;
227
228        Ok(Self {
229            tag,
230            target_volume: init_volume,
231            track,
232            source: None,
233            looping,
234        })
235    }
236
237    pub fn set_source(&mut self, source_handle: Option<AnySoundHandle>) {
238        self.source = source_handle;
239    }
240
241    pub fn play(&mut self, mut source: AnySoundData, fade_in: Option<f32>, delay: Option<f32>) {
242        let mut tween = Tween::default();
243        if let Some(fade_in) = fade_in {
244            tween.duration = Duration::from_secs_f32(fade_in);
245        }
246        if let Some(delay) = delay {
247            tween.start_time = StartTime::Delayed(Duration::from_secs_f32(delay));
248        }
249        source = source.fade_in_tween(tween);
250        match self.track.play(source) {
251            Ok(handle) => self.source = Some(handle),
252            Err(e) => {
253                warn!(?e, "Cannot play ambience")
254            },
255        }
256    }
257
258    /// Stop whatever is playing on this channel with an optional fadeout and
259    /// delay
260    pub fn stop(&mut self, duration: Option<f32>, delay: Option<f32>) {
261        if let Some(source) = self.source.as_mut() {
262            let tween = Tween {
263                duration: Duration::from_secs_f32(duration.unwrap_or(0.1)),
264                start_time: StartTime::Delayed(Duration::from_secs_f32(delay.unwrap_or(0.0))),
265                ..Default::default()
266            };
267            source.stop(tween)
268        }
269    }
270
271    /// Set the channel to a volume, fading over a given duration
272    pub fn fade_to(&mut self, volume: f32, duration: f32) {
273        self.track.set_volume(audio::to_decibels(volume), Tween {
274            start_time: StartTime::Immediate,
275            duration: Duration::from_secs_f32(duration),
276            easing: Easing::Linear,
277        });
278        self.target_volume = volume;
279    }
280
281    /// Set whether this channel's sound loops or not
282    pub fn set_looping(&mut self, loops: bool) {
283        if let Some(source) = self.source.as_mut() {
284            if loops {
285                source.set_loop_region(0.0..);
286            } else {
287                source.set_loop_region(None);
288            }
289        }
290    }
291
292    pub fn get_source(&mut self) -> Option<&mut AnySoundHandle> { self.source.as_mut() }
293
294    /// Get an immutable reference to the channel's track for purposes of
295    /// setting the output destination of a sound
296    pub fn get_track(&self) -> &TrackHandle { &self.track }
297
298    /// Get a mutable reference to the channel's track
299    pub fn get_track_mut(&mut self) -> &mut TrackHandle { &mut self.track }
300
301    /// Get the volume of this channel. The volume may be in the process of
302    /// being faded to.
303    pub fn get_target_volume(&self) -> f32 { self.target_volume }
304
305    pub fn get_tag(&self) -> AmbienceChannelTag { self.tag }
306
307    pub fn set_tag(&mut self, tag: AmbienceChannelTag) { self.tag = tag }
308
309    pub fn is_active(&self) -> bool { self.get_target_volume() == 0.0 }
310
311    pub fn is_stopped(&self) -> bool {
312        if let Some(source) = self.source.as_ref() {
313            source.state() == PlaybackState::Stopped
314        } else {
315            false
316        }
317    }
318}
319
320/// An SfxChannel uses a positional audio sink, and is designed for short-lived
321/// audio which can be spatially controlled, but does not need control over
322/// playback or fading/transitions
323///
324/// Note: currently, emitters are static once spawned
325#[derive(Debug)]
326pub struct SfxChannel {
327    track: SpatialTrackHandle,
328    source: Option<AnySoundHandle>,
329    pub pos: Vec3<f32>,
330}
331
332impl SfxChannel {
333    pub fn new(
334        route_to: &mut TrackHandle,
335        listener: ListenerId,
336    ) -> Result<Self, kira::ResourceLimitReached> {
337        let sfx_track_builder = SpatialTrackBuilder::new()
338            .distances((1.0, 200.0))
339            .attenuation_function(Some(Easing::OutPowf(0.45)));
340        let track = route_to.add_spatial_sub_track(listener, Vec3::zero(), sfx_track_builder)?;
341        Ok(Self {
342            track,
343            source: None,
344            pos: Vec3::zero(),
345        })
346    }
347
348    pub fn set_source(&mut self, source_handle: Option<AnySoundHandle>) {
349        self.source = source_handle;
350    }
351
352    pub fn play(&mut self, source: AnySoundData) {
353        match self.track.play(source) {
354            Ok(handle) => self.source = Some(handle),
355            Err(e) => {
356                warn!(?e, "Cannot play sfx")
357            },
358        }
359    }
360
361    pub fn stop(&mut self) {
362        if let Some(source) = self.source.as_mut() {
363            source.stop(Tween::default())
364        }
365    }
366
367    pub fn set_volume(&mut self, volume: f32) {
368        if let Some(source) = self.source.as_mut() {
369            source.set_volume(audio::to_decibels(volume), Tween::default())
370        }
371    }
372
373    pub fn is_done(&self) -> bool {
374        self.source
375            .as_ref()
376            .is_none_or(|source| source.state() == PlaybackState::Stopped)
377    }
378
379    pub fn update(&mut self, pos: Vec3<f32>) {
380        let tween = Tween {
381            duration: Duration::from_secs_f32(0.0),
382            ..Default::default()
383        };
384        self.track.set_position(pos, tween);
385        self.pos = pos;
386    }
387}
388
389/// An UiChannel uses a non-spatial audio sink, and is designed for short-lived
390/// audio which is not spatially controlled, but does not need control over
391/// playback or fading/transitions
392pub struct UiChannel {
393    track: TrackHandle,
394    source: Option<AnySoundHandle>,
395}
396
397impl UiChannel {
398    pub fn new(route_to: &mut TrackHandle) -> Result<Self, kira::ResourceLimitReached> {
399        let track = route_to.add_sub_track(TrackBuilder::default())?;
400        Ok(Self {
401            track,
402            source: None,
403        })
404    }
405
406    pub fn set_source(&mut self, source_handle: Option<AnySoundHandle>) {
407        self.source = source_handle;
408    }
409
410    pub fn play(&mut self, source: AnySoundData) {
411        match self.track.play(source) {
412            Ok(handle) => self.source = Some(handle),
413            Err(e) => {
414                warn!(?e, "Cannot play ui sfx")
415            },
416        }
417    }
418
419    pub fn stop(&mut self) {
420        if let Some(source) = self.source.as_mut() {
421            source.stop(Tween::default())
422        }
423    }
424
425    pub fn set_volume(&mut self, volume: f32) {
426        self.track
427            .set_volume(audio::to_decibels(volume), Tween::default())
428    }
429
430    pub fn is_done(&self) -> bool {
431        self.source
432            .as_ref()
433            .is_none_or(|source| source.state() == PlaybackState::Stopped)
434    }
435}