Replaced controller/key/mouse updaters with event queues

This commit is contained in:
transistor 2023-05-06 10:04:44 -07:00
parent 3471eb4e8c
commit 112bd8219b
22 changed files with 472 additions and 281 deletions

View File

@ -8,7 +8,7 @@ pub enum ControllerDevice {
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ControllerEvent {
pub enum ControllerInput {
DpadUp(bool),
DpadDown(bool),
DpadLeft(bool),
@ -23,3 +23,18 @@ pub enum ControllerEvent {
Mode(bool),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ControllerEvent {
pub device: ControllerDevice,
pub input: ControllerInput,
}
impl ControllerEvent {
pub fn new(device: ControllerDevice, input: ControllerInput) -> Self {
Self {
device,
input,
}
}
}

View File

@ -1,6 +1,5 @@
use std::sync::{Arc, Mutex};
use crate::Error;
use crate::ClockTime;
use crate::host::traits::ClockedQueue;
@ -113,9 +112,8 @@ impl Frame {
pub fn frame_queue(width: u32, height: u32) -> (FrameSender, FrameReceiver) {
let sender = FrameSender {
max_size: (width, height),
encoding: Arc::new(Mutex::new(PixelEncoding::RGBA)),
queue: Default::default(),
queue: ClockedQueue::new(10),
};
let receiver = FrameReceiver {
@ -128,20 +126,11 @@ pub fn frame_queue(width: u32, height: u32) -> (FrameSender, FrameReceiver) {
}
pub struct FrameSender {
max_size: (u32, u32),
encoding: Arc<Mutex<PixelEncoding>>,
queue: ClockedQueue<Frame>,
}
impl FrameSender {
pub fn new(width: u32, height: u32) -> Self {
Self {
max_size: (width, height),
encoding: Arc::new(Mutex::new(PixelEncoding::RGBA)),
queue: Default::default(),
}
}
pub fn encoding(&self) -> PixelEncoding {
*self.encoding.lock().unwrap()
}

View File

@ -110,6 +110,7 @@ pub enum Key {
Unknown,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct KeyEvent {
pub key: Key,
pub state: bool,

View File

@ -2,12 +2,14 @@
mod traits;
mod keys;
mod gfx;
mod input;
mod controllers;
mod mouse;
pub use self::gfx::{Pixel, PixelEncoding, Frame, FrameSender, FrameReceiver, frame_queue};
pub use self::keys::{Key, KeyEvent};
pub use self::mouse::{MouseButton, MouseEventType, MouseEvent, MouseState};
pub use self::controllers::{ControllerDevice, ControllerEvent};
pub use self::traits::{Host, Tty, ControllerUpdater, KeyboardUpdater, MouseUpdater, Audio, HostData, ClockedQueue, DummyAudio};
pub use self::controllers::{ControllerDevice, ControllerInput, ControllerEvent};
pub use self::input::{EventSender, EventReceiver, event_queue};
pub use self::traits::{Host, Tty, Audio, HostData, ClockedQueue, DummyAudio};

View File

@ -1,22 +1,25 @@
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MouseButton {
Left,
Right,
Middle,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MouseEventType {
Down(MouseButton),
Up(MouseButton),
Move,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct MouseEvent {
pub etype: MouseEventType,
pub pos: (u32, u32),
}
#[derive(Clone, Default, PartialEq, Eq)]
#[derive(Copy, Clone, Default, PartialEq, Eq)]
pub struct MouseState {
pub buttons: [bool; 3],
pub pos: (u32, u32),

View File

@ -3,10 +3,11 @@ use std::collections::VecDeque;
use std::sync::{Arc, Mutex, MutexGuard};
use crate::{ClockTime, Error};
use crate::host::gfx::{PixelEncoding, Pixel, Frame, FrameReceiver};
use crate::host::gfx::FrameReceiver;
use crate::host::keys::KeyEvent;
use crate::host::controllers::{ControllerDevice, ControllerEvent};
use crate::host::controllers::ControllerEvent;
use crate::host::mouse::MouseEvent;
use crate::host::input::EventSender;
pub trait Host {
fn add_pty(&self) -> Result<Box<dyn Tty>, Error> {
@ -21,15 +22,15 @@ pub trait Host {
Err(Error::new("This frontend doesn't support the sound"))
}
fn register_controller(&mut self, _device: ControllerDevice, _input: Box<dyn ControllerUpdater>) -> Result<(), Error> {
fn register_controllers(&mut self, _sender: EventSender<ControllerEvent>) -> Result<(), Error> {
Err(Error::new("This frontend doesn't support game controllers"))
}
fn register_keyboard(&mut self, _input: Box<dyn KeyboardUpdater>) -> Result<(), Error> {
fn register_keyboard(&mut self, _sender: EventSender<KeyEvent>) -> Result<(), Error> {
Err(Error::new("This frontend doesn't support the keyboard"))
}
fn register_mouse(&mut self, _input: Box<dyn MouseUpdater>) -> Result<(), Error> {
fn register_mouse(&mut self, _sender: EventSender<MouseEvent>) -> Result<(), Error> {
Err(Error::new("This frontend doesn't support the mouse"))
}
}
@ -41,18 +42,6 @@ pub trait Tty {
fn write(&mut self, output: u8) -> bool;
}
pub trait ControllerUpdater: Send {
fn update_controller(&self, event: ControllerEvent);
}
pub trait KeyboardUpdater: Send {
fn update_keyboard(&self, event: KeyEvent);
}
pub trait MouseUpdater: Send {
fn update_mouse(&self, event: MouseEvent);
}
pub trait Audio {
fn samples_per_second(&self) -> usize;
fn space_available(&self) -> usize;
@ -84,12 +73,22 @@ impl<T: Copy> HostData<T> {
}
}
#[derive(Clone, Default)]
pub struct ClockedQueue<T>(Arc<Mutex<VecDeque<(ClockTime, T)>>>);
pub struct ClockedQueue<T>(Arc<Mutex<VecDeque<(ClockTime, T)>>>, usize);
impl<T: Clone> ClockedQueue<T> {
pub fn new(max: usize) -> Self {
Self(Arc::new(Mutex::new(VecDeque::new())), max)
}
pub fn push(&self, clock: ClockTime, data: T) {
self.0.lock().unwrap().push_back((clock, data));
let mut queue = self.0.lock().unwrap();
if queue.len() > self.1 {
//log::warn!("dropping data from queue due to limit of {} items", self.1);
queue.pop_front();
}
queue.push_back((clock, data));
}
pub fn pop_next(&self) -> Option<(ClockTime, T)> {
@ -100,7 +99,7 @@ impl<T: Clone> ClockedQueue<T> {
self.0.lock().unwrap().drain(..).last()
}
pub fn unpop(&mut self, clock: ClockTime, data: T) {
pub fn unpop(&self, clock: ClockTime, data: T) {
self.0.lock().unwrap().push_front((clock, data));
}

View File

@ -8,8 +8,11 @@ use moa_core::host::{Audio, ClockedQueue};
pub const SAMPLE_RATE: usize = 48000;
pub struct Sample(f32, f32);
#[derive(Clone, Default)]
pub struct AudioFrame {
//pub sample_rate: usize,
pub data: Vec<(f32, f32)>,
}
@ -23,7 +26,7 @@ pub struct AudioSource {
impl AudioSource {
pub fn new(mixer: Arc<Mutex<AudioMixer>>) -> Self {
let queue = ClockedQueue::default();
let queue = ClockedQueue::new(5000);
let (id, sample_rate, frame_size) = {
let mut mixer = mixer.lock().unwrap();
let id = mixer.add_source(queue.clone());
@ -124,6 +127,10 @@ impl AudioMixer {
self.sources.len() - 1
}
pub fn num_sources(&self) -> usize {
self.sources.len()
}
pub fn get_sink(&mut self) -> Arc<Mutex<AudioOutput>> {
self.output.clone()
}
@ -214,11 +221,9 @@ impl AudioMixer {
}
}
#[allow(dead_code)]
pub struct AudioOutput {
frame_size: usize,
sequence_num: usize,
last_frame: Option<AudioFrame>,
output: VecDeque<AudioFrame>,
}
@ -227,7 +232,6 @@ impl AudioOutput {
Arc::new(Mutex::new(Self {
frame_size: 0,
sequence_num: 0,
last_frame: None,
output: VecDeque::with_capacity(2),
}))
}

View File

@ -1,6 +1,6 @@
use std::sync::{Arc, Mutex};
use cpal::{Stream, SampleRate, SampleFormat, StreamConfig, traits::{DeviceTrait, HostTrait, StreamTrait}};
use cpal::{Stream, SampleRate, SampleFormat, StreamConfig, StreamInstant, OutputCallbackInfo, traits::{DeviceTrait, HostTrait, StreamTrait}};
use moa_core::{warn, error};
@ -25,7 +25,7 @@ impl CpalAudioOutput {
.with_sample_rate(SampleRate(SAMPLE_RATE as u32))
.into();
let data_callback = move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
let data_callback = move |data: &mut [f32], info: &OutputCallbackInfo| {
let result = if let Ok(mut output) = output.lock() {
output.set_frame_size(data.len() / 2);
output.pop_next()
@ -59,5 +59,13 @@ impl CpalAudioOutput {
stream,
}
}
pub fn set_mute(&self, mute: bool) {
if mute {
self.stream.pause().unwrap();
} else {
self.stream.play().unwrap();
}
}
}

View File

@ -1,43 +1,29 @@
use std::sync::mpsc;
use moa_peripherals_yamaha::{Ym2612, Sn76489};
use moa_core::host::{Host, Frame, FrameQueue, PixelEncoding, WindowUpdater, KeyboardUpdater, Key, KeyEvent /*, MouseUpdater, MouseState, MouseEvent*/};
use moa_core::host::{self, Host, Frame, FrameSender, PixelEncoding, Key, KeyEvent, EventReceiver};
use moa_core::{System, Error, ClockTime, ClockDuration, Frequency, Address, Addressable, Steppable, Transmutable, TransmutableBox, wrap_transmutable};
pub struct SynthControlsUpdater(mpsc::Sender<KeyEvent>);
impl KeyboardUpdater for SynthControlsUpdater {
fn update_keyboard(&mut self, event: KeyEvent) {
self.0.send(event).unwrap();
}
}
//impl MouseUpdater for SynthControlsUpdater {
// fn update_mouse(&mut self, event: MouseEvent) {
// self.0.send(event).unwrap();
// }
//}
const SCREEN_WIDTH: u32 = 384;
const SCREEN_HEIGHT: u32 = 128;
struct SynthControl {
queue: FrameQueue,
receiver: mpsc::Receiver<KeyEvent>,
key_receiver: EventReceiver<KeyEvent>,
frame_sender: FrameSender,
}
impl SynthControl {
pub fn new(queue: FrameQueue, receiver: mpsc::Receiver<KeyEvent>) -> Self {
pub fn new(key_receiver: EventReceiver<KeyEvent>, frame_sender: FrameSender) -> Self {
Self {
queue,
receiver,
key_receiver,
frame_sender,
}
}
}
impl Steppable for SynthControl {
fn step(&mut self, system: &System) -> Result<ClockDuration, Error> {
if let Ok(event) = self.receiver.try_recv() {
if let Some(event) = self.key_receiver.receive() {
match event.key {
Key::Enter => {
@ -55,11 +41,10 @@ impl Steppable for SynthControl {
}
}
let size = self.queue.max_size();
let frame = Frame::new(size.0, size.1, PixelEncoding::RGBA);
self.queue.add(system.clock, frame);
let frame = Frame::new(SCREEN_WIDTH, SCREEN_HEIGHT, PixelEncoding::RGBA);
self.frame_sender.add(system.clock, frame);
Ok(ClockDuration::from_millis(1))
Ok(ClockDuration::from_micros(100))
}
}
@ -98,21 +83,20 @@ fn main() {
moa_minifb::run(matches, |host| {
let mut system = System::default();
let queue = FrameQueue::new(384, 128);
let (sender, receiver) = mpsc::channel();
let control = wrap_transmutable(SynthControl::new(queue.clone(), receiver));
let (frame_sender, frame_receiver) = host::frame_queue(SCREEN_WIDTH, SCREEN_HEIGHT);
let (key_sender, key_receiver) = host::event_queue();
let control = wrap_transmutable(SynthControl::new(key_receiver, frame_sender));
system.add_device("control", control)?;
let ym_sound = wrap_transmutable(Ym2612::create(host, Frequency::from_hz(7_670_454))?);
let ym_sound = wrap_transmutable(Ym2612::new(host, Frequency::from_hz(7_670_454))?);
initialize_ym(ym_sound.clone())?;
system.add_addressable_device(0x00, ym_sound)?;
let sn_sound = wrap_transmutable(Sn76489::create(host, Frequency::from_hz(3_579_545))?);
let sn_sound = wrap_transmutable(Sn76489::new(host, Frequency::from_hz(3_579_545))?);
system.add_addressable_device(0x10, sn_sound)?;
host.add_window(Box::new(queue))?;
host.register_keyboard(Box::new(SynthControlsUpdater(sender)))?;
//host.register_mouse(Box::new(SynthControlsUpdater(sender)))?;
host.add_video_source(frame_receiver)?;
host.register_keyboard(key_sender)?;
Ok(system)
});

View File

@ -1,18 +1,18 @@
use minifb::Key as MiniKey;
use moa_core::host::ControllerEvent;
use moa_core::host::ControllerInput;
pub fn map_controller_a(key: MiniKey, state: bool) -> Option<ControllerEvent> {
pub fn map_controller_a(key: MiniKey, state: bool) -> Option<ControllerInput> {
match key {
MiniKey::A => { Some(ControllerEvent::ButtonA(state)) },
MiniKey::O => { Some(ControllerEvent::ButtonB(state)) },
MiniKey::E => { Some(ControllerEvent::ButtonC(state)) },
MiniKey::Up => { Some(ControllerEvent::DpadUp(state)) },
MiniKey::Down => { Some(ControllerEvent::DpadDown(state)) },
MiniKey::Left => { Some(ControllerEvent::DpadLeft(state)) },
MiniKey::Right => { Some(ControllerEvent::DpadRight(state)) },
MiniKey::Enter => { Some(ControllerEvent::Start(state)) },
MiniKey::M => { Some(ControllerEvent::Mode(state)) },
MiniKey::A => { Some(ControllerInput::ButtonA(state)) },
MiniKey::O => { Some(ControllerInput::ButtonB(state)) },
MiniKey::E => { Some(ControllerInput::ButtonC(state)) },
MiniKey::Up => { Some(ControllerInput::DpadUp(state)) },
MiniKey::Down => { Some(ControllerInput::DpadDown(state)) },
MiniKey::Left => { Some(ControllerInput::DpadLeft(state)) },
MiniKey::Right => { Some(ControllerInput::DpadRight(state)) },
MiniKey::Enter => { Some(ControllerInput::Start(state)) },
MiniKey::M => { Some(ControllerInput::Mode(state)) },
_ => None,
}
}

View File

@ -8,7 +8,7 @@ use minifb::{self, Key, MouseMode, MouseButton};
use clap::{App, Arg, ArgMatches};
use moa_core::{System, Error, ClockDuration};
use moa_core::host::{Host, ControllerUpdater, KeyboardUpdater, KeyEvent, MouseUpdater, MouseState, Audio, ControllerDevice, PixelEncoding, Frame, FrameReceiver};
use moa_core::host::{Host, Audio, KeyEvent, MouseEvent, MouseState, ControllerDevice, ControllerEvent, EventSender, PixelEncoding, Frame, FrameReceiver};
use moa_common::{AudioMixer, AudioSource};
use moa_common::CpalAudioOutput;
@ -96,9 +96,9 @@ fn wait_until_initialized(frontend: Arc<Mutex<MiniFrontendBuilder>>) {
pub struct MiniFrontendBuilder {
video: Option<FrameReceiver>,
controller: Option<Box<dyn ControllerUpdater>>,
keyboard: Option<Box<dyn KeyboardUpdater>>,
mouse: Option<Box<dyn MouseUpdater>>,
controllers: Option<EventSender<ControllerEvent>>,
keyboard: Option<EventSender<KeyEvent>>,
mouse: Option<EventSender<MouseEvent>>,
mixer: Option<Arc<Mutex<AudioMixer>>>,
finalized: bool,
}
@ -107,7 +107,7 @@ impl Default for MiniFrontendBuilder {
fn default() -> Self {
Self {
video: None,
controller: None,
controllers: None,
keyboard: None,
mouse: None,
mixer: Some(AudioMixer::with_default_rate()),
@ -123,11 +123,11 @@ impl MiniFrontendBuilder {
pub fn build(&mut self) -> MiniFrontend {
let video = std::mem::take(&mut self.video);
let controller = std::mem::take(&mut self.controller);
let controllers = std::mem::take(&mut self.controllers);
let keyboard = std::mem::take(&mut self.keyboard);
let mouse = std::mem::take(&mut self.mouse);
let mixer = std::mem::take(&mut self.mixer);
MiniFrontend::new(video, controller, keyboard, mouse, mixer.unwrap())
MiniFrontend::new(video, controllers, keyboard, mouse, mixer.unwrap())
}
}
@ -145,31 +145,27 @@ impl Host for MiniFrontendBuilder {
Ok(Box::new(source))
}
fn register_controller(&mut self, device: ControllerDevice, input: Box<dyn ControllerUpdater>) -> Result<(), Error> {
if device != ControllerDevice::A {
return Ok(())
}
if self.controller.is_some() {
fn register_controllers(&mut self, sender: EventSender<ControllerEvent>) -> Result<(), Error> {
if self.controllers.is_some() {
return Err(Error::new("A controller updater has already been registered with the frontend"));
}
self.controller = Some(input);
self.controllers = Some(sender);
Ok(())
}
fn register_keyboard(&mut self, input: Box<dyn KeyboardUpdater>) -> Result<(), Error> {
fn register_keyboard(&mut self, sender: EventSender<KeyEvent>) -> Result<(), Error> {
if self.keyboard.is_some() {
return Err(Error::new("A keyboard updater has already been registered with the frontend"));
}
self.keyboard = Some(input);
self.keyboard = Some(sender);
Ok(())
}
fn register_mouse(&mut self, input: Box<dyn MouseUpdater>) -> Result<(), Error> {
fn register_mouse(&mut self, sender: EventSender<MouseEvent>) -> Result<(), Error> {
if self.mouse.is_some() {
return Err(Error::new("A mouse updater has already been registered with the frontend"));
}
self.mouse = Some(input);
self.mouse = Some(sender);
Ok(())
}
}
@ -179,9 +175,9 @@ pub struct MiniFrontend {
pub modifiers: u16,
pub mouse_state: MouseState,
pub video: Option<FrameReceiver>,
pub controller: Option<Box<dyn ControllerUpdater>>,
pub keyboard: Option<Box<dyn KeyboardUpdater>>,
pub mouse: Option<Box<dyn MouseUpdater>>,
pub controllers: Option<EventSender<ControllerEvent>>,
pub keyboard: Option<EventSender<KeyEvent>>,
pub mouse: Option<EventSender<MouseEvent>>,
pub audio: Option<CpalAudioOutput>,
pub mixer: Arc<Mutex<AudioMixer>>,
}
@ -189,16 +185,16 @@ pub struct MiniFrontend {
impl MiniFrontend {
pub fn new(
video: Option<FrameReceiver>,
controller: Option<Box<dyn ControllerUpdater>>,
keyboard: Option<Box<dyn KeyboardUpdater>>,
mouse: Option<Box<dyn MouseUpdater>>,
controllers: Option<EventSender<ControllerEvent>>,
keyboard: Option<EventSender<KeyEvent>>,
mouse: Option<EventSender<MouseEvent>>,
mixer: Arc<Mutex<AudioMixer>>,
) -> Self {
Self {
modifiers: 0,
mouse_state: Default::default(),
video,
controller,
controllers,
keyboard,
mouse,
audio: None,
@ -218,7 +214,7 @@ impl MiniFrontend {
}
}
if matches.occurrences_of("disable-audio") == 0 {
if self.mixer.lock().unwrap().num_sources() != 0 && matches.occurrences_of("disable-audio") == 0 {
self.audio = Some(CpalAudioOutput::create_audio_output(self.mixer.lock().unwrap().get_sink()));
}
@ -292,7 +288,7 @@ impl MiniFrontend {
}
}
if let Some(updater) = self.mouse.as_mut() {
if let Some(sender) = self.mouse.as_mut() {
if let Some((x, y)) = window.get_mouse_pos(MouseMode::Clamp) {
let left = window.get_mouse_down(MouseButton::Left);
let right = window.get_mouse_down(MouseButton::Right);
@ -302,7 +298,7 @@ impl MiniFrontend {
self.mouse_state
.to_events(next_state)
.into_iter()
.for_each(|event| updater.update_mouse(event));
.for_each(|event| sender.send(event));
}
}
@ -316,13 +312,14 @@ impl MiniFrontend {
}
fn check_key(&mut self, key: Key, state: bool) {
if let Some(updater) = self.keyboard.as_mut() {
updater.update_keyboard(KeyEvent::new(map_key(key), state));
if let Some(sender) = self.keyboard.as_mut() {
sender.send(KeyEvent::new(map_key(key), state));
}
if let Some(updater) = self.controller.as_mut() {
if let Some(event) = map_controller_a(key, state) {
updater.update_controller(event);
if let Some(sender) = self.controllers.as_mut() {
if let Some(input) = map_controller_a(key, state) {
let event = ControllerEvent::new(ControllerDevice::A, input);
sender.send(event);
}
}
}

View File

@ -1,24 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sega Genesis - Moa</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css">
<title>Sega Genesis - Moa</title>
<script type="module" src="./interface.js"></script>
</head>
<body>
<div id="config">
<label>ROM File (only .bin format)</label>
<input type="button" id="mute" value="&#x1F508;" />
<label>ROM File</label>
<input type="file" id="rom-file" accept=".bin,.smd,.md" />
<input type="button" id="reset" value="Reset" />
<input type="button" id="power" value="Power" />
<input type="text" id="speed" value="4.0" />
<select id="scale">
<option value="1">320 x 224</option>
<option value="2" selected>640 x 448</option>
<option value="3">960 x 672</option>
</select>
<input type="button" id="reset" value="Reset" />
</div>
<div id="metrics">
@ -27,7 +22,19 @@
</div>
<div id="video-screen">
<canvas id="video" tabindex="0" draw-raw-handle="1" style="width: 640px; height: 448px;" width="640" height="448" />
<canvas id="video" tabindex="0" draw-raw-handle="1" />
</div>
<div id="controller">
<button name="left">&#x21E6;</button>
<button name="up">&#x21E7;</button>
<button name="down">&#x21E8;</button>
<button name="right">&#x21E9;</button>
<button name="start">Start</button>
<button name="a">A</button>
<button name="b">B</button>
<button name="c">C</button>
</div>
</body>
</html>

View File

@ -5,6 +5,7 @@ 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();
@ -22,6 +23,12 @@ function initialize_emulator() {
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();
});
@ -55,14 +62,31 @@ document.getElementById("power").addEventListener("click", () => {
initialize_emulator();
});
document.getElementById("speed").addEventListener("change", (e) => {
document.getElementById("video").focus();
Emulator.set_speed(e.target.value);
var mute_state = false;
var mute = document.getElementById("mute");
mute.addEventListener("click", () => {
mute_state = !mute_state;
if (mute_state) {
mute.value = "\uD83D\uDD07";
} else {
mute.value = "\uD83D\uDD08";
}
Emulator.set_mute(mute_state);
});
// 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);
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);
});

View File

@ -1,4 +1,5 @@
body {
font-family: sans;
background-color: #000;
margin: 0;
overflow: hidden;
@ -19,3 +20,16 @@ body {
color: #DDD;
background-color: #000;
}
#mute {
cursor: pointer;
}
#controller .updown {
overflow: hidden;
}
#controller button[name="left"] {
}

View File

@ -7,7 +7,7 @@ use winit::event::{Event, VirtualKeyCode, WindowEvent, ElementState};
use winit::event_loop::{ControlFlow, EventLoop};
use moa_core::{System, Error};
use moa_core::host::{Host, PixelEncoding, Frame, ControllerDevice, ControllerEvent, ControllerUpdater, Audio, DummyAudio, FrameReceiver};
use moa_core::host::{Host, PixelEncoding, Frame, ControllerDevice, ControllerInput, ControllerEvent, EventSender, Audio, DummyAudio, FrameReceiver};
use moa_common::{AudioMixer, AudioSource, CpalAudioOutput};
use crate::settings;
@ -21,22 +21,22 @@ pub type LoadSystemFn = fn (&mut PixelsFrontend, Vec<u8>) -> Result<System, Erro
pub struct PixelsFrontend {
video: Option<FrameReceiver>,
controller: Option<Box<dyn ControllerUpdater>>,
//mixer: Arc<Mutex<AudioMixer>>,
//audio_output: CpalAudioOutput,
controllers: Option<EventSender<ControllerEvent>>,
mixer: Arc<Mutex<AudioMixer>>,
audio_output: CpalAudioOutput,
}
impl PixelsFrontend {
pub fn new() -> PixelsFrontend {
settings::get().run = true;
//let mixer = AudioMixer::with_default_rate();
//let audio_output = CpalAudioOutput::create_audio_output(mixer.lock().unwrap().get_sink());
let mixer = AudioMixer::with_default_rate();
let audio_output = CpalAudioOutput::create_audio_output(mixer.lock().unwrap().get_sink());
PixelsFrontend {
video: None,
controller: None,
//mixer,
//audio_output,
controllers: None,
mixer,
audio_output,
}
}
}
@ -47,19 +47,15 @@ impl Host for PixelsFrontend {
Ok(())
}
fn register_controller(&mut self, device: ControllerDevice, input: Box<dyn ControllerUpdater>) -> Result<(), Error> {
if device != ControllerDevice::A {
return Ok(())
}
self.controller = Some(input);
fn register_controllers(&mut self, sender: EventSender<ControllerEvent>) -> Result<(), Error> {
self.controllers = Some(sender);
Ok(())
}
fn add_audio_source(&mut self) -> Result<Box<dyn Audio>, Error> {
//let source = AudioSource::new(self.mixer.clone());
//Ok(Box::new(source))
Ok(Box::new(DummyAudio()))
let source = AudioSource::new(self.mixer.clone());
Ok(Box::new(source))
//Ok(Box::new(DummyAudio()))
}
}
@ -68,8 +64,8 @@ pub async fn run_loop(host: PixelsFrontend) {
let window = create_window(&event_loop);
if let Some(recevier) = host.video.as_ref() {
recevier.request_encoding(PixelEncoding::ABGR);
if let Some(receiver) = host.video.as_ref() {
receiver.request_encoding(PixelEncoding::ABGR);
}
let mut pixels = {
@ -81,6 +77,7 @@ pub async fn run_loop(host: PixelsFrontend) {
.expect("Pixels error")
};
let mut mute = false;
let mut last_size = (WIDTH, HEIGHT);
let mut last_frame = Frame::new(WIDTH, HEIGHT, PixelEncoding::ABGR);
//let mut update_timer = Instant::now();
@ -131,14 +128,22 @@ pub async fn run_loop(host: PixelsFrontend) {
}
};
if let Some(updater) = host.controller.as_ref() {
if let Some(sender) = host.controllers.as_ref() {
if let Some(key) = key {
updater.update_controller(key);
let event = ControllerEvent::new(ControllerDevice::A, key);
sender.send(event);
}
}
}
}
let requested_mute = settings::get().mute;
if requested_mute != mute {
mute = requested_mute;
host.audio_output.set_mute(mute);
log::info!("setting mute to {}", mute);
}
// Check if the run flag is no longer true, and exit the loop
if !settings::get().run {
// Clear the screen
@ -161,17 +166,17 @@ pub async fn run_loop(host: PixelsFrontend) {
});
}
pub fn map_controller_a(key: VirtualKeyCode, state: bool) -> Option<ControllerEvent> {
pub fn map_controller_a(key: VirtualKeyCode, state: bool) -> Option<ControllerInput> {
match key {
VirtualKeyCode::A => { Some(ControllerEvent::ButtonA(state)) },
VirtualKeyCode::S => { Some(ControllerEvent::ButtonB(state)) },
VirtualKeyCode::D => { Some(ControllerEvent::ButtonC(state)) },
VirtualKeyCode::Up => { Some(ControllerEvent::DpadUp(state)) },
VirtualKeyCode::Down => { Some(ControllerEvent::DpadDown(state)) },
VirtualKeyCode::Left => { Some(ControllerEvent::DpadLeft(state)) },
VirtualKeyCode::Right => { Some(ControllerEvent::DpadRight(state)) },
VirtualKeyCode::Return => { Some(ControllerEvent::Start(state)) },
VirtualKeyCode::M => { Some(ControllerEvent::Mode(state)) },
VirtualKeyCode::A => { Some(ControllerInput::ButtonA(state)) },
VirtualKeyCode::S => { Some(ControllerInput::ButtonB(state)) },
VirtualKeyCode::D => { Some(ControllerInput::ButtonC(state)) },
VirtualKeyCode::Up => { Some(ControllerInput::DpadUp(state)) },
VirtualKeyCode::Down => { Some(ControllerInput::DpadDown(state)) },
VirtualKeyCode::Left => { Some(ControllerInput::DpadLeft(state)) },
VirtualKeyCode::Right => { Some(ControllerInput::DpadRight(state)) },
VirtualKeyCode::Return => { Some(ControllerInput::Start(state)) },
VirtualKeyCode::M => { Some(ControllerInput::Mode(state)) },
_ => None,
}
}

View File

@ -7,7 +7,9 @@ pub struct EmulatorSettings {
pub rom_data: Vec<u8>,
pub run: bool,
pub speed: f32,
pub size: (u32, u32),
pub frames_since: usize,
pub mute: bool,
}
impl EmulatorSettings {
@ -16,7 +18,9 @@ impl EmulatorSettings {
rom_data: vec![],
run: false,
speed: 4.0,
size: (640, 448),
frames_since: 0,
mute: false,
}
}
}
@ -29,6 +33,10 @@ pub fn set_rom_data(rom_data: Vec<u8>) {
get().rom_data = rom_data;
}
pub fn set_size(width: u32, height: u32) {
get().size = (width, height);
}
pub fn get_frames_since() -> usize {
let mut options = get();
let frames_since = options.frames_since;

View File

@ -1,12 +1,17 @@
#![cfg(target_arch = "wasm32")]
use std::rc::Rc;
use std::cell::RefCell;
use instant::Instant;
use wasm_bindgen::prelude::*;
use winit::dpi::LogicalSize;
use winit::event_loop::EventLoop;
use winit::window::{Window, WindowBuilder};
use web_sys::Event;
use wasm_bindgen::JsCast;
use wasm_bindgen::closure::Closure;
use moa_core::{ClockDuration, System};
use crate::settings;
@ -26,6 +31,11 @@ pub fn set_rom_data(rom_data: Vec<u8>) {
settings::set_rom_data(rom_data);
}
#[wasm_bindgen]
pub fn set_size(width: u32, height: u32) {
settings::set_size(width, height);
}
#[wasm_bindgen]
pub fn request_stop() {
settings::request_stop();
@ -56,6 +66,17 @@ pub fn get_frames_since() -> usize {
settings::get_frames_since()
}
#[wasm_bindgen]
pub fn set_mute(mute: bool) {
settings::get().mute = mute;
}
#[wasm_bindgen]
pub fn button_press(name: String, state: bool) {
}
#[wasm_bindgen]
pub struct HostHandle(PixelsFrontend);
@ -115,7 +136,7 @@ pub fn create_window<T>(event_loop: &EventLoop<T>) -> Rc<Window> {
let size = LogicalSize::new(frontend::WIDTH as f64, frontend::HEIGHT as f64);
WindowBuilder::new()
.with_canvas(Some(canvas))
.with_title("Hello Pixels + Web")
.with_title("Moa Emulator")
//.with_inner_size(size)
//.with_min_inner_size(size)
.build(event_loop)
@ -171,6 +192,24 @@ 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();
@ -196,6 +235,64 @@ pub fn create_window<T>(event_loop: &EventLoop<T>) -> Rc<Window> {
closure.forget();
*/
window
}
#[wasm_bindgen]
pub fn start_system(handle: SystemHandle) -> Handle {
let emulator = Emulator::new(handle.0);
set_timeout(emulator.clone(), 17);
Handle(emulator)
}
#[wasm_bindgen]
pub struct Handle(Rc<RefCell<Emulator>>);
pub struct Emulator {
running: bool,
//frontend: PixelsFrontend,
system: System,
}
impl Emulator {
pub fn new(system: System) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self {
running: false,
system,
}))
}
pub fn stop(&mut self) {
self.running = false;
}
}
fn update(emulator: Rc<RefCell<Emulator>>) {
let run_timer = Instant::now();
let nanoseconds_per_frame = (16_600_000 as f32 * settings::get().speed) as u64;
if let Err(err) = emulator.borrow_mut().system.run_for(ClockDuration::from_nanos(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 running = emulator.borrow().running;
if running {
set_timeout(emulator, 17);
}
}
fn set_timeout(emulator: Rc<RefCell<Emulator>>, timeout: i32) {
emulator.borrow_mut().running = true;
let closure: Closure<dyn Fn(Event)> = Closure::new(move |_e: Event| {
update(emulator.clone());
});
let client_window = web_sys::window().unwrap();
client_window
.set_timeout_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), timeout)
.unwrap();
closure.forget();
}

View File

@ -249,8 +249,7 @@ impl EnvelopeGenerator {
calculate_rate(self.rates[etype as usize], rate_adjust)
}
fn notify_key_change(&mut self, state: bool, envelope_clock: EnvelopeClock, rate_adjust: usize) {
fn notify_key_change(&mut self, state: bool, _envelope_clock: EnvelopeClock, rate_adjust: usize) {
if state {
let rate = self.get_scaled_rate(EnvelopeState::Attack, rate_adjust);
if rate < 62 {
@ -262,9 +261,6 @@ impl EnvelopeGenerator {
} else {
self.envelope_state = EnvelopeState::Release;
}
//if self.debug_name == "ch 2, op 1" {
//println!("change: {} {:?} {}", state, self.envelope_state, self.envelope);
//}
}
fn update_envelope(&mut self, envelope_clock: EnvelopeClock, rate_adjust: usize) {
@ -403,7 +399,7 @@ impl PhaseGenerator {
let increment = if self.block == 0 {
increment >> 1
} else {
increment << self.block - 1
increment << (self.block - 1)
};
// Apply detune
@ -539,7 +535,7 @@ impl Operator {
// If the original phase was in the negative portion, invert the output
// since the sine wave's second half is a mirror of the first half
if mod_phase & 0x200 != 0 {
output = output * -1;
output *= -1;
}
// The output is now represented with a 16-bit signed number in the range of -0x1FFF and 0x1FFF

View File

@ -1,10 +1,7 @@
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU16, Ordering};
use moa_core::{warn, info};
use moa_core::{System, Error, ClockTime, ClockDuration, Address, Addressable, Steppable, Transmutable};
use moa_core::host::{Host, ControllerUpdater, HostData, ControllerDevice, ControllerEvent};
use moa_core::host::{self, Host, HostData, ControllerDevice, ControllerInput, ControllerEvent, EventReceiver};
const REG_VERSION: Address = 0x01;
@ -25,7 +22,7 @@ pub struct GenesisControllerPort {
/// Data contains bits:
/// 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
/// X | Y | Z | MODE | START | A | C | B | RIGHT | LEFT | DOWN | UP
buttons: Arc<AtomicU16>,
buttons: u16,
ctrl: u8,
outputs: u8,
@ -37,7 +34,7 @@ pub struct GenesisControllerPort {
impl Default for GenesisControllerPort {
fn default() -> Self {
Self {
buttons: Arc::new(AtomicU16::new(0xffff)),
buttons: 0xffff,
ctrl: 0,
outputs: 0,
th_count: 0,
@ -48,7 +45,7 @@ impl Default for GenesisControllerPort {
impl GenesisControllerPort {
pub fn get_data(&mut self) -> u8 {
let inputs = self.buttons.load(Ordering::Relaxed);
let inputs = self.buttons;
let th_state = (self.outputs & 0x40) != 0;
match (th_state, self.th_count) {
@ -88,70 +85,60 @@ impl GenesisControllerPort {
}
}
pub struct GenesisControllersUpdater(Arc<AtomicU16>, Arc<AtomicBool>);
impl ControllerUpdater for GenesisControllersUpdater {
fn update_controller(&self, event: ControllerEvent) {
let (mask, state) = match event {
ControllerEvent::ButtonA(state) => (0x0040, state),
ControllerEvent::ButtonB(state) => (0x0010, state),
ControllerEvent::ButtonC(state) => (0x0020, state),
ControllerEvent::DpadUp(state) => (0x0001, state),
ControllerEvent::DpadDown(state) => (0x0002, state),
ControllerEvent::DpadLeft(state) => (0x0004, state),
ControllerEvent::DpadRight(state) => (0x0008, state),
ControllerEvent::Start(state) => (0x0080, state),
ControllerEvent::Mode(state) => (0x0100, state),
_ => (0x0000, false),
};
let buttons = (self.0.load(Ordering::Acquire) & !mask) | (if !state { mask } else { 0 });
self.0.store(buttons, Ordering::Release);
if buttons != 0 {
self.1.store(true, Ordering::Release);
}
}
}
pub struct GenesisControllers {
receiver: EventReceiver<ControllerEvent>,
port_1: GenesisControllerPort,
port_2: GenesisControllerPort,
expansion: GenesisControllerPort,
has_changed: Arc<AtomicBool>,
interrupt: HostData<bool>,
reset_timer: ClockDuration,
}
impl Default for GenesisControllers {
fn default() -> Self {
Self {
impl GenesisControllers {
pub fn new<H: Host>(host: &mut H) -> Result<Self, Error> {
let (sender, receiver) = host::event_queue();
host.register_controllers(sender)?;