diff --git a/src/sound/mod.rs b/src/sound/mod.rs index 4247040..5e91661 100644 --- a/src/sound/mod.rs +++ b/src/sound/mod.rs @@ -9,7 +9,7 @@ use crate::engine_constants::EngineConstants; use crate::ggez::{Context, filesystem, GameResult}; use crate::ggez::GameError::AudioError; use crate::sound::organya::Song; -use crate::sound::playback::PlaybackEngine; +use crate::sound::playback::{PlaybackEngine, SavedPlaybackState}; use crate::sound::wave_bank::SoundBank; use crate::str; @@ -101,20 +101,39 @@ impl SoundManager { return Ok(()); } - if let Some(song_name) = SONGS.get(song_id) { + if song_id == 0 { + log::info!("Stopping BGM"); + + self.current_song_id = 0; + self.tx.send(PlaybackMessage::Stop)?; + } else if let Some(song_name) = SONGS.get(song_id) { let org = organya::Song::load_from(filesystem::open(ctx, ["/base/Org/", &song_name.to_lowercase(), ".org"].join(""))?)?; - log::info!("Playing song: {}", song_name); + log::info!("Playing BGM: {}", song_name); self.current_song_id = song_id; - self.tx.send(PlaybackMessage::PlaySong(org)); + self.tx.send(PlaybackMessage::PlaySong(Box::new(org)))?; } Ok(()) } + + pub fn save_state(&mut self) -> GameResult { + self.tx.send(PlaybackMessage::SaveState)?; + + Ok(()) + } + + pub fn restore_state(&mut self) -> GameResult { + self.tx.send(PlaybackMessage::RestoreState)?; + + Ok(()) + } } enum PlaybackMessage { Stop, - PlaySong(Song), + PlaySong(Box), + SaveState, + RestoreState, } #[derive(PartialEq, Eq)] @@ -130,7 +149,7 @@ fn run(rx: Receiver, bank: SoundBank, let sample_rate = config.sample_rate.0 as f32; let channels = config.channels as usize; let mut state = PlaybackState::Stopped; - let mut new_song: Option = None; + let mut saved_state: Option = None; let mut engine = PlaybackEngine::new(Song::empty(), &bank); log::info!("Audio format: {} {}", sample_rate, channels); @@ -148,7 +167,7 @@ fn run(rx: Receiver, bank: SoundBank, move |data: &mut [T], _: &cpal::OutputCallbackInfo| { match rx.try_recv() { Ok(PlaybackMessage::PlaySong(song)) => { - engine.start_song(song, &bank); + engine.start_song(*song, &bank); for i in &mut buf[0..frames] { *i = 0x8080 }; frames = engine.render_to(&mut buf); @@ -156,6 +175,21 @@ fn run(rx: Receiver, bank: SoundBank, state = PlaybackState::Playing; } + Ok(PlaybackMessage::SaveState) => { + saved_state = Some(engine.get_state()); + } + Ok(PlaybackMessage::RestoreState) => { + if saved_state.is_some() { + engine.set_state(saved_state.clone().unwrap(), &bank); + saved_state = None; + + for i in &mut buf[0..frames] { *i = 0x8080 }; + frames = engine.render_to(&mut buf); + index = 0; + + state = PlaybackState::Playing; + } + } _ => {} } diff --git a/src/sound/playback.rs b/src/sound/playback.rs index 4c17f4b..72246a3 100644 --- a/src/sound/playback.rs +++ b/src/sound/playback.rs @@ -18,11 +18,20 @@ pub struct PlaybackEngine { pub loops: usize, } -pub struct PlaybackState { +pub struct SavedPlaybackState { song: Organya, play_pos: i32, } +impl Clone for SavedPlaybackState { + fn clone(&self) -> SavedPlaybackState { + SavedPlaybackState { + song: self.song.clone(), + play_pos: self.play_pos, + } + } +} + impl PlaybackEngine { pub fn new(song: Organya, samples: &SoundBank) -> Self { @@ -98,14 +107,14 @@ impl PlaybackEngine { self.frames_per_tick = (sample_rate / 1000) * self.song.time.wait as usize; } - pub fn get_state(&self) -> PlaybackState { - PlaybackState { + pub fn get_state(&self) -> SavedPlaybackState { + SavedPlaybackState { song: self.song.clone(), play_pos: self.play_pos, } } - pub fn set_state(&mut self, state: PlaybackState, samples: &SoundBank) { + pub fn set_state(&mut self, state: SavedPlaybackState, samples: &SoundBank) { self.start_song(state.song, samples); self.play_pos = state.play_pos; } diff --git a/src/text_script.rs b/src/text_script.rs index a6c36cf..8832c96 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -628,14 +628,25 @@ impl TextScriptVM { OpCode::CMU => { let song_id = read_cur_varint(&mut cursor)? as usize; state.sound_manager.play_song(song_id, &state.constants, ctx)?; + + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } + OpCode::FMU => { + state.sound_manager.save_state()?; + + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); + } + OpCode::RMU => { + state.sound_manager.restore_state()?; + exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } // unimplemented opcodes // Zero operands OpCode::AEp | OpCode::CAT | OpCode::CIL | OpCode::CPS | - OpCode::CRE | OpCode::CSS | OpCode::ESC | OpCode::FLA | OpCode::FMU | + OpCode::CRE | OpCode::CSS | OpCode::ESC | OpCode::FLA | OpCode::INI | OpCode::LDP | OpCode::MLP | OpCode::MNA | - OpCode::RMU | OpCode::SAT | OpCode::SLP | OpCode::SPS | + OpCode::SAT | OpCode::SLP | OpCode::SPS | OpCode::STC | OpCode::SVP | OpCode::TUR | OpCode::WAS | OpCode::ZAM => { log::warn!("unimplemented opcode: {:?}", op); @@ -751,7 +762,7 @@ impl TextScript { /// Compiles a decrypted text script data into internal bytecode. pub fn compile(data: &[u8], strict: bool) -> GameResult { - println!("data: {}", String::from_utf8_lossy(data)); + log::debug!("data: {}", String::from_utf8_lossy(data)); let mut event_map = HashMap::new(); let mut iter = data.iter().copied().peekable(); @@ -778,7 +789,6 @@ impl TextScript { let bytecode = TextScript::compile_event(&mut iter, strict)?; log::info!("Successfully compiled event #{} ({} bytes generated).", event_num, bytecode.len()); - println!("{:x?}", &bytecode); event_map.insert(event_num, bytecode); } b'\r' | b'\n' | b' ' | b'\t' => {