mirror of
https://github.com/transistorfet/moa.git
synced 2025-02-17 18:30:33 +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)
|
(sender, receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct EventSender<T> {
|
pub struct EventSender<T> {
|
||||||
queue: Arc<Mutex<VecDeque<T>>>,
|
queue: Arc<Mutex<VecDeque<T>>>,
|
||||||
}
|
}
|
||||||
@ -23,10 +24,6 @@ impl<T> EventSender<T> {
|
|||||||
pub fn send(&self, event: T) {
|
pub fn send(&self, event: T) {
|
||||||
self.queue.lock().unwrap().push_back(event);
|
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> {
|
pub struct EventReceiver<T> {
|
||||||
|
@ -24,11 +24,12 @@
|
|||||||
<div id="video-screen">
|
<div id="video-screen">
|
||||||
<canvas id="video" tabindex="0" draw-raw-handle="1" />
|
<canvas id="video" tabindex="0" draw-raw-handle="1" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="controller">
|
<div id="controller">
|
||||||
<button name="left">⇦</button>
|
<button name="left">⇦</button>
|
||||||
<button name="up">⇧</button>
|
<button name="up">⇧</button>
|
||||||
<button name="down">⇨</button>
|
<button name="down">⇩</button>
|
||||||
<button name="right">⇩</button>
|
<button name="right">⇨</button>
|
||||||
|
|
||||||
<button name="start">Start</button>
|
<button name="start">Start</button>
|
||||||
|
|
||||||
|
@ -1,49 +1,70 @@
|
|||||||
|
|
||||||
import * as Emulator from './moa-genesis.js';
|
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", () => {
|
window.addEventListener("load", () => {
|
||||||
Emulator.default();
|
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
|
// Load a new ROM file
|
||||||
var reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onloadend = function (e) {
|
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 the SMD file magic number is present, then convert it before loading
|
||||||
if (data[8] == 0xAA && data[9] == 0xBB)
|
if (data[8] == 0xAA && data[9] == 0xBB)
|
||||||
data = Emulator.smd_to_bin(data);
|
data = Emulator.smd_to_bin(data);
|
||||||
Emulator.set_rom_data(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 => {
|
file_input.addEventListener("change", e => {
|
||||||
document.getElementById("video").focus();
|
document.getElementById("video").focus();
|
||||||
reader.readAsArrayBuffer(file_input.files[0])
|
reader.readAsArrayBuffer(file_input.files[0])
|
||||||
@ -62,8 +83,8 @@ document.getElementById("power").addEventListener("click", () => {
|
|||||||
initialize_emulator();
|
initialize_emulator();
|
||||||
});
|
});
|
||||||
|
|
||||||
var mute_state = false;
|
let mute_state = false;
|
||||||
var mute = document.getElementById("mute");
|
const mute = document.getElementById("mute");
|
||||||
mute.addEventListener("click", () => {
|
mute.addEventListener("click", () => {
|
||||||
mute_state = !mute_state;
|
mute_state = !mute_state;
|
||||||
if (mute_state) {
|
if (mute_state) {
|
||||||
@ -74,19 +95,3 @@ mute.addEventListener("click", () => {
|
|||||||
Emulator.set_mute(mute_state);
|
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;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#controller {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#controller button {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
#controller .updown {
|
#controller .updown {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use instant::Instant;
|
use instant::Instant;
|
||||||
use pixels::{Pixels, SurfaceTexture};
|
use pixels::{Pixels, SurfaceTexture};
|
||||||
use winit::event::{Event, VirtualKeyCode, WindowEvent, ElementState};
|
use winit::event::{Event, VirtualKeyCode, WindowEvent, ElementState};
|
||||||
@ -38,6 +40,10 @@ impl PixelsFrontend {
|
|||||||
pub fn get_mixer(&self) -> AudioMixer {
|
pub fn get_mixer(&self) -> AudioMixer {
|
||||||
self.mixer.clone()
|
self.mixer.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_controllers(&self) -> Option<EventSender<ControllerEvent>> {
|
||||||
|
self.controllers.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Host for PixelsFrontend {
|
impl Host for PixelsFrontend {
|
||||||
|
@ -5,12 +5,13 @@ use winit::dpi::LogicalSize;
|
|||||||
use winit::event_loop::EventLoop;
|
use winit::event_loop::EventLoop;
|
||||||
use winit::window::{Window, WindowBuilder};
|
use winit::window::{Window, WindowBuilder};
|
||||||
|
|
||||||
use crate::frontend::{self, LoadSystemFn};
|
use crate::frontend::{self, LoadSystemFn, PixelsFrontend};
|
||||||
|
|
||||||
pub fn start(load: LoadSystemFn) {
|
pub fn start(load: LoadSystemFn) {
|
||||||
env_logger::init();
|
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> {
|
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 wasm_bindgen::closure::Closure;
|
||||||
|
|
||||||
use moa_core::{ClockDuration, System, wrap_transmutable};
|
use moa_core::{ClockDuration, System, wrap_transmutable};
|
||||||
|
use moa_core::host::{ControllerInput, ControllerDevice, ControllerEvent, EventSender};
|
||||||
|
|
||||||
use crate::settings;
|
use crate::settings;
|
||||||
use crate::frontend::{self, PixelsFrontend, LoadSystemFn};
|
use crate::frontend::{self, PixelsFrontend, LoadSystemFn};
|
||||||
@ -72,19 +73,41 @@ pub fn set_mute(mute: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[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]
|
#[wasm_bindgen]
|
||||||
pub struct HostHandle(PixelsFrontend);
|
pub struct HostHandle(PixelsFrontend);
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct ControllersHandle(EventSender<ControllerEvent>);
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn new_host() -> HostHandle {
|
pub fn new_host() -> HostHandle {
|
||||||
HostHandle(PixelsFrontend::new())
|
HostHandle(PixelsFrontend::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn get_controllers(handle: &HostHandle) -> ControllersHandle {
|
||||||
|
ControllersHandle(handle.0.get_controllers().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn host_run_loop(handle: HostHandle) {
|
pub fn host_run_loop(handle: HostHandle) {
|
||||||
wasm_bindgen_futures::spawn_local(frontend::run_loop(handle.0));
|
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
|
window
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -691,7 +691,7 @@ fn sign_extend_u16(value: u16, size: usize) -> i16 {
|
|||||||
|
|
||||||
struct Dac {
|
struct Dac {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
samples: VecDeque<f32>,
|
samples: VecDeque<(FmClock, f32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Dac {
|
impl Default for Dac {
|
||||||
@ -704,16 +704,19 @@ impl Default for Dac {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Dac {
|
impl Dac {
|
||||||
fn add_sample(&mut self, sample: f32) {
|
fn add_sample(&mut self, clock: FmClock, sample: f32) {
|
||||||
self.samples.push_back(sample);
|
self.samples.push_back((clock, sample));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sample(&mut self) -> f32 {
|
fn get_sample_after(&mut self, clock: FmClock) -> f32 {
|
||||||
if let Some(data) = self.samples.pop_front() {
|
if let Some((sample_clock, data)) = self.samples.front().cloned() {
|
||||||
data
|
if clock > sample_clock {
|
||||||
} else {
|
self.samples.pop_front();
|
||||||
0.0
|
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
|
// The DAC uses an 8000 Hz sample rate, so we don't want to skip clocks
|
||||||
if self.dac.enabled {
|
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
|
// TODO add stereo output, which is supported by ym2612
|
||||||
@ -872,8 +875,9 @@ impl Ym2612 {
|
|||||||
|
|
||||||
0x2a => {
|
0x2a => {
|
||||||
if self.dac.enabled {
|
if self.dac.enabled {
|
||||||
for _ in 0..3 {
|
let fm_clock = clock.as_duration() / self.fm_clock_period;
|
||||||
self.dac.add_sample(((data as f32 - 128.0) / 255.0) * 2.0);
|
for i in 0..3 {
|
||||||
|
self.dac.add_sample(fm_clock + i, ((data as f32 - 128.0) / 255.0) * 2.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user