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}