mirror of
				https://github.com/doukutsu-rs/doukutsu-rs
				synced 2025-10-31 13:25:47 +00:00 
			
		
		
		
	Added volume settings (#57)
This commit is contained in:
		
							parent
							
								
									88fdb7b0ce
								
							
						
					
					
						commit
						bd0762f812
					
				|  | @ -1597,6 +1597,7 @@ impl EngineConstants { | |||
|         self.tex_sizes.insert("Caret".to_owned(), (320, 320)); | ||||
|         self.tex_sizes.insert("MyChar".to_owned(), (200, 384)); | ||||
|         self.tex_sizes.insert("Npc/NpcRegu".to_owned(), (320, 410)); | ||||
|         self.tex_sizes.insert("ui".to_owned(), (128, 32)); | ||||
|         self.title.logo_rect = Rect { left: 0, top: 0, right: 214, bottom: 50 }; | ||||
|         self.font_path = "csfont.fnt".to_owned(); | ||||
|         self.font_scale = 0.5; | ||||
|  | @ -1638,6 +1639,7 @@ impl EngineConstants { | |||
|         self.supports_og_textures = true; | ||||
|         self.tex_sizes.insert("bkMoon".to_owned(), (427, 240)); | ||||
|         self.tex_sizes.insert("bkFog".to_owned(), (427, 240)); | ||||
|         self.tex_sizes.insert("ui".to_owned(), (128, 32)); | ||||
|         self.title.logo_rect = Rect { left: 0, top: 0, right: 214, bottom: 62 }; | ||||
|         self.inventory_dim_color = Color::from_rgba(0, 0, 32, 150); | ||||
|         self.textscript.encoding = TextScriptEncoding::UTF8; | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| use std::cell::Cell; | ||||
| 
 | ||||
| use crate::common::Rect; | ||||
| use crate::common::{Color, Rect}; | ||||
| use crate::framework::context::Context; | ||||
| use crate::framework::error::GameResult; | ||||
| use crate::framework::graphics; | ||||
| use crate::input::combined_menu_controller::CombinedMenuController; | ||||
| use crate::shared_game_state::{MenuCharacter, SharedGameState}; | ||||
| 
 | ||||
|  | @ -19,6 +20,7 @@ pub enum MenuEntry { | |||
|     Toggle(String, bool), | ||||
|     Options(String, usize, Vec<String>), | ||||
|     DescriptiveOptions(String, usize, Vec<String>, Vec<String>), | ||||
|     OptionsBar(String, f32), | ||||
|     SaveData(MenuSaveInfo), | ||||
|     NewSave, | ||||
| } | ||||
|  | @ -33,6 +35,7 @@ impl MenuEntry { | |||
|             MenuEntry::Toggle(_, _) => 16.0, | ||||
|             MenuEntry::Options(_, _, _) => 16.0, | ||||
|             MenuEntry::DescriptiveOptions(_, _, _, _) => 16.0, | ||||
|             MenuEntry::OptionsBar(_, _) => 16.0, | ||||
|             MenuEntry::SaveData(_) => 32.0, | ||||
|             MenuEntry::NewSave => 32.0, | ||||
|         } | ||||
|  | @ -47,6 +50,7 @@ impl MenuEntry { | |||
|             MenuEntry::Toggle(_, _) => true, | ||||
|             MenuEntry::Options(_, _, _) => true, | ||||
|             MenuEntry::DescriptiveOptions(_, _, _, _) => true, | ||||
|             MenuEntry::OptionsBar(_, _) => true, | ||||
|             MenuEntry::SaveData(_) => true, | ||||
|             MenuEntry::NewSave => true, | ||||
|         } | ||||
|  | @ -57,8 +61,8 @@ pub enum MenuSelectionResult<'a> { | |||
|     None, | ||||
|     Canceled, | ||||
|     Selected(usize, &'a mut MenuEntry), | ||||
|     Left(usize, &'a mut MenuEntry), | ||||
|     Right(usize, &'a mut MenuEntry), | ||||
|     Left(usize, &'a mut MenuEntry, i16), | ||||
|     Right(usize, &'a mut MenuEntry, i16), | ||||
| } | ||||
| 
 | ||||
| pub struct Menu { | ||||
|  | @ -352,6 +356,52 @@ impl Menu { | |||
|                         ctx, | ||||
|                     )?; | ||||
|                 } | ||||
|                 MenuEntry::OptionsBar(name, percent) => { | ||||
|                     state.font.draw_text( | ||||
|                         name.chars(), | ||||
|                         self.x as f32 + 20.0, | ||||
|                         y, | ||||
|                         &state.constants, | ||||
|                         &mut state.texture_set, | ||||
|                         ctx, | ||||
|                     )?; | ||||
| 
 | ||||
|                     if state.constants.is_switch || state.constants.is_cs_plus { | ||||
|                         let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ui")?; | ||||
|                         let bar_width = if state.constants.is_switch { 81.0 } else { 109.0 }; | ||||
| 
 | ||||
|                         let rect = Rect::new(0, 18, (bar_width - (bar_width * (1.0 - percent))) as u16, 32); | ||||
| 
 | ||||
|                         batch.add_rect( | ||||
|                             (self.x + self.width as isize) as f32 - (bar_width + (2.0 * state.scale)), | ||||
|                             y - (state.scale * 2.0), | ||||
|                             &rect, | ||||
|                         ); | ||||
|                         batch.draw(ctx)?; | ||||
|                     } else { | ||||
|                         let scale = state.scale; | ||||
| 
 | ||||
|                         let bar_rect = Rect::new_size( | ||||
|                             ((self.x + self.width as isize - 80) as f32 * scale) as isize, | ||||
|                             (y * scale) as isize, | ||||
|                             (75.0 * scale * percent) as isize, | ||||
|                             (8.0 * scale) as isize, | ||||
|                         ); | ||||
| 
 | ||||
|                         graphics::draw_rect( | ||||
|                             ctx, | ||||
|                             Rect::new_size( | ||||
|                                 bar_rect.left + (2.0 * scale) as isize, | ||||
|                                 bar_rect.top + (2.0 * scale) as isize, | ||||
|                                 (75.0 * scale) as isize, | ||||
|                                 (8.0 * scale) as isize, | ||||
|                             ), | ||||
|                             Color::new(0.0, 0.0, 0.0, 1.0), | ||||
|                         )?; | ||||
| 
 | ||||
|                         graphics::draw_rect(ctx, bar_rect, Color::new(1.0, 1.0, 1.0, 1.0))?; | ||||
|                     } | ||||
|                 } | ||||
|                 _ => {} | ||||
|             } | ||||
| 
 | ||||
|  | @ -416,21 +466,25 @@ impl Menu { | |||
|                     state.sound_manager.play_sfx(18); | ||||
|                     return MenuSelectionResult::Selected(idx, entry); | ||||
|                 } | ||||
|                 MenuEntry::Options(_, _, _) if controller.trigger_left() => { | ||||
|                 MenuEntry::Options(_, _, _) | MenuEntry::OptionsBar(_, _) | ||||
|                     if self.selected == idx && controller.trigger_left() => | ||||
|                 { | ||||
|                     state.sound_manager.play_sfx(1); | ||||
|                     return MenuSelectionResult::Left(self.selected, entry); | ||||
|                     return MenuSelectionResult::Left(self.selected, entry, -1); | ||||
|                 } | ||||
|                 MenuEntry::Options(_, _, _) if controller.trigger_right() => { | ||||
|                 MenuEntry::Options(_, _, _) | MenuEntry::OptionsBar(_, _) | ||||
|                     if self.selected == idx && controller.trigger_right() => | ||||
|                 { | ||||
|                     state.sound_manager.play_sfx(1); | ||||
|                     return MenuSelectionResult::Right(self.selected, entry); | ||||
|                     return MenuSelectionResult::Right(self.selected, entry, 1); | ||||
|                 } | ||||
|                 MenuEntry::DescriptiveOptions(_, _, _, _) if controller.trigger_left() => { | ||||
|                 MenuEntry::DescriptiveOptions(_, _, _, _) if self.selected == idx && controller.trigger_left() => { | ||||
|                     state.sound_manager.play_sfx(1); | ||||
|                     return MenuSelectionResult::Left(self.selected, entry); | ||||
|                     return MenuSelectionResult::Left(self.selected, entry, -1); | ||||
|                 } | ||||
|                 MenuEntry::DescriptiveOptions(_, _, _, _) if controller.trigger_right() => { | ||||
|                 MenuEntry::DescriptiveOptions(_, _, _, _) if self.selected == idx && controller.trigger_right() => { | ||||
|                     state.sound_manager.play_sfx(1); | ||||
|                     return MenuSelectionResult::Right(self.selected, entry); | ||||
|                     return MenuSelectionResult::Right(self.selected, entry, 1); | ||||
|                 } | ||||
|                 _ => {} | ||||
|             } | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| use crate::framework::context::Context; | ||||
| use crate::framework::error::GameResult; | ||||
| use crate::input::combined_menu_controller::CombinedMenuController; | ||||
| use crate::menu::{Menu, MenuSelectionResult}; | ||||
| use crate::menu::MenuEntry; | ||||
| use crate::menu::{Menu, MenuSelectionResult}; | ||||
| use crate::shared_game_state::{SharedGameState, TimingMode}; | ||||
| use crate::sound::InterpolationMode; | ||||
| 
 | ||||
|  | @ -72,6 +72,9 @@ impl SettingsMenu { | |||
| 
 | ||||
|         self.main.push_entry(MenuEntry::Active("< Back".to_owned())); | ||||
| 
 | ||||
|         self.sound.push_entry(MenuEntry::OptionsBar("Music Volume".to_owned(), state.settings.bgm_volume)); | ||||
|         self.sound.push_entry(MenuEntry::OptionsBar("Effects Volume".to_owned(), state.settings.sfx_volume)); | ||||
| 
 | ||||
|         self.sound.push_entry(MenuEntry::DescriptiveOptions( | ||||
|             "BGM Interpolation:".to_owned(), | ||||
|             state.settings.organya_interpolation as usize, | ||||
|  | @ -80,14 +83,14 @@ impl SettingsMenu { | |||
|                 "Linear".to_owned(), | ||||
|                 "Cosine".to_owned(), | ||||
|                 "Cubic".to_owned(), | ||||
|                 "Polyphase".to_owned() | ||||
|                 "Polyphase".to_owned(), | ||||
|             ], | ||||
|             vec![ | ||||
|                 "(Fastest, lowest quality)".to_owned(), | ||||
|                 "(Fast, similar to freeware on Vista+)".to_owned(), | ||||
|                 "(Cosine interpolation)".to_owned(), | ||||
|                 "(Cubic interpolation)".to_owned(), | ||||
|                 "(Slowest, similar to freeware on XP)".to_owned() | ||||
|                 "(Slowest, similar to freeware on XP)".to_owned(), | ||||
|             ], | ||||
|         )); | ||||
|         self.sound.push_entry(MenuEntry::DisabledWhite("".to_owned())); | ||||
|  | @ -211,7 +214,25 @@ impl SettingsMenu { | |||
|                 _ => (), | ||||
|             }, | ||||
|             CurrentMenu::SoundMenu => match self.sound.tick(controller, state) { | ||||
|                 MenuSelectionResult::Selected(0, toggle) => { | ||||
|                 MenuSelectionResult::Left(0, bgm, direction) | MenuSelectionResult::Right(0, bgm, direction) => { | ||||
|                     if let MenuEntry::OptionsBar(_, value) = bgm { | ||||
|                         *value = (*value + (direction as f32 * 0.1)).clamp(0.0, 1.0); | ||||
|                         state.settings.bgm_volume = *value; | ||||
|                         state.sound_manager.set_song_volume(*value); | ||||
| 
 | ||||
|                         let _ = state.settings.save(ctx); | ||||
|                     } | ||||
|                 } | ||||
|                 MenuSelectionResult::Left(1, sfx, direction) | MenuSelectionResult::Right(1, sfx, direction) => { | ||||
|                     if let MenuEntry::OptionsBar(_, value) = sfx { | ||||
|                         *value = (*value + (direction as f32 * 0.1)).clamp(0.0, 1.0); | ||||
|                         state.settings.sfx_volume = *value; | ||||
|                         state.sound_manager.set_sfx_volume(*value); | ||||
| 
 | ||||
|                         let _ = state.settings.save(ctx); | ||||
|                     } | ||||
|                 } | ||||
|                 MenuSelectionResult::Selected(2, toggle) => { | ||||
|                     if let MenuEntry::DescriptiveOptions(_, value, _, _) = toggle { | ||||
|                         let (new_mode, new_value) = match *value { | ||||
|                             0 => (InterpolationMode::Linear, 1), | ||||
|  | @ -228,11 +249,11 @@ impl SettingsMenu { | |||
|                         let _ = state.settings.save(ctx); | ||||
|                     } | ||||
|                 } | ||||
|                 MenuSelectionResult::Selected(3, _) | MenuSelectionResult::Canceled => { | ||||
|                 MenuSelectionResult::Selected(5, _) | MenuSelectionResult::Canceled => { | ||||
|                     self.current = CurrentMenu::MainMenu | ||||
|                 } | ||||
|                 _ => (), | ||||
|             } | ||||
|             }, | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  |  | |||
|  | @ -25,6 +25,10 @@ pub struct Settings { | |||
|     pub motion_interpolation: bool, | ||||
|     pub touch_controls: bool, | ||||
|     pub soundtrack: String, | ||||
|     #[serde(default = "default_vol")] | ||||
|     pub bgm_volume: f32, | ||||
|     #[serde(default = "default_vol")] | ||||
|     pub sfx_volume: f32, | ||||
|     #[serde(default = "default_timing")] | ||||
|     pub timing_mode: TimingMode, | ||||
|     #[serde(default = "default_interpolation")] | ||||
|  | @ -45,22 +49,35 @@ pub struct Settings { | |||
|     pub fps_counter: bool, | ||||
| } | ||||
| 
 | ||||
| fn default_true() -> bool { true } | ||||
| fn default_true() -> bool { | ||||
|     true | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| fn current_version() -> u32 { 4 } | ||||
| fn current_version() -> u32 { | ||||
|     5 | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| fn default_timing() -> TimingMode { TimingMode::_50Hz } | ||||
| fn default_timing() -> TimingMode { | ||||
|     TimingMode::_50Hz | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| fn default_interpolation() -> InterpolationMode { InterpolationMode::Linear } | ||||
| fn default_interpolation() -> InterpolationMode { | ||||
|     InterpolationMode::Linear | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| fn default_speed() -> f64 { | ||||
|     1.0 | ||||
| } | ||||
| 
 | ||||
| #[inline(always)] | ||||
| fn default_vol() -> f32 { | ||||
|     1.0 | ||||
| } | ||||
| 
 | ||||
| impl Settings { | ||||
|     pub fn load(ctx: &Context) -> GameResult<Settings> { | ||||
|         if let Ok(file) = user_open(ctx, "/settings.json") { | ||||
|  | @ -86,6 +103,12 @@ impl Settings { | |||
|             self.timing_mode = default_timing(); | ||||
|         } | ||||
| 
 | ||||
|         if self.version == 4 { | ||||
|             self.version = 5; | ||||
|             self.bgm_volume = default_vol(); | ||||
|             self.sfx_volume = default_vol(); | ||||
|         } | ||||
| 
 | ||||
|         if self.version != initial_version { | ||||
|             log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version); | ||||
|         } | ||||
|  | @ -125,6 +148,8 @@ impl Default for Settings { | |||
|             motion_interpolation: true, | ||||
|             touch_controls: cfg!(target_os = "android"), | ||||
|             soundtrack: "".to_string(), | ||||
|             bgm_volume: 1.0, | ||||
|             sfx_volume: 1.0, | ||||
|             timing_mode: default_timing(), | ||||
|             organya_interpolation: InterpolationMode::Linear, | ||||
|             player1_key_map: p1_default_keymap(), | ||||
|  |  | |||
|  | @ -247,6 +247,9 @@ impl SharedGameState { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         sound_manager.set_song_volume(settings.bgm_volume); | ||||
|         sound_manager.set_sfx_volume(settings.sfx_volume); | ||||
| 
 | ||||
|         #[cfg(feature = "hooks")] | ||||
|         init_hooks(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,16 +4,16 @@ use std::str::FromStr; | |||
| use std::sync::mpsc; | ||||
| use std::sync::mpsc::{Receiver, Sender}; | ||||
| 
 | ||||
| use cpal::Sample; | ||||
| use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; | ||||
| use cpal::Sample; | ||||
| #[cfg(feature = "ogg-playback")] | ||||
| use lewton::inside_ogg::OggStreamReader; | ||||
| use num_traits::clamp; | ||||
| 
 | ||||
| use crate::engine_constants::EngineConstants; | ||||
| use crate::framework::context::Context; | ||||
| use crate::framework::error::{GameError, GameResult}; | ||||
| use crate::framework::error::GameError::{AudioError, InvalidValue}; | ||||
| use crate::framework::error::{GameError, GameResult}; | ||||
| use crate::framework::filesystem; | ||||
| use crate::framework::filesystem::File; | ||||
| use crate::settings::Settings; | ||||
|  | @ -142,6 +142,20 @@ impl SoundManager { | |||
|         let _ = self.tx.send(PlaybackMessage::SetOrgInterpolation(interpolation)); | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_song_volume(&self, volume: f32) { | ||||
|         if self.no_audio { | ||||
|             return; | ||||
|         } | ||||
|         let _ = self.tx.send(PlaybackMessage::SetSongVolume(volume.powf(3.0))); | ||||
|     } | ||||
| 
 | ||||
|     pub fn set_sfx_volume(&self, volume: f32) { | ||||
|         if self.no_audio { | ||||
|             return; | ||||
|         } | ||||
|         let _ = self.tx.send(PlaybackMessage::SetSampleVolume(volume.powf(3.0))); | ||||
|     } | ||||
| 
 | ||||
|     pub fn play_song( | ||||
|         &mut self, | ||||
|         song_id: usize, | ||||
|  | @ -403,6 +417,8 @@ pub(in crate::sound) enum PlaybackMessage { | |||
|     LoopSampleFreq(u8, f32), | ||||
|     StopSample(u8), | ||||
|     SetSpeed(f32), | ||||
|     SetSongVolume(f32), | ||||
|     SetSampleVolume(f32), | ||||
|     SaveState, | ||||
|     RestoreState, | ||||
|     SetSampleParams(u8, PixToneParameters), | ||||
|  | @ -464,6 +480,8 @@ where | |||
|     let mut bgm_index = 0; | ||||
|     let mut pxt_index = 0; | ||||
|     let mut samples = 0; | ||||
|     let mut bgm_vol = 1.0_f32; | ||||
|     let mut sfx_vol = 1.0_f32; | ||||
|     pixtone.mix(&mut pxt_buf, sample_rate); | ||||
| 
 | ||||
|     let err_fn = |err| eprintln!("an error occurred on stream: {}", err); | ||||
|  | @ -547,6 +565,14 @@ where | |||
|                         ogg_engine.set_sample_rate((sample_rate / new_speed) as usize); | ||||
|                         org_engine.set_sample_rate((sample_rate / new_speed) as usize); | ||||
|                     } | ||||
|                     Ok(PlaybackMessage::SetSongVolume(new_volume)) => { | ||||
|                         assert!(bgm_vol >= 0.0); | ||||
|                         bgm_vol = new_volume; | ||||
|                     } | ||||
|                     Ok(PlaybackMessage::SetSampleVolume(new_volume)) => { | ||||
|                         assert!(sfx_vol >= 0.0); | ||||
|                         sfx_vol = new_volume; | ||||
|                     } | ||||
|                     Ok(PlaybackMessage::SaveState) => { | ||||
|                         saved_state = match state { | ||||
|                             PlaybackState::Stopped => PlaybackStateType::None, | ||||
|  | @ -647,13 +673,15 @@ where | |||
| 
 | ||||
|                 if frame.len() >= 2 { | ||||
|                     let sample_l = clamp( | ||||
|                         (((bgm_sample_l ^ 0x8000) as i16) as isize) + (((pxt_sample ^ 0x8000) as i16) as isize), | ||||
|                         (((bgm_sample_l ^ 0x8000) as i16) as f32 * bgm_vol) as isize | ||||
|                             + (((pxt_sample ^ 0x8000) as i16) as f32 * sfx_vol) as isize, | ||||
|                         -0x7fff, | ||||
|                         0x7fff, | ||||
|                     ) as u16 | ||||
|                         ^ 0x8000; | ||||
|                     let sample_r = clamp( | ||||
|                         (((bgm_sample_r ^ 0x8000) as i16) as isize) + (((pxt_sample ^ 0x8000) as i16) as isize), | ||||
|                         (((bgm_sample_r ^ 0x8000) as i16) as f32 * bgm_vol) as isize | ||||
|                             + (((pxt_sample ^ 0x8000) as i16) as f32 * sfx_vol) as isize, | ||||
|                         -0x7fff, | ||||
|                         0x7fff, | ||||
|                     ) as u16 | ||||
|  | @ -663,8 +691,9 @@ where | |||
|                     frame[1] = Sample::from::<u16>(&sample_r); | ||||
|                 } else { | ||||
|                     let sample = clamp( | ||||
|                         ((((bgm_sample_l ^ 0x8000) as i16) + ((bgm_sample_r ^ 0x8000) as i16)) / 2) as isize | ||||
|                             + (((pxt_sample ^ 0x8000) as i16) as isize), | ||||
|                         ((((bgm_sample_l ^ 0x8000) as i16) + ((bgm_sample_r ^ 0x8000) as i16)) as f32 * bgm_vol / 2.0) | ||||
|                             as isize | ||||
|                             + (((pxt_sample ^ 0x8000) as i16) as f32 * sfx_vol) as isize, | ||||
|                         -0x7fff, | ||||
|                         0x7fff, | ||||
|                     ) as u16 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue