mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-11-05 00:14:57 +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