diff --git a/emulator/core/src/host/input.rs b/emulator/core/src/host/input.rs index cd9f9b9..3000dfa 100644 --- a/emulator/core/src/host/input.rs +++ b/emulator/core/src/host/input.rs @@ -15,6 +15,7 @@ pub fn event_queue() -> (EventSender, EventReceiver) { (sender, receiver) } +#[derive(Clone)] pub struct EventSender { queue: Arc>>, } @@ -23,10 +24,6 @@ impl EventSender { pub fn send(&self, event: T) { self.queue.lock().unwrap().push_back(event); } - - //pub fn send_at_instant(&self, instant: Instant, event: T) { - // self.queue.lock().unwrap().push_back((instant, event)); - //} } pub struct EventReceiver { diff --git a/emulator/frontends/pixels/assets/moa-genesis/index.html b/emulator/frontends/pixels/assets/moa-genesis/index.html index 0c4b22c..2cedcef 100644 --- a/emulator/frontends/pixels/assets/moa-genesis/index.html +++ b/emulator/frontends/pixels/assets/moa-genesis/index.html @@ -24,11 +24,12 @@
+
- - + + diff --git a/emulator/frontends/pixels/assets/moa-genesis/interface.js b/emulator/frontends/pixels/assets/moa-genesis/interface.js index 1e48afe..ebb6475 100644 --- a/emulator/frontends/pixels/assets/moa-genesis/interface.js +++ b/emulator/frontends/pixels/assets/moa-genesis/interface.js @@ -1,49 +1,70 @@ import * as Emulator from './moa-genesis.js'; -function initialize_emulator() { - let host = Emulator.new_host(); - let system = Emulator.load_system(host, Emulator.get_load_system_fn()); - - //Emulator.start_system(system); - let last_update = performance.now(); - setTimeout(function refreshFrame() { - let current = performance.now(); - let diff = current - last_update; - //let remaining = Math.max((16 * Emulator.get_speed()) - diff, 0); - last_update = current; - - let runtime = Emulator.run_system_for(system, diff * 1_000_000); - if (Emulator.is_running()) { - let remaining = Math.max(diff - runtime - (diff * 0.1), 1); - setTimeout(refreshFrame, remaining); - } - }, 0); - - Emulator.host_run_loop(host); -} - -// Update the frame rate display -var frame_rate_el = document.getElementById("frame-rate"); -var frame_rate = setInterval(function () { - frame_rate_el.value = Emulator.get_frames_since(); -}, 1000); - window.addEventListener("load", () => { Emulator.default(); }); +function initialize_emulator() { + const host = Emulator.new_host(); + const system = Emulator.load_system(host, Emulator.get_load_system_fn()); + + //Emulator.start_system(system); + let last_update = performance.now(); + setTimeout(function refreshFrame() { + // Calculate the time difference since the last update cycle + const current = performance.now(); + const diff = current - last_update; + last_update = current; + + // Run the system for the difference, and get the realtime runtime in millis + const runtime = Emulator.run_system_for(system, diff * 1_000_000); + + if (Emulator.is_running()) { + // Calculate the timeout needed to fill the time that was *not* taken by the sim + const remaining = Math.max(diff - runtime - (diff * 0.1), 1); + setTimeout(refreshFrame, remaining); + } + }, 0); + + const controllers = Emulator.get_controllers(host); + function button_event(e) { + let state; + if (e.type == 'mousedown' || e.type == 'touchstart') { + state = true; + } else { + state = false; + } + Emulator.button_press(controllers, e.target.name, state); + } + + document.getElementById("controller").querySelectorAll('button').forEach(function (button) { + button.addEventListener('mousedown', button_event); + button.addEventListener('mouseup', button_event); + button.addEventListener('touchstart', button_event); + button.addEventListener('touchend', button_event); + }); + + Emulator.host_run_loop(host); +} + +// Update the frame rate display +const frame_rate_el = document.getElementById("frame-rate"); +const frame_rate = setInterval(function () { + frame_rate_el.value = Emulator.get_frames_since(); +}, 1000); + // Load a new ROM file -var reader = new FileReader(); +const reader = new FileReader(); reader.onloadend = function (e) { - var data = new Uint8Array(reader.result); + let data = new Uint8Array(reader.result); // If the SMD file magic number is present, then convert it before loading if (data[8] == 0xAA && data[9] == 0xBB) data = Emulator.smd_to_bin(data); Emulator.set_rom_data(data); }; -var file_input = document.getElementById("rom-file"); +const file_input = document.getElementById("rom-file"); file_input.addEventListener("change", e => { document.getElementById("video").focus(); reader.readAsArrayBuffer(file_input.files[0]) @@ -62,8 +83,8 @@ document.getElementById("power").addEventListener("click", () => { initialize_emulator(); }); -var mute_state = false; -var mute = document.getElementById("mute"); +let mute_state = false; +const mute = document.getElementById("mute"); mute.addEventListener("click", () => { mute_state = !mute_state; if (mute_state) { @@ -74,19 +95,3 @@ mute.addEventListener("click", () => { Emulator.set_mute(mute_state); }); -function button_event(e) { - var state; - if (e.type == 'mousedown' || e.type == 'touchstart') { - state = true; - } else { - state = false; - } - Emulator.button_press(e.target.name, state); -} - -document.getElementById("controller").querySelectorAll('button').forEach(function (button) { - button.addEventListener('mousedown', button_event); - button.addEventListener('mouseup', button_event); - button.addEventListener('touchstart', button_event); - button.addEventListener('touchend', button_event); -}); diff --git a/emulator/frontends/pixels/assets/moa-genesis/style.css b/emulator/frontends/pixels/assets/moa-genesis/style.css index 12a8ad3..a415999 100644 --- a/emulator/frontends/pixels/assets/moa-genesis/style.css +++ b/emulator/frontends/pixels/assets/moa-genesis/style.css @@ -25,6 +25,19 @@ body { cursor: pointer; } +#controller { + width: 100%; + display: flex; +} + +#controller button { + flex: 1; + width: 100%; + height: 40px; + margin-top: 2px; + margin-bottom: 2px; +} + #controller .updown { overflow: hidden; } diff --git a/emulator/frontends/pixels/src/frontend.rs b/emulator/frontends/pixels/src/frontend.rs index 12a0f0b..29cddad 100644 --- a/emulator/frontends/pixels/src/frontend.rs +++ b/emulator/frontends/pixels/src/frontend.rs @@ -1,4 +1,6 @@ +use std::rc::Rc; + use instant::Instant; use pixels::{Pixels, SurfaceTexture}; use winit::event::{Event, VirtualKeyCode, WindowEvent, ElementState}; @@ -38,6 +40,10 @@ impl PixelsFrontend { pub fn get_mixer(&self) -> AudioMixer { self.mixer.clone() } + + pub fn get_controllers(&self) -> Option> { + self.controllers.clone() + } } impl Host for PixelsFrontend { diff --git a/emulator/frontends/pixels/src/native.rs b/emulator/frontends/pixels/src/native.rs index dbf86d1..d4e5d04 100644 --- a/emulator/frontends/pixels/src/native.rs +++ b/emulator/frontends/pixels/src/native.rs @@ -5,12 +5,13 @@ use winit::dpi::LogicalSize; use winit::event_loop::EventLoop; use winit::window::{Window, WindowBuilder}; -use crate::frontend::{self, LoadSystemFn}; +use crate::frontend::{self, LoadSystemFn, PixelsFrontend}; pub fn start(load: LoadSystemFn) { env_logger::init(); - pollster::block_on(frontend::run(load)); + let host = PixelsFrontend::new(); + pollster::block_on(frontend::run_loop(host)); } pub fn create_window(event_loop: &EventLoop) -> Rc { diff --git a/emulator/frontends/pixels/src/web.rs b/emulator/frontends/pixels/src/web.rs index 1e3bfb1..8e16017 100644 --- a/emulator/frontends/pixels/src/web.rs +++ b/emulator/frontends/pixels/src/web.rs @@ -13,6 +13,7 @@ use wasm_bindgen::JsCast; use wasm_bindgen::closure::Closure; use moa_core::{ClockDuration, System, wrap_transmutable}; +use moa_core::host::{ControllerInput, ControllerDevice, ControllerEvent, EventSender}; use crate::settings; use crate::frontend::{self, PixelsFrontend, LoadSystemFn}; @@ -72,19 +73,41 @@ pub fn set_mute(mute: bool) { } #[wasm_bindgen] -pub fn button_press(name: String, state: bool) { - +pub fn button_press(handle: &ControllersHandle, name: String, state: bool) { + let input = match name.as_str() { + "up" => Some(ControllerInput::DpadUp(state)), + "down" => Some(ControllerInput::DpadDown(state)), + "left" => Some(ControllerInput::DpadLeft(state)), + "right" => Some(ControllerInput::DpadRight(state)), + "start" => Some(ControllerInput::Start(state)), + "a" => Some(ControllerInput::ButtonA(state)), + "b" => Some(ControllerInput::ButtonB(state)), + "c" => Some(ControllerInput::ButtonC(state)), + _ => None + }; + + if let Some(input) = input { + handle.0.send(ControllerEvent::new(ControllerDevice::A, input)); + } } #[wasm_bindgen] pub struct HostHandle(PixelsFrontend); +#[wasm_bindgen] +pub struct ControllersHandle(EventSender); + #[wasm_bindgen] pub fn new_host() -> HostHandle { HostHandle(PixelsFrontend::new()) } +#[wasm_bindgen] +pub fn get_controllers(handle: &HostHandle) -> ControllersHandle { + ControllersHandle(handle.0.get_controllers().unwrap()) +} + #[wasm_bindgen] pub fn host_run_loop(handle: HostHandle) { wasm_bindgen_futures::spawn_local(frontend::run_loop(handle.0)); @@ -196,50 +219,6 @@ pub fn create_window(event_loop: &EventLoop) -> Rc { } */ - /* - fn ycombinator(f: &F) - where - F: Fn(&F) - { - f(f) - } - - - let closure: Closure = Closure::new(move |_e: Event| { - - }); - client_window - .set_interval_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), 17) - .unwrap(); - closure.forget(); - */ - - /* - let mut update_timer = Instant::now(); - let mut system = load(&mut host, settings::get().rom_data.clone()).unwrap(); - let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |_e: web_sys::Event| { - let run_timer = Instant::now(); - let nanoseconds_per_frame = (16_600_000 as f32 * settings::get().speed) as Clock; - if let Err(err) = system.run_for(nanoseconds_per_frame) { - log::error!("{:?}", err); - } - log::info!("ran simulation for {:?}ms in {:?}ms", nanoseconds_per_frame / 1_000_000, run_timer.elapsed().as_millis()); - - let settings = settings::get(); - if settings.run { - //match load(&mut host.lock().unwrap(), settings.rom_data.clone()) { - // Ok(s) => { system = s; }, - // Err(err) => log::error!("{:?}", err), - //} - } - }) as Box); - client_window - .set_interval_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), 17) - .unwrap(); - closure.forget(); - */ - - window } diff --git a/emulator/peripherals/yamaha/src/ym2612.rs b/emulator/peripherals/yamaha/src/ym2612.rs index 669699e..52b440b 100644 --- a/emulator/peripherals/yamaha/src/ym2612.rs +++ b/emulator/peripherals/yamaha/src/ym2612.rs @@ -691,7 +691,7 @@ fn sign_extend_u16(value: u16, size: usize) -> i16 { struct Dac { enabled: bool, - samples: VecDeque, + samples: VecDeque<(FmClock, f32)>, } impl Default for Dac { @@ -704,16 +704,19 @@ impl Default for Dac { } impl Dac { - fn add_sample(&mut self, sample: f32) { - self.samples.push_back(sample); + fn add_sample(&mut self, clock: FmClock, sample: f32) { + self.samples.push_back((clock, sample)); } - fn get_sample(&mut self) -> f32 { - if let Some(data) = self.samples.pop_front() { - data - } else { - 0.0 + fn get_sample_after(&mut self, clock: FmClock) -> f32 { + if let Some((sample_clock, data)) = self.samples.front().cloned() { + if clock > sample_clock { + self.samples.pop_front(); + return data; + } } + + 0.0 } } @@ -798,7 +801,7 @@ impl Steppable for Ym2612 { // The DAC uses an 8000 Hz sample rate, so we don't want to skip clocks if self.dac.enabled { - sample += self.dac.get_sample(); + sample += self.dac.get_sample_after(fm_clock); } // TODO add stereo output, which is supported by ym2612 @@ -872,8 +875,9 @@ impl Ym2612 { 0x2a => { if self.dac.enabled { - for _ in 0..3 { - self.dac.add_sample(((data as f32 - 128.0) / 255.0) * 2.0); + let fm_clock = clock.as_duration() / self.fm_clock_period; + for i in 0..3 { + self.dac.add_sample(fm_clock + i, ((data as f32 - 128.0) / 255.0) * 2.0); } } },