lib.rsannotatedlib.rssource848 lines · 26.7 KB · raw
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}

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}
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}