serial and midi
This commit is contained in:
parent
b5fa2e6690
commit
75bd223e98
|
@ -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,22 +252,31 @@ 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)
|
||||
} else {
|
||||
let options = gb_emu_lib::Options::new(rom)
|
||||
.with_bootrom(bootrom)
|
||||
.force_no_save();
|
||||
let (serial_tx, gb_serial_rx) = mpsc::channel::<u8>();
|
||||
let serial_target = SerialTarget::Custom {
|
||||
rx: Some(gb_serial_rx),
|
||||
tx: None,
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
self.vars = Some(EmuVars {
|
||||
rx,
|
||||
sender,
|
||||
emulator_core,
|
||||
serial_tx,
|
||||
});
|
||||
self.update_save_state();
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
print!("{}", self.output_byte as char);
|
||||
stdout().flush().unwrap();
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue