Some checks failed
CI / rust (push) Has been cancelled
Full NES emulation: CPU, PPU, APU, 47 mappers, iNES/NES 2.0 parsing. GTK4 desktop client with HeaderBar, pixel-perfect Cairo rendering, drag-and-drop ROM loading, and keyboard shortcuts. 187 tests covering core emulation, mappers, and runtime.
265 lines
10 KiB
Rust
265 lines
10 KiB
Rust
use super::types::{Apu, ApuStateTail};
|
|
|
|
impl Apu {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
io: [0; 0x20],
|
|
frame_cycle: 0,
|
|
frame_mode_5step: false,
|
|
frame_irq_inhibit: false,
|
|
frame_irq_pending: false,
|
|
channel_enable_mask: 0,
|
|
length_counters: [0; 4],
|
|
dmc_bytes_remaining: 0,
|
|
dmc_irq_enabled: false,
|
|
dmc_irq_pending: false,
|
|
dmc_cycle_counter: 0,
|
|
dmc_current_addr: 0xC000,
|
|
dmc_sample_buffer: 0,
|
|
dmc_sample_buffer_valid: false,
|
|
dmc_shift_reg: 0,
|
|
dmc_bits_remaining: 8,
|
|
dmc_silence: true,
|
|
dmc_output_level: 0,
|
|
dmc_dma_request: false,
|
|
envelope_divider: [0; 3],
|
|
envelope_decay: [0; 3],
|
|
envelope_start_flags: 0,
|
|
triangle_linear_counter: 0,
|
|
triangle_linear_reload_flag: false,
|
|
sweep_divider: [0; 2],
|
|
sweep_reload_flags: 0,
|
|
cpu_cycle_parity: false,
|
|
frame_reset_pending: false,
|
|
frame_reset_delay: 0,
|
|
pending_frame_mode_5step: false,
|
|
pending_frame_irq_inhibit: false,
|
|
}
|
|
}
|
|
|
|
pub fn registers(&self) -> &[u8; 0x20] {
|
|
&self.io
|
|
}
|
|
|
|
pub fn set_registers(&mut self, regs: [u8; 0x20]) {
|
|
self.io = regs;
|
|
}
|
|
|
|
pub fn read(&mut self, addr: u16) -> u8 {
|
|
match addr {
|
|
0x4000..=0x4013 => self.io[(addr as usize) - 0x4000],
|
|
0x4015 => self.read_status(),
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
pub fn write(&mut self, addr: u16, value: u8) {
|
|
match addr {
|
|
0x4000..=0x4013 => {
|
|
self.io[(addr as usize) - 0x4000] = value;
|
|
match addr {
|
|
0x4003 => {
|
|
self.reload_length_counter(0, value >> 3);
|
|
self.envelope_start_flags |= 1 << 0;
|
|
}
|
|
0x4001 => {
|
|
self.sweep_reload_flags |= 1 << 0;
|
|
}
|
|
0x4007 => {
|
|
self.reload_length_counter(1, value >> 3);
|
|
self.envelope_start_flags |= 1 << 1;
|
|
}
|
|
0x4005 => {
|
|
self.sweep_reload_flags |= 1 << 1;
|
|
}
|
|
0x400B => {
|
|
self.reload_length_counter(2, value >> 3);
|
|
self.triangle_linear_reload_flag = true;
|
|
}
|
|
0x400F => {
|
|
self.reload_length_counter(3, value >> 3);
|
|
self.envelope_start_flags |= 1 << 2;
|
|
}
|
|
0x4010 => {
|
|
self.dmc_irq_enabled = (value & 0x80) != 0;
|
|
if !self.dmc_irq_enabled {
|
|
self.dmc_irq_pending = false;
|
|
}
|
|
}
|
|
0x4011 => {
|
|
self.dmc_output_level = value & 0x7F;
|
|
}
|
|
0x4012 => {
|
|
self.dmc_current_addr = self.dmc_sample_start_addr();
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
0x4014 => {
|
|
self.io[0x14] = value;
|
|
}
|
|
0x4015 => {
|
|
self.io[(addr as usize) - 0x4000] = value;
|
|
self.channel_enable_mask = value & 0x1F;
|
|
self.dmc_irq_pending = false;
|
|
for chan in 0..4usize {
|
|
if (self.channel_enable_mask & (1 << chan)) == 0 {
|
|
self.length_counters[chan] = 0;
|
|
}
|
|
}
|
|
if (self.channel_enable_mask & 0x10) == 0 {
|
|
self.dmc_bytes_remaining = 0;
|
|
self.dmc_cycle_counter = 0;
|
|
self.dmc_dma_request = false;
|
|
} else if self.dmc_bytes_remaining == 0 {
|
|
self.dmc_bytes_remaining = self.dmc_sample_length_bytes();
|
|
self.dmc_cycle_counter = self.dmc_byte_period();
|
|
self.dmc_current_addr = self.dmc_sample_start_addr();
|
|
if !self.dmc_sample_buffer_valid {
|
|
self.dmc_dma_request = true;
|
|
}
|
|
}
|
|
}
|
|
0x4017 => {
|
|
self.io[(addr as usize) - 0x4000] = value;
|
|
self.pending_frame_mode_5step = (value & 0x80) != 0;
|
|
self.pending_frame_irq_inhibit = (value & 0x40) != 0;
|
|
self.frame_reset_delay = if self.cpu_cycle_parity { 4 } else { 3 };
|
|
self.frame_reset_pending = true;
|
|
if self.pending_frame_irq_inhibit {
|
|
self.frame_irq_pending = false;
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
pub fn clock_cpu_cycle(&mut self) {
|
|
let mut skip_frame_counter_clock = false;
|
|
if self.frame_reset_pending {
|
|
self.frame_reset_delay = self.frame_reset_delay.saturating_sub(1);
|
|
if self.frame_reset_delay == 0 {
|
|
self.frame_reset_pending = false;
|
|
self.frame_mode_5step = self.pending_frame_mode_5step;
|
|
self.frame_irq_inhibit = self.pending_frame_irq_inhibit;
|
|
self.frame_cycle = 0;
|
|
if self.frame_mode_5step {
|
|
self.clock_quarter_frame();
|
|
self.clock_half_frame();
|
|
}
|
|
skip_frame_counter_clock = true;
|
|
}
|
|
}
|
|
if !skip_frame_counter_clock {
|
|
self.clock_frame_counter();
|
|
}
|
|
self.clock_dmc();
|
|
self.cpu_cycle_parity = !self.cpu_cycle_parity;
|
|
}
|
|
|
|
pub fn poll_irq(&self) -> bool {
|
|
self.dmc_irq_pending || (self.frame_irq_pending && !self.frame_irq_inhibit)
|
|
}
|
|
|
|
pub fn take_dmc_dma_request(&mut self) -> Option<u16> {
|
|
if self.dmc_dma_request {
|
|
Some(self.dmc_current_addr)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn provide_dmc_dma_byte(&mut self, byte: u8) {
|
|
if !self.dmc_dma_request {
|
|
return;
|
|
}
|
|
self.dmc_dma_request = false;
|
|
self.dmc_sample_buffer = byte;
|
|
self.dmc_sample_buffer_valid = true;
|
|
self.dmc_current_addr = if self.dmc_current_addr == 0xFFFF {
|
|
0x8000
|
|
} else {
|
|
self.dmc_current_addr.wrapping_add(1)
|
|
};
|
|
|
|
if self.dmc_bytes_remaining > 0 {
|
|
self.dmc_bytes_remaining -= 1;
|
|
}
|
|
if self.dmc_bytes_remaining == 0 {
|
|
if (self.io[0x10] & 0x40) != 0 {
|
|
self.dmc_bytes_remaining = self.dmc_sample_length_bytes();
|
|
self.dmc_current_addr = self.dmc_sample_start_addr();
|
|
self.dmc_dma_request = true;
|
|
} else if self.dmc_irq_enabled {
|
|
self.dmc_irq_pending = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn save_state_tail(&self, out: &mut Vec<u8>) {
|
|
out.extend_from_slice(&self.frame_cycle.to_le_bytes());
|
|
out.push(u8::from(self.frame_mode_5step));
|
|
out.push(u8::from(self.frame_irq_inhibit));
|
|
out.push(u8::from(self.frame_irq_pending));
|
|
out.push(self.channel_enable_mask);
|
|
out.extend_from_slice(&self.length_counters);
|
|
out.extend_from_slice(&self.dmc_bytes_remaining.to_le_bytes());
|
|
out.push(u8::from(self.dmc_irq_enabled));
|
|
out.push(u8::from(self.dmc_irq_pending));
|
|
out.extend_from_slice(&self.dmc_cycle_counter.to_le_bytes());
|
|
out.extend_from_slice(&self.dmc_current_addr.to_le_bytes());
|
|
out.push(self.dmc_sample_buffer);
|
|
out.push(u8::from(self.dmc_sample_buffer_valid));
|
|
out.push(self.dmc_shift_reg);
|
|
out.push(self.dmc_bits_remaining);
|
|
out.push(u8::from(self.dmc_silence));
|
|
out.push(self.dmc_output_level);
|
|
out.push(u8::from(self.dmc_dma_request));
|
|
out.extend_from_slice(&self.envelope_divider);
|
|
out.extend_from_slice(&self.envelope_decay);
|
|
out.push(self.envelope_start_flags);
|
|
out.push(self.triangle_linear_counter);
|
|
out.push(u8::from(self.triangle_linear_reload_flag));
|
|
out.extend_from_slice(&self.sweep_divider);
|
|
out.push(self.sweep_reload_flags);
|
|
out.push(u8::from(self.cpu_cycle_parity));
|
|
out.push(u8::from(self.frame_reset_pending));
|
|
out.push(self.frame_reset_delay);
|
|
out.push(u8::from(self.pending_frame_mode_5step));
|
|
out.push(u8::from(self.pending_frame_irq_inhibit));
|
|
}
|
|
|
|
pub fn load_state_tail(&mut self, state: ApuStateTail) {
|
|
self.frame_cycle = state.frame_cycle;
|
|
self.frame_mode_5step = state.frame_mode_5step;
|
|
self.frame_irq_inhibit = state.frame_irq_inhibit;
|
|
self.frame_irq_pending = state.frame_irq_pending;
|
|
self.channel_enable_mask = state.channel_enable_mask;
|
|
self.length_counters = state.length_counters;
|
|
self.dmc_bytes_remaining = state.dmc_bytes_remaining;
|
|
self.dmc_irq_enabled = state.dmc_irq_enabled;
|
|
self.dmc_irq_pending = state.dmc_irq_pending;
|
|
self.dmc_cycle_counter = state.dmc_cycle_counter;
|
|
self.dmc_current_addr = state.dmc_current_addr;
|
|
self.dmc_sample_buffer = state.dmc_sample_buffer;
|
|
self.dmc_sample_buffer_valid = state.dmc_sample_buffer_valid;
|
|
self.dmc_shift_reg = state.dmc_shift_reg;
|
|
self.dmc_bits_remaining = state.dmc_bits_remaining.max(1);
|
|
self.dmc_silence = state.dmc_silence;
|
|
self.dmc_output_level = state.dmc_output_level & 0x7F;
|
|
self.dmc_dma_request = state.dmc_dma_request;
|
|
self.envelope_divider = state.envelope_divider;
|
|
self.envelope_decay = state.envelope_decay;
|
|
self.envelope_start_flags = state.envelope_start_flags & 0x07;
|
|
self.triangle_linear_counter = state.triangle_linear_counter & 0x7F;
|
|
self.triangle_linear_reload_flag = state.triangle_linear_reload_flag;
|
|
self.sweep_divider = state.sweep_divider;
|
|
self.sweep_reload_flags = state.sweep_reload_flags & 0x03;
|
|
self.cpu_cycle_parity = state.cpu_cycle_parity;
|
|
self.frame_reset_pending = state.frame_reset_pending;
|
|
self.frame_reset_delay = state.frame_reset_delay;
|
|
self.pending_frame_mode_5step = state.pending_frame_mode_5step;
|
|
self.pending_frame_irq_inhibit = state.pending_frame_irq_inhibit;
|
|
}
|
|
}
|