mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
ability to create/destroy audio output device on demand
- this commit finalizes adding audio support for android - fixes #435
This commit is contained in:
+1
-2
@@ -526,11 +526,10 @@ impl Editor {
|
||||
graphics_context_params,
|
||||
resource_manager: ResourceManager::new(serialization_context.clone()),
|
||||
serialization_context,
|
||||
headless: false,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Editor cannot run on Android so we can safely call `resume` here.
|
||||
// Editor cannot run on Android so we can safely initialize graphics context here.
|
||||
engine.initialize_graphics_context(event_loop).unwrap();
|
||||
|
||||
let graphics_context = engine.graphics_context.as_initialized_mut();
|
||||
|
||||
@@ -203,7 +203,6 @@ fn main() {
|
||||
graphics_context_params,
|
||||
resource_manager: ResourceManager::new(serialization_context.clone()),
|
||||
serialization_context,
|
||||
headless: false,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ fn main() {
|
||||
graphics_context_params,
|
||||
resource_manager: ResourceManager::new(serialization_context.clone()),
|
||||
serialization_context,
|
||||
headless: false,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -121,7 +121,6 @@ fn main() {
|
||||
graphics_context_params,
|
||||
resource_manager: ResourceManager::new(serialization_context.clone()),
|
||||
serialization_context,
|
||||
headless: false,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -305,7 +305,6 @@ fn main() {
|
||||
graphics_context_params,
|
||||
resource_manager: ResourceManager::new(serialization_context.clone()),
|
||||
serialization_context,
|
||||
headless: false,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -159,7 +159,6 @@ fn main() {
|
||||
graphics_context_params,
|
||||
resource_manager: ResourceManager::new(serialization_context.clone()),
|
||||
serialization_context,
|
||||
headless: false,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -93,7 +93,6 @@ fn main() {
|
||||
graphics_context_params,
|
||||
resource_manager: ResourceManager::new(serialization_context.clone()),
|
||||
serialization_context,
|
||||
headless: false,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -138,7 +138,6 @@ impl Game {
|
||||
graphics_context_params,
|
||||
resource_manager: ResourceManager::new(serialization_context.clone()),
|
||||
serialization_context,
|
||||
headless: false,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -338,7 +338,6 @@ fn main() {
|
||||
graphics_context_params,
|
||||
resource_manager: ResourceManager::new(serialization_context.clone()),
|
||||
serialization_context,
|
||||
headless: false,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ use std::{
|
||||
|
||||
fn main() {
|
||||
// Initialize sound engine with default output device.
|
||||
let engine = SoundEngine::new();
|
||||
let engine = SoundEngine::new().unwrap();
|
||||
|
||||
let hrir_sphere =
|
||||
HrirSphere::from_file("examples/data/IRC_1002_C.bin", context::SAMPLE_RATE).unwrap();
|
||||
@@ -24,7 +24,7 @@ fn main() {
|
||||
// Initialize new sound context with default output device.
|
||||
let context = SoundContext::new();
|
||||
|
||||
engine.lock().unwrap().add_context(context.clone());
|
||||
engine.state().add_context(context.clone());
|
||||
|
||||
// Set HRTF renderer instead of default.
|
||||
context
|
||||
|
||||
@@ -14,12 +14,12 @@ use std::{
|
||||
|
||||
fn main() {
|
||||
// Initialize sound engine with default output device.
|
||||
let engine = SoundEngine::new();
|
||||
let engine = SoundEngine::new().unwrap();
|
||||
|
||||
// Initialize new sound context.
|
||||
let context = SoundContext::new();
|
||||
|
||||
engine.lock().unwrap().add_context(context.clone());
|
||||
engine.state().add_context(context.clone());
|
||||
|
||||
// Load sound buffer.
|
||||
let drop_buffer = SoundBufferResource::new_generic(
|
||||
|
||||
@@ -9,13 +9,13 @@ use std::{thread, time::Duration};
|
||||
|
||||
fn main() {
|
||||
// Initialize sound engine with default output device.
|
||||
let engine = SoundEngine::new();
|
||||
let engine = SoundEngine::new().unwrap();
|
||||
|
||||
// Create new context.
|
||||
let context = SoundContext::new();
|
||||
|
||||
// Register context in the engine.
|
||||
engine.lock().unwrap().add_context(context.clone());
|
||||
engine.state().add_context(context.clone());
|
||||
|
||||
// Load sound buffer.
|
||||
let door_open_buffer = SoundBufferResource::new_generic(
|
||||
|
||||
@@ -14,12 +14,12 @@ use std::{
|
||||
|
||||
fn main() {
|
||||
// Initialize sound engine with default output device.
|
||||
let engine = SoundEngine::new();
|
||||
let engine = SoundEngine::new().unwrap();
|
||||
|
||||
// Initialize new sound context.
|
||||
let context = SoundContext::new();
|
||||
|
||||
engine.lock().unwrap().add_context(context.clone());
|
||||
engine.state().add_context(context.clone());
|
||||
|
||||
// Load sound buffer.
|
||||
let drop_buffer = SoundBufferResource::new_generic(
|
||||
|
||||
@@ -8,12 +8,12 @@ use std::{thread, time::Duration};
|
||||
|
||||
fn main() {
|
||||
// Initialize sound engine with default output device.
|
||||
let engine = SoundEngine::new();
|
||||
let engine = SoundEngine::new().unwrap();
|
||||
|
||||
// Initialize new sound context.
|
||||
let context = SoundContext::new();
|
||||
|
||||
engine.lock().unwrap().add_context(context.clone());
|
||||
engine.state().add_context(context.clone());
|
||||
|
||||
// Create sine wave.
|
||||
let sample_rate = 44100;
|
||||
|
||||
@@ -52,12 +52,12 @@ impl RawStreamingDataSource for SamplesGenerator {
|
||||
|
||||
fn main() {
|
||||
// Initialize sound engine with default output device.
|
||||
let engine = SoundEngine::new();
|
||||
let engine = SoundEngine::new().unwrap();
|
||||
|
||||
// Initialize new sound context.
|
||||
let context = SoundContext::new();
|
||||
|
||||
engine.lock().unwrap().add_context(context.clone());
|
||||
engine.state().add_context(context.clone());
|
||||
|
||||
// Create sine wave generator
|
||||
let sine_wave = DataSource::RawStreaming(Box::new(SamplesGenerator::new()));
|
||||
|
||||
@@ -19,12 +19,12 @@ fn main() {
|
||||
HrirSphere::from_file("examples/data/IRC_1002_C.bin", context::SAMPLE_RATE).unwrap();
|
||||
|
||||
// Initialize sound engine with default output device.
|
||||
let engine = SoundEngine::new();
|
||||
let engine = SoundEngine::new().unwrap();
|
||||
|
||||
// Initialize new sound context.
|
||||
let context = SoundContext::new();
|
||||
|
||||
engine.lock().unwrap().add_context(context.clone());
|
||||
engine.state().add_context(context.clone());
|
||||
|
||||
// Set HRTF renderer instead of default for binaural sound.
|
||||
context
|
||||
|
||||
@@ -10,12 +10,12 @@ use std::{thread, time::Duration};
|
||||
|
||||
fn main() {
|
||||
// Initialize sound engine with default output device.
|
||||
let engine = SoundEngine::new();
|
||||
let engine = SoundEngine::new().unwrap();
|
||||
|
||||
// Initialize new sound context.
|
||||
let context = SoundContext::new();
|
||||
|
||||
engine.lock().unwrap().add_context(context.clone());
|
||||
engine.state().add_context(context.clone());
|
||||
|
||||
// Load sound buffer.
|
||||
let waterfall_buffer = SoundBufferResource::new_streaming(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use fyrox_sound::engine::State;
|
||||
use fyrox_sound::{
|
||||
buffer::{DataSource, SoundBufferResource},
|
||||
context::SoundContext,
|
||||
@@ -14,7 +15,7 @@ fn main() {
|
||||
let context = SoundContext::new();
|
||||
|
||||
// Register context in the engine.
|
||||
engine.lock().unwrap().add_context(context.clone());
|
||||
engine.state().add_context(context.clone());
|
||||
|
||||
// Load sound buffer.
|
||||
let door_open_buffer = SoundBufferResource::new_generic(
|
||||
@@ -46,13 +47,13 @@ fn main() {
|
||||
let mut wav_writer = hound::WavWriter::create("output.wav", wav_spec).unwrap();
|
||||
|
||||
// Create an output buffer.
|
||||
let buf_len = SoundEngine::render_buffer_len();
|
||||
let buf_len = State::render_buffer_len();
|
||||
let mut buf = vec![(0.0f32, 0.0f32); buf_len];
|
||||
let mut samples_written = 0;
|
||||
|
||||
// Wait until sound will play completely.
|
||||
while samples_written < 3 * fyrox_sound::context::SAMPLE_RATE {
|
||||
engine.lock().unwrap().render(&mut buf);
|
||||
engine.state().render(&mut buf);
|
||||
for &(l, r) in buf.iter() {
|
||||
wav_writer.write_sample(l).unwrap();
|
||||
wav_writer.write_sample(r).unwrap();
|
||||
|
||||
+66
-54
@@ -6,11 +6,22 @@
|
||||
|
||||
use crate::context::{SoundContext, SAMPLE_RATE};
|
||||
use fyrox_core::visitor::{Visit, VisitResult, Visitor};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::error::Error;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
/// Internal state of sound engine.
|
||||
#[derive(Default)]
|
||||
pub struct SoundEngine {
|
||||
/// Sound engine manages contexts, feeds output device with data. Sound engine instance can be cloned,
|
||||
/// however this is always a "shallow" clone, because actual sound engine data is wrapped in Arc.
|
||||
#[derive(Clone)]
|
||||
pub struct SoundEngine(Arc<Mutex<State>>);
|
||||
|
||||
impl Default for SoundEngine {
|
||||
fn default() -> Self {
|
||||
Self::without_device()
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal state of the sound engine.
|
||||
pub struct State {
|
||||
contexts: Vec<SoundContext>,
|
||||
output_device: Option<Box<dyn tinyaudio::BaseAudioOutputDevice>>,
|
||||
}
|
||||
@@ -19,63 +30,64 @@ impl SoundEngine {
|
||||
/// Creates new instance of the sound engine. It is possible to have multiple engines running at
|
||||
/// the same time, but you shouldn't do this because you can create multiple contexts which
|
||||
/// should cover 99% of use cases.
|
||||
pub fn new() -> Arc<Mutex<Self>> {
|
||||
Self::new_inner(false)
|
||||
pub fn new() -> Result<Self, Box<dyn Error>> {
|
||||
let engine = Self::without_device();
|
||||
engine.initialize_audio_output_device()?;
|
||||
Ok(engine)
|
||||
}
|
||||
|
||||
/// Like `new()` but won't use any OS sound devices.
|
||||
/// Can be useful for running on CI where those might not be available.
|
||||
pub fn new_headless() -> Arc<Mutex<Self>> {
|
||||
Self::new_inner(true)
|
||||
}
|
||||
|
||||
fn new_inner(headless: bool) -> Arc<Mutex<Self>> {
|
||||
let engine = Arc::new(Mutex::new(Self {
|
||||
/// Creates new instance of a sound engine without OS audio output device (so called headless mode).
|
||||
/// The user should periodically run [`Self::render`] if they want to implement their own sample sending
|
||||
/// method to an output device (or a file, etc.).
|
||||
pub fn without_device() -> Self {
|
||||
Self(Arc::new(Mutex::new(State {
|
||||
contexts: Default::default(),
|
||||
output_device: None,
|
||||
}));
|
||||
|
||||
if !headless {
|
||||
let device = tinyaudio::run_output_device(
|
||||
tinyaudio::OutputDeviceParameters {
|
||||
sample_rate: SAMPLE_RATE as usize,
|
||||
channels_count: 2,
|
||||
channel_sample_count: SoundContext::SAMPLES_PER_CHANNEL,
|
||||
},
|
||||
{
|
||||
let state = engine.clone();
|
||||
move |buf| {
|
||||
// SAFETY: This is safe as long as channels count above is 2.
|
||||
let data = unsafe {
|
||||
std::slice::from_raw_parts_mut(
|
||||
buf.as_mut_ptr() as *mut (f32, f32),
|
||||
buf.len() / 2,
|
||||
)
|
||||
};
|
||||
|
||||
if let Ok(mut state) = state.lock() {
|
||||
state.render(data);
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
engine.lock().unwrap().output_device = Some(device);
|
||||
}
|
||||
|
||||
engine
|
||||
})))
|
||||
}
|
||||
|
||||
/// Creates new instance of a sound engine without running a device thread. The user must
|
||||
/// periodically run [`Self::render`].
|
||||
pub fn without_device() -> Arc<Mutex<Self>> {
|
||||
Arc::new(Mutex::new(Self {
|
||||
contexts: Default::default(),
|
||||
output_device: None,
|
||||
}))
|
||||
/// Tries to initialize default audio output device.
|
||||
pub fn initialize_audio_output_device(&self) -> Result<(), Box<dyn Error>> {
|
||||
let state = self.clone();
|
||||
|
||||
let device = tinyaudio::run_output_device(
|
||||
tinyaudio::OutputDeviceParameters {
|
||||
sample_rate: SAMPLE_RATE as usize,
|
||||
channels_count: 2,
|
||||
channel_sample_count: SoundContext::SAMPLES_PER_CHANNEL,
|
||||
},
|
||||
{
|
||||
move |buf| {
|
||||
// SAFETY: This is safe as long as channels count above is 2.
|
||||
let data = unsafe {
|
||||
std::slice::from_raw_parts_mut(
|
||||
buf.as_mut_ptr() as *mut (f32, f32),
|
||||
buf.len() / 2,
|
||||
)
|
||||
};
|
||||
|
||||
state.state().render(data);
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
self.state().output_device = Some(device);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Destroys current audio output device (if any).
|
||||
pub fn destroy_audio_output_device(&self) {
|
||||
self.state().output_device = None;
|
||||
}
|
||||
|
||||
/// Provides direct access to actual engine data.
|
||||
pub fn state(&self) -> MutexGuard<State> {
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Adds new context to the engine. Each context must be added to the engine to emit
|
||||
/// sounds.
|
||||
pub fn add_context(&mut self, context: SoundContext) {
|
||||
@@ -131,7 +143,7 @@ impl SoundEngine {
|
||||
}
|
||||
}
|
||||
|
||||
impl Visit for SoundEngine {
|
||||
impl Visit for State {
|
||||
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
|
||||
if visitor.is_reading() {
|
||||
self.contexts.clear();
|
||||
|
||||
@@ -73,7 +73,6 @@ impl Executor {
|
||||
graphics_context_params,
|
||||
resource_manager: ResourceManager::new(serialization_context.clone()),
|
||||
serialization_context,
|
||||
headless: false,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
||||
+8
-16
@@ -57,7 +57,7 @@ use std::{
|
||||
ops::Deref,
|
||||
sync::{
|
||||
mpsc::{channel, Receiver},
|
||||
Arc, Mutex,
|
||||
Arc,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
@@ -201,7 +201,7 @@ pub struct Engine {
|
||||
model_events_receiver: Receiver<ResourceEvent<Model>>,
|
||||
|
||||
#[allow(dead_code)] // Keep engine instance alive.
|
||||
sound_engine: Arc<Mutex<SoundEngine>>,
|
||||
sound_engine: SoundEngine,
|
||||
|
||||
// A set of plugin constructors.
|
||||
plugin_constructors: Vec<Box<dyn PluginConstructor>>,
|
||||
@@ -705,12 +705,6 @@ pub struct EngineInitParams {
|
||||
pub serialization_context: Arc<SerializationContext>,
|
||||
/// A resource manager.
|
||||
pub resource_manager: ResourceManager,
|
||||
/// (experimental) Run the engine without opening a window (TODO) and without sound.
|
||||
/// Useful for dedicated game servers or running on CI.
|
||||
///
|
||||
/// Headless support is incomplete, for progress see
|
||||
/// <https://github.com/FyroxEngine/Fyrox/issues/222>.
|
||||
pub headless: bool,
|
||||
}
|
||||
|
||||
macro_rules! define_process_node {
|
||||
@@ -818,7 +812,6 @@ impl Engine {
|
||||
/// graphics_context_params,
|
||||
/// resource_manager: ResourceManager::new(serialization_context.clone()),
|
||||
/// serialization_context,
|
||||
/// headless: false,
|
||||
/// })
|
||||
/// .unwrap();
|
||||
/// ```
|
||||
@@ -829,15 +822,8 @@ impl Engine {
|
||||
graphics_context_params,
|
||||
serialization_context,
|
||||
resource_manager,
|
||||
headless,
|
||||
} = params;
|
||||
|
||||
let sound_engine = if headless {
|
||||
SoundEngine::new_headless()
|
||||
} else {
|
||||
SoundEngine::new()
|
||||
};
|
||||
|
||||
let (rx, tx) = channel();
|
||||
resource_manager
|
||||
.state()
|
||||
@@ -846,6 +832,8 @@ impl Engine {
|
||||
.event_broadcaster
|
||||
.add(rx);
|
||||
|
||||
let sound_engine = SoundEngine::without_device();
|
||||
|
||||
Ok(Self {
|
||||
graphics_context: GraphicsContext::Uninitialized(graphics_context_params),
|
||||
model_events_receiver: tx,
|
||||
@@ -1017,6 +1005,8 @@ impl Engine {
|
||||
params: params.clone(),
|
||||
});
|
||||
|
||||
self.sound_engine.initialize_audio_output_device()?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(EngineError::Custom(
|
||||
@@ -1060,6 +1050,8 @@ impl Engine {
|
||||
vsync: params.vsync,
|
||||
});
|
||||
|
||||
self.sound_engine.destroy_audio_output_device();
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(EngineError::Custom(
|
||||
|
||||
+5
-7
@@ -64,7 +64,7 @@ use std::{
|
||||
fmt::{Display, Formatter},
|
||||
ops::{Index, IndexMut},
|
||||
path::Path,
|
||||
sync::{Arc, Mutex},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// A container for navigational meshes.
|
||||
@@ -741,12 +741,12 @@ impl Scene {
|
||||
/// Container for scenes in the engine.
|
||||
pub struct SceneContainer {
|
||||
pool: Pool<Scene>,
|
||||
sound_engine: Arc<Mutex<SoundEngine>>,
|
||||
sound_engine: SoundEngine,
|
||||
pub(crate) destruction_list: Vec<(Handle<Scene>, Scene)>,
|
||||
}
|
||||
|
||||
impl SceneContainer {
|
||||
pub(crate) fn new(sound_engine: Arc<Mutex<SoundEngine>>) -> Self {
|
||||
pub(crate) fn new(sound_engine: SoundEngine) -> Self {
|
||||
Self {
|
||||
pool: Pool::new(),
|
||||
sound_engine,
|
||||
@@ -795,8 +795,7 @@ impl SceneContainer {
|
||||
#[inline]
|
||||
pub fn add(&mut self, scene: Scene) -> Handle<Scene> {
|
||||
self.sound_engine
|
||||
.lock()
|
||||
.unwrap()
|
||||
.state()
|
||||
.add_context(scene.graph.sound_context.native.clone());
|
||||
self.pool.spawn(scene)
|
||||
}
|
||||
@@ -811,8 +810,7 @@ impl SceneContainer {
|
||||
#[inline]
|
||||
pub fn remove(&mut self, handle: Handle<Scene>) {
|
||||
self.sound_engine
|
||||
.lock()
|
||||
.unwrap()
|
||||
.state()
|
||||
.remove_context(self.pool[handle].graph.sound_context.native.clone());
|
||||
self.destruction_list.push((handle, self.pool.free(handle)));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user