Files
nesemu/src/native_core/apu/api.rs
se.cherkasov bdf23de8db
Some checks failed
CI / rust (push) Has been cancelled
Initial commit: NES emulator with GTK4 desktop frontend
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.
2026-03-13 11:48:45 +03:00

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