serial and midi
This commit is contained in:
parent
b5fa2e6690
commit
75bd223e98
|
@ -1,13 +1,16 @@
|
||||||
use async_ringbuf::AsyncHeapConsumer;
|
use async_ringbuf::AsyncHeapConsumer;
|
||||||
use futures::executor;
|
use futures::executor;
|
||||||
use gb_emu_lib::{
|
use gb_emu_lib::{
|
||||||
connect::{AudioOutput, CpuSaveState, DownsampleType, EmulatorMessage, JoypadButtons, RomFile},
|
connect::{
|
||||||
|
AudioOutput, CpuSaveState, DownsampleType, EmulatorMessage, JoypadButtons, RomFile,
|
||||||
|
SerialTarget,
|
||||||
|
},
|
||||||
EmulatorCore,
|
EmulatorCore,
|
||||||
};
|
};
|
||||||
use nih_plug::prelude::*;
|
use nih_plug::prelude::*;
|
||||||
use nih_plug::{midi::MidiResult::Basic, params::persist::PersistentField};
|
use nih_plug::{midi::MidiResult::Basic, params::persist::PersistentField};
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
mpsc::{channel, Receiver, Sender},
|
mpsc::{self, channel, Receiver, Sender},
|
||||||
Arc, Mutex,
|
Arc, Mutex,
|
||||||
};
|
};
|
||||||
use ui::{Emulator, EmulatorRenderer};
|
use ui::{Emulator, EmulatorRenderer};
|
||||||
|
@ -42,6 +45,7 @@ struct EmuVars {
|
||||||
rx: AsyncHeapConsumer<[f32; 2]>,
|
rx: AsyncHeapConsumer<[f32; 2]>,
|
||||||
sender: Sender<EmulatorMessage>,
|
sender: Sender<EmulatorMessage>,
|
||||||
emulator_core: EmulatorCore<[u8; 4]>,
|
emulator_core: EmulatorCore<[u8; 4]>,
|
||||||
|
serial_tx: Sender<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -101,12 +105,84 @@ impl Plugin for GameboyEmu {
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut Buffer,
|
buffer: &mut Buffer,
|
||||||
_: &mut AuxiliaryBuffers,
|
_: &mut AuxiliaryBuffers,
|
||||||
_c: &mut impl ProcessContext<Self>,
|
context: &mut impl ProcessContext<Self>,
|
||||||
) -> ProcessStatus {
|
) -> ProcessStatus {
|
||||||
while let Some(event) = _c.next_event() {
|
|
||||||
if let Some(Basic(_midi)) = event.as_midi() {}
|
|
||||||
}
|
|
||||||
if let Some(ref mut vars) = self.vars {
|
if let Some(ref mut vars) = self.vars {
|
||||||
|
while let Some(event) = context.next_event() {
|
||||||
|
if let Some(Basic(as_bytes)) = event.as_midi() {
|
||||||
|
// nih_log!(
|
||||||
|
// "bytes: {:#X}, {:#X}, {:#X}",
|
||||||
|
// as_bytes[0],
|
||||||
|
// as_bytes[1],
|
||||||
|
// as_bytes[2]
|
||||||
|
// );
|
||||||
|
match event {
|
||||||
|
NoteEvent::NoteOn {
|
||||||
|
timing: _,
|
||||||
|
voice_id: _,
|
||||||
|
channel,
|
||||||
|
note: _,
|
||||||
|
velocity: _,
|
||||||
|
} => {
|
||||||
|
if channel < 5 {
|
||||||
|
println!("noteon: {as_bytes:#?}");
|
||||||
|
vars.serial_tx.send(0x90 + channel).unwrap();
|
||||||
|
vars.serial_tx.send(as_bytes[1]).unwrap();
|
||||||
|
vars.serial_tx.send(as_bytes[2]).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NoteEvent::NoteOff {
|
||||||
|
timing: _,
|
||||||
|
voice_id: _,
|
||||||
|
channel,
|
||||||
|
note: _,
|
||||||
|
velocity: _,
|
||||||
|
} => {
|
||||||
|
if channel < 5 {
|
||||||
|
println!("noteoff: {as_bytes:#?}");
|
||||||
|
vars.serial_tx.send(0x80 + channel).unwrap();
|
||||||
|
vars.serial_tx.send(as_bytes[1]).unwrap();
|
||||||
|
vars.serial_tx.send(as_bytes[2]).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NoteEvent::MidiPitchBend {
|
||||||
|
timing: _,
|
||||||
|
channel,
|
||||||
|
value: _,
|
||||||
|
} => {
|
||||||
|
if channel < 5 {
|
||||||
|
vars.serial_tx.send(0xE0 + channel).unwrap();
|
||||||
|
vars.serial_tx.send(as_bytes[1]).unwrap();
|
||||||
|
vars.serial_tx.send(as_bytes[2]).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NoteEvent::MidiCC {
|
||||||
|
timing: _,
|
||||||
|
channel,
|
||||||
|
cc: _,
|
||||||
|
value: _,
|
||||||
|
} => {
|
||||||
|
if channel < 5 {
|
||||||
|
vars.serial_tx.send(0xB0 + channel).unwrap();
|
||||||
|
vars.serial_tx.send(as_bytes[1]).unwrap();
|
||||||
|
vars.serial_tx.send(as_bytes[2]).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NoteEvent::MidiProgramChange {
|
||||||
|
timing: _,
|
||||||
|
channel,
|
||||||
|
program: _,
|
||||||
|
} => {
|
||||||
|
if channel < 5 {
|
||||||
|
vars.serial_tx.send(0xC0 + channel).unwrap();
|
||||||
|
vars.serial_tx.send(as_bytes[1]).unwrap();
|
||||||
|
vars.serial_tx.send(as_bytes[2]).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if buffer.channels() != 2 {
|
if buffer.channels() != 2 {
|
||||||
panic!()
|
panic!()
|
||||||
}
|
}
|
||||||
|
@ -121,6 +197,8 @@ impl Plugin for GameboyEmu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vars.emulator_core.run_until_buffer_full();
|
vars.emulator_core.run_until_buffer_full();
|
||||||
|
} else {
|
||||||
|
while context.next_event().is_some() {}
|
||||||
}
|
}
|
||||||
self.update_save_state();
|
self.update_save_state();
|
||||||
ProcessStatus::KeepAlive
|
ProcessStatus::KeepAlive
|
||||||
|
@ -174,22 +252,31 @@ impl Plugin for GameboyEmu {
|
||||||
|
|
||||||
let window = Box::new(renderer);
|
let window = Box::new(renderer);
|
||||||
|
|
||||||
let mut emulator_core =
|
let (serial_tx, gb_serial_rx) = mpsc::channel::<u8>();
|
||||||
if let Some(state) = self.params.last_save_state.state.lock().unwrap().take() {
|
let serial_target = SerialTarget::Custom {
|
||||||
EmulatorCore::from_save_state(state, rom, receiver, window, output)
|
rx: Some(gb_serial_rx),
|
||||||
} else {
|
tx: None,
|
||||||
let options = gb_emu_lib::Options::new(rom)
|
};
|
||||||
.with_bootrom(bootrom)
|
|
||||||
.force_no_save();
|
|
||||||
|
|
||||||
EmulatorCore::init(receiver, options, window, output, None)
|
let mut emulator_core = if let Some(state) =
|
||||||
};
|
self.params.last_save_state.state.lock().unwrap().take()
|
||||||
|
{
|
||||||
|
EmulatorCore::from_save_state(state, rom, receiver, window, output, serial_target)
|
||||||
|
} else {
|
||||||
|
let options = gb_emu_lib::Options::new(rom)
|
||||||
|
.with_bootrom(bootrom)
|
||||||
|
.with_serial_target(serial_target)
|
||||||
|
.force_no_save();
|
||||||
|
|
||||||
|
EmulatorCore::init(receiver, options, window, output, None)
|
||||||
|
};
|
||||||
emulator_core.run_until_buffer_full();
|
emulator_core.run_until_buffer_full();
|
||||||
|
|
||||||
self.vars = Some(EmuVars {
|
self.vars = Some(EmuVars {
|
||||||
rx,
|
rx,
|
||||||
sender,
|
sender,
|
||||||
emulator_core,
|
emulator_core,
|
||||||
|
serial_tx,
|
||||||
});
|
});
|
||||||
self.update_save_state();
|
self.update_save_state();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::processor::memory::mmio::gpu::Colour;
|
use crate::processor::memory::mmio::gpu::Colour;
|
||||||
pub use crate::processor::memory::mmio::joypad::{JoypadButtons, JoypadState};
|
pub use crate::processor::memory::mmio::joypad::{JoypadButtons, JoypadState};
|
||||||
|
pub use crate::processor::memory::mmio::serial::SerialTarget;
|
||||||
pub use crate::processor::CpuSaveState;
|
pub use crate::processor::CpuSaveState;
|
||||||
pub use crate::{HEIGHT, WIDTH};
|
pub use crate::{HEIGHT, WIDTH};
|
||||||
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};
|
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#![feature(exclusive_range_pattern, let_chains, bigint_helper_methods)]
|
#![feature(exclusive_range_pattern, let_chains, bigint_helper_methods)]
|
||||||
|
|
||||||
use crate::{processor::memory::Memory, util::pause};
|
use crate::{processor::memory::Memory, util::pause};
|
||||||
use connect::{AudioOutput, EmulatorMessage, Renderer, RomFile};
|
use connect::{AudioOutput, EmulatorMessage, Renderer, RomFile, SerialTarget};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use processor::{
|
use processor::{
|
||||||
memory::{mmio::gpu::Colour, Rom},
|
memory::{mmio::gpu::Colour, Rom},
|
||||||
|
@ -28,7 +28,7 @@ pub struct Options {
|
||||||
pub save_path: Option<String>,
|
pub save_path: Option<String>,
|
||||||
pub no_save: bool,
|
pub no_save: bool,
|
||||||
pub bootrom: Option<RomFile>,
|
pub bootrom: Option<RomFile>,
|
||||||
pub connect_serial: bool,
|
pub serial_target: SerialTarget,
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ impl Options {
|
||||||
save_path: None,
|
save_path: None,
|
||||||
no_save: false,
|
no_save: false,
|
||||||
bootrom: None,
|
bootrom: None,
|
||||||
connect_serial: false,
|
serial_target: SerialTarget::None,
|
||||||
verbose: false,
|
verbose: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,8 +59,13 @@ impl Options {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_serial(mut self) -> Self {
|
pub fn with_stdout(mut self) -> Self {
|
||||||
self.connect_serial = true;
|
self.serial_target = SerialTarget::Stdout;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_serial_target(mut self, target: SerialTarget) -> Self {
|
||||||
|
self.serial_target = target;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +146,7 @@ impl<ColourFormat: From<Colour> + Clone> EmulatorCore<ColourFormat> {
|
||||||
rom,
|
rom,
|
||||||
window,
|
window,
|
||||||
output,
|
output,
|
||||||
options.connect_serial,
|
options.serial_target,
|
||||||
tile_window,
|
tile_window,
|
||||||
),
|
),
|
||||||
bootrom_enabled,
|
bootrom_enabled,
|
||||||
|
@ -212,6 +217,7 @@ impl<ColourFormat: From<Colour> + Clone> EmulatorCore<ColourFormat> {
|
||||||
receiver: Receiver<EmulatorMessage>,
|
receiver: Receiver<EmulatorMessage>,
|
||||||
window: Box<dyn Renderer<ColourFormat>>,
|
window: Box<dyn Renderer<ColourFormat>>,
|
||||||
output: AudioOutput,
|
output: AudioOutput,
|
||||||
|
serial_target: SerialTarget,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let data = match rom {
|
let data = match rom {
|
||||||
RomFile::Path(path) => match fs::read(path) {
|
RomFile::Path(path) => match fs::read(path) {
|
||||||
|
@ -225,7 +231,7 @@ impl<ColourFormat: From<Colour> + Clone> EmulatorCore<ColourFormat> {
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
receiver,
|
receiver,
|
||||||
cpu: Cpu::from_save_state(state, data, window, output),
|
cpu: Cpu::from_save_state(state, data, window, output, serial_target),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,13 @@ use self::{
|
||||||
mmio::{
|
mmio::{
|
||||||
apu::ApuSaveState,
|
apu::ApuSaveState,
|
||||||
gpu::{Colour, GpuSaveState},
|
gpu::{Colour, GpuSaveState},
|
||||||
|
serial::SerialSaveState,
|
||||||
Apu, Gpu, Joypad, Serial, Timer,
|
Apu, Gpu, Joypad, Serial, Timer,
|
||||||
},
|
},
|
||||||
rom::RomSaveState,
|
rom::RomSaveState,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
connect::{AudioOutput, JoypadState, Renderer},
|
connect::{AudioOutput, JoypadState, Renderer, SerialTarget},
|
||||||
processor::SplitRegister,
|
processor::SplitRegister,
|
||||||
verbose_println, Cpu,
|
verbose_println, Cpu,
|
||||||
};
|
};
|
||||||
|
@ -52,7 +53,7 @@ pub struct MemorySaveState<ColourFormat: From<Colour> + Clone> {
|
||||||
joypad: Joypad,
|
joypad: Joypad,
|
||||||
gpu: GpuSaveState<ColourFormat>,
|
gpu: GpuSaveState<ColourFormat>,
|
||||||
apu: ApuSaveState,
|
apu: ApuSaveState,
|
||||||
serial: Serial,
|
serial: SerialSaveState,
|
||||||
timers: Timer,
|
timers: Timer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +70,7 @@ impl<ColourFormat: From<Colour> + Clone> MemorySaveState<ColourFormat> {
|
||||||
joypad: memory.joypad,
|
joypad: memory.joypad,
|
||||||
gpu: GpuSaveState::create(&memory.gpu),
|
gpu: GpuSaveState::create(&memory.gpu),
|
||||||
apu: ApuSaveState::create(&memory.apu),
|
apu: ApuSaveState::create(&memory.apu),
|
||||||
serial: memory.serial,
|
serial: SerialSaveState::create(&memory.serial),
|
||||||
timers: memory.timers,
|
timers: memory.timers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,14 +82,9 @@ impl<ColourFormat: From<Colour> + Clone> Memory<ColourFormat> {
|
||||||
rom: Rom,
|
rom: Rom,
|
||||||
window: Box<dyn Renderer<ColourFormat>>,
|
window: Box<dyn Renderer<ColourFormat>>,
|
||||||
output: AudioOutput,
|
output: AudioOutput,
|
||||||
connect_serial: bool,
|
serial_target: SerialTarget,
|
||||||
tile_window: Option<Box<dyn Renderer<ColourFormat>>>,
|
tile_window: Option<Box<dyn Renderer<ColourFormat>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let serial = if connect_serial {
|
|
||||||
Serial::default().connected()
|
|
||||||
} else {
|
|
||||||
Serial::default()
|
|
||||||
};
|
|
||||||
Self {
|
Self {
|
||||||
bootrom,
|
bootrom,
|
||||||
rom,
|
rom,
|
||||||
|
@ -101,7 +97,7 @@ impl<ColourFormat: From<Colour> + Clone> Memory<ColourFormat> {
|
||||||
joypad: Joypad::default(),
|
joypad: Joypad::default(),
|
||||||
gpu: Gpu::new(window, tile_window),
|
gpu: Gpu::new(window, tile_window),
|
||||||
apu: Apu::new(output),
|
apu: Apu::new(output),
|
||||||
serial,
|
serial: Serial::new(serial_target),
|
||||||
timers: Timer::init(),
|
timers: Timer::init(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,6 +278,7 @@ impl<ColourFormat: From<Colour> + Clone> Memory<ColourFormat> {
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
window: Box<dyn Renderer<ColourFormat>>,
|
window: Box<dyn Renderer<ColourFormat>>,
|
||||||
output: AudioOutput,
|
output: AudioOutput,
|
||||||
|
serial_target: SerialTarget,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bootrom: None,
|
bootrom: None,
|
||||||
|
@ -295,7 +292,7 @@ impl<ColourFormat: From<Colour> + Clone> Memory<ColourFormat> {
|
||||||
joypad: state.joypad,
|
joypad: state.joypad,
|
||||||
gpu: Gpu::from_save_state(state.gpu, window, None),
|
gpu: Gpu::from_save_state(state.gpu, window, None),
|
||||||
apu: Apu::from_save_state(state.apu, output),
|
apu: Apu::from_save_state(state.apu, output),
|
||||||
serial: state.serial,
|
serial: Serial::from_save_state(state.serial, serial_target),
|
||||||
timers: state.timers,
|
timers: state.timers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
pub(crate) mod apu;
|
pub(crate) mod apu;
|
||||||
pub(crate) mod gpu;
|
pub(crate) mod gpu;
|
||||||
pub(crate) mod joypad;
|
pub(crate) mod joypad;
|
||||||
mod serial;
|
pub(crate) mod serial;
|
||||||
mod timer;
|
mod timer;
|
||||||
pub use apu::Apu;
|
pub use apu::Apu;
|
||||||
pub use gpu::Gpu;
|
pub use gpu::Gpu;
|
||||||
|
|
|
@ -1,24 +1,27 @@
|
||||||
use std::io::{stdout, Write};
|
use std::{
|
||||||
|
io::{stdout, Write},
|
||||||
|
sync::mpsc::{Receiver, Sender},
|
||||||
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::util::get_bit;
|
use crate::util::get_bit;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||||
enum ClockSource {
|
enum ClockSource {
|
||||||
Internal,
|
Internal,
|
||||||
External,
|
External,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Copy, Clone, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
|
||||||
enum ClockSpeed {
|
enum ClockSpeed {
|
||||||
Normal,
|
Normal,
|
||||||
Fast,
|
Fast,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Copy, Clone, Serialize, Deserialize)]
|
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
|
||||||
struct SerialControl {
|
struct SerialControl {
|
||||||
transfer_in_progress: bool,
|
transfer_in_progress: bool,
|
||||||
clock_speed: ClockSpeed,
|
clock_speed: ClockSpeed,
|
||||||
|
@ -35,36 +38,157 @@ impl Default for SerialControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum SerialTarget {
|
||||||
|
Stdout,
|
||||||
|
Custom {
|
||||||
|
#[serde(skip)]
|
||||||
|
rx: Option<Receiver<u8>>,
|
||||||
|
#[serde(skip)]
|
||||||
|
tx: Option<Sender<u8>>,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Copy, Default)]
|
||||||
|
struct InputByte {
|
||||||
|
byte: Option<u8>,
|
||||||
|
progress: u8,
|
||||||
|
input_delay: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
const BYTE_DELAY: usize = 20000;
|
||||||
|
|
||||||
|
impl InputByte {
|
||||||
|
fn advance(&mut self, rx: &Option<Receiver<u8>>) -> u8 {
|
||||||
|
if let Some(byte) = self.byte {
|
||||||
|
let val = (byte >> (7 - self.progress)) & 0b1;
|
||||||
|
self.progress += 1;
|
||||||
|
if self.progress >= 8 {
|
||||||
|
self.byte = None;
|
||||||
|
}
|
||||||
|
val
|
||||||
|
} else if let Some(byte) = self.try_fill(rx) {
|
||||||
|
self.progress += 1;
|
||||||
|
(byte >> 7) & 0b1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_fill(&mut self, rx: &Option<Receiver<u8>>) -> Option<u8> {
|
||||||
|
self.byte = None;
|
||||||
|
self.progress = 0;
|
||||||
|
if let Some(rx) = rx {
|
||||||
|
if let Ok(new) = rx.try_recv() {
|
||||||
|
self.byte = Some(new);
|
||||||
|
self.input_delay = BYTE_DELAY;
|
||||||
|
return self.byte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ready(&mut self, rx: &Option<Receiver<u8>>) -> bool {
|
||||||
|
if self.byte.is_none() {
|
||||||
|
self.try_fill(rx);
|
||||||
|
}
|
||||||
|
self.byte.is_some() && self.input_delay == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Serial {
|
pub struct Serial {
|
||||||
byte: u8,
|
byte: u8,
|
||||||
output_byte: u8,
|
output_byte: u8,
|
||||||
|
input_byte: InputByte,
|
||||||
bits_remaining: u8,
|
bits_remaining: u8,
|
||||||
control: SerialControl,
|
control: SerialControl,
|
||||||
is_connected: bool,
|
target: SerialTarget,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct SerialSaveState {
|
||||||
|
byte: u8,
|
||||||
|
output_byte: u8,
|
||||||
|
input_byte: InputByte,
|
||||||
|
bits_remaining: u8,
|
||||||
|
control: SerialControl,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerialSaveState {
|
||||||
|
pub fn create(serial: &Serial) -> Self {
|
||||||
|
Self {
|
||||||
|
byte: serial.byte,
|
||||||
|
output_byte: serial.output_byte,
|
||||||
|
input_byte: serial.input_byte,
|
||||||
|
bits_remaining: serial.bits_remaining,
|
||||||
|
control: serial.control,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serial {
|
impl Serial {
|
||||||
pub fn connected(mut self) -> Self {
|
pub fn new(target: SerialTarget) -> Self {
|
||||||
self.is_connected = true;
|
Self {
|
||||||
self
|
byte: 0,
|
||||||
|
output_byte: 0,
|
||||||
|
input_byte: InputByte::default(),
|
||||||
|
bits_remaining: 7,
|
||||||
|
control: SerialControl::default(),
|
||||||
|
target,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_save_state(state: SerialSaveState, target: SerialTarget) -> Self {
|
||||||
|
Self {
|
||||||
|
byte: state.byte,
|
||||||
|
output_byte: state.output_byte,
|
||||||
|
input_byte: state.input_byte,
|
||||||
|
bits_remaining: state.bits_remaining,
|
||||||
|
control: state.control,
|
||||||
|
target,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_connected(&self) -> bool {
|
||||||
|
!matches!(&self.target, SerialTarget::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self, steps: usize) -> bool {
|
pub fn tick(&mut self, steps: usize) -> bool {
|
||||||
let mut will_interrupt = false;
|
let mut will_interrupt = false;
|
||||||
if !self.is_connected {
|
if !self.is_connected() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
let rx = if let SerialTarget::Custom { rx, tx: _ } = &self.target {
|
||||||
|
rx
|
||||||
|
} else {
|
||||||
|
&None
|
||||||
|
};
|
||||||
for _ in 0..steps {
|
for _ in 0..steps {
|
||||||
if self.control.transfer_in_progress {
|
self.input_byte.input_delay = self.input_byte.input_delay.saturating_sub(1);
|
||||||
|
if (self.control.transfer_in_progress
|
||||||
|
&& self.control.clock_source == ClockSource::Internal)
|
||||||
|
|| (self.control.clock_source == ClockSource::External
|
||||||
|
&& self.input_byte.is_ready(rx))
|
||||||
|
{
|
||||||
self.output_byte = self.output_byte << 1 | self.byte >> 7;
|
self.output_byte = self.output_byte << 1 | self.byte >> 7;
|
||||||
self.byte = (self.byte << 1) | 0b1;
|
self.byte = (self.byte << 1) | self.input_byte.advance(rx);
|
||||||
let (remainder, finished) = self.bits_remaining.overflowing_sub(1);
|
let (remainder, finished) = self.bits_remaining.overflowing_sub(1);
|
||||||
self.bits_remaining = if finished {
|
self.bits_remaining = if finished {
|
||||||
self.control.transfer_in_progress = false;
|
self.control.transfer_in_progress = false;
|
||||||
will_interrupt = true;
|
will_interrupt = true;
|
||||||
print!("{}", self.output_byte as char);
|
match &self.target {
|
||||||
stdout().flush().unwrap();
|
SerialTarget::Stdout => {
|
||||||
|
print!("{}", self.output_byte as char);
|
||||||
|
stdout().flush().unwrap();
|
||||||
|
}
|
||||||
|
SerialTarget::Custom { rx: _, tx } => {
|
||||||
|
if let Some(tx) = tx {
|
||||||
|
tx.send(self.output_byte).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SerialTarget::None => {}
|
||||||
|
}
|
||||||
7
|
7
|
||||||
} else {
|
} else {
|
||||||
remainder
|
remainder
|
||||||
|
@ -84,6 +208,7 @@ impl Serial {
|
||||||
|
|
||||||
pub fn update_control(&mut self, data: u8) {
|
pub fn update_control(&mut self, data: u8) {
|
||||||
self.control.transfer_in_progress = get_bit(data, 7);
|
self.control.transfer_in_progress = get_bit(data, 7);
|
||||||
|
|
||||||
// CGB - add clock speed
|
// CGB - add clock speed
|
||||||
self.control.clock_source = if get_bit(data, 0) {
|
self.control.clock_source = if get_bit(data, 0) {
|
||||||
ClockSource::Internal
|
ClockSource::Internal
|
||||||
|
@ -105,15 +230,3 @@ impl Serial {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Serial {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
byte: 0,
|
|
||||||
output_byte: 0,
|
|
||||||
bits_remaining: 7,
|
|
||||||
control: SerialControl::default(),
|
|
||||||
is_connected: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use self::memory::{mmio::gpu::Colour, Interrupt, Memory, MemorySaveState};
|
use self::memory::{mmio::gpu::Colour, Interrupt, Memory, MemorySaveState};
|
||||||
use crate::{
|
use crate::{
|
||||||
connect::{AudioOutput, Renderer},
|
connect::{AudioOutput, Renderer, SerialTarget},
|
||||||
verbose_println,
|
verbose_println,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -159,9 +159,10 @@ impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
window: Box<dyn Renderer<ColourFormat>>,
|
window: Box<dyn Renderer<ColourFormat>>,
|
||||||
output: AudioOutput,
|
output: AudioOutput,
|
||||||
|
serial_target: SerialTarget,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
memory: Memory::from_save_state(state.memory, data, window, output),
|
memory: Memory::from_save_state(state.memory, data, window, output, serial_target),
|
||||||
reg: state.reg,
|
reg: state.reg,
|
||||||
last_instruction: state.last_instruction,
|
last_instruction: state.last_instruction,
|
||||||
last_instruction_addr: state.last_instruction_addr,
|
last_instruction_addr: state.last_instruction_addr,
|
||||||
|
|
Loading…
Reference in a new issue