Skip to main content

fyrox_sound/
engine.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 engine module
22//!
23//! ## Overview
24//!
25//! Sound engine manages contexts, feeds output device with data.
26
27use crate::context::{SoundContext, SAMPLE_RATE};
28use fyrox_core::visitor::{Visit, VisitResult, Visitor};
29use fyrox_core::SafeLock;
30use std::error::Error;
31use std::sync::{Arc, Mutex, MutexGuard};
32
33/// Sound engine manages contexts, feeds output device with data. Sound engine instance can be cloned,
34/// however this is always a "shallow" clone, because actual sound engine data is wrapped in Arc.
35#[derive(Clone)]
36pub struct SoundEngine(Arc<Mutex<State>>);
37
38impl Default for SoundEngine {
39    fn default() -> Self {
40        Self::without_device()
41    }
42}
43
44/// Internal state of the sound engine.
45pub struct State {
46    contexts: Vec<SoundContext>,
47    output_device: Option<tinyaudio::OutputDevice>,
48}
49
50impl SoundEngine {
51    /// Creates new instance of the sound engine. It is possible to have multiple engines running at
52    /// the same time, but you shouldn't do this because you can create multiple contexts which
53    /// should cover 99% of use cases.
54    pub fn new() -> Result<Self, Box<dyn Error>> {
55        let engine = Self::without_device();
56        engine.initialize_audio_output_device()?;
57        Ok(engine)
58    }
59
60    /// Creates new instance of a sound engine without OS audio output device (so called headless mode).
61    /// The user should periodically run [`State::render`] if they want to implement their own sample sending
62    /// method to an output device (or a file, etc.).
63    pub fn without_device() -> Self {
64        Self(Arc::new(Mutex::new(State {
65            contexts: Default::default(),
66            output_device: None,
67        })))
68    }
69
70    /// Tries to initialize default audio output device.
71    pub fn initialize_audio_output_device(&self) -> Result<(), Box<dyn Error>> {
72        let state = self.clone();
73
74        let device = tinyaudio::run_output_device(
75            tinyaudio::OutputDeviceParameters {
76                sample_rate: SAMPLE_RATE as usize,
77                channels_count: 2,
78                channel_sample_count: SoundContext::SAMPLES_PER_CHANNEL,
79            },
80            {
81                move |buf| {
82                    // SAFETY: This is safe as long as channels count above is 2.
83                    let data = unsafe {
84                        std::slice::from_raw_parts_mut(
85                            buf.as_mut_ptr() as *mut (f32, f32),
86                            buf.len() / 2,
87                        )
88                    };
89
90                    state.state().render(data);
91                }
92            },
93        )?;
94
95        self.state().output_device = Some(device);
96
97        Ok(())
98    }
99
100    /// Destroys current audio output device (if any).
101    pub fn destroy_audio_output_device(&self) {
102        self.state().output_device = None;
103    }
104
105    /// Provides direct access to actual engine data.
106    pub fn state(&self) -> MutexGuard<State> {
107        self.0.safe_lock().unwrap()
108    }
109}
110
111impl State {
112    /// Adds new context to the engine. Each context must be added to the engine to emit
113    /// sounds.
114    pub fn add_context(&mut self, context: SoundContext) {
115        self.contexts.push(context);
116    }
117
118    /// Removes a context from the engine. Removed context will no longer produce any sound.
119    pub fn remove_context(&mut self, context: SoundContext) {
120        if let Some(position) = self.contexts.iter().position(|c| c == &context) {
121            self.contexts.remove(position);
122        }
123    }
124
125    /// Removes all contexts from the engine.
126    pub fn remove_all_contexts(&mut self) {
127        self.contexts.clear()
128    }
129
130    /// Checks if a context is registered in the engine.
131    pub fn has_context(&self, context: &SoundContext) -> bool {
132        self.contexts
133            .iter()
134            .any(|c| Arc::ptr_eq(c.state.as_ref().unwrap(), context.state.as_ref().unwrap()))
135    }
136
137    /// Returns a reference to context container.
138    pub fn contexts(&self) -> &[SoundContext] {
139        &self.contexts
140    }
141
142    /// Returns the length of buf to be passed to [`Self::render()`].
143    pub fn render_buffer_len() -> usize {
144        SoundContext::SAMPLES_PER_CHANNEL
145    }
146
147    /// Renders the sound into buf. The buf must have at least [`Self::render_buffer_len()`]
148    /// elements. This method must be used if and only if the engine was created via
149    /// [`SoundEngine::without_device`].
150    ///
151    /// ## Deadlocks
152    ///
153    /// This method internally locks added sound contexts so it must be called when all the contexts
154    /// are unlocked or you'll get a deadlock.
155    pub fn render(&mut self, buf: &mut [(f32, f32)]) {
156        buf.fill((0.0, 0.0));
157        self.render_inner(buf);
158    }
159
160    fn render_inner(&mut self, buf: &mut [(f32, f32)]) {
161        for context in self.contexts.iter_mut() {
162            context.state().render(buf);
163        }
164    }
165}
166
167impl Visit for State {
168    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
169        if visitor.is_reading() {
170            self.contexts.clear();
171        }
172
173        let mut region = visitor.enter_region(name)?;
174
175        self.contexts.visit("Contexts", &mut region)?;
176
177        Ok(())
178    }
179}