config.rsannotatedconfig.rssource788 lines · 25.5 KB · raw
1use crate::js;
2use gba_config::{GbaAspectRatio, GbaAudioInterpolation, GbaButton, GbaColorCorrection, GbaInputs};
3use gba_core::api::{GbaAudioConfig, GbaEmulatorConfig};
4use genesis_config::{
5    GenesisAspectRatio, GenesisButton, GenesisInputs, Opn2BusyBehavior, PcmInterpolation,
6    S32XColorTint, S32XPwmResampling, S32XVideoOut, S32XVoidColor,
7};
8use genesis_config::{GenesisEmulatorConfig, Sega32XEmulatorConfig, SegaCdEmulatorConfig};
9use jgenesis_common::frontend::{ColorCorrection, FiniteF32, MappableInputs, TimingMode};
10use jgenesis_common::input::Player;
11use jgenesis_renderer::config::{
12    AntiDitherShader, FilterMode, FrameRotation, NtscShaderConfig, PreprocessShader,
13    PrescaleFactor, PrescaleMode, RendererConfig, VSyncMode, WgpuBackend, WgpuPowerPreference,
14};
15use serde::{Deserialize, Serialize};
16use smsgg_config::{GgAspectRatio, SmsAspectRatio, SmsGgButton, SmsGgInputs, SmsModel};
17use smsgg_core::SmsGgEmulatorConfig;
18use snes_config::{AudioInterpolationMode, SnesAspectRatio, SnesButton};
19use snes_core::api::SnesEmulatorConfig;
20use snes_core::input::SnesInputs;
21use std::cell::RefCell;
22use std::collections::VecDeque;
23use std::num::{NonZeroU16, NonZeroU32, NonZeroU64};
24use std::ops::Deref;
25use std::rc::Rc;
26use wasm_bindgen::prelude::*;
27use winit::keyboard::KeyCode;
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct CommonWebConfig {
31    pub filter_mode: FilterMode,
32    pub preprocess_shader: PreprocessShader,
33    pub auto_prescale: bool,
34}
35
36impl Default for CommonWebConfig {
37    fn default() -> Self {
38        Self {
39            filter_mode: FilterMode::default(),
40            preprocess_shader: PreprocessShader::default(),
41            auto_prescale: true,
42        }
43    }
44}
45
46impl CommonWebConfig {
47    pub fn to_renderer_config(&self) -> RendererConfig {
48        RendererConfig {
49            wgpu_backend: WgpuBackend::Auto,
50            wgpu_power_preference: WgpuPowerPreference::HighPerformance,
51            vsync_mode: VSyncMode::Enabled,
52            // Frame time sync does not work on web because it blocks until the next frame time
53            frame_time_sync: false,
54            prescale_mode: if self.auto_prescale {
55                PrescaleMode::Auto
56            } else {
57                PrescaleMode::Manual { width: PrescaleFactor::ONE, height: PrescaleFactor::ONE }
58            },
59            scanlines_enabled: false,
60            scanlines_brightness: 1.0,
61            force_integer_height_scaling: false,
62            filter_mode: self.filter_mode,
63            supersample_minification: true,
64            preprocess_shader: self.preprocess_shader,
65            anti_dither_shader: AntiDitherShader::default(),
66            frame_rotation: FrameRotation::default(),
67            ntsc_config: NtscShaderConfig::default(),
68        }
69    }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73pub struct SmsGgWebConfig {
74    timing_mode: TimingMode,
75    sms_aspect_ratio: SmsAspectRatio,
76    gg_aspect_ratio: GgAspectRatio,
77    remove_sprite_limit: bool,
78    sms_crop_vertical_border: bool,
79    sms_crop_left_border: bool,
80    fm_unit_enabled: bool,
81}
82
83impl Default for SmsGgWebConfig {
84    fn default() -> Self {
85        Self {
86            timing_mode: TimingMode::default(),
87            sms_aspect_ratio: SmsAspectRatio::default(),
88            gg_aspect_ratio: GgAspectRatio::default(),
89            remove_sprite_limit: false,
90            sms_crop_vertical_border: true,
91            sms_crop_left_border: false,
92            fm_unit_enabled: true,
93        }
94    }
95}
96
97impl SmsGgWebConfig {
98    pub(crate) fn to_emulator_config(&self) -> SmsGgEmulatorConfig {
99        SmsGgEmulatorConfig {
100            sms_timing_mode: self.timing_mode,
101            sms_model: SmsModel::default(),
102            forced_psg_version: None,
103            sms_aspect_ratio: self.sms_aspect_ratio,
104            gg_aspect_ratio: self.gg_aspect_ratio,
105            forced_region: None,
106            remove_sprite_limit: self.remove_sprite_limit,
107            sms_crop_left_border: self.sms_crop_left_border,
108            sms_crop_vertical_border: self.sms_crop_vertical_border,
109            gg_frame_blending: false,
110            gg_use_sms_resolution: false,
111            fm_sound_unit_enabled: self.fm_unit_enabled,
112            z80_divider: NonZeroU32::new(smsgg_core::NATIVE_Z80_DIVIDER).unwrap(),
113            allow_opposing_joypad_directions: false,
114            cheat_codes: vec![],
115        }
116    }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120pub struct GenesisWebConfig {
121    aspect_ratio: GenesisAspectRatio,
122    remove_sprite_limits: bool,
123    non_linear_color_scale: bool,
124    lpf_enabled: bool,
125    render_vertical_border: bool,
126    render_horizontal_border: bool,
127    m68k_divider: u64,
128}
129
130impl Default for GenesisWebConfig {
131    fn default() -> Self {
132        Self {
133            aspect_ratio: GenesisAspectRatio::default(),
134            remove_sprite_limits: false,
135            non_linear_color_scale: true,
136            lpf_enabled: true,
137            render_vertical_border: false,
138            render_horizontal_border: false,
139            m68k_divider: genesis_config::NATIVE_M68K_DIVIDER,
140        }
141    }
142}
143
144impl GenesisWebConfig {
145    pub fn to_emulator_config(
146        &self,
147        sega_cd: SegaCdEmulatorConfig,
148        sega_32x: Sega32XEmulatorConfig,
149    ) -> GenesisEmulatorConfig {
150        GenesisEmulatorConfig {
151            sega_cd,
152            sega_32x,
153            forced_timing_mode: None,
154            forced_region: None,
155            allow_opposing_joypad_directions: false,
156            mega_mouse_sensitivity: 1.0,
157            auto_3_button_mode: true,
158            aspect_ratio: self.aspect_ratio,
159            force_square_pixels_in_h40: false,
160            adjust_aspect_ratio_in_2x_resolution: true,
161            anamorphic_widescreen: false,
162            remove_sprite_limits: self.remove_sprite_limits,
163            m68k_clock_divider: self.m68k_divider,
164            non_linear_color_scale: self.non_linear_color_scale,
165            deinterlace: true,
166            render_vertical_border: self.render_vertical_border,
167            render_horizontal_border: self.render_horizontal_border,
168            plane_a_enabled: true,
169            plane_b_enabled: true,
170            sprites_enabled: true,
171            window_enabled: true,
172            quantize_ym2612_output: true,
173            emulate_ym2612_ladder_effect: true,
174            opn2_busy_behavior: Opn2BusyBehavior::default(),
175            genesis_lpf_enabled: self.lpf_enabled,
176            genesis_lpf_cutoff: genesis_config::MODEL_1_VA2_LPF_CUTOFF,
177            ym2612_2nd_lpf_enabled: false,
178            ym2612_2nd_lpf_cutoff: genesis_config::MODEL_2_2ND_LPF_CUTOFF,
179            ym2612_channels_enabled: [true; 6],
180            ym2612_enabled: true,
181            psg_enabled: true,
182            ym2612_volume_adjustment_db: 0.0,
183            psg_volume_adjustment_db: 0.0,
184            cheat_codes: vec![],
185        }
186    }
187}
188
189#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
190pub struct SnesWebConfig {
191    aspect_ratio: SnesAspectRatio,
192    audio_interpolation: AudioInterpolationMode,
193}
194
195impl SnesWebConfig {
196    pub fn to_emulator_config(&self) -> SnesEmulatorConfig {
197        SnesEmulatorConfig {
198            forced_timing_mode: None,
199            aspect_ratio: self.aspect_ratio,
200            deinterlace: true,
201            audio_interpolation: self.audio_interpolation,
202            audio_60hz_hack: true,
203            gsu_overclock_factor: NonZeroU64::new(1).unwrap(),
204            allow_opposing_joypad_directions: false,
205            mouse_sensitivity: 1.0,
206        }
207    }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
211pub struct GbaWebConfig {
212    skip_bios_intro_animation: bool,
213    color_correction: GbaColorCorrection,
214    frame_blending: bool,
215    audio_interpolation: GbaAudioInterpolation,
216    psg_low_pass: bool,
217}
218
219impl Default for GbaWebConfig {
220    fn default() -> Self {
221        Self {
222            skip_bios_intro_animation: false,
223            color_correction: GbaColorCorrection::default(),
224            frame_blending: true,
225            audio_interpolation: GbaAudioInterpolation::default(),
226            psg_low_pass: false,
227        }
228    }
229}
230
231impl GbaWebConfig {
232    pub fn to_emulator_config(&self) -> GbaEmulatorConfig {
233        GbaEmulatorConfig {
234            skip_bios_animation: self.skip_bios_intro_animation,
235            aspect_ratio: GbaAspectRatio::SquarePixels,
236            color_correction: match self.color_correction {
237                GbaColorCorrection::None => ColorCorrection::None,
238                GbaColorCorrection::GbaLcd => {
239                    ColorCorrection::GbaLcd { screen_gamma: FiniteF32::try_from(3.2).unwrap() }
240                }
241            },
242            frame_blending: self.frame_blending,
243            forced_save_memory_type: None,
244            allow_opposing_joypad_directions: false,
245            audio: GbaAudioConfig {
246                audio_interpolation: self.audio_interpolation,
247                psg_low_pass: self.psg_low_pass,
248                ..GbaAudioConfig::default()
249            },
250        }
251    }
252}
253
254macro_rules! define_input_config {
255    (
256        config = $config:ident,
257        button = $button_t:ident,
258        inputs = $inputs:ident,
259        fields = {
260            $($field:ident: name $name:literal button $button:ident default $default:ident),* $(,)?
261        } $(,)?
262    ) => {
263        #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
264        pub struct $config {
265            $(
266                $field: KeyCode,
267            )*
268        }
269
270        impl Default for $config {
271            fn default() -> Self {
272                Self {
273                    $(
274                        $field: KeyCode::$default,
275                    )*
276                }
277            }
278        }
279
280        impl $config {
281            pub fn fields_iter(&self) -> impl Iterator<Item = (&'static str, KeyCode)> {
282                [
283                    $(
284                        ($name, self.$field),
285                    )*
286                ]
287                    .into_iter()
288            }
289
290            pub fn update_field(&mut self, name: &str, key: KeyCode) {
291                match name {
292                    $(
293                        $name => self.$field = key,
294                    )*
295                    _ => {}
296                }
297            }
298
299            pub fn handle_input(&self, key: KeyCode, pressed: bool, inputs: &mut $inputs) {
300                $(
301                    if key == self.$field {
302                        inputs.set_field($button_t::$button, Player::One, pressed);
303                    }
304                )*
305            }
306        }
307    }
308}
309
310fn split_input_iterator<'a>(
311    iter: impl Iterator<Item = (&'a str, KeyCode)>,
312) -> (Vec<String>, Vec<String>) {
313    iter.map(|(name, key)| (String::from(name), format!("{key:?}"))).unzip()
314}
315
316define_input_config! {
317    config = SmsGgInputConfig,
318    button = SmsGgButton,
319    inputs = SmsGgInputs,
320    fields = {
321        up: name "Up" button Up default ArrowUp,
322        left: name "Left" button Left default ArrowLeft,
323        right: name "Right" button Right default ArrowRight,
324        down: name "Down" button Down default ArrowDown,
325        button1: name "Button 1" button Button1 default KeyS,
326        button2: name "Button 2" button Button2 default KeyA,
327        pause: name "Start/Pause" button Pause default Enter,
328    }
329}
330
331define_input_config! {
332    config = GenesisInputConfig,
333    button = GenesisButton,
334    inputs = GenesisInputs,
335    fields = {
336        up: name "Up" button Up default ArrowUp,
337        left: name "Left" button Left default ArrowLeft,
338        right: name "Right" button Right default ArrowRight,
339        down: name "Down" button Down default ArrowDown,
340        a: name "A" button A default KeyA,
341        b: name "B" button B default KeyS,
342        c: name "C" button C default KeyD,
343        x: name "X" button X default KeyQ,
344        y: name "Y" button Y default KeyW,
345        z: name "Z" button Z default KeyE,
346        start: name "Start" button Start default Enter,
347        mode: name "Mode" button Mode default ShiftRight,
348    }
349}
350
351define_input_config! {
352    config = SnesInputConfig,
353    button = SnesButton,
354    inputs = SnesInputs,
355    fields = {
356        up: name "Up" button Up default ArrowUp,
357        left: name "Left" button Left default ArrowLeft,
358        right: name "Right" button Right default ArrowRight,
359        down: name "Down" button Down default ArrowDown,
360        a: name "A" button A default KeyS,
361        b: name "B" button B default KeyX,
362        x: name "X" button X default KeyA,
363        y: name "Y" button Y default KeyZ,
364        l: name "L" button L default KeyD,
365        r: name "R" button R default KeyC,
366        start: name "Start" button Start default Enter,
367        select: name "Select" button Select default ShiftRight,
368    }
369}
370
371define_input_config! {
372    config = GbaInputConfig,
373    button = GbaButton,
374    inputs = GbaInputs,
375    fields = {
376        up: name "Up" button Up default ArrowUp,
377        left: name "Left" button Left default ArrowLeft,
378        right: name "Right" button Right default ArrowRight,
379        down: name "Down" button Down default ArrowDown,
380        a: name "A" button A default KeyA,
381        b: name "B" button B default KeyS,
382        l: name "L" button L default KeyQ,
383        r: name "R" button R default KeyW,
384        start: name "Start" button Start default Enter,
385        select: name "Select" button Select default ShiftRight,
386    }
387}
388
389#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
390pub struct InputConfig {
391    pub smsgg: SmsGgInputConfig,
392    pub genesis: GenesisInputConfig,
393    pub snes: SnesInputConfig,
394    pub gba: GbaInputConfig,
395}
396
397impl InputConfig {
398    pub fn smsgg_inputs(&self) -> (Vec<String>, Vec<String>) {
399        split_input_iterator(self.smsgg.fields_iter())
400    }
401
402    pub fn genesis_inputs(&self) -> (Vec<String>, Vec<String>) {
403        split_input_iterator(self.genesis.fields_iter())
404    }
405
406    pub fn snes_inputs(&self) -> (Vec<String>, Vec<String>) {
407        split_input_iterator(self.snes.fields_iter())
408    }
409
410    pub fn gba_inputs(&self) -> (Vec<String>, Vec<String>) {
411        split_input_iterator(self.gba.fields_iter())
412    }
413}
414
415#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
416pub struct WebConfig {
417    pub common: CommonWebConfig,
418    pub smsgg: SmsGgWebConfig,
419    pub genesis: GenesisWebConfig,
420    pub snes: SnesWebConfig,
421    pub gba: GbaWebConfig,
422    pub inputs: InputConfig,
423}
424
425impl WebConfig {
426    const LOCAL_STORAGE_KEY: &str = "config";
427
428    pub fn read_from_local_storage() -> Option<Self> {
429        let config_str = js::localStorageGet(Self::LOCAL_STORAGE_KEY)?;
430        serde_json::from_str(&config_str).ok()
431    }
432
433    pub fn save_to_local_storage(&self) {
434        let config_str = match serde_json::to_string(self) {
435            Ok(config_str) => config_str,
436            Err(err) => {
437                log::error!("Error serializing config: {err}");
438                return;
439            }
440        };
441
442        js::localStorageSet(Self::LOCAL_STORAGE_KEY, &config_str);
443    }
444
445    pub fn to_genesis_config(&self) -> GenesisEmulatorConfig {
446        let sega_cd_config = SegaCdEmulatorConfig {
447            pcm_interpolation: PcmInterpolation::CubicHermite6Point,
448            enable_ram_cartridge: true,
449            load_disc_into_ram: true,
450            disc_drive_speed: NonZeroU16::new(1).unwrap(),
451            sub_cpu_divider: NonZeroU64::new(segacd_core::api::DEFAULT_SUB_CPU_DIVIDER).unwrap(),
452            pcm_lpf_enabled: true,
453            pcm_lpf_cutoff: segacd_core::DEFAULT_PCM_LPF_CUTOFF,
454            apply_genesis_lpf_to_pcm: false,
455            apply_genesis_lpf_to_cd_da: false,
456            pcm_enabled: true,
457            cd_audio_enabled: true,
458            pcm_volume_adjustment_db: 0.0,
459            cd_volume_adjustment_db: 0.0,
460        };
461
462        let sega_32x_config = Sega32XEmulatorConfig {
463            sh2_clock_multiplier: NonZeroU64::new(genesis_config::NATIVE_SH2_MULTIPLIER).unwrap(),
464            video_out: S32XVideoOut::default(),
465            darken_genesis_colors: true,
466            color_tint: S32XColorTint::default(),
467            show_high_priority: true,
468            show_low_priority: true,
469            void_color: S32XVoidColor::default(),
470            apply_genesis_lpf_to_pwm: true,
471            pwm_resampling: S32XPwmResampling::CubicHermite,
472            pwm_enabled: true,
473            pwm_volume_adjustment_db: 0.0,
474        };
475
476        self.genesis.to_emulator_config(sega_cd_config, sega_32x_config)
477    }
478
479    pub fn to_renderer_config(&self) -> RendererConfig {
480        self.common.to_renderer_config()
481    }
482}
483
484#[wasm_bindgen]
485pub struct WebConfigRef(Rc<RefCell<WebConfig>>);
486
487#[wasm_bindgen]
488impl WebConfigRef {
489    #[wasm_bindgen(constructor)]
490    pub fn new() -> Self {
491        let config = WebConfig::read_from_local_storage().unwrap_or_default();
492        Self(Rc::new(RefCell::new(config)))
493    }
494
495    pub fn filter_mode(&self) -> String {
496        self.borrow().common.filter_mode.to_string()
497    }
498
499    pub fn set_filter_mode(&self, filter_mode: &str) {
500        let Ok(filter_mode) = filter_mode.parse() else { return };
501        self.borrow_mut().common.filter_mode = filter_mode;
502    }
503
504    pub fn preprocess_shader(&self) -> String {
505        self.borrow().common.preprocess_shader.to_string()
506    }
507
508    pub fn set_preprocess_shader(&self, preprocess_shader: &str) {
509        let Ok(preprocess_shader) = preprocess_shader.parse() else { return };
510        self.borrow_mut().common.preprocess_shader = preprocess_shader;
511    }
512
513    pub fn auto_prescale(&self) -> bool {
514        self.borrow().common.auto_prescale
515    }
516
517    pub fn set_auto_prescale(&self, auto_prescale: bool) {
518        self.borrow_mut().common.auto_prescale = auto_prescale;
519    }
520
521    pub fn sms_timing_mode(&self) -> String {
522        self.borrow().smsgg.timing_mode.to_string()
523    }
524
525    pub fn set_sms_timing_mode(&self, timing_mode: &str) {
526        let Ok(timing_mode) = timing_mode.parse() else { return };
527        self.borrow_mut().smsgg.timing_mode = timing_mode;
528    }
529
530    pub fn sms_aspect_ratio(&self) -> String {
531        self.borrow().smsgg.sms_aspect_ratio.to_string()
532    }
533
534    pub fn set_sms_aspect_ratio(&self, aspect_ratio: &str) {
535        let Ok(aspect_ratio) = aspect_ratio.parse() else { return };
536        self.borrow_mut().smsgg.sms_aspect_ratio = aspect_ratio;
537    }
538
539    pub fn gg_aspect_ratio(&self) -> String {
540        self.borrow().smsgg.gg_aspect_ratio.to_string()
541    }
542
543    pub fn set_gg_aspect_ratio(&self, aspect_ratio: &str) {
544        let Ok(aspect_ratio) = aspect_ratio.parse() else { return };
545        self.borrow_mut().smsgg.gg_aspect_ratio = aspect_ratio;
546    }
547
548    pub fn sms_remove_sprite_limit(&self) -> bool {
549        self.borrow().smsgg.remove_sprite_limit
550    }
551
552    pub fn set_sms_remove_sprite_limit(&self, remove_sprite_limit: bool) {
553        self.borrow_mut().smsgg.remove_sprite_limit = remove_sprite_limit;
554    }
555
556    pub fn sms_crop_vertical_border(&self) -> bool {
557        self.borrow().smsgg.sms_crop_vertical_border
558    }
559
560    pub fn set_sms_crop_vertical_border(&self, crop: bool) {
561        self.borrow_mut().smsgg.sms_crop_vertical_border = crop;
562    }
563
564    pub fn sms_crop_left_border(&self) -> bool {
565        self.borrow().smsgg.sms_crop_left_border
566    }
567
568    pub fn set_sms_crop_left_border(&self, crop: bool) {
569        self.borrow_mut().smsgg.sms_crop_left_border = crop;
570    }
571
572    pub fn sms_fm_enabled(&self) -> bool {
573        self.borrow().smsgg.fm_unit_enabled
574    }
575
576    pub fn set_sms_fm_enabled(&self, enabled: bool) {
577        self.borrow_mut().smsgg.fm_unit_enabled = enabled;
578    }
579
580    pub fn genesis_m68k_divider(&self) -> u32 {
581        self.borrow().genesis.m68k_divider as u32
582    }
583
584    pub fn set_genesis_m68k_divider(&self, m68k_divider: &str) {
585        let Ok(m68k_divider) = m68k_divider.parse() else { return };
586        self.borrow_mut().genesis.m68k_divider = m68k_divider;
587    }
588
589    pub fn genesis_aspect_ratio(&self) -> String {
590        self.borrow().genesis.aspect_ratio.to_string()
591    }
592
593    pub fn set_genesis_aspect_ratio(&self, aspect_ratio: &str) {
594        let Ok(aspect_ratio) = aspect_ratio.parse() else { return };
595        self.borrow_mut().genesis.aspect_ratio = aspect_ratio;
596    }
597
598    pub fn genesis_remove_sprite_limits(&self) -> bool {
599        self.borrow().genesis.remove_sprite_limits
600    }
601
602    pub fn set_genesis_remove_sprite_limits(&self, remove_sprite_limits: bool) {
603        self.borrow_mut().genesis.remove_sprite_limits = remove_sprite_limits;
604    }
605
606    pub fn genesis_non_linear_color_scale(&self) -> bool {
607        self.borrow().genesis.non_linear_color_scale
608    }
609
610    pub fn set_genesis_non_linear_color_scale(&self, non_linear_color_scale: bool) {
611        self.borrow_mut().genesis.non_linear_color_scale = non_linear_color_scale;
612    }
613
614    pub fn genesis_emulate_low_pass(&self) -> bool {
615        self.borrow().genesis.lpf_enabled
616    }
617
618    pub fn set_genesis_emulate_low_pass(&self, emulate_low_pass: bool) {
619        self.borrow_mut().genesis.lpf_enabled = emulate_low_pass;
620    }
621
622    pub fn genesis_render_vertical_border(&self) -> bool {
623        self.borrow().genesis.render_vertical_border
624    }
625
626    pub fn set_genesis_render_vertical_border(&self, render_vertical_border: bool) {
627        self.borrow_mut().genesis.render_vertical_border = render_vertical_border;
628    }
629
630    pub fn genesis_render_horizontal_border(&self) -> bool {
631        self.borrow().genesis.render_horizontal_border
632    }
633
634    pub fn set_genesis_render_horizontal_border(&self, render_horizontal_border: bool) {
635        self.borrow_mut().genesis.render_horizontal_border = render_horizontal_border;
636    }
637
638    pub fn snes_aspect_ratio(&self) -> String {
639        self.borrow().snes.aspect_ratio.to_string()
640    }
641
642    pub fn set_snes_aspect_ratio(&self, aspect_ratio: &str) {
643        let Ok(aspect_ratio) = aspect_ratio.parse() else { return };
644        self.borrow_mut().snes.aspect_ratio = aspect_ratio;
645    }
646
647    pub fn snes_audio_interpolation(&self) -> String {
648        self.borrow().snes.audio_interpolation.to_string()
649    }
650
651    pub fn set_snes_audio_interpolation(&self, audio_interpolation: &str) {
652        let Ok(audio_interpolation) = audio_interpolation.parse() else { return };
653        self.borrow_mut().snes.audio_interpolation = audio_interpolation;
654    }
655
656    pub fn gba_skip_bios_animation(&self) -> bool {
657        self.borrow().gba.skip_bios_intro_animation
658    }
659
660    pub fn set_gba_skip_bios_animation(&self, skip_bios_animation: bool) {
661        self.borrow_mut().gba.skip_bios_intro_animation = skip_bios_animation;
662    }
663
664    pub fn gba_color_correction(&self) -> String {
665        self.borrow().gba.color_correction.to_string()
666    }
667
668    pub fn set_gba_color_correction(&self, color_correction: &str) {
669        let Ok(color_correction) = color_correction.parse() else { return };
670        self.borrow_mut().gba.color_correction = color_correction;
671    }
672
673    pub fn gba_frame_blending(&self) -> bool {
674        self.borrow().gba.frame_blending
675    }
676
677    pub fn set_gba_frame_blending(&self, frame_blending: bool) {
678        self.borrow_mut().gba.frame_blending = frame_blending;
679    }
680
681    pub fn gba_audio_interpolation(&self) -> String {
682        self.borrow().gba.audio_interpolation.to_string()
683    }
684
685    pub fn set_gba_audio_interpolation(&self, audio_interpolation: &str) {
686        let Ok(audio_interpolation) = audio_interpolation.parse() else { return };
687        self.borrow_mut().gba.audio_interpolation = audio_interpolation;
688    }
689
690    pub fn gba_psg_low_pass(&self) -> bool {
691        self.borrow().gba.psg_low_pass
692    }
693
694    pub fn set_gba_psg_low_pass(&self, psg_low_pass: bool) {
695        self.borrow_mut().gba.psg_low_pass = psg_low_pass;
696    }
697
698    pub fn clone(&self) -> Self {
699        Self(Rc::clone(&self.0))
700    }
701
702    pub fn restore_defaults(&self) {
703        let mut config = self.borrow_mut();
704        *config = WebConfig::default();
705        config.save_to_local_storage();
706    }
707}
708
709impl Deref for WebConfigRef {
710    type Target = Rc<RefCell<WebConfig>>;
711
712    fn deref(&self) -> &Self::Target {
713        &self.0
714    }
715}
716
717impl Default for WebConfigRef {
718    fn default() -> Self {
719        Self::new()
720    }
721}
722
723#[derive(Debug, Clone, PartialEq, Eq)]
724pub enum EmulatorCommand {
725    OpenFile,
726    OpenSegaCdBios,
727    OpenGbaBios,
728    Reset,
729    UploadSaveFile,
730    ConfigureInput { name: String },
731}
732
733#[wasm_bindgen]
734#[derive(Clone, Default)]
735pub struct EmulatorChannel {
736    commands: Rc<RefCell<VecDeque<EmulatorCommand>>>,
737    current_file_name: Rc<RefCell<String>>,
738}
739
740#[wasm_bindgen]
741impl EmulatorChannel {
742    #[wasm_bindgen(constructor)]
743    pub fn new() -> Self {
744        Self::default()
745    }
746
747    pub fn request_open_file(&self) {
748        self.commands.borrow_mut().push_back(EmulatorCommand::OpenFile);
749    }
750
751    pub fn request_open_sega_cd_bios(&self) {
752        self.commands.borrow_mut().push_back(EmulatorCommand::OpenSegaCdBios);
753    }
754
755    pub fn request_open_gba_bios(&self) {
756        self.commands.borrow_mut().push_back(EmulatorCommand::OpenGbaBios);
757    }
758
759    pub fn request_reset(&self) {
760        self.commands.borrow_mut().push_back(EmulatorCommand::Reset);
761    }
762
763    pub fn request_upload_save_file(&self) {
764        self.commands.borrow_mut().push_back(EmulatorCommand::UploadSaveFile);
765    }
766
767    pub fn request_configure_input(&self, name: &str) {
768        self.commands.borrow_mut().push_back(EmulatorCommand::ConfigureInput { name: name.into() });
769    }
770
771    pub fn current_file_name(&self) -> String {
772        self.current_file_name.borrow().clone()
773    }
774
775    pub fn clone(&self) -> Self {
776        <Self as Clone>::clone(self)
777    }
778}
779
780impl EmulatorChannel {
781    pub fn pop_command(&self) -> Option<EmulatorCommand> {
782        self.commands.borrow_mut().pop_front()
783    }
784
785    pub fn set_current_file_name(&self, current_file_name: String) {
786        *self.current_file_name.borrow_mut() = current_file_name;
787    }
788}