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::BaseSceneGraph;
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)]
77pub struct Sound {
78    base: Base,
79
80    #[reflect(setter = "set_buffer")]
81    buffer: InheritableVariable<Option<SoundBufferResource>>,
82
83    #[reflect(setter = "set_play_once")]
84    play_once: InheritableVariable<bool>,
85
86    #[reflect(min_value = 0.0, step = 0.05)]
87    #[reflect(setter = "set_gain")]
88    gain: InheritableVariable<f32>,
89
90    #[reflect(min_value = -1.0, max_value = 1.0, step = 0.05)]
91    #[reflect(setter = "set_panning")]
92    panning: InheritableVariable<f32>,
93
94    #[reflect(setter = "set_status")]
95    pub(crate) status: InheritableVariable<Status>,
96
97    #[reflect(setter = "set_looping")]
98    looping: InheritableVariable<bool>,
99
100    #[reflect(min_value = 0.0, step = 0.05)]
101    #[reflect(setter = "set_pitch")]
102    pitch: InheritableVariable<f64>,
103
104    #[reflect(min_value = 0.0, step = 0.05)]
105    #[reflect(setter = "set_radius")]
106    radius: InheritableVariable<f32>,
107
108    #[reflect(min_value = 0.0, step = 0.05)]
109    #[reflect(setter = "set_max_distance")]
110    max_distance: InheritableVariable<f32>,
111
112    #[reflect(min_value = 0.0, step = 0.05)]
113    #[reflect(setter = "set_rolloff_factor")]
114    rolloff_factor: InheritableVariable<f32>,
115
116    #[visit(optional)]
117    #[reflect(setter = "set_playback_time", min_value = 0.0)]
118    playback_time: InheritableVariable<f32>,
119
120    #[reflect(setter = "set_spatial_blend")]
121    spatial_blend: InheritableVariable<f32>,
122
123    #[visit(optional)]
124    #[reflect(
125        description = "A name of a sound effect to which the sound will attach to when instantiated."
126    )]
127    audio_bus: InheritableVariable<String>,
128
129    #[reflect(hidden)]
130    #[visit(skip)]
131    pub(crate) native: Cell<Handle<SoundSource>>,
132}
133
134impl Deref for Sound {
135    type Target = Base;
136
137    fn deref(&self) -> &Self::Target {
138        &self.base
139    }
140}
141
142impl DerefMut for Sound {
143    fn deref_mut(&mut self) -> &mut Self::Target {
144        &mut self.base
145    }
146}
147
148impl Default for Sound {
149    fn default() -> Self {
150        Self {
151            base: Default::default(),
152            buffer: InheritableVariable::new_modified(None),
153            play_once: InheritableVariable::new_modified(false),
154            gain: InheritableVariable::new_modified(1.0),
155            panning: InheritableVariable::new_modified(0.0),
156            status: InheritableVariable::new_modified(Status::Stopped),
157            looping: InheritableVariable::new_modified(false),
158            pitch: InheritableVariable::new_modified(1.0),
159            radius: InheritableVariable::new_modified(10.0),
160            max_distance: InheritableVariable::new_modified(f32::MAX),
161            rolloff_factor: InheritableVariable::new_modified(1.0),
162            playback_time: Default::default(),
163            spatial_blend: InheritableVariable::new_modified(1.0),
164            audio_bus: InheritableVariable::new_modified(AudioBusGraph::PRIMARY_BUS.to_string()),
165            native: Default::default(),
166        }
167    }
168}
169
170impl Clone for Sound {
171    fn clone(&self) -> Self {
172        Self {
173            base: self.base.clone(),
174            buffer: self.buffer.clone(),
175            play_once: self.play_once.clone(),
176            gain: self.gain.clone(),
177            panning: self.panning.clone(),
178            status: self.status.clone(),
179            looping: self.looping.clone(),
180            pitch: self.pitch.clone(),
181            radius: self.radius.clone(),
182            max_distance: self.max_distance.clone(),
183            rolloff_factor: self.rolloff_factor.clone(),
184            playback_time: self.playback_time.clone(),
185            spatial_blend: self.spatial_blend.clone(),
186            audio_bus: self.audio_bus.clone(),
187            // Do not copy. The copy will have its own native representation.
188            native: Default::default(),
189        }
190    }
191}
192
193impl TypeUuidProvider for Sound {
194    fn type_uuid() -> Uuid {
195        uuid!("28621735-8cd1-4fad-8faf-ecd24bf8aa99")
196    }
197}
198
199impl Sound {
200    /// Changes buffer of source. Source will continue playing from beginning, old
201    /// position will be discarded.
202    pub fn set_buffer(
203        &mut self,
204        buffer: Option<SoundBufferResource>,
205    ) -> Option<SoundBufferResource> {
206        self.buffer.set_value_and_mark_modified(buffer)
207    }
208
209    /// Returns current buffer if any.
210    pub fn buffer(&self) -> Option<SoundBufferResource> {
211        (*self.buffer).clone()
212    }
213
214    /// Marks buffer for single play. It will be automatically destroyed when it will finish playing.
215    ///
216    /// # Notes
217    ///
218    /// Make sure you not using handles to "play once" sounds, attempt to get reference of "play once" sound
219    /// may result in panic if source already deleted. Looping sources will never be automatically deleted
220    /// because their playback never stops.
221    pub fn set_play_once(&mut self, play_once: bool) -> bool {
222        self.play_once.set_value_and_mark_modified(play_once)
223    }
224
225    /// Returns true if this source is marked for single play, false - otherwise.
226    pub fn is_play_once(&self) -> bool {
227        *self.play_once
228    }
229
230    /// Sets spatial blend factor. It defines how much the source will be 2D and 3D sound at the same
231    /// time. Set it to 0.0 to make the sound fully 2D and 1.0 to make it fully 3D. Middle values
232    /// will make sound proportionally 2D and 3D at the same time.
233    pub fn set_spatial_blend(&mut self, k: f32) -> f32 {
234        self.spatial_blend
235            .set_value_and_mark_modified(k.clamp(0.0, 1.0))
236    }
237
238    /// Returns spatial blend factor.
239    pub fn spatial_blend(&self) -> f32 {
240        *self.spatial_blend
241    }
242
243    /// Sets new gain (volume) of sound. Value should be in 0..1 range, but it is not clamped
244    /// and larger values can be used to "overdrive" sound.
245    ///
246    /// # Notes
247    ///
248    /// Physical volume has non-linear scale (logarithmic) so perception of sound at 0.25 gain
249    /// will be different if logarithmic scale was used.
250    pub fn set_gain(&mut self, gain: f32) -> f32 {
251        self.gain.set_value_and_mark_modified(gain)
252    }
253
254    /// Returns current gain (volume) of sound. Value is in 0..1 range.
255    pub fn gain(&self) -> f32 {
256        *self.gain
257    }
258
259    /// Sets panning coefficient. Value must be in -1..+1 range. Where -1 - only left channel will be audible,
260    /// 0 - both, +1 - only right.
261    pub fn set_panning(&mut self, panning: f32) -> f32 {
262        self.panning
263            .set_value_and_mark_modified(panning.clamp(-1.0, 1.0))
264    }
265
266    /// Returns current panning coefficient in -1..+1 range. For more info see `set_panning`. Default value is 0.
267    pub fn panning(&self) -> f32 {
268        *self.panning
269    }
270
271    /// Sets playback status.    
272    pub fn set_status(&mut self, status: Status) -> Status {
273        let prev = self.status();
274        match status {
275            Status::Stopped => self.stop(),
276            Status::Playing => self.play(),
277            Status::Paused => self.pause(),
278        }
279        prev
280    }
281
282    /// Returns status of sound source.
283    pub fn status(&self) -> Status {
284        *self.status
285    }
286
287    /// Changes status to `Playing`.
288    pub fn play(&mut self) {
289        self.status.set_value_and_mark_modified(Status::Playing);
290    }
291
292    /// Tries to play a sound. Will succeed, only if the sound is not already playing. Could be useful, if you need
293    /// to prevent the sound to start over until it is fully played.
294    pub fn try_play(&mut self) -> bool {
295        if *self.status == Status::Playing {
296            false
297        } else {
298            self.play();
299            true
300        }
301    }
302
303    /// Changes status to `Paused`
304    pub fn pause(&mut self) {
305        self.status.set_value_and_mark_modified(Status::Paused);
306    }
307
308    /// Enabled or disables sound looping. Looping sound will never stop by itself, but can be stopped or paused
309    /// by calling `stop` or `pause` methods. Useful for music, ambient sounds, etc.
310    pub fn set_looping(&mut self, looping: bool) -> bool {
311        self.looping.set_value_and_mark_modified(looping)
312    }
313
314    /// Returns looping status.
315    pub fn is_looping(&self) -> bool {
316        *self.looping
317    }
318
319    /// Sets sound pitch. Defines "tone" of sounds. Default value is 1.0
320    pub fn set_pitch(&mut self, pitch: f64) -> f64 {
321        self.pitch.set_value_and_mark_modified(pitch.abs())
322    }
323
324    /// Returns pitch of sound source.
325    pub fn pitch(&self) -> f64 {
326        *self.pitch
327    }
328
329    /// Stops sound source. Automatically rewinds streaming buffers.
330    pub fn stop(&mut self) {
331        self.status.set_value_and_mark_modified(Status::Stopped);
332    }
333
334    /// Returns playback time.
335    pub fn playback_time(&self) -> f32 {
336        *self.playback_time
337    }
338
339    /// Sets playback duration.
340    pub fn set_playback_time(&mut self, time: f32) -> f32 {
341        self.playback_time.set_value_and_mark_modified(time)
342    }
343
344    /// Sets radius of imaginable sphere around source in which no distance attenuation is applied.
345    pub fn set_radius(&mut self, radius: f32) -> f32 {
346        self.radius.set_value_and_mark_modified(radius)
347    }
348
349    /// Returns radius of source.
350    pub fn radius(&self) -> f32 {
351        *self.radius
352    }
353
354    /// Sets rolloff factor. Rolloff factor is used in distance attenuation and has different meaning
355    /// in various distance models. It is applicable only for InverseDistance and ExponentDistance
356    /// distance models. See DistanceModel docs for formulae.
357    pub fn set_rolloff_factor(&mut self, rolloff_factor: f32) -> f32 {
358        self.rolloff_factor
359            .set_value_and_mark_modified(rolloff_factor)
360    }
361
362    /// Returns rolloff factor.
363    pub fn rolloff_factor(&self) -> f32 {
364        *self.rolloff_factor
365    }
366
367    /// Sets maximum distance until which distance gain will be applicable. Basically it doing this
368    /// min(max(distance, radius), max_distance) which clamps distance in radius..max_distance range.
369    /// From listener's perspective this will sound like source has stopped decreasing its volume even
370    /// if distance continue to grow.
371    pub fn set_max_distance(&mut self, max_distance: f32) -> f32 {
372        self.max_distance.set_value_and_mark_modified(max_distance)
373    }
374
375    /// Returns max distance.
376    pub fn max_distance(&self) -> f32 {
377        *self.max_distance
378    }
379
380    /// Sets new audio bus name to which the sound will be attached.
381    pub fn set_audio_bus(&mut self, name: String) {
382        self.audio_bus.set_value_and_mark_modified(name);
383    }
384
385    /// Returns the name of an audio bus to which the sound is attached.
386    pub fn audio_bus(&self) -> &str {
387        &self.audio_bus
388    }
389}
390
391impl ConstructorProvider<Node, Graph> for Sound {
392    fn constructor() -> NodeConstructor {
393        NodeConstructor::new::<Self>()
394            .with_variant("Sound Source", |_| {
395                SoundBuilder::new(BaseBuilder::new().with_name("Sound Source"))
396                    .build_node()
397                    .into()
398            })
399            .with_group("Sound")
400    }
401}
402
403impl NodeTrait for Sound {
404    fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
405        AxisAlignedBoundingBox::unit()
406    }
407
408    fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
409        self.local_bounding_box()
410            .transform(&self.global_transform())
411    }
412
413    fn id(&self) -> Uuid {
414        Self::type_uuid()
415    }
416
417    fn on_removed_from_graph(&mut self, graph: &mut Graph) {
418        graph
419            .sound_context
420            .remove_sound(self.native.get(), &self.name);
421        self.native.set(Default::default());
422    }
423
424    fn sync_native(&self, _self_handle: Handle<Node>, context: &mut SyncContext) {
425        context.sound_context.sync_to_sound(
426            _self_handle,
427            self,
428            context.switches.and_then(|s| s.node_overrides.as_ref()),
429        )
430    }
431
432    fn on_global_transform_changed(
433        &self,
434        new_global_transform: &Matrix4<f32>,
435        context: &mut SyncContext,
436    ) {
437        if !m4x4_approx_eq(new_global_transform, &self.global_transform()) {
438            context.sound_context.set_sound_position(self);
439        }
440    }
441
442    fn is_alive(&self) -> bool {
443        if self.is_play_once() {
444            self.status() != Status::Stopped
445        } else {
446            true
447        }
448    }
449
450    fn update(&mut self, context: &mut UpdateContext) {
451        context.sound_context.sync_with_sound(self);
452    }
453
454    fn validate(&self, _scene: &Scene) -> Result<(), String> {
455        match self.buffer.as_ref() {
456            Some(buffer) => {
457                let header = buffer.header();
458                match header.state {
459                    ResourceState::Pending { .. } | ResourceState::Ok(_) => Ok(()),
460                    ResourceState::LoadError { ref error, .. } => {
461                        match &error.0 {
462                            None => Err("Sound buffer is failed to load, the reason is unknown!"
463                                .to_string()),
464                            Some(err) => {
465                                Err(format!("Sound buffer is failed to load. Reason: {err:?}"))
466                            }
467                        }
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<Node> {
610        graph.add_node(self.build_node())
611    }
612}