refactor sound manager to prevent audio crashes (fixes #112)

This commit is contained in:
Sallai József 2022-07-09 16:49:56 +03:00
parent 9d7c63571d
commit d8636bc693
6 changed files with 141 additions and 54 deletions

View File

@ -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;

View File

@ -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

View File

@ -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 {

View File

@ -287,21 +287,21 @@ pub struct SharedGameState {
impl SharedGameState {
pub fn new(ctx: &mut Context) -> GameResult<SharedGameState> {
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;

View File

@ -36,10 +36,12 @@ mod wav;
mod wave_bank;
pub struct SoundManager {
soundbank: Option<SoundBank>,
tx: Sender<PlaybackMessage>,
prev_song_id: usize,
current_song_id: usize,
no_audio: bool,
load_failed: bool,
stream: Option<cpal::Stream>,
}
@ -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<PlaybackMessage>,
rx: Receiver<PlaybackMessage>,
) -> GameResult<SoundManager> {
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::<f32>(rx, bnk, device, config.into()),
cpal::SampleFormat::I16 => run::<i16>(rx, bnk, device, config.into()),
cpal::SampleFormat::U16 => run::<u16>(rx, bnk, device, config.into()),
cpal::SampleFormat::F32 => run::<f32>(rx, soundbank.to_owned(), device, config.into()),
cpal::SampleFormat::I16 => run::<i16>(rx, soundbank.to_owned(), device, config.into()),
cpal::SampleFormat::U16 => run::<u16>(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<PlaybackMessage>, Receiver<PlaybackMessage>) = 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<i16>) {
pub fn set_sfx_samples(&mut self, id: u8, data: Vec<i16>) {
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<R: io::Read>(&self, id: u8, data: R) -> GameResult {
pub fn set_sample_params_from_file<R: io::Read>(&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<String>) -> GameResult {
pub fn load_custom_sound_effects(&mut self, ctx: &mut Context, roots: &Vec<String>) -> 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)
}

View File

@ -3,6 +3,7 @@ use std::io;
use crate::sound::wav;
#[derive(Clone)]
pub struct SoundBank {
pub wave100: Box<[u8; 25600]>,