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 futures::executor;
use gb_emu_lib::{
connect::{AudioOutput, CpuSaveState, DownsampleType, EmulatorMessage, JoypadButtons, RomFile},
connect::{
AudioOutput, CpuSaveState, DownsampleType, EmulatorMessage, JoypadButtons, RomFile,
SerialTarget,
},
EmulatorCore,
};
use nih_plug::prelude::*;
use nih_plug::{midi::MidiResult::Basic, params::persist::PersistentField};
use std::sync::{
mpsc::{channel, Receiver, Sender},
mpsc::{self, channel, Receiver, Sender},
Arc, Mutex,
};
use ui::{Emulator, EmulatorRenderer};
@ -42,6 +45,7 @@ struct EmuVars {
rx: AsyncHeapConsumer<[f32; 2]>,
sender: Sender<EmulatorMessage>,
emulator_core: EmulatorCore<[u8; 4]>,
serial_tx: Sender<u8>,
}
#[derive(Default)]
@ -101,12 +105,84 @@ impl Plugin for GameboyEmu {
&mut self,
buffer: &mut Buffer,
_: &mut AuxiliaryBuffers,
_c: &mut impl ProcessContext<Self>,
context: &mut impl ProcessContext<Self>,
) -> ProcessStatus {
while let Some(event) = _c.next_event() {
if let Some(Basic(_midi)) = event.as_midi() {}
}
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 {
panic!()
}
@ -121,6 +197,8 @@ impl Plugin for GameboyEmu {
}
}
vars.emulator_core.run_until_buffer_full();
} else {
while context.next_event().is_some() {}
}
self.update_save_state();
ProcessStatus::KeepAlive
@ -174,12 +252,20 @@ impl Plugin for GameboyEmu {
let window = Box::new(renderer);
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)
let (serial_tx, gb_serial_rx) = mpsc::channel::<u8>();
let serial_target = SerialTarget::Custom {
rx: Some(gb_serial_rx),
tx: 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)
@ -190,6 +276,7 @@ impl Plugin for GameboyEmu {
rx,
sender,
emulator_core,
serial_tx,
});
self.update_save_state();
}

View file

@ -1,5 +1,6 @@
use crate::processor::memory::mmio::gpu::Colour;
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::{HEIGHT, WIDTH};
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};

View file

@ -1,7 +1,7 @@
#![feature(exclusive_range_pattern, let_chains, bigint_helper_methods)]
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 processor::{
memory::{mmio::gpu::Colour, Rom},
@ -28,7 +28,7 @@ pub struct Options {
pub save_path: Option<String>,
pub no_save: bool,
pub bootrom: Option<RomFile>,
pub connect_serial: bool,
pub serial_target: SerialTarget,
pub verbose: bool,
}
@ -39,7 +39,7 @@ impl Options {
save_path: None,
no_save: false,
bootrom: None,
connect_serial: false,
serial_target: SerialTarget::None,
verbose: false,
}
}
@ -59,8 +59,13 @@ impl Options {
self
}
pub fn with_serial(mut self) -> Self {
self.connect_serial = true;
pub fn with_stdout(mut self) -> Self {
self.serial_target = SerialTarget::Stdout;
self
}
pub fn with_serial_target(mut self, target: SerialTarget) -> Self {
self.serial_target = target;
self
}
@ -141,7 +146,7 @@ impl<ColourFormat: From<Colour> + Clone> EmulatorCore<ColourFormat> {
rom,
window,
output,
options.connect_serial,
options.serial_target,
tile_window,
),
bootrom_enabled,
@ -212,6 +217,7 @@ impl<ColourFormat: From<Colour> + Clone> EmulatorCore<ColourFormat> {
receiver: Receiver<EmulatorMessage>,
window: Box<dyn Renderer<ColourFormat>>,
output: AudioOutput,
serial_target: SerialTarget,
) -> Self {
let data = match rom {
RomFile::Path(path) => match fs::read(path) {
@ -225,7 +231,7 @@ impl<ColourFormat: From<Colour> + Clone> EmulatorCore<ColourFormat> {
};
Self {
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::{
apu::ApuSaveState,
gpu::{Colour, GpuSaveState},
serial::SerialSaveState,
Apu, Gpu, Joypad, Serial, Timer,
},
rom::RomSaveState,
};
use crate::{
connect::{AudioOutput, JoypadState, Renderer},
connect::{AudioOutput, JoypadState, Renderer, SerialTarget},
processor::SplitRegister,
verbose_println, Cpu,
};
@ -52,7 +53,7 @@ pub struct MemorySaveState<ColourFormat: From<Colour> + Clone> {
joypad: Joypad,
gpu: GpuSaveState<ColourFormat>,
apu: ApuSaveState,
serial: Serial,
serial: SerialSaveState,
timers: Timer,
}
@ -69,7 +70,7 @@ impl<ColourFormat: From<Colour> + Clone> MemorySaveState<ColourFormat> {
joypad: memory.joypad,
gpu: GpuSaveState::create(&memory.gpu),
apu: ApuSaveState::create(&memory.apu),
serial: memory.serial,
serial: SerialSaveState::create(&memory.serial),
timers: memory.timers,
}
}
@ -81,14 +82,9 @@ impl<ColourFormat: From<Colour> + Clone> Memory<ColourFormat> {
rom: Rom,
window: Box<dyn Renderer<ColourFormat>>,
output: AudioOutput,
connect_serial: bool,
serial_target: SerialTarget,
tile_window: Option<Box<dyn Renderer<ColourFormat>>>,
) -> Self {
let serial = if connect_serial {
Serial::default().connected()
} else {
Serial::default()
};
Self {
bootrom,
rom,
@ -101,7 +97,7 @@ impl<ColourFormat: From<Colour> + Clone> Memory<ColourFormat> {
joypad: Joypad::default(),
gpu: Gpu::new(window, tile_window),
apu: Apu::new(output),
serial,
serial: Serial::new(serial_target),
timers: Timer::init(),
}
}
@ -282,6 +278,7 @@ impl<ColourFormat: From<Colour> + Clone> Memory<ColourFormat> {
data: Vec<u8>,
window: Box<dyn Renderer<ColourFormat>>,
output: AudioOutput,
serial_target: SerialTarget,
) -> Self {
Self {
bootrom: None,
@ -295,7 +292,7 @@ impl<ColourFormat: From<Colour> + Clone> Memory<ColourFormat> {
joypad: state.joypad,
gpu: Gpu::from_save_state(state.gpu, window, None),
apu: Apu::from_save_state(state.apu, output),
serial: state.serial,
serial: Serial::from_save_state(state.serial, serial_target),
timers: state.timers,
}
}

View file

@ -1,7 +1,7 @@
pub(crate) mod apu;
pub(crate) mod gpu;
pub(crate) mod joypad;
mod serial;
pub(crate) mod serial;
mod timer;
pub use apu::Apu;
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 crate::util::get_bit;
#[derive(Copy, Clone, Serialize, Deserialize)]
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq)]
enum ClockSource {
Internal,
External,
}
#[allow(dead_code)]
#[derive(Copy, Clone, Serialize, Deserialize)]
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
enum ClockSpeed {
Normal,
Fast,
}
#[allow(dead_code)]
#[derive(Copy, Clone, Serialize, Deserialize)]
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
struct SerialControl {
transfer_in_progress: bool,
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 {
byte: u8,
output_byte: u8,
input_byte: InputByte,
bits_remaining: u8,
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 {
pub fn connected(mut self) -> Self {
self.is_connected = true;
self
pub fn new(target: SerialTarget) -> 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 {
let mut will_interrupt = false;
if !self.is_connected {
if !self.is_connected() {
return false;
}
let rx = if let SerialTarget::Custom { rx, tx: _ } = &self.target {
rx
} else {
&None
};
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.byte = (self.byte << 1) | 0b1;
self.byte = (self.byte << 1) | self.input_byte.advance(rx);
let (remainder, finished) = self.bits_remaining.overflowing_sub(1);
self.bits_remaining = if finished {
self.control.transfer_in_progress = false;
will_interrupt = true;
match &self.target {
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
} else {
remainder
@ -84,6 +208,7 @@ impl Serial {
pub fn update_control(&mut self, data: u8) {
self.control.transfer_in_progress = get_bit(data, 7);
// CGB - add clock speed
self.control.clock_source = if get_bit(data, 0) {
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 crate::{
connect::{AudioOutput, Renderer},
connect::{AudioOutput, Renderer, SerialTarget},
verbose_println,
};
@ -159,9 +159,10 @@ impl<ColourFormat: From<Colour> + Clone> Cpu<ColourFormat> {
data: Vec<u8>,
window: Box<dyn Renderer<ColourFormat>>,
output: AudioOutput,
serial_target: SerialTarget,
) -> 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,
last_instruction: state.last_instruction,
last_instruction_addr: state.last_instruction_addr,