fyrox_impl/scene/sound/
context.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//! Sound context.
22
23use crate::{
24    core::{
25        log::{Log, MessageKind},
26        pool::Handle,
27        visitor::prelude::*,
28    },
29    scene::{node::Node, sound::Sound},
30};
31use fxhash::FxHashSet;
32use fyrox_sound::{
33    bus::AudioBusGraph,
34    context::DistanceModel,
35    renderer::Renderer,
36    source::{SoundSource, SoundSourceBuilder, Status},
37};
38use std::{sync::MutexGuard, time::Duration};
39
40/// Sound context.
41#[derive(Debug, Visit)]
42pub struct SoundContext {
43    #[visit(optional)]
44    pub(crate) native: fyrox_sound::context::SoundContext,
45}
46
47/// Proxy for guarded access to the sound context.
48pub struct SoundContextGuard<'a> {
49    guard: MutexGuard<'a, fyrox_sound::context::State>,
50}
51
52impl SoundContextGuard<'_> {
53    /// Returns a reference to the audio bus graph.
54    pub fn bus_graph_ref(&self) -> &AudioBusGraph {
55        self.guard.bus_graph_ref()
56    }
57
58    /// Returns a reference to the audio bus graph.
59    pub fn bus_graph_mut(&mut self) -> &mut AudioBusGraph {
60        self.guard.bus_graph_mut()
61    }
62
63    /// Pause/unpause the sound context. Paused context won't play any sounds.
64    pub fn pause(&mut self, pause: bool) {
65        self.guard.pause(pause);
66    }
67
68    /// Returns true if the sound context is paused, false - otherwise.
69    pub fn is_paused(&self) -> bool {
70        self.guard.is_paused()
71    }
72
73    /// Sets new distance model.
74    pub fn set_distance_model(&mut self, distance_model: DistanceModel) {
75        self.guard.set_distance_model(distance_model);
76    }
77
78    /// Returns current distance model.
79    pub fn distance_model(&self) -> DistanceModel {
80        self.guard.distance_model()
81    }
82
83    /// Normalizes given frequency using context's sampling rate. Normalized frequency then can be used
84    /// to create filters.
85    pub fn normalize_frequency(&self, f: f32) -> f32 {
86        self.guard.normalize_frequency(f)
87    }
88
89    /// Returns amount of time context spent on rendering all sound sources.
90    pub fn full_render_duration(&self) -> Duration {
91        self.guard.full_render_duration()
92    }
93
94    /// Returns current renderer.
95    pub fn renderer(&self) -> Renderer {
96        self.guard.renderer().clone()
97    }
98
99    /// Returns current renderer.
100    pub fn renderer_ref(&self) -> &Renderer {
101        self.guard.renderer()
102    }
103
104    /// Returns current renderer.
105    pub fn renderer_ref_mut(&mut self) -> &mut Renderer {
106        self.guard.renderer_mut()
107    }
108
109    /// Sets new renderer.
110    pub fn set_renderer(&mut self, renderer: Renderer) -> Renderer {
111        self.guard.set_renderer(renderer)
112    }
113
114    /// Destroys all backing sound entities.
115    pub fn destroy_sound_sources(&mut self) {
116        self.guard.sources_mut().clear();
117    }
118}
119
120impl Default for SoundContext {
121    fn default() -> Self {
122        let native = fyrox_sound::context::SoundContext::new();
123        let mut state = native.state();
124        // There's no need to serialize native sources, because they'll be re-created automatically.
125        state.serialization_options.skip_sources = true;
126        drop(state);
127        Self { native }
128    }
129}
130
131impl SoundContext {
132    pub(crate) fn new() -> Self {
133        Default::default()
134    }
135
136    /// Creates a full copy of the context, instead of shallow that could be done via [`Clone::clone`]
137    pub fn deep_clone(&self) -> Self {
138        Self {
139            native: self.native.deep_clone(),
140        }
141    }
142
143    /// Returns locked inner state of the sound context.
144    pub fn state(&self) -> SoundContextGuard {
145        SoundContextGuard {
146            guard: self.native.state(),
147        }
148    }
149
150    pub(crate) fn remove_sound(&mut self, sound: Handle<SoundSource>, name: &str) {
151        let mut state = self.native.state();
152        if state.is_valid_handle(sound) {
153            state.remove_source(sound);
154
155            Log::info(format!("Native sound source was removed for node: {name}"));
156        }
157    }
158
159    pub(crate) fn set_sound_position(&mut self, sound: &Sound) {
160        if let Some(source) = self.native.state().try_get_source_mut(sound.native.get()) {
161            source.set_position(sound.global_position());
162        }
163    }
164
165    pub(crate) fn sync_with_sound(&self, sound: &mut Sound) {
166        if let Some(source) = self.native.state().try_get_source_mut(sound.native.get()) {
167            // Sync back.
168            sound.status.set_value_silent(source.status());
169            sound
170                .playback_time
171                .set_value_silent(source.playback_time().as_secs_f32());
172        }
173    }
174
175    pub(crate) fn sync_to_sound(
176        &mut self,
177        sound_handle: Handle<Node>,
178        sound: &Sound,
179        node_overrides: Option<&FxHashSet<Handle<Node>>>,
180    ) {
181        if !sound.is_globally_enabled()
182            || !node_overrides.map_or(true, |f| f.contains(&sound_handle))
183        {
184            self.remove_sound(sound.native.get(), &sound.name);
185            sound.native.set(Default::default());
186            return;
187        }
188
189        if sound.native.get().is_some() {
190            let mut state = self.native.state();
191            let source = state.source_mut(sound.native.get());
192            sound.buffer.try_sync_model(|v| {
193                Log::verify(source.set_buffer(v));
194            });
195            sound.max_distance.try_sync_model(|v| {
196                source.set_max_distance(v);
197            });
198            sound.rolloff_factor.try_sync_model(|v| {
199                source.set_rolloff_factor(v);
200            });
201            sound.radius.try_sync_model(|v| {
202                source.set_radius(v);
203            });
204            sound.playback_time.try_sync_model(|v| {
205                source.set_playback_time(Duration::from_secs_f32(v));
206            });
207            sound.pitch.try_sync_model(|v| {
208                source.set_pitch(v);
209            });
210            sound.looping.try_sync_model(|v| {
211                source.set_looping(v);
212            });
213            sound.panning.try_sync_model(|v| {
214                source.set_panning(v);
215            });
216            sound.gain.try_sync_model(|v| {
217                source.set_gain(v);
218            });
219            sound
220                .spatial_blend
221                .try_sync_model(|v| source.set_spatial_blend(v));
222            sound.status.try_sync_model(|v| match v {
223                Status::Stopped => {
224                    Log::verify(source.stop());
225                }
226                Status::Playing => {
227                    source.play();
228                }
229                Status::Paused => {
230                    source.pause();
231                }
232            });
233            sound.audio_bus.try_sync_model(|audio_bus| {
234                source.set_bus(audio_bus);
235            });
236        } else {
237            match SoundSourceBuilder::new()
238                .with_gain(sound.gain())
239                .with_opt_buffer(sound.buffer())
240                .with_looping(sound.is_looping())
241                .with_panning(sound.panning())
242                .with_pitch(sound.pitch())
243                .with_status(sound.status())
244                .with_playback_time(Duration::from_secs_f32(sound.playback_time()))
245                .with_position(sound.global_position())
246                .with_radius(sound.radius())
247                .with_max_distance(sound.max_distance())
248                .with_bus(sound.audio_bus())
249                .with_rolloff_factor(sound.rolloff_factor())
250                .build()
251            {
252                Ok(source) => {
253                    sound.native.set(self.native.state().add_source(source));
254
255                    Log::writeln(
256                        MessageKind::Information,
257                        format!("Native sound source was created for node: {}", sound.name()),
258                    );
259                }
260                Err(err) => {
261                    Log::writeln(
262                        MessageKind::Error,
263                        format!(
264                            "Unable to create native sound source for node: {}. Reason: {:?}",
265                            sound.name(),
266                            err
267                        ),
268                    );
269                }
270            }
271        }
272    }
273}