serial and midi

This commit is contained in:
Alex Janka 2023-03-15 17:45:56 +11:00
parent b5fa2e6690
commit 75bd223e98
7 changed files with 267 additions and 62 deletions

View file

@ -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();
} }

View file

@ -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};

View file

@ -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),
} }
} }
} }

View file

@ -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,
} }
} }

View file

@ -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;

View file

@ -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,
}
}
}

View file

@ -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,