Files
nesemu/src/native_core/apu/api.rs

371 lines
15 KiB
Rust

use super::types::{Apu, ApuStateTail, ChannelOutputs};
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,
pulse_timer_counter: [0; 2],
pulse_duty_step: [0; 2],
triangle_timer_counter: 0,
triangle_step: 0,
noise_timer_counter: 0,
noise_lfsr: 1, // LFSR initialized to 1 per NES hardware
}
}
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;
self.pulse_duty_step[0] = 0;
self.pulse_timer_counter[0] = self.pulse_timer_period(0x02);
}
0x4001 => {
self.sweep_reload_flags |= 1 << 0;
}
0x4007 => {
self.reload_length_counter(1, value >> 3);
self.envelope_start_flags |= 1 << 1;
self.pulse_duty_step[1] = 0;
self.pulse_timer_counter[1] = self.pulse_timer_period(0x06);
}
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.clock_pulse_timers();
self.clock_triangle_timer();
self.clock_noise_timer();
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 {
// Loop mode: reset address and byte counter.
// Do NOT request another DMA here — the sample buffer is full
// right now. clock_dmc will request the next fetch when the
// output unit empties the buffer into the shift register, which
// is the correct NES hardware behaviour (reader only fills an
// empty buffer). Requesting early would overwrite the valid
// buffer and skip the last byte of each loop iteration.
self.dmc_bytes_remaining = self.dmc_sample_length_bytes();
self.dmc_current_addr = self.dmc_sample_start_addr();
} 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));
out.extend_from_slice(&self.pulse_timer_counter[0].to_le_bytes());
out.extend_from_slice(&self.pulse_timer_counter[1].to_le_bytes());
out.extend_from_slice(&self.pulse_duty_step);
out.extend_from_slice(&self.triangle_timer_counter.to_le_bytes());
out.push(self.triangle_step);
out.extend_from_slice(&self.noise_timer_counter.to_le_bytes());
out.extend_from_slice(&self.noise_lfsr.to_le_bytes());
}
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;
self.pulse_timer_counter = state.pulse_timer_counter;
self.pulse_duty_step = [state.pulse_duty_step[0] & 0x07, state.pulse_duty_step[1] & 0x07];
self.triangle_timer_counter = state.triangle_timer_counter;
self.triangle_step = state.triangle_step & 0x1F;
self.noise_timer_counter = state.noise_timer_counter;
self.noise_lfsr = if state.noise_lfsr == 0 { 1 } else { state.noise_lfsr };
}
pub fn channel_outputs(&self) -> ChannelOutputs {
const PULSE_DUTY_TABLE: [[u8; 8]; 4] = [
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 0, 0, 0],
[1, 0, 0, 1, 1, 1, 1, 1],
];
const TRIANGLE_SEQUENCE: [u8; 32] = [
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
];
let pulse1 = {
let duty = (self.io[0x00] >> 6) as usize;
let step = self.pulse_duty_step[0] as usize;
let volume = if (self.io[0x00] & 0x10) != 0 {
self.io[0x00] & 0x0F
} else {
self.envelope_decay[0]
};
let active = (self.channel_enable_mask & 0x01) != 0
&& self.length_counters[0] > 0
&& PULSE_DUTY_TABLE[duty][step] != 0
&& !self.sweep_mutes_channel(0, 0x02);
if active { volume } else { 0 }
};
let pulse2 = {
let duty = (self.io[0x04] >> 6) as usize;
let step = self.pulse_duty_step[1] as usize;
let volume = if (self.io[0x04] & 0x10) != 0 {
self.io[0x04] & 0x0F
} else {
self.envelope_decay[1]
};
let active = (self.channel_enable_mask & 0x02) != 0
&& self.length_counters[1] > 0
&& PULSE_DUTY_TABLE[duty][step] != 0
&& !self.sweep_mutes_channel(1, 0x06);
if active { volume } else { 0 }
};
let triangle = {
// Timer period < 2 produces ultrasonic output (~28-56 kHz) that aliases
// to audible frequencies when sampled at 48 kHz. Real hardware filters
// this via the RC output stage; mute here to match that behaviour.
let active = (self.channel_enable_mask & 0x04) != 0
&& self.length_counters[2] > 0
&& self.triangle_linear_counter > 0
&& self.triangle_timer_period() >= 2;
if active {
TRIANGLE_SEQUENCE[self.triangle_step as usize & 0x1F]
} else {
0
}
};
let noise = {
let volume = if (self.io[0x0C] & 0x10) != 0 {
self.io[0x0C] & 0x0F
} else {
self.envelope_decay[2]
};
let active = (self.channel_enable_mask & 0x08) != 0
&& self.length_counters[3] > 0
&& (self.noise_lfsr & 1) == 0;
if active { volume } else { 0 }
};
let dmc = self.dmc_output_level;
ChannelOutputs { pulse1, pulse2, triangle, noise, dmc, expansion: 0.0 }
}
}