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}