1pub mod cheats; 2 3use bincode::{Decode, Encode}; 4use jgenesis_common::define_controller_inputs; 5use jgenesis_common::frontend::{ 6 DisplayInfo, EmulatorConfigTrait, FiniteF64, FrameSize, MappableInputs, TimingMode, 7}; 8use jgenesis_common::input::Player; 9use jgenesis_proc_macros::{ConfigDisplay, EnumAll, EnumDisplay, EnumFromStr}; 10use std::fmt::{Display, Formatter}; 11use std::num::{NonZeroU16, NonZeroU64}; 12 13pub const NATIVE_M68K_DIVIDER: u64 = 7; 14 15pub const NATIVE_SUB_CPU_DIVIDER: u64 = 4; 16 17pub const NATIVE_SH2_MULTIPLIER: u64 = 3; 18 19pub const MODEL_1_VA2_LPF_CUTOFF: u32 = 3390; 20pub const MODEL_1_VA3_LPF_CUTOFF: u32 = 2840; 21pub const MODEL_2_1ST_LPF_CUTOFF: u32 = 3789; 22pub const MODEL_2_2ND_LPF_CUTOFF: u32 = 6725; 23 24pub const DEFAULT_PCM_LPF_CUTOFF: u32 = 7973; 25 26#[derive(Debug, Clone, Copy)] 27pub struct GenParParams { 28 pub force_square_in_h40: bool, 29 pub adjust_for_2x_resolution: bool, 30 pub anamorphic_widescreen: bool, 31} 32 33#[derive( 34 Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumDisplay, EnumFromStr, EnumAll, 35)] 36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 37#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 38pub enum GenesisAspectRatio { 39 #[default] 40 Auto, 41 Ntsc, 42 Pal, 43 SquarePixels, 44 Stretched, 45} 46 47impl GenesisAspectRatio { 48 #[inline] 49 #[must_use] 50 pub fn to_h40_pixel_aspect_ratio(self, timing_mode: TimingMode) -> Option<f64> { 51 if self == Self::Auto { 52 let auto_aspect = match timing_mode { 53 TimingMode::Ntsc => Self::Ntsc, 54 TimingMode::Pal => Self::Pal, 55 }; 56 return auto_aspect.to_h40_pixel_aspect_ratio(timing_mode); 57 } 58 59 match self { 60 Self::Ntsc => Some(32.0 / 35.0), 61 Self::Pal => Some(11.0 / 10.0), 62 Self::SquarePixels => Some(1.0), 63 Self::Stretched => None, 64 Self::Auto => unreachable!("Auto checked at start of function with early return"), 65 } 66 } 67 68 #[must_use] 69 #[allow(clippy::missing_panics_doc)] 70 pub fn to_pixel_aspect_ratio( 71 self, 72 timing_mode: TimingMode, 73 frame_size: FrameSize, 74 params: GenParParams, 75 ) -> Option<FiniteF64> { 76 if self == Self::Auto { 77 let auto_aspect = match timing_mode { 78 TimingMode::Ntsc => Self::Ntsc, 79 TimingMode::Pal => Self::Pal, 80 }; 81 return auto_aspect.to_pixel_aspect_ratio(timing_mode, frame_size, params); 82 } 83 84 let GenParParams { force_square_in_h40, adjust_for_2x_resolution, anamorphic_widescreen } = 85 params; 86 87 let mut pixel_aspect_ratio = match (self, frame_size.width) { 88 (Self::SquarePixels, _) => Some(1.0), 89 (Self::Stretched, _) => None, 90 (Self::Ntsc, 256..=284) => { 91 // NTSC H32/H256px 92 Some(8.0 / 7.0) 93 } 94 (Self::Ntsc, 320..=347) => { 95 // NTSC H40/H320px 96 if force_square_in_h40 { Some(1.0) } else { Some(32.0 / 35.0) } 97 } 98 (Self::Pal, 256..=284) => { 99 // PAL H32/H256px 100 Some(11.0 / 8.0) 101 } 102 (Self::Pal, 320..=347) => { 103 // PAL H40/H320px 104 if force_square_in_h40 { Some(1.0) } else { Some(11.0 / 10.0) } 105 } 106 (Self::Ntsc | Self::Pal, _) => { 107 log::error!("unexpected Genesis frame width: {}", frame_size.width); 108 None 109 } 110 (Self::Auto, _) => unreachable!("Auto checked at start of function with early return"), 111 }; 112 113 if adjust_for_2x_resolution && frame_size.height >= 448 { 114 pixel_aspect_ratio = pixel_aspect_ratio.map(|par| par * 2.0); 115 } 116 117 if anamorphic_widescreen { 118 pixel_aspect_ratio = pixel_aspect_ratio.map(|par| par * 4.0 / 3.0); 119 } 120 121 pixel_aspect_ratio.map(|par| FiniteF64::try_from(par).unwrap()) 122 } 123} 124 125#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, EnumDisplay, EnumAll)] 126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 127#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 128pub enum GenesisRegion { 129 Americas, 130 Japan, 131 Europe, 132} 133 134impl GenesisRegion { 135 #[must_use] 136 pub fn short_name(self) -> &'static str { 137 match self { 138 Self::Americas => "US", 139 Self::Europe => "EU", 140 Self::Japan => "JP", 141 } 142 } 143 144 #[must_use] 145 pub fn long_name(self) -> &'static str { 146 match self { 147 Self::Americas => "US", 148 Self::Europe => "Europe", 149 Self::Japan => "Japan", 150 } 151 } 152} 153 154#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumAll, EnumDisplay)] 155#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 156#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 157pub enum Opn2BusyBehavior { 158 Ym2612, 159 #[default] 160 Ym3438, 161 AlwaysZero, 162} 163 164#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumDisplay, EnumAll)] 165#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 166#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 167pub enum GenesisControllerType { 168 ThreeButton, 169 #[default] 170 SixButton, 171 MegaMouse, 172 Xe1ap, 173 None, 174} 175 176#[derive( 177 Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumDisplay, EnumFromStr, EnumAll, 178)] 179#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 180#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 181pub enum PcmInterpolation { 182 #[default] 183 None, 184 Linear, 185 CubicHermite, 186 CubicHermite6Point, 187} 188 189#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumDisplay, EnumAll)] 190#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 191#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 192pub enum S32XVideoOut { 193 #[default] 194 Combined, 195 GenesisOnly, 196 S32XOnly, 197} 198 199#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumDisplay, EnumAll)] 200#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 201#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 202pub enum S32XColorTint { 203 #[default] 204 None, 205 SlightYellow, 206 Yellow, 207 SlightPurple, 208 Purple, 209} 210 211#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 212#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 213pub enum S32XVoidColor { 214 PaletteRam { idx: u8 }, 215 Direct { r: u8, g: u8, b: u8, a: bool }, 216} 217 218impl Default for S32XVoidColor { 219 fn default() -> Self { 220 Self::Direct { r: 0, g: 0, b: 0, a: false } 221 } 222} 223 224impl Display for S32XVoidColor { 225 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 226 match *self { 227 Self::PaletteRam { idx } => write!(f, "Palette RAM index {idx}"), 228 Self::Direct { r, g, b, a } => write!(f, "RGBA({r}, {g}, {b}, {})", u8::from(a)), 229 } 230 } 231} 232 233#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumDisplay, EnumAll)] 234#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 235#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 236pub enum S32XVoidColorType { 237 PaletteRam, 238 #[default] 239 Direct, 240} 241 242#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumDisplay, EnumAll)] 243#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 244#[cfg_attr(feature = "clap", derive(jgenesis_proc_macros::CustomValueEnum))] 245pub enum S32XPwmResampling { 246 CubicHermite, 247 #[default] 248 WindowedSinc, 249} 250 251// TODO this is a little awful... 252define_controller_inputs! { 253 buttons: GenesisButton { 254 Up -> up "Up", 255 Left -> left "Left", 256 Right -> right "Right", 257 Down -> down "Down", 258 A -> a "A", 259 B -> b "B", 260 C -> c "C", 261 X -> x "X", 262 Y -> y "Y", 263 Z -> z "Z", 264 Start -> start "Start", 265 Mode -> mode "Mode", 266 }, 267 non_gamepad_buttons: [ 268 MegaMouseLeft "Left Button", 269 MegaMouseRight "Right Button", 270 MegaMouseMiddle "Middle Button", 271 MegaMouseStart "Start", 272 Xe1apAnalogLeft "Stick - Left", 273 Xe1apAnalogRight "Stick - Right", 274 Xe1apAnalogUp "Stick - Up", 275 Xe1apAnalogDown "Stick - Down", 276 Xe1apSliderForward "Slider - Forward", 277 Xe1apSliderBackward "Slider - Backward", 278 Xe1apA "A", 279 Xe1apB "B", 280 Xe1apC "C", 281 Xe1apD "D", 282 Xe1apE1 "E1", 283 Xe1apE2 "E2", 284 Xe1apAp "A'", 285 Xe1apBp "B'", 286 Xe1apStart "Start", 287 Xe1apSelect "Select", 288 ], 289 joypad: GenesisJoypadState impl with_allow_opposing_directions, 290} 291 292impl GenesisButton { 293 #[inline] 294 #[must_use] 295 pub fn is_gamepad(self) -> bool { 296 matches!( 297 self, 298 Self::Up 299 | Self::Left 300 | Self::Right 301 | Self::Down 302 | Self::A 303 | Self::B 304 | Self::C 305 | Self::X 306 | Self::Y 307 | Self::Z 308 | Self::Start 309 | Self::Mode 310 ) 311 } 312 313 #[inline] 314 #[must_use] 315 pub fn is_mouse(self) -> bool { 316 matches!( 317 self, 318 Self::MegaMouseLeft 319 | Self::MegaMouseRight 320 | Self::MegaMouseMiddle 321 | Self::MegaMouseStart 322 ) 323 } 324 325 #[inline] 326 #[must_use] 327 pub fn is_xe1ap(self) -> bool { 328 matches!( 329 self, 330 Self::Xe1apAnalogLeft 331 | Self::Xe1apAnalogRight 332 | Self::Xe1apAnalogUp 333 | Self::Xe1apAnalogDown 334 | Self::Xe1apSliderForward 335 | Self::Xe1apSliderBackward 336 | Self::Xe1apA 337 | Self::Xe1apB 338 | Self::Xe1apC 339 | Self::Xe1apD 340 | Self::Xe1apE1 341 | Self::Xe1apE2 342 | Self::Xe1apAp 343 | Self::Xe1apBp 344 | Self::Xe1apStart 345 | Self::Xe1apSelect 346 ) 347 } 348} 349 350#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 351pub struct MegaMouseJoypadState { 352 // Absolute position values are meaningless, only differences between positions are meaningful 353 pub x_position: FiniteF64, 354 pub y_position: FiniteF64, 355 pub left: bool, 356 pub right: bool, 357 pub middle: bool, 358 pub start: bool, 359} 360 361impl MegaMouseJoypadState { 362 pub fn set_button(&mut self, button: GenesisButton, pressed: bool) { 363 match button { 364 GenesisButton::MegaMouseLeft => self.left = pressed, 365 GenesisButton::MegaMouseRight => self.right = pressed, 366 GenesisButton::MegaMouseMiddle => self.middle = pressed, 367 GenesisButton::MegaMouseStart => self.start = pressed, 368 _ => {} 369 } 370 } 371} 372 373#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 374pub struct Xe1apJoypadState { 375 pub analog_x: u8, 376 pub analog_y: u8, 377 pub slider: u8, 378 pub a: bool, 379 pub b: bool, 380 pub c: bool, 381 pub d: bool, 382 pub e1: bool, 383 pub e2: bool, 384 pub ap: bool, 385 pub bp: bool, 386 pub start: bool, 387 pub select: bool, 388 // Fields used only for mapping real world digital inputs to XE-1 AP analog inputs 389 analog_left: bool, 390 analog_right: bool, 391 analog_up: bool, 392 analog_down: bool, 393 slider_forward: bool, 394 slider_backward: bool, 395} 396 397impl Default for Xe1apJoypadState { 398 fn default() -> Self { 399 Self { 400 analog_x: xe1ap_analog_positive(0), 401 analog_y: xe1ap_analog_positive(0), 402 slider: xe1ap_analog_positive(0), 403 a: false, 404 b: false, 405 c: false, 406 d: false, 407 e1: false, 408 e2: false, 409 ap: false, 410 bp: false, 411 start: false, 412 select: false, 413 analog_left: false, 414 analog_right: false, 415 analog_up: false, 416 analog_down: false, 417 slider_forward: false, 418 slider_backward: false, 419 } 420 } 421} 422 423impl Xe1apJoypadState { 424 pub fn set_button(&mut self, button: GenesisButton, pressed: bool) { 425 match button { 426 GenesisButton::Xe1apAnalogLeft => { 427 self.analog_left = pressed; 428 self.analog_x = xe1ap_digital_to_analog(self.analog_left, self.analog_right); 429 } 430 GenesisButton::Xe1apAnalogRight => { 431 self.analog_right = pressed; 432 self.analog_x = xe1ap_digital_to_analog(self.analog_left, self.analog_right); 433 } 434 GenesisButton::Xe1apAnalogUp => { 435 self.analog_up = pressed; 436 self.analog_y = xe1ap_digital_to_analog(self.analog_up, self.analog_down); 437 } 438 GenesisButton::Xe1apAnalogDown => { 439 self.analog_down = pressed; 440 self.analog_y = xe1ap_digital_to_analog(self.analog_up, self.analog_down); 441 } 442 GenesisButton::Xe1apSliderForward => { 443 self.slider_forward = pressed; 444 self.slider = xe1ap_digital_to_analog(self.slider_backward, self.slider_forward); 445 } 446 GenesisButton::Xe1apSliderBackward => { 447 self.slider_backward = pressed; 448 self.slider = xe1ap_digital_to_analog(self.slider_backward, self.slider_forward); 449 } 450 GenesisButton::Xe1apA => self.a = pressed, 451 GenesisButton::Xe1apB => self.b = pressed, 452 GenesisButton::Xe1apC => self.c = pressed, 453 GenesisButton::Xe1apD => self.d = pressed, 454 GenesisButton::Xe1apE1 => self.e1 = pressed, 455 GenesisButton::Xe1apE2 => self.e2 = pressed, 456 GenesisButton::Xe1apAp => self.ap = pressed, 457 GenesisButton::Xe1apBp => self.bp = pressed, 458 GenesisButton::Xe1apStart => self.start = pressed, 459 GenesisButton::Xe1apSelect => self.select = pressed, 460 _ => {} 461 } 462 } 463 464 pub fn set_analog(&mut self, button: GenesisButton, value: i16) { 465 match button { 466 GenesisButton::Xe1apAnalogLeft => { 467 self.analog_x = xe1ap_analog_negative(value); 468 } 469 GenesisButton::Xe1apAnalogRight => { 470 self.analog_x = xe1ap_analog_positive(value); 471 } 472 GenesisButton::Xe1apAnalogUp => { 473 self.analog_y = xe1ap_analog_negative(value); 474 } 475 GenesisButton::Xe1apAnalogDown => { 476 self.analog_y = xe1ap_analog_positive(value); 477 } 478 GenesisButton::Xe1apSliderForward => { 479 self.slider = xe1ap_analog_positive(value); 480 } 481 GenesisButton::Xe1apSliderBackward => { 482 self.slider = xe1ap_analog_negative(value); 483 } 484 _ => {} 485 } 486 } 487} 488 489fn xe1ap_analog_negative(value: i16) -> u8 { 490 // Negative values map to 0-128 491 492 // Round towards negative infinity instead of 0 493 let shifted = (value >> 8) + i16::from(value & 0xFF != 0); 494 (128 - shifted) as u8 495} 496 497fn xe1ap_analog_positive(value: i16) -> u8 { 498 // Positive values map to 128-255 499 ((value >> 8) + 128) as u8 500} 501 502fn xe1ap_digital_to_analog(negative: bool, positive: bool) -> u8 { 503 if negative == positive { 504 // Neither is pressed or both are pressed 505 xe1ap_analog_positive(0) 506 } else if negative { 507 xe1ap_analog_negative(i16::MAX) 508 } else { 509 xe1ap_analog_positive(i16::MAX) 510 } 511} 512 513#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 514pub enum GenesisController { 515 ThreeButton(GenesisJoypadState), 516 SixButton(GenesisJoypadState), 517 MegaMouse(MegaMouseJoypadState), 518 Xe1ap(Xe1apJoypadState), 519 None, 520} 521 522impl GenesisController { 523 #[must_use] 524 pub fn new(controller_type: GenesisControllerType) -> Self { 525 match controller_type { 526 GenesisControllerType::ThreeButton => Self::ThreeButton(GenesisJoypadState::default()), 527 GenesisControllerType::SixButton => Self::SixButton(GenesisJoypadState::default()), 528 GenesisControllerType::MegaMouse => Self::MegaMouse(MegaMouseJoypadState::default()), 529 GenesisControllerType::Xe1ap => Self::Xe1ap(Xe1apJoypadState::default()), 530 GenesisControllerType::None => Self::None, 531 } 532 } 533 534 pub fn set_field(&mut self, button: GenesisButton, pressed: bool) { 535 match self { 536 Self::ThreeButton(state) | Self::SixButton(state) => state.set_button(button, pressed), 537 Self::MegaMouse(state) => state.set_button(button, pressed), 538 Self::Xe1ap(state) => state.set_button(button, pressed), 539 Self::None => {} 540 } 541 } 542 543 pub fn set_analog(&mut self, button: GenesisButton, value: i16) { 544 if let Self::Xe1ap(state) = self { 545 state.set_analog(button, value); 546 } 547 } 548 549 #[must_use] 550 pub fn controller_type(self) -> GenesisControllerType { 551 match self { 552 Self::ThreeButton(_) => GenesisControllerType::ThreeButton, 553 Self::SixButton(_) => GenesisControllerType::SixButton, 554 Self::MegaMouse(_) => GenesisControllerType::MegaMouse, 555 Self::Xe1ap(_) => GenesisControllerType::Xe1ap, 556 Self::None => GenesisControllerType::None, 557 } 558 } 559} 560 561impl Default for GenesisController { 562 fn default() -> Self { 563 Self::new(GenesisControllerType::SixButton) 564 } 565} 566 567#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 568pub struct GenesisInputs { 569 pub p1: GenesisController, 570 pub p2: GenesisController, 571} 572 573impl Default for GenesisInputs { 574 fn default() -> Self { 575 Self { p1: GenesisController::default(), p2: GenesisController::None } 576 } 577} 578 579impl MappableInputs<GenesisButton> for GenesisInputs { 580 #[inline] 581 fn set_field(&mut self, button: GenesisButton, player: Player, pressed: bool) { 582 match player { 583 Player::One => self.p1.set_field(button, pressed), 584 Player::Two => self.p2.set_field(button, pressed), 585 _ => {} 586 } 587 } 588 589 #[inline] 590 fn set_analog(&mut self, button: GenesisButton, player: Player, value: i16) { 591 match player { 592 Player::One => self.p1.set_analog(button, value), 593 Player::Two => self.p2.set_analog(button, value), 594 _ => {} 595 } 596 } 597 598 #[inline] 599 fn handle_mouse_motion( 600 &mut self, 601 _position: (f32, f32), 602 (dx, dy): (f32, f32), 603 _display_info: DisplayInfo, 604 ) { 605 for controller in [&mut self.p1, &mut self.p2] { 606 if let GenesisController::MegaMouse(state) = controller { 607 if let Ok(dx) = FiniteF64::try_from(f64::from(dx)) { 608 state.x_position += dx; 609 } 610 if let Ok(dy) = FiniteF64::try_from(f64::from(dy)) { 611 state.y_position += dy; 612 } 613 } 614 } 615 } 616 617 #[inline] 618 fn needs_relative_mouse_mode(&self) -> bool { 619 [&self.p1, &self.p2] 620 .into_iter() 621 .any(|controller| matches!(controller, GenesisController::MegaMouse(_))) 622 } 623} 624 625#[derive(Debug, Clone, Encode, Decode, ConfigDisplay)] 626pub struct GenesisEmulatorConfig { 627 #[cfg_display(indent_nested)] 628 pub sega_cd: SegaCdEmulatorConfig, 629 #[cfg_display(indent_nested)] 630 pub sega_32x: Sega32XEmulatorConfig, 631 pub forced_timing_mode: Option<TimingMode>, 632 pub forced_region: Option<GenesisRegion>, 633 pub allow_opposing_joypad_directions: bool, 634 pub mega_mouse_sensitivity: f64, 635 pub auto_3_button_mode: bool, 636 pub aspect_ratio: GenesisAspectRatio, 637 pub force_square_pixels_in_h40: bool, 638 pub adjust_aspect_ratio_in_2x_resolution: bool, 639 pub anamorphic_widescreen: bool, 640 pub remove_sprite_limits: bool, 641 pub m68k_clock_divider: u64, 642 pub non_linear_color_scale: bool, 643 pub deinterlace: bool, 644 pub render_vertical_border: bool, 645 pub render_horizontal_border: bool, 646 pub plane_a_enabled: bool, 647 pub plane_b_enabled: bool, 648 pub sprites_enabled: bool, 649 pub window_enabled: bool, 650 pub quantize_ym2612_output: bool, 651 pub emulate_ym2612_ladder_effect: bool, 652 pub opn2_busy_behavior: Opn2BusyBehavior, 653 pub genesis_lpf_enabled: bool, 654 pub genesis_lpf_cutoff: u32, 655 pub ym2612_2nd_lpf_enabled: bool, 656 pub ym2612_2nd_lpf_cutoff: u32, 657 #[cfg_display(debug_fmt)] 658 pub ym2612_channels_enabled: [bool; 6], 659 pub ym2612_enabled: bool, 660 pub psg_enabled: bool, 661 pub ym2612_volume_adjustment_db: f64, 662 pub psg_volume_adjustment_db: f64, 663 #[cfg_display(skip)] 664 pub cheat_codes: Vec<(u32, u16)>, 665} 666 667impl Default for GenesisEmulatorConfig { 668 fn default() -> Self { 669 Self { 670 sega_cd: SegaCdEmulatorConfig::default(), 671 sega_32x: Sega32XEmulatorConfig::default(), 672 forced_timing_mode: None, 673 forced_region: None, 674 allow_opposing_joypad_directions: false, 675 mega_mouse_sensitivity: 1.0, 676 auto_3_button_mode: true, 677 aspect_ratio: GenesisAspectRatio::default(), 678 force_square_pixels_in_h40: false, 679 adjust_aspect_ratio_in_2x_resolution: true, 680 anamorphic_widescreen: false, 681 remove_sprite_limits: false, 682 m68k_clock_divider: NATIVE_M68K_DIVIDER, 683 non_linear_color_scale: true, 684 deinterlace: true, 685 render_vertical_border: false, 686 render_horizontal_border: false, 687 plane_a_enabled: true, 688 plane_b_enabled: true, 689 sprites_enabled: true, 690 window_enabled: true, 691 quantize_ym2612_output: true, 692 emulate_ym2612_ladder_effect: true, 693 opn2_busy_behavior: Opn2BusyBehavior::default(), 694 genesis_lpf_enabled: true, 695 genesis_lpf_cutoff: MODEL_1_VA2_LPF_CUTOFF, 696 ym2612_2nd_lpf_enabled: false, 697 ym2612_2nd_lpf_cutoff: MODEL_2_2ND_LPF_CUTOFF, 698 ym2612_channels_enabled: [true; 6], 699 ym2612_enabled: true, 700 psg_enabled: true, 701 ym2612_volume_adjustment_db: 0.0, 702 psg_volume_adjustment_db: 0.0, 703 cheat_codes: vec![], 704 } 705 } 706} 707 708impl GenesisEmulatorConfig { 709 #[must_use] 710 #[allow(clippy::missing_panics_doc)] 711 pub fn clamped_m68k_divider(&self) -> NonZeroU64 { 712 let clamped_divider = self.m68k_clock_divider.clamp(1, NATIVE_M68K_DIVIDER); 713 if clamped_divider != self.m68k_clock_divider { 714 log::warn!( 715 "Clamped M68K clock divider from {} to {clamped_divider}", 716 self.m68k_clock_divider 717 ); 718 } 719 NonZeroU64::new(clamped_divider).unwrap() 720 } 721 722 #[inline] 723 #[must_use] 724 pub fn to_gen_par_params(&self) -> GenParParams { 725 GenParParams { 726 force_square_in_h40: self.force_square_pixels_in_h40, 727 adjust_for_2x_resolution: self.adjust_aspect_ratio_in_2x_resolution, 728 anamorphic_widescreen: self.anamorphic_widescreen, 729 } 730 } 731} 732 733impl EmulatorConfigTrait for GenesisEmulatorConfig { 734 fn with_overclocking_disabled(&self) -> Self { 735 Self { 736 sega_cd: self.sega_cd.with_overclocking_disabled(), 737 sega_32x: self.sega_32x.with_overclocking_disabled(), 738 m68k_clock_divider: NATIVE_M68K_DIVIDER, 739 ..self.clone() 740 } 741 } 742} 743 744#[derive(Debug, Clone, Encode, Decode, ConfigDisplay)] 745pub struct SegaCdEmulatorConfig { 746 pub pcm_interpolation: PcmInterpolation, 747 pub enable_ram_cartridge: bool, 748 pub load_disc_into_ram: bool, 749 pub disc_drive_speed: NonZeroU16, 750 pub sub_cpu_divider: NonZeroU64, 751 pub pcm_lpf_enabled: bool, 752 pub pcm_lpf_cutoff: u32, 753 pub apply_genesis_lpf_to_pcm: bool, 754 pub apply_genesis_lpf_to_cd_da: bool, 755 pub pcm_enabled: bool, 756 pub cd_audio_enabled: bool, 757 pub pcm_volume_adjustment_db: f64, 758 pub cd_volume_adjustment_db: f64, 759} 760 761impl Default for SegaCdEmulatorConfig { 762 fn default() -> Self { 763 Self { 764 pcm_interpolation: PcmInterpolation::default(), 765 enable_ram_cartridge: true, 766 load_disc_into_ram: false, 767 disc_drive_speed: NonZeroU16::new(1).unwrap(), 768 sub_cpu_divider: NonZeroU64::new(NATIVE_SUB_CPU_DIVIDER).unwrap(), 769 pcm_lpf_enabled: true, 770 pcm_lpf_cutoff: DEFAULT_PCM_LPF_CUTOFF, 771 apply_genesis_lpf_to_pcm: false, 772 apply_genesis_lpf_to_cd_da: false, 773 pcm_enabled: true, 774 cd_audio_enabled: true, 775 pcm_volume_adjustment_db: 0.0, 776 cd_volume_adjustment_db: 0.0, 777 } 778 } 779} 780 781impl EmulatorConfigTrait for SegaCdEmulatorConfig { 782 fn with_overclocking_disabled(&self) -> Self { 783 Self { 784 disc_drive_speed: NonZeroU16::new(1).unwrap(), 785 sub_cpu_divider: NonZeroU64::new(NATIVE_SUB_CPU_DIVIDER).unwrap(), 786 ..*self 787 } 788 } 789} 790 791#[derive(Debug, Clone, Encode, Decode, ConfigDisplay)] 792pub struct Sega32XEmulatorConfig { 793 pub sh2_clock_multiplier: NonZeroU64, 794 pub video_out: S32XVideoOut, 795 pub darken_genesis_colors: bool, 796 pub color_tint: S32XColorTint, 797 pub show_high_priority: bool, 798 pub show_low_priority: bool, 799 pub void_color: S32XVoidColor, 800 pub apply_genesis_lpf_to_pwm: bool, 801 pub pwm_resampling: S32XPwmResampling, 802 pub pwm_enabled: bool, 803 pub pwm_volume_adjustment_db: f64, 804} 805 806impl Default for Sega32XEmulatorConfig { 807 fn default() -> Self { 808 Self { 809 sh2_clock_multiplier: NonZeroU64::new(NATIVE_SH2_MULTIPLIER).unwrap(), 810 video_out: S32XVideoOut::default(), 811 darken_genesis_colors: true, 812 color_tint: S32XColorTint::default(), 813 show_high_priority: true, 814 show_low_priority: true, 815 void_color: S32XVoidColor::default(), 816 apply_genesis_lpf_to_pwm: true, 817 pwm_resampling: S32XPwmResampling::default(), 818 pwm_enabled: true, 819 pwm_volume_adjustment_db: 0.0, 820 } 821 } 822} 823 824impl EmulatorConfigTrait for Sega32XEmulatorConfig { 825 fn with_overclocking_disabled(&self) -> Self { 826 Self { sh2_clock_multiplier: NonZeroU64::new(NATIVE_SH2_MULTIPLIER).unwrap(), ..*self } 827 } 828} 829 830#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 831pub struct LowPassSettings { 832 pub genesis_enabled: bool, 833 pub genesis_cutoff: u32, 834 pub ym2612_2nd_enabled: bool, 835 pub ym2612_2nd_cutoff: u32, 836} 837 838impl LowPassSettings { 839 #[must_use] 840 pub fn from_config(config: &GenesisEmulatorConfig) -> Self { 841 Self { 842 genesis_enabled: config.genesis_lpf_enabled, 843 genesis_cutoff: config.genesis_lpf_cutoff, 844 ym2612_2nd_enabled: config.ym2612_2nd_lpf_enabled, 845 ym2612_2nd_cutoff: config.ym2612_2nd_lpf_cutoff, 846 } 847 } 848}