Skip to main content

fyrox_impl/scene/sound/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Everything related to sound in the engine.
22
23use crate::{
24    core::{
25        algebra::Matrix4,
26        math::{aabb::AxisAlignedBoundingBox, m4x4_approx_eq},
27        pool::Handle,
28        reflect::prelude::*,
29        type_traits::prelude::*,
30        uuid::{uuid, Uuid},
31        variable::InheritableVariable,
32        visitor::prelude::*,
33    },
34    define_with,
35    scene::{
36        base::{Base, BaseBuilder},
37        graph::Graph,
38        node::{Node, NodeTrait, SyncContext, UpdateContext},
39    },
40};
41
42// Re-export some the fyrox_sound entities.
43pub use fyrox_sound::{
44    buffer::{
45        generic::Samples,
46        loader::{SoundBufferImportOptions, SoundBufferLoader},
47        DataSource, SoundBuffer, SoundBufferResource, SoundBufferResourceLoadError,
48    },
49    bus::*,
50    context::{DistanceModel, SAMPLE_RATE},
51    dsp::{filters::*, DelayLine},
52    effects::*,
53    engine::SoundEngine,
54    error::SoundError,
55    hrtf::HrirSphere,
56    renderer::{hrtf::*, Renderer},
57    source::Status,
58};
59
60use crate::scene::node::constructor::NodeConstructor;
61use crate::scene::Scene;
62use fyrox_graph::constructor::ConstructorProvider;
63use fyrox_graph::SceneGraph;
64use fyrox_resource::state::ResourceState;
65use fyrox_sound::source::SoundSource;
66use std::{
67    cell::Cell,
68    ops::{Deref, DerefMut},
69    time::Duration,
70};
71
72pub mod context;
73pub mod listener;
74
75/// Sound source.
76#[derive(Visit, Reflect, Debug, ComponentProvider)]
77#[reflect(derived_type = "Node")]
78pub struct Sound {
79    base: Base,
80
81    #[reflect(setter = "set_buffer")]
82    buffer: InheritableVariable<Option<SoundBufferResource>>,
83
84    #[reflect(setter = "set_play_once")]
85    play_once: InheritableVariable<bool>,
86
87    #[reflect(min_value = 0.0, step = 0.05)]
88    #[reflect(setter = "set_gain")]
89    gain: InheritableVariable<f32>,
90
91    #[reflect(min_value = -1.0, max_value = 1.0, step = 0.05)]
92    #[reflect(setter = "set_panning")]
93    panning: InheritableVariable<f32>,
94
95    #[reflect(setter = "set_status")]
96    pub(crate) status: InheritableVariable<Status>,
97
98    #[reflect(setter = "set_looping")]
99    looping: InheritableVariable<bool>,
100
101    #[reflect(min_value = 0.0, step = 0.05)]
102    #[reflect(setter = "set_pitch")]
103    pitch: InheritableVariable<f64>,
104
105    #[reflect(min_value = 0.0, step = 0.05)]
106    #[reflect(setter = "set_radius")]
107    radius: InheritableVariable<f32>,
108
109    #[reflect(min_value = 0.0, step = 0.05)]
110    #[reflect(setter = "set_max_distance")]
111    max_distance: InheritableVariable<f32>,
112
113    #[reflect(min_value = 0.0, step = 0.05)]
114    #[reflect(setter = "set_rolloff_factor")]
115    rolloff_factor: InheritableVariable<f32>,
116
117    #[visit(optional)]
118    #[reflect(setter = "set_playback_time", min_value = 0.0)]
119    playback_time: InheritableVariable<f32>,
120
121    #[reflect(setter = "set_spatial_blend")]
122    spatial_blend: InheritableVariable<f32>,
123
124    /// A name of a sound effect to which the sound will attach to when instantiated.
125    #[visit(optional)]
126    audio_bus: InheritableVariable<String>,
127
128    #[reflect(hidden)]
129    #[visit(skip)]
130    pub(crate) native: Cell<Handle<SoundSource>>,
131}
132
133impl Deref for Sound {
134    type Target = Base;
135
136    fn deref(&self) -> &Self::Target {
137        &self.base
138    }
139}
140
141impl DerefMut for Sound {
142    fn deref_mut(&mut self) -> &mut Self::Target {
143        &mut self.base
144    }
145}
146
147impl Default for Sound {
148    fn default() -> Self {
149        Self {
150            base: Default::default(),
151            buffer: InheritableVariable::new_modified(None),
152            play_once: InheritableVariable::new_modified(false),
153            gain: InheritableVariable::new_modified(1.0),
154            panning: InheritableVariable::new_modified(0.0),
155            status: InheritableVariable::new_modified(Status::Stopped),
156            looping: InheritableVariable::new_modified(false),
157            pitch: InheritableVariable::new_modified(1.0),
158            radius: InheritableVariable::new_modified(10.0),
159            max_distance: InheritableVariable::new_modified(f32::MAX),
160            rolloff_factor: InheritableVariable::new_modified(1.0),
161            playback_time: Default::default(),
162            spatial_blend: InheritableVariable::new_modified(1.0),
163            audio_bus: InheritableVariable::new_modified(AudioBusGraph::PRIMARY_BUS.to_string()),
164            native: Default::default(),
165        }
166    }
167}
168
169impl Clone for Sound {
170    fn clone(&self) -> Self {
171        Self {
172            base: self.base.clone(),
173            buffer: self.buffer.clone(),
174            play_once: self.play_once.clone(),
175            gain: self.gain.clone(),
176            panning: self.panning.clone(),
177            status: self.status.clone(),
178            looping: self.looping.clone(),
179            pitch: self.pitch.clone(),
180            radius: self.radius.clone(),
181            max_distance: self.max_distance.clone(),
182            rolloff_factor: self.rolloff_factor.clone(),
183            playback_time: self.playback_time.clone(),
184            spatial_blend: self.spatial_blend.clone(),
185            audio_bus: self.audio_bus.clone(),
186            // Do not copy. The copy will have its own native representation.
187            native: Default::default(),
188        }
189    }
190}
191
192impl TypeUuidProvider for Sound {
193    fn type_uuid() -> Uuid {
194        uuid!("28621735-8cd1-4fad-8faf-ecd24bf8aa99")
195    }
196}
197
198impl Sound {
199    /// Changes buffer of source. Source will continue playing from beginning, old
200    /// position will be discarded.
201    pub fn set_buffer(
202        &mut self,
203        buffer: Option<SoundBufferResource>,
204    ) -> Option<SoundBufferResource> {
205        self.buffer.set_value_and_mark_modified(buffer)
206    }
207
208    /// Returns current buffer if any.
209    pub fn buffer(&self) -> Option<SoundBufferResource> {
210        (*self.buffer).clone()
211    }
212
213    /// Marks buffer for single play. It will be automatically destroyed when it will finish playing.
214    ///
215    /// # Notes
216    ///
217    /// Make sure you not using handles to "play once" sounds, attempt to get reference of "play once" sound
218    /// may result in panic if source already deleted. Looping sources will never be automatically deleted
219    /// because their playback never stops.
220    pub fn set_play_once(&mut self, play_once: bool) -> bool {
221        self.play_once.set_value_and_mark_modified(play_once)
222    }
223
224    /// Returns true if this source is marked for single play, false - otherwise.
225    pub fn is_play_once(&self) -> bool {
226        *self.play_once
227    }
228
229    /// Sets spatial blend factor. It defines how much the source will be 2D and 3D sound at the same
230    /// time. Set it to 0.0 to make the sound fully 2D and 1.0 to make it fully 3D. Middle values
231    /// will make sound proportionally 2D and 3D at the same time.
232    pub fn set_spatial_blend(&mut self, k: f32) -> f32 {
233        self.spatial_blend
234            .set_value_and_mark_modified(k.clamp(0.0, 1.0))
235    }
236
237    /// Returns spatial blend factor.
238    pub fn spatial_blend(&self) -> f32 {
239        *self.spatial_blend
240    }
241
242    /// Sets new gain (volume) of sound. Value should be in 0..1 range, but it is not clamped
243    /// and larger values can be used to "overdrive" sound.
244    ///
245    /// # Notes
246    ///
247    /// Physical volume has non-linear scale (logarithmic) so perception of sound at 0.25 gain
248    /// will be different if logarithmic scale was used.
249    pub fn set_gain(&mut self, gain: f32) -> f32 {
250        self.gain.set_value_and_mark_modified(gain)
251    }
252
253    /// Returns current gain (volume) of sound. Value is in 0..1 range.
254    pub fn gain(&self) -> f32 {
255        *self.gain
256    }
257
258    /// Sets panning coefficient. Value must be in -1..+1 range. Where -1 - only left channel will be audible,
259    /// 0 - both, +1 - only right.
260    pub fn set_panning(&mut self, panning: f32) -> f32 {
261        self.panning
262            .set_value_and_mark_modified(panning.clamp(-1.0, 1.0))
263    }
264
265    /// Returns current panning coefficient in -1..+1 range. For more info see `set_panning`. Default value is 0.
266    pub fn panning(&self) -> f32 {
267        *self.panning
268    }
269
270    /// Sets playback status.
271    pub fn set_status(&mut self, status: Status) -> Status {
272        let prev = self.status();
273        match status {
274            Status::Stopped => self.stop(),
275            Status::Playing => self.play(),
276            Status::Paused => self.pause(),
277        }
278        prev
279    }
280
281    /// Returns status of sound source.
282    pub fn status(&self) -> Status {
283        *self.status
284    }
285
286    /// Changes status to `Playing`.
287    pub fn play(&mut self) {
288        self.status.set_value_and_mark_modified(Status::Playing);
289    }
290
291    /// Tries to play a sound. Will succeed, only if the sound is not already playing. Could be useful, if you need
292    /// to prevent the sound to start over until it is fully played.
293    pub fn try_play(&mut self) -> bool {
294        if *self.status == Status::Playing {
295            false
296        } else {
297            self.play();
298            true
299        }
300    }
301
302    /// Changes status to `Paused`
303    pub fn pause(&mut self) {
304        self.status.set_value_and_mark_modified(Status::Paused);
305    }
306
307    /// Enabled or disables sound looping. Looping sound will never stop by itself, but can be stopped or paused
308    /// by calling `stop` or `pause` methods. Useful for music, ambient sounds, etc.
309    pub fn set_looping(&mut self, looping: bool) -> bool {
310        self.looping.set_value_and_mark_modified(looping)
311    }
312
313    /// Returns looping status.
314    pub fn is_looping(&self) -> bool {
315        *self.looping
316    }
317
318    /// Sets sound pitch. Defines "tone" of sounds. Default value is 1.0
319    pub fn set_pitch(&mut self, pitch: f64) -> f64 {
320        self.pitch.set_value_and_mark_modified(pitch.abs())
321    }
322
323    /// Returns pitch of sound source.
324    pub fn pitch(&self) -> f64 {
325        *self.pitch
326    }
327
328    /// Stops sound source. Automatically rewinds streaming buffers.
329    pub fn stop(&mut self) {
330        self.status.set_value_and_mark_modified(Status::Stopped);
331    }
332
333    /// Returns playback time.
334    pub fn playback_time(&self) -> f32 {
335        *self.playback_time
336    }
337
338    /// Sets playback duration.
339    pub fn set_playback_time(&mut self, time: f32) -> f32 {
340        self.playback_time.set_value_and_mark_modified(time)
341    }
342
343    /// Sets radius of imaginable sphere around source in which no distance attenuation is applied.
344    pub fn set_radius(&mut self, radius: f32) -> f32 {
345        self.radius.set_value_and_mark_modified(radius)
346    }
347
348    /// Returns radius of source.
349    pub fn radius(&self) -> f32 {
350        *self.radius
351    }
352
353    /// Sets rolloff factor. Rolloff factor is used in distance attenuation and has different meaning
354    /// in various distance models. It is applicable only for InverseDistance and ExponentDistance
355    /// distance models. See DistanceModel docs for formulae.
356    pub fn set_rolloff_factor(&mut self, rolloff_factor: f32) -> f32 {
357        self.rolloff_factor
358            .set_value_and_mark_modified(rolloff_factor)
359    }
360
361    /// Returns rolloff factor.
362    pub fn rolloff_factor(&self) -> f32 {
363        *self.rolloff_factor
364    }
365
366    /// Sets maximum distance until which distance gain will be applicable. Basically it doing this
367    /// min(max(distance, radius), max_distance) which clamps distance in radius..max_distance range.
368    /// From listener's perspective this will sound like source has stopped decreasing its volume even
369    /// if distance continue to grow.
370    pub fn set_max_distance(&mut self, max_distance: f32) -> f32 {
371        self.max_distance.set_value_and_mark_modified(max_distance)
372    }
373
374    /// Returns max distance.
375    pub fn max_distance(&self) -> f32 {
376        *self.max_distance
377    }
378
379    /// Sets new audio bus name to which the sound will be attached.
380    pub fn set_audio_bus(&mut self, name: String) {
381        self.audio_bus.set_value_and_mark_modified(name);
382    }
383
384    /// Returns the name of an audio bus to which the sound is attached.
385    pub fn audio_bus(&self) -> &str {
386        &self.audio_bus
387    }
388}
389
390impl ConstructorProvider<Node, Graph> for Sound {
391    fn constructor() -> NodeConstructor {
392        NodeConstructor::new::<Self>()
393            .with_variant("Sound Source", |_| {
394                SoundBuilder::new(BaseBuilder::new().with_name("Sound Source"))
395                    .build_node()
396                    .into()
397            })
398            .with_group("Sound")
399    }
400}
401
402impl NodeTrait for Sound {
403    fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
404        AxisAlignedBoundingBox::unit()
405    }
406
407    fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
408        self.local_bounding_box()
409            .transform(&self.global_transform())
410    }
411
412    fn id(&self) -> Uuid {
413        Self::type_uuid()
414    }
415
416    fn on_removed_from_graph(&mut self, graph: &mut Graph) {
417        graph
418            .sound_context
419            .remove_sound(self.native.get(), &self.name);
420        self.native.set(Default::default());
421    }
422
423    fn sync_native(&self, _self_handle: Handle<Node>, context: &mut SyncContext) {
424        context.sound_context.sync_to_sound(
425            _self_handle,
426            self,
427            context.switches.and_then(|s| s.node_overrides.as_ref()),
428        )
429    }
430
431    fn on_global_transform_changed(
432        &self,
433        new_global_transform: &Matrix4<f32>,
434        context: &mut SyncContext,
435    ) {
436        if !m4x4_approx_eq(new_global_transform, &self.global_transform()) {
437            context.sound_context.set_sound_position(self);
438        }
439    }
440
441    fn is_alive(&self) -> bool {
442        if self.is_play_once() {
443            self.status() != Status::Stopped
444        } else {
445            true
446        }
447    }
448
449    fn update(&mut self, context: &mut UpdateContext) {
450        context.sound_context.sync_with_sound(self);
451    }
452
453    fn validate(&self, _scene: &Scene) -> Result<(), String> {
454        match self.buffer.as_ref() {
455            Some(buffer) => {
456                let header = buffer.header();
457                match header.state {
458                    ResourceState::Pending { .. } | ResourceState::Ok { .. } => Ok(()),
459                    ResourceState::Unloaded => {
460                        Err("Sound buffer is unloaded because it was never requested.".to_string())
461                    }
462                    ResourceState::LoadError { ref error, .. } => match &error.0 {
463                        None => {
464                            Err("Sound buffer is failed to load, the reason is unknown!"
465                                .to_string())
466                        }
467                        Some(err) => Err(format!("Sound buffer is failed to load. Reason: {err}")),
468                    },
469                }
470            }
471            None => Err("Sound buffer is not set, the sound won't play!".to_string()),
472        }
473    }
474}
475
476/// Sound builder, allows you to create a new [`Sound`] instance.
477pub struct SoundBuilder {
478    base_builder: BaseBuilder,
479    buffer: Option<SoundBufferResource>,
480    play_once: bool,
481    gain: f32,
482    panning: f32,
483    status: Status,
484    looping: bool,
485    pitch: f64,
486    radius: f32,
487    max_distance: f32,
488    rolloff_factor: f32,
489    playback_time: Duration,
490    spatial_blend: f32,
491    audio_bus: String,
492}
493
494impl SoundBuilder {
495    /// Creates new sound builder.
496    pub fn new(base_builder: BaseBuilder) -> Self {
497        Self {
498            base_builder,
499            buffer: None,
500            play_once: false,
501            gain: 1.0,
502            panning: 0.0,
503            status: Status::Stopped,
504            looping: false,
505            pitch: 1.0,
506            radius: 10.0,
507            max_distance: f32::MAX,
508            rolloff_factor: 1.0,
509            spatial_blend: 1.0,
510            playback_time: Default::default(),
511            audio_bus: AudioBusGraph::PRIMARY_BUS.to_string(),
512        }
513    }
514
515    define_with!(
516        /// Sets desired buffer. See [`Sound::set_buffer`] for more info.
517        fn with_buffer(buffer: Option<SoundBufferResource>)
518    );
519
520    define_with!(
521        /// Sets play-once mode. See [`Sound::set_play_once`] for more info.
522        fn with_play_once(play_once: bool)
523    );
524
525    define_with!(
526        /// Sets desired gain. See [`Sound::set_gain`] for more info.
527        fn with_gain(gain: f32)
528    );
529
530    define_with!(
531        /// Sets desired panning. See [`Sound::set_panning`] for more info.
532        fn with_panning(panning: f32)
533    );
534
535    define_with!(
536        /// Sets desired status. See [`Sound::play`], [`Sound::stop`], [`Sound::stop`] for more info.
537        fn with_status(status: Status)
538    );
539
540    define_with!(
541        /// Sets desired looping. See [`Sound::set_looping`] for more info.
542        fn with_looping(looping: bool)
543    );
544
545    define_with!(
546        /// Sets desired pitch. See [`Sound::set_pitch`] for more info.
547        fn with_pitch(pitch: f64)
548    );
549
550    define_with!(
551        /// Sets desired radius. See [`Sound::set_radius`] for more info.
552        fn with_radius(radius: f32)
553    );
554
555    define_with!(
556        /// Sets desired max distance. See [`Sound::set_max_distance`] for more info.
557        fn with_max_distance(max_distance: f32)
558    );
559
560    define_with!(
561        /// Sets desired rolloff factor. See [`Sound::set_rolloff_factor`] for more info.
562        fn with_rolloff_factor(rolloff_factor: f32)
563    );
564
565    define_with!(
566        /// Sets desired spatial blend factor. See [`Sound::set_spatial_blend`] for more info.
567        fn with_spatial_blend_factor(spatial_blend: f32)
568    );
569
570    define_with!(
571        /// Sets desired playback time. See [`Sound::set_playback_time`] for more info.
572        fn with_playback_time(playback_time: Duration)
573    );
574
575    define_with!(
576        /// Sets desired playback time. See [`Sound::set_audio_bus`] for more info.
577        fn with_audio_bus(audio_bus: String)
578    );
579
580    /// Creates a new [`Sound`] node.
581    #[must_use]
582    pub fn build_sound(self) -> Sound {
583        Sound {
584            base: self.base_builder.build_base(),
585            buffer: self.buffer.into(),
586            play_once: self.play_once.into(),
587            gain: self.gain.into(),
588            panning: self.panning.into(),
589            status: self.status.into(),
590            looping: self.looping.into(),
591            pitch: self.pitch.into(),
592            radius: self.radius.into(),
593            max_distance: self.max_distance.into(),
594            rolloff_factor: self.rolloff_factor.into(),
595            playback_time: self.playback_time.as_secs_f32().into(),
596            spatial_blend: self.spatial_blend.into(),
597            audio_bus: self.audio_bus.into(),
598            native: Default::default(),
599        }
600    }
601
602    /// Creates a new [`Sound`] node.
603    #[must_use]
604    pub fn build_node(self) -> Node {
605        Node::new(self.build_sound())
606    }
607
608    /// Create a new [`Sound`] node and adds it to the graph.
609    pub fn build(self, graph: &mut Graph) -> Handle<Sound> {
610        graph.add_node(self.build_node()).to_variant()
611    }
612}