mirror of
https://github.com/transistorfet/moa.git
synced 2024-12-30 03:29:43 +00:00
Replaced controller/key/mouse updaters with event queues
This commit is contained in:
parent
3471eb4e8c
commit
112bd8219b
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -110,6 +110,7 @@ pub enum Key {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct KeyEvent {
|
||||
pub key: Key,
|
||||
pub state: bool,
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
}))
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
});
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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="🔈" />
|
||||
<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">⇦</button>
|
||||
<button name="up">⇧</button>
|
||||
<button name="down">⇨</button>
|
||||
<button name="right">⇩</button>
|
||||
|
||||
<button name="start">Start</button>
|
||||
|
||||
<button name="a">A</button>
|
||||
<button name="b">B</button>
|
||||
<button name="c">C</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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"] {
|
||||
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)?;
|
||||
|
||||
Ok(Self {
|
||||
receiver,
|
||||
port_1: GenesisControllerPort::default(),
|
||||
port_2: GenesisControllerPort::default(),
|
||||
expansion: GenesisControllerPort::default(),
|
||||
has_changed: Arc::new(AtomicBool::new(false)),
|
||||
interrupt: HostData::new(false),
|
||||
reset_timer: ClockDuration::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GenesisControllers {
|
||||
pub fn new<H: Host>(host: &mut H) -> Result<Self, Error> {
|
||||
let controller = GenesisControllers::default();
|
||||
|
||||
let controller1 = Box::new(GenesisControllersUpdater(controller.port_1.buttons.clone(), controller.has_changed.clone()));
|
||||
host.register_controller(ControllerDevice::A, controller1)?;
|
||||
let controller2 = Box::new(GenesisControllersUpdater(controller.port_2.buttons.clone(), controller.has_changed.clone()));
|
||||
host.register_controller(ControllerDevice::B, controller2)?;
|
||||
|
||||
Ok(controller)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_interrupt_signal(&self) -> HostData<bool> {
|
||||
self.interrupt.clone()
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: ControllerEvent) {
|
||||
let (mask, state) = match event.input {
|
||||
ControllerInput::ButtonA(state) => (0x0040, state),
|
||||
ControllerInput::ButtonB(state) => (0x0010, state),
|
||||
ControllerInput::ButtonC(state) => (0x0020, state),
|
||||
ControllerInput::DpadUp(state) => (0x0001, state),
|
||||
ControllerInput::DpadDown(state) => (0x0002, state),
|
||||
ControllerInput::DpadLeft(state) => (0x0004, state),
|
||||
ControllerInput::DpadRight(state) => (0x0008, state),
|
||||
ControllerInput::Start(state) => (0x0080, state),
|
||||
ControllerInput::Mode(state) => (0x0100, state),
|
||||
_ => (0x0000, false),
|
||||
};
|
||||
|
||||
let prev_state = match event.device {
|
||||
ControllerDevice::A => &mut self.port_1.buttons,
|
||||
ControllerDevice::B => &mut self.port_2.buttons,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
*prev_state = (*prev_state & !mask) | (if !state { mask } else { 0 });
|
||||
if *prev_state != 0 {
|
||||
self.interrupt.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Addressable for GenesisControllers {
|
||||
@ -208,9 +195,8 @@ impl Steppable for GenesisControllers {
|
||||
fn step(&mut self, _system: &System) -> Result<ClockDuration, Error> {
|
||||
let duration = ClockDuration::from_micros(100); // Update every 100us
|
||||
|
||||
if self.has_changed.load(Ordering::Acquire) {
|
||||
self.has_changed.store(false, Ordering::Release);
|
||||
self.interrupt.set(true);
|
||||
while let Some(event) = self.receiver.receive() {
|
||||
self.process_event(event);
|
||||
}
|
||||
|
||||
self.reset_timer += duration;
|
||||
|
@ -1,8 +1,6 @@
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use moa_core::{System, Error, ClockTime, ClockDuration, Address, Addressable, Steppable, Transmutable, debug, warn};
|
||||
use moa_core::host::{self, Host, Frame, FrameSender, KeyboardUpdater, KeyEvent};
|
||||
use moa_core::host::{self, Host, Frame, FrameSender, KeyEvent, EventReceiver};
|
||||
|
||||
use super::keymap;
|
||||
use super::charset::CharacterGenerator;
|
||||
@ -11,38 +9,94 @@ use super::charset::CharacterGenerator;
|
||||
const DEV_NAME: &str = "model1";
|
||||
const SCREEN_SIZE: (u32, u32) = (384, 128);
|
||||
|
||||
pub struct Model1Peripherals {
|
||||
|
||||
pub struct Model1Keyboard {
|
||||
receiver: EventReceiver<KeyEvent>,
|
||||
keyboard_mem: [u8; 8],
|
||||
}
|
||||
|
||||
impl Model1Keyboard {
|
||||
pub fn new<H: Host>(host: &mut H) -> Result<Self, Error> {
|
||||
let (sender, receiver) = host::event_queue();
|
||||
host.register_keyboard(sender)?;
|
||||
|
||||
Ok(Self {
|
||||
receiver,
|
||||
keyboard_mem: [0; 8],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Addressable for Model1Keyboard {
|
||||
fn len(&self) -> usize {
|
||||
0x420
|
||||
}
|
||||
|
||||
fn read(&mut self, _clock: ClockTime, addr: Address, data: &mut [u8]) -> Result<(), Error> {
|
||||
if (0x20..=0xA0).contains(&addr) {
|
||||
let offset = addr - 0x20;
|
||||
data[0] = 0;
|
||||
if (offset & 0x01) != 0 { data[0] |= self.keyboard_mem[0]; }
|
||||
if (offset & 0x02) != 0 { data[0] |= self.keyboard_mem[1]; }
|
||||
if (offset & 0x04) != 0 { data[0] |= self.keyboard_mem[2]; }
|
||||
if (offset & 0x08) != 0 { data[0] |= self.keyboard_mem[3]; }
|
||||
if (offset & 0x10) != 0 { data[0] |= self.keyboard_mem[4]; }
|
||||
if (offset & 0x20) != 0 { data[0] |= self.keyboard_mem[5]; }
|
||||
if (offset & 0x40) != 0 { data[0] |= self.keyboard_mem[6]; }
|
||||
if (offset & 0x80) != 0 { data[0] |= self.keyboard_mem[7]; }
|
||||
//info!("{}: read from keyboard {:x} of {:?}", DEV_NAME, addr, data);
|
||||
} else {
|
||||
warn!("{}: !!! unhandled read from {:0x}", DEV_NAME, addr);
|
||||
}
|
||||
debug!("{}: read from register {:x} of {:?}", DEV_NAME, addr, data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, _clock: ClockTime, addr: Address, data: &[u8]) -> Result<(), Error> {
|
||||
warn!("{}: !!! unhandled write {:0x} to {:0x}", DEV_NAME, data[0], addr);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Steppable for Model1Keyboard {
|
||||
fn step(&mut self, _system: &System) -> Result<ClockDuration, Error> {
|
||||
while let Some(event) = self.receiver.receive() {
|
||||
keymap::record_key_press(&mut self.keyboard_mem, event.key, event.state);
|
||||
}
|
||||
|
||||
Ok(ClockDuration::from_millis(1))
|
||||
}
|
||||
}
|
||||
|
||||
impl Transmutable for Model1Keyboard {
|
||||
fn as_addressable(&mut self) -> Option<&mut dyn Addressable> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn as_steppable(&mut self) -> Option<&mut dyn Steppable> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Model1Video {
|
||||
frame_sender: FrameSender,
|
||||
keyboard_mem: Arc<Mutex<[u8; 8]>>,
|
||||
video_mem: [u8; 1024],
|
||||
}
|
||||
|
||||
impl Model1Peripherals {
|
||||
impl Model1Video {
|
||||
pub fn new<H: Host>(host: &mut H) -> Result<Self, Error> {
|
||||
let (frame_sender, frame_receiver) = host::frame_queue(SCREEN_SIZE.0, SCREEN_SIZE.1);
|
||||
let keyboard_mem = Arc::new(Mutex::new([0; 8]));
|
||||
|
||||
host.add_video_source(frame_receiver)?;
|
||||
host.register_keyboard(Box::new(Model1KeyboardUpdater(keyboard_mem.clone())))?;
|
||||
|
||||
Ok(Self {
|
||||
frame_sender,
|
||||
keyboard_mem,
|
||||
video_mem: [0x20; 1024],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Model1KeyboardUpdater(Arc<Mutex<[u8; 8]>>);
|
||||
|
||||
impl KeyboardUpdater for Model1KeyboardUpdater {
|
||||
fn update_keyboard(&self, event: KeyEvent) {
|
||||
println!(">>> {:?}", event.key);
|
||||
keymap::record_key_press(&mut self.0.lock().unwrap(), event.key, event.state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Steppable for Model1Peripherals {
|
||||
impl Steppable for Model1Video {
|
||||
fn step(&mut self, system: &System) -> Result<ClockDuration, Error> {
|
||||
let mut frame = Frame::new(SCREEN_SIZE.0, SCREEN_SIZE.1, self.frame_sender.encoding());
|
||||
for y in 0..16 {
|
||||
@ -58,45 +112,25 @@ impl Steppable for Model1Peripherals {
|
||||
}
|
||||
}
|
||||
|
||||
impl Addressable for Model1Peripherals {
|
||||
impl Addressable for Model1Video {
|
||||
fn len(&self) -> usize {
|
||||
0x820
|
||||
0x400
|
||||
}
|
||||
|
||||
fn read(&mut self, _clock: ClockTime, addr: Address, data: &mut [u8]) -> Result<(), Error> {
|
||||
if (0x20..=0xA0).contains(&addr) {
|
||||
let offset = addr - 0x20;
|
||||
data[0] = 0;
|
||||
if (offset & 0x01) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[0]; }
|
||||
if (offset & 0x02) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[1]; }
|
||||
if (offset & 0x04) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[2]; }
|
||||
if (offset & 0x08) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[3]; }
|
||||
if (offset & 0x10) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[4]; }
|
||||
if (offset & 0x20) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[5]; }
|
||||
if (offset & 0x40) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[6]; }
|
||||
if (offset & 0x80) != 0 { data[0] |= self.keyboard_mem.lock().unwrap()[7]; }
|
||||
//info!("{}: read from keyboard {:x} of {:?}", DEV_NAME, addr, data);
|
||||
} else if (0x420..=0x820).contains(&addr) {
|
||||
data[0] = self.video_mem[addr as usize - 0x420];
|
||||
} else {
|
||||
warn!("{}: !!! unhandled read from {:0x}", DEV_NAME, addr);
|
||||
}
|
||||
data[0] = self.video_mem[addr as usize];
|
||||
debug!("{}: read from register {:x} of {:?}", DEV_NAME, addr, data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, _clock: ClockTime, addr: Address, data: &[u8]) -> Result<(), Error> {
|
||||
debug!("{}: write to register {:x} with {:x}", DEV_NAME, addr, data[0]);
|
||||
if (0x420..0x820).contains(&addr) {
|
||||
self.video_mem[addr as usize - 0x420] = data[0];
|
||||
} else {
|
||||
warn!("{}: !!! unhandled write {:0x} to {:0x}", DEV_NAME, data[0], addr);
|
||||
}
|
||||
self.video_mem[addr as usize] = data[0];
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Transmutable for Model1Peripherals {
|
||||
impl Transmutable for Model1Video {
|
||||
fn as_addressable(&mut self) -> Option<&mut dyn Addressable> {
|
||||
Some(self)
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use moa_core::host::Host;
|
||||
|
||||
use moa_z80::{Z80, Z80Type};
|
||||
|
||||
use crate::peripherals::model1::Model1Peripherals;
|
||||
use crate::peripherals::model1::{Model1Keyboard, Model1Video};
|
||||
|
||||
|
||||
pub struct Trs80Options {
|
||||
@ -37,8 +37,10 @@ pub fn build_trs80<H: Host>(host: &mut H, options: Trs80Options) -> Result<Syste
|
||||
let ram = MemoryBlock::new(vec![0; options.memory as usize]);
|
||||
system.add_addressable_device(0x4000, wrap_transmutable(ram))?;
|
||||
|
||||
let model1 = Model1Peripherals::new(host)?;
|
||||
system.add_addressable_device(0x37E0, wrap_transmutable(model1)).unwrap();
|
||||
let keyboard = Model1Keyboard::new(host)?;
|
||||
system.add_addressable_device(0x37E0, wrap_transmutable(keyboard)).unwrap();
|
||||
let video = Model1Video::new(host)?;
|
||||
system.add_addressable_device(0x37E0 + 0x420, wrap_transmutable(video)).unwrap();
|
||||
|
||||
let cpu = Z80::new(Z80Type::Z80, options.frequency, BusPort::new(0, 16, 8, system.bus.clone()));
|
||||
//cpu.add_breakpoint(0x0);
|
||||
|
16
todo.txt
16
todo.txt
@ -1,4 +1,20 @@
|
||||
|
||||
* what if, to allow a device to have multiple steppable functions, you pass the system in, or otherwise provide some mechanism for
|
||||
each device to create sub devices which are scheduled independently
|
||||
|
||||
* for some unknown reason, the js-based updater works much better than the rust based one, but the rust based one just goes back to
|
||||
a fixed time per loop instead of trying to speed up
|
||||
|
||||
* need to remove HostData and replace the interrupt with Signal
|
||||
* make Signal directional, by making SignalDriver and SignalInput or SignalReceiver
|
||||
* should you combine the input updaters into one thing and have a queue for communicating? Even if you have 3 input channels, you can
|
||||
make them all use the same generic type for the input queue
|
||||
* you need to refactor the audio mixer stuff to reject data quicker and stay in sync, but how to deal with sim time-dilation
|
||||
* make the pixels frontend use an rc refcell object, or rc object to directly control the frontend object while also making it accessible
|
||||
to the run loop
|
||||
* add support for the controller inputs in the pixels web frontend
|
||||
* clean up pixels frontend
|
||||
|
||||
* make the ym generate audio in sync so the DAC timings can be more accurate
|
||||
* change the host things to use queues instead
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user