mirror of
https://github.com/transistorfet/moa.git
synced 2024-11-24 23:32:46 +00:00
Connected the web ui controller inputs to sim
This commit is contained in:
parent
6f871a3b0b
commit
6389fa0482
@ -15,6 +15,7 @@ pub fn event_queue<T>() -> (EventSender<T>, EventReceiver<T>) {
|
||||
(sender, receiver)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventSender<T> {
|
||||
queue: Arc<Mutex<VecDeque<T>>>,
|
||||
}
|
||||
@ -23,10 +24,6 @@ impl<T> EventSender<T> {
|
||||
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<T> {
|
||||
|
@ -24,11 +24,12 @@
|
||||
<div id="video-screen">
|
||||
<canvas id="video" tabindex="0" draw-raw-handle="1" />
|
||||
</div>
|
||||
|
||||
<div id="controller">
|
||||
<button name="left">⇦</button>
|
||||
<button name="up">⇧</button>
|
||||
<button name="down">⇨</button>
|
||||
<button name="right">⇩</button>
|
||||
<button name="down">⇩</button>
|
||||
<button name="right">⇨</button>
|
||||
|
||||
<button name="start">Start</button>
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<EventSender<ControllerEvent>> {
|
||||
self.controllers.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Host for PixelsFrontend {
|
||||
|
@ -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<T>(event_loop: &EventLoop<T>) -> Rc<Window> {
|
||||
|
@ -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<ControllerEvent>);
|
||||
|
||||
#[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<T>(event_loop: &EventLoop<T>) -> Rc<Window> {
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
fn ycombinator<F>(f: &F)
|
||||
where
|
||||
F: Fn(&F)
|
||||
{
|
||||
f(f)
|
||||
}
|
||||
|
||||
|
||||
let closure: Closure<dyn Fn(Event)> = 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<dyn FnMut(_)>);
|
||||
client_window
|
||||
.set_interval_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), 17)
|
||||
.unwrap();
|
||||
closure.forget();
|
||||
*/
|
||||
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
|
@ -691,7 +691,7 @@ fn sign_extend_u16(value: u16, size: usize) -> i16 {
|
||||
|
||||
struct Dac {
|
||||
enabled: bool,
|
||||
samples: VecDeque<f32>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user