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