From d8636bc693f4e4a91afedce772f2d478efcfbe58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sallai=20J=C3=B3zsef?= Date: Sat, 9 Jul 2022 16:49:56 +0300 Subject: [PATCH] refactor sound manager to prevent audio crashes (fixes #112) --- src/engine_constants/mod.rs | 2 +- src/framework/backend_sdl2.rs | 2 +- src/live_debugger.rs | 1 + src/shared_game_state.rs | 15 ++- src/sound/mod.rs | 174 +++++++++++++++++++++++++--------- src/sound/wave_bank.rs | 1 + 6 files changed, 141 insertions(+), 54 deletions(-) diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index cd1edb5..cf3c482 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -1631,7 +1631,7 @@ impl EngineConstants { } } - pub fn apply_csplus_patches(&mut self, sound_manager: &SoundManager) { + pub fn apply_csplus_patches(&mut self, sound_manager: &mut SoundManager) { log::info!("Applying Cave Story+ constants patches..."); self.is_cs_plus = true; diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs index e3bac9b..20ec807 100644 --- a/src/framework/backend_sdl2.rs +++ b/src/framework/backend_sdl2.rs @@ -256,7 +256,7 @@ impl BackendEventLoop for SDL2EventLoop { Event::KeyDown { scancode: Some(scancode), repeat, keymod, .. } => { if let Some(drs_scan) = conv_scancode(scancode) { if !repeat { - state.process_debug_keys(drs_scan); + state.process_debug_keys(ctx, drs_scan); if keymod.intersects(keyboard::Mod::RALTMOD | keyboard::Mod::LALTMOD) && drs_scan == ScanCode::Return diff --git a/src/live_debugger.rs b/src/live_debugger.rs index 5d8c916..1ed2618 100644 --- a/src/live_debugger.rs +++ b/src/live_debugger.rs @@ -416,6 +416,7 @@ impl LiveDebugger { "F10 > Debug Overlay", "F11 > Toggle FPS Counter", "F12 > Toggle Debugger", + "Ctrl + F3 > Reload Sound Manager", ]; for hotkeys in key.iter() { match hotkeys { diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index 5309af3..85b930f 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -287,21 +287,21 @@ pub struct SharedGameState { impl SharedGameState { pub fn new(ctx: &mut Context) -> GameResult { let mut constants = EngineConstants::defaults(); - let sound_manager = SoundManager::new(ctx)?; + let mut sound_manager = SoundManager::new(ctx)?; let settings = Settings::load(ctx)?; let mod_requirements = ModRequirements::load(ctx)?; if filesystem::exists(ctx, "/base/lighting.tbl") { info!("Cave Story+ (Switch) data files detected."); ctx.size_hint = (854, 480); - constants.apply_csplus_patches(&sound_manager); + constants.apply_csplus_patches(&mut sound_manager); constants.apply_csplus_nx_patches(); constants.load_nx_stringtable(ctx)?; } else if filesystem::exists(ctx, "/base/ogph/SellScreen.bmp") { error!("WiiWare DEMO data files detected. !UNSUPPORTED!"); //Missing credits.tsc and crashes due to missing Stage 13 (Start) } else if filesystem::exists(ctx, "/base/strap_a_en.bmp") { info!("WiiWare data files detected."); //Missing Challenges and Remastered Soundtrack but identical to CS+ PC otherwise - constants.apply_csplus_patches(&sound_manager); + constants.apply_csplus_patches(&mut sound_manager); } else if filesystem::exists(ctx, "/root/buid_time.txt") { error!("DSiWare data files detected. !UNSUPPORTED!"); //Freeware 2.0, sprites are arranged VERY differently + separate drowned carets } else if filesystem::exists(ctx, "/darken.tex") || filesystem::exists(ctx, "/darken.png") { @@ -310,7 +310,7 @@ impl SharedGameState { error!("CS3D data files detected. !UNSUPPORTED!"); //Sprites are technically all there but filenames differ, + no n3ddta support } else if filesystem::exists(ctx, "/base/Nicalis.bmp") || filesystem::exists(ctx, "/base/Nicalis.png") { info!("Cave Story+ (PC) data files detected."); - constants.apply_csplus_patches(&sound_manager); + constants.apply_csplus_patches(&mut sound_manager); } else if filesystem::exists(ctx, "/mrmap.bin") { info!("CSE2E data files detected."); } else if filesystem::exists(ctx, "/stage.dat") { @@ -414,7 +414,12 @@ impl SharedGameState { }) } - pub fn process_debug_keys(&mut self, key_code: ScanCode) { + pub fn process_debug_keys(&mut self, ctx: &mut Context, key_code: ScanCode) { + if key_code == ScanCode::F3 && ctx.keyboard_context.active_mods().ctrl() { + let _ = self.sound_manager.reload(); + return; + } + #[cfg(not(debug_assertions))] if !self.settings.debug_mode { return; diff --git a/src/sound/mod.rs b/src/sound/mod.rs index 4b08f0a..84a9acc 100644 --- a/src/sound/mod.rs +++ b/src/sound/mod.rs @@ -36,10 +36,12 @@ mod wav; mod wave_bank; pub struct SoundManager { + soundbank: Option, tx: Sender, prev_song_id: usize, current_song_id: usize, no_audio: bool, + load_failed: bool, stream: Option, } @@ -68,31 +70,100 @@ impl SoundManager { log::info!("Running in headless mode, skipping initialization."); return Ok(SoundManager { + soundbank: None, tx: tx.clone(), prev_song_id: 0, current_song_id: 0, no_audio: true, + load_failed: false, stream: None, }); } - let host = cpal::default_host(); - let device = - host.default_output_device().ok_or_else(|| AudioError("Error initializing audio device.".to_owned()))?; - let config = device.default_output_config()?; - let bnk = wave_bank::SoundBank::load_from(filesystem::open(ctx, "/builtin/organya-wavetable-doukutsu.bin")?)?; + Ok(SoundManager::bootstrap(&bnk, tx, rx)?) + } + + fn bootstrap( + soundbank: &SoundBank, + tx: Sender, + rx: Receiver, + ) -> GameResult { + let mut sound_manager = SoundManager { + soundbank: Some(soundbank.to_owned()), + tx, + prev_song_id: 0, + current_song_id: 0, + no_audio: false, + load_failed: false, + stream: None, + }; + + let host = cpal::default_host(); + + let device_result = + host.default_output_device().ok_or_else(|| AudioError("Error initializing audio device.".to_owned())); + + if device_result.is_err() { + log::error!("{}", device_result.err().unwrap().to_string()); + sound_manager.load_failed = true; + return Ok(sound_manager); + } + + let device = device_result.unwrap(); + + let config_result = device.default_output_config(); + + if config_result.is_err() { + log::error!("{}", config_result.err().unwrap().to_string()); + sound_manager.load_failed = true; + return Ok(sound_manager); + } + + let config = config_result.unwrap(); + let res = match config.sample_format() { - cpal::SampleFormat::F32 => run::(rx, bnk, device, config.into()), - cpal::SampleFormat::I16 => run::(rx, bnk, device, config.into()), - cpal::SampleFormat::U16 => run::(rx, bnk, device, config.into()), + cpal::SampleFormat::F32 => run::(rx, soundbank.to_owned(), device, config.into()), + cpal::SampleFormat::I16 => run::(rx, soundbank.to_owned(), device, config.into()), + cpal::SampleFormat::U16 => run::(rx, soundbank.to_owned(), device, config.into()), }; if let Err(res) = &res { log::error!("Error initializing audio: {}", res); } - Ok(SoundManager { tx: tx.clone(), prev_song_id: 0, current_song_id: 0, no_audio: false, stream: res.ok() }) + sound_manager.stream = res.ok(); + Ok(sound_manager) + } + + pub fn reload(&mut self) -> GameResult<()> { + if self.no_audio { + log::info!("Skipping sound manager reload because audio is not enabled."); + return Ok(()); + } + + log::info!("Reloading sound manager."); + + let (tx, rx): (Sender, Receiver) = mpsc::channel(); + let soundbank = self.soundbank.take().unwrap(); + *self = SoundManager::bootstrap(&soundbank, tx, rx)?; + + Ok(()) + } + + fn send(&mut self, message: PlaybackMessage) -> GameResult<()> { + if self.no_audio { + return Ok(()); + } + + if self.tx.send(message).is_err() { + if !self.load_failed { + log::error!("Error sending message to audio thread. Press Ctrl + F3 to reload sound manager."); + self.reload()?; + } + } + + Ok(()) } pub fn pause(&mut self) { @@ -107,60 +178,62 @@ impl SoundManager { } } - pub fn play_sfx(&self, id: u8) { + pub fn play_sfx(&mut self, id: u8) { if self.no_audio { return; } - let _ = self.tx.send(PlaybackMessage::PlaySample(id)); + + self.send(PlaybackMessage::PlaySample(id)).unwrap(); } pub fn loop_sfx(&self, id: u8) { if self.no_audio { return; } - let _ = self.tx.send(PlaybackMessage::LoopSample(id)); + + self.tx.send(PlaybackMessage::LoopSample(id)).unwrap(); } - pub fn loop_sfx_freq(&self, id: u8, freq: f32) { + pub fn loop_sfx_freq(&mut self, id: u8, freq: f32) { if self.no_audio { return; } - let _ = self.tx.send(PlaybackMessage::LoopSampleFreq(id, freq)); + self.send(PlaybackMessage::LoopSampleFreq(id, freq)).unwrap(); } - pub fn stop_sfx(&self, id: u8) { + pub fn stop_sfx(&mut self, id: u8) { if self.no_audio { return; } - let _ = self.tx.send(PlaybackMessage::StopSample(id)); + self.send(PlaybackMessage::StopSample(id)).unwrap(); } - pub fn set_org_interpolation(&self, interpolation: InterpolationMode) { + pub fn set_org_interpolation(&mut self, interpolation: InterpolationMode) { if self.no_audio { return; } - let _ = self.tx.send(PlaybackMessage::SetOrgInterpolation(interpolation)); + self.send(PlaybackMessage::SetOrgInterpolation(interpolation)).unwrap(); } - pub fn set_song_volume(&self, volume: f32) { + pub fn set_song_volume(&mut self, volume: f32) { if self.no_audio { return; } - let _ = self.tx.send(PlaybackMessage::SetSongVolume(volume.powf(3.0))); + self.send(PlaybackMessage::SetSongVolume(volume.powf(3.0))).unwrap(); } - pub fn set_sfx_volume(&self, volume: f32) { + pub fn set_sfx_volume(&mut self, volume: f32) { if self.no_audio { return; } - let _ = self.tx.send(PlaybackMessage::SetSampleVolume(volume.powf(3.0))); + self.send(PlaybackMessage::SetSampleVolume(volume.powf(3.0))).unwrap(); } - pub fn set_sfx_samples(&self, id: u8, data: Vec) { + pub fn set_sfx_samples(&mut self, id: u8, data: Vec) { if self.no_audio { return; } - let _ = self.tx.send(PlaybackMessage::SetSampleData(id, data)); + self.send(PlaybackMessage::SetSampleData(id, data)).unwrap(); } pub fn reload_songs(&mut self, constants: &EngineConstants, settings: &Settings, ctx: &mut Context) -> GameResult { @@ -192,9 +265,9 @@ impl SoundManager { self.prev_song_id = self.current_song_id; self.current_song_id = 0; - self.tx.send(PlaybackMessage::SetOrgInterpolation(settings.organya_interpolation))?; - self.tx.send(PlaybackMessage::SaveState)?; - self.tx.send(PlaybackMessage::Stop)?; + self.send(PlaybackMessage::SetOrgInterpolation(settings.organya_interpolation)).unwrap(); + self.send(PlaybackMessage::SaveState).unwrap(); + self.send(PlaybackMessage::Stop).unwrap(); } else if let Some(song_name) = constants.music_table.get(song_id) { let mut paths = constants.organya_paths.clone(); @@ -234,10 +307,11 @@ impl SoundManager { self.prev_song_id = self.current_song_id; self.current_song_id = song_id; - self.tx - .send(PlaybackMessage::SetOrgInterpolation(settings.organya_interpolation))?; - self.tx.send(PlaybackMessage::SaveState)?; - self.tx.send(PlaybackMessage::PlayOrganyaSong(Box::new(org)))?; + let _ = self + .send(PlaybackMessage::SetOrgInterpolation(settings.organya_interpolation)) + .unwrap(); + self.send(PlaybackMessage::SaveState).unwrap(); + self.send(PlaybackMessage::PlayOrganyaSong(Box::new(org))).unwrap(); return Ok(()); } @@ -259,8 +333,8 @@ impl SoundManager { self.prev_song_id = self.current_song_id; self.current_song_id = song_id; - self.tx.send(PlaybackMessage::SaveState)?; - self.tx.send(PlaybackMessage::PlayOggSongSinglePart(Box::new(song)))?; + self.send(PlaybackMessage::SaveState).unwrap(); + self.send(PlaybackMessage::PlayOggSongSinglePart(Box::new(song))).unwrap(); return Ok(()); } @@ -293,11 +367,12 @@ impl SoundManager { self.prev_song_id = self.current_song_id; self.current_song_id = song_id; - self.tx.send(PlaybackMessage::SaveState)?; - self.tx.send(PlaybackMessage::PlayOggSongMultiPart( + self.send(PlaybackMessage::SaveState).unwrap(); + self.send(PlaybackMessage::PlayOggSongMultiPart( Box::new(song_intro), Box::new(song_loop), - ))?; + )) + .unwrap(); return Ok(()); } @@ -319,7 +394,7 @@ impl SoundManager { return Ok(()); } - self.tx.send(PlaybackMessage::SaveState)?; + self.send(PlaybackMessage::SaveState).unwrap(); self.prev_song_id = self.current_song_id; Ok(()) @@ -330,13 +405,13 @@ impl SoundManager { return Ok(()); } - self.tx.send(PlaybackMessage::RestoreState)?; + self.send(PlaybackMessage::RestoreState).unwrap(); self.current_song_id = self.prev_song_id; Ok(()) } - pub fn set_speed(&self, speed: f32) -> GameResult { + pub fn set_speed(&mut self, speed: f32) -> GameResult { if self.no_audio { return Ok(()); } @@ -345,7 +420,7 @@ impl SoundManager { return Err(InvalidValue("Speed must be bigger than 0.0!".to_owned())); } - self.tx.send(PlaybackMessage::SetSpeed(speed))?; + self.send(PlaybackMessage::SetSpeed(speed)).unwrap(); Ok(()) } @@ -354,7 +429,7 @@ impl SoundManager { self.current_song_id } - pub fn set_sample_params_from_file(&self, id: u8, data: R) -> GameResult { + pub fn set_sample_params_from_file(&mut self, id: u8, data: R) -> GameResult { if self.no_audio { return Ok(()); } @@ -415,17 +490,17 @@ impl SoundManager { self.set_sample_params(id, params) } - pub fn set_sample_params(&self, id: u8, params: PixToneParameters) -> GameResult { + pub fn set_sample_params(&mut self, id: u8, params: PixToneParameters) -> GameResult { if self.no_audio { return Ok(()); } - self.tx.send(PlaybackMessage::SetSampleParams(id, params))?; + self.send(PlaybackMessage::SetSampleParams(id, params)).unwrap(); Ok(()) } - pub fn load_custom_sound_effects(&self, ctx: &mut Context, roots: &Vec) -> GameResult { + pub fn load_custom_sound_effects(&mut self, ctx: &mut Context, roots: &Vec) -> GameResult { for path in roots.iter().rev() { let wavs = filesystem::read_dir(ctx, [path, "sfx/"].join(""))? .filter(|f| f.to_string_lossy().to_lowercase().ends_with(".wav")); @@ -542,7 +617,7 @@ where let err_fn = |err| eprintln!("an error occurred on stream: {}", err); - let stream = device.build_output_stream( + let stream_result = device.build_output_stream( &config, move |data: &mut [T], _: &cpal::OutputCallbackInfo| { loop { @@ -763,9 +838,14 @@ where } }, err_fn, - )?; + ); - stream.play()?; + if stream_result.is_err() { + return Err(GameError::AudioError(stream_result.err().unwrap().to_string())); + } + + let stream = stream_result.unwrap(); + let _ = stream.play(); Ok(stream) } diff --git a/src/sound/wave_bank.rs b/src/sound/wave_bank.rs index c8d2a5a..d3403f6 100644 --- a/src/sound/wave_bank.rs +++ b/src/sound/wave_bank.rs @@ -3,6 +3,7 @@ use std::io; use crate::sound::wav; +#[derive(Clone)] pub struct SoundBank { pub wave100: Box<[u8; 25600]>,