diff --git a/src/game/profile.rs b/src/game/profile.rs index b1c7448..e5c0fde 100644 --- a/src/game/profile.rs +++ b/src/game/profile.rs @@ -54,7 +54,7 @@ impl GameProfile { state.control_flags.set_tick_world(true); state.control_flags.set_control_enabled(true); - let _ = state.sound_manager.play_song(self.current_song as usize, &state.constants, &state.settings, ctx); + let _ = state.sound_manager.play_song(self.current_song as usize, &state.constants, &state.settings, ctx, false); game_scene.inventory_player1.current_weapon = self.current_weapon as u16; game_scene.inventory_player1.current_item = self.current_item as u16; diff --git a/src/game/scripting/lua/boot.lua b/src/game/scripting/lua/boot.lua index ac54430..da3ff3e 100644 --- a/src/game/scripting/lua/boot.lua +++ b/src/game/scripting/lua/boot.lua @@ -390,8 +390,8 @@ function doukutsu.playSfxLoop(id) __doukutsu_rs:playSfxLoop(id) end -function doukutsu.playSong(id) - __doukutsu_rs:playSong(id) +function doukutsu.playSong(id, fadeout) + __doukutsu_rs:playSong(id, fadeout) end function doukutsu.players() @@ -523,7 +523,7 @@ function ModCS.SkipFlag.Get(id) end function ModCS.Organya.Play(id) - __doukutsu_rs:playSong(id) + __doukutsu_rs:playSong(id, false) end function ModCS.Sound.Play(id, loop) diff --git a/src/game/scripting/lua/doukutsu.d.ts b/src/game/scripting/lua/doukutsu.d.ts index fa96f0c..faf5337 100644 --- a/src/game/scripting/lua/doukutsu.d.ts +++ b/src/game/scripting/lua/doukutsu.d.ts @@ -229,8 +229,9 @@ declare namespace doukutsu { /** * Changes current music to one with specified ID. * If ID equals 0, the music is stopped. + * If ID equals 0 and fadeout is true, the music is faded out. */ - function playMusic(id: number): void; + function playMusic(id: number, fadeout: boolean = false): void; /** * Returns the value of a certain TSC flag. diff --git a/src/game/scripting/lua/doukutsu.rs b/src/game/scripting/lua/doukutsu.rs index 0f6260d..97a73f6 100644 --- a/src/game/scripting/lua/doukutsu.rs +++ b/src/game/scripting/lua/doukutsu.rs @@ -47,8 +47,10 @@ impl Doukutsu { let game_state = &mut (*(*self.ptr).state_ptr); let ctx = &mut (*(*self.ptr).ctx_ptr); + let fadeout = if let Some(fadeout_flag) = state.to_bool(3) { fadeout_flag } else { false }; + let _ = - game_state.sound_manager.play_song(index as usize, &game_state.constants, &game_state.settings, ctx); + game_state.sound_manager.play_song(index as usize, &game_state.constants, &game_state.settings, ctx, fadeout); } 0 diff --git a/src/game/scripting/tsc/credit_script.rs b/src/game/scripting/tsc/credit_script.rs index a3ae494..7729473 100644 --- a/src/game/scripting/tsc/credit_script.rs +++ b/src/game/scripting/tsc/credit_script.rs @@ -158,7 +158,7 @@ impl CreditScriptVM { CreditOpCode::ChangeMusic => { let song = read_cur_varint(&mut cursor)? as u16; - state.sound_manager.play_song(song as usize, &state.constants, &state.settings, ctx)?; + state.sound_manager.play_song(song as usize, &state.constants, &state.settings, ctx, false)?; state.creditscript_vm.state = CreditScriptExecutionState::Running(cursor.position() as u32); } diff --git a/src/game/scripting/tsc/text_script.rs b/src/game/scripting/tsc/text_script.rs index a7914af..a6f8d99 100644 --- a/src/game/scripting/tsc/text_script.rs +++ b/src/game/scripting/tsc/text_script.rs @@ -1345,12 +1345,12 @@ impl TextScriptVM { } TSCOpCode::CMU => { let song_id = read_cur_varint(&mut cursor)? as usize; - state.sound_manager.play_song(song_id, &state.constants, &state.settings, ctx)?; + state.sound_manager.play_song(song_id, &state.constants, &state.settings, ctx, false)?; exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } TSCOpCode::FMU => { - state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?; + state.sound_manager.play_song(0, &state.constants, &state.settings, ctx, true)?; exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } @@ -1733,7 +1733,7 @@ impl TextScriptVM { exec_state = TextScriptExecutionState::Running(state.constants.game.intro_event, 0); state.textscript_vm.suspend = true; - state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?; + state.sound_manager.play_song(0, &state.constants, &state.settings, ctx, false)?; state.reset(); state.start_intro(ctx)?; diff --git a/src/menu/pause_menu.rs b/src/menu/pause_menu.rs index cf1a1ae..3a26ee1 100644 --- a/src/menu/pause_menu.rs +++ b/src/menu/pause_menu.rs @@ -181,7 +181,7 @@ impl PauseMenu { // Shortcut for quick restart if ctx.keyboard_context.is_key_pressed(ScanCode::F2) { state.stop_noise(); - state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?; + state.sound_manager.play_song(0, &state.constants, &state.settings, ctx, false)?; state.load_or_start_game(ctx)?; } @@ -201,7 +201,7 @@ impl PauseMenu { } MenuSelectionResult::Selected(PauseMenuEntry::Retry, _) => { state.stop_noise(); - state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?; + state.sound_manager.play_song(0, &state.constants, &state.settings, ctx, false)?; state.load_or_start_game(ctx)?; } MenuSelectionResult::Selected(PauseMenuEntry::AddPlayer2, _) => { diff --git a/src/scene/editor_scene.rs b/src/scene/editor_scene.rs index d9ae8e1..b0c01ad 100644 --- a/src/scene/editor_scene.rs +++ b/src/scene/editor_scene.rs @@ -137,7 +137,7 @@ impl ExtraWidgetsExt for imgui::Ui<'_> { impl Scene for EditorScene { fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { - state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?; + state.sound_manager.play_song(0, &state.constants, &state.settings, ctx, false)?; Ok(()) } @@ -164,7 +164,7 @@ impl Scene for EditorScene { } if subscene_ref.is_none() { - state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?; + state.sound_manager.play_song(0, &state.constants, &state.settings, ctx, false)?; } return Ok(()); diff --git a/src/scene/jukebox_scene.rs b/src/scene/jukebox_scene.rs index 779d7af..b5d14e4 100644 --- a/src/scene/jukebox_scene.rs +++ b/src/scene/jukebox_scene.rs @@ -145,7 +145,7 @@ impl Scene for JukeboxScene { .position(|song_comp| song_comp == &self.song_list[song as usize]) .unwrap_or(0); - state.sound_manager.play_song(song_id, &state.constants, &state.settings, ctx)?; + state.sound_manager.play_song(song_id, &state.constants, &state.settings, ctx, false)?; } if self.controller.trigger_shift_left() { diff --git a/src/scene/title_scene.rs b/src/scene/title_scene.rs index 0dd896b..eeaaa30 100644 --- a/src/scene/title_scene.rs +++ b/src/scene/title_scene.rs @@ -171,7 +171,7 @@ impl TitleScene { } if song_id != state.sound_manager.current_song() { - state.sound_manager.play_song(song_id, &state.constants, &state.settings, ctx)?; + state.sound_manager.play_song(song_id, &state.constants, &state.settings, ctx, false)?; } Ok(()) } diff --git a/src/sound/mod.rs b/src/sound/mod.rs index 0f707df..dc88d68 100644 --- a/src/sound/mod.rs +++ b/src/sound/mod.rs @@ -247,10 +247,10 @@ impl SoundManager { let prev_song = self.prev_song_id; let current_song = self.current_song_id; - self.play_song(0, constants, settings, ctx)?; - self.play_song(prev_song, constants, settings, ctx)?; + self.play_song(0, constants, settings, ctx, false)?; + self.play_song(prev_song, constants, settings, ctx, false)?; self.save_state()?; - self.play_song(current_song, constants, settings, ctx)?; + self.play_song(current_song, constants, settings, ctx, false)?; Ok(()) } @@ -261,6 +261,7 @@ impl SoundManager { constants: &EngineConstants, settings: &Settings, ctx: &mut Context, + fadeout: bool, ) -> GameResult { if self.current_song_id == song_id || self.no_audio { return Ok(()); @@ -274,7 +275,12 @@ impl SoundManager { self.send(PlaybackMessage::SetOrgInterpolation(settings.organya_interpolation)).unwrap(); self.send(PlaybackMessage::SaveState).unwrap(); - self.send(PlaybackMessage::Stop).unwrap(); + + if fadeout { + self.send(PlaybackMessage::FadeoutSong).unwrap(); + } else { + self.send(PlaybackMessage::Stop).unwrap(); + } } else if let Some(song_name) = constants.music_table.get(song_id) { let mut paths = constants.organya_paths.clone(); @@ -556,6 +562,7 @@ pub(in crate::sound) enum PlaybackMessage { SetSpeed(f32), SetSongVolume(f32), SetSampleVolume(f32), + FadeoutSong, SaveState, RestoreState, SetSampleParams(u8, PixToneParameters), @@ -619,7 +626,9 @@ fn run( let mut pxt_index = 0; let mut samples = 0; let mut bgm_vol = 1.0_f32; + let mut bgm_vol_saved = 1.0_f32; let mut sfx_vol = 1.0_f32; + let mut bgm_fadeout = false; pixtone.mix(&mut pxt_buf, sample_rate); let err_fn = |err| eprintln!("an error occurred on stream: {}", err); @@ -628,12 +637,21 @@ fn run( &config, move |data: &mut [T], _: &cpal::OutputCallbackInfo| { loop { + if bgm_fadeout && bgm_vol > 0.0 { + bgm_vol -= 0.02; + } + match rx.try_recv() { Ok(PlaybackMessage::PlayOrganyaSong(song)) => { if state == PlaybackState::Stopped { saved_state = PlaybackStateType::None; } + if bgm_fadeout { + bgm_fadeout = false; + bgm_vol = bgm_vol_saved; + } + org_engine.start_song(*song, &bank); for i in &mut bgm_buf[0..samples] { @@ -650,6 +668,11 @@ fn run( saved_state = PlaybackStateType::None; } + if bgm_fadeout { + bgm_fadeout = false; + bgm_vol = bgm_vol_saved; + } + ogg_engine.start_single(data); for i in &mut bgm_buf[0..samples] { @@ -666,6 +689,11 @@ fn run( saved_state = PlaybackStateType::None; } + if bgm_fadeout { + bgm_fadeout = false; + bgm_vol = bgm_vol_saved; + } + ogg_engine.start_multi(data_intro, data_loop); for i in &mut bgm_buf[0..samples] { @@ -711,6 +739,10 @@ fn run( assert!(sfx_vol >= 0.0); sfx_vol = new_volume; } + Ok(PlaybackMessage::FadeoutSong) => { + bgm_fadeout = true; + bgm_vol_saved = bgm_vol; + } Ok(PlaybackMessage::SaveState) => { saved_state = match state { PlaybackState::Stopped => PlaybackStateType::None, @@ -739,6 +771,9 @@ fn run( samples = org_engine.render_to(&mut bgm_buf); bgm_index = 0; + bgm_fadeout = false; + bgm_vol = bgm_vol_saved; + state = PlaybackState::PlayingOrg; } #[cfg(feature = "ogg-playback")] @@ -755,6 +790,9 @@ fn run( samples = ogg_engine.render_to(&mut bgm_buf); bgm_index = 0; + bgm_fadeout = false; + bgm_vol = bgm_vol_saved; + state = PlaybackState::PlayingOgg; } }