use crate::common::{NesRegion, Reset, ResetKind};
use serde::{Deserialize, Serialize};
use tracing::trace;
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct FrameCounter {
pub region: NesRegion,
pub step_cycles: [u32; 6],
pub step: usize,
pub mode: u8,
pub write_buffer: Option<u8>,
pub write_delay: u8,
pub block_counter: u8,
pub cycle: u32,
pub inhibit_irq: bool, pub irq_pending: bool,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FrameType {
#[default]
None,
Quarter,
Half,
}
impl FrameCounter {
const STEP4_CYCLES_NTSC: [u32; 6] = [7457, 14913, 22371, 29828, 29829, 29830];
const STEP5_CYCLES_NTSC: [u32; 6] = [7457, 14913, 22371, 29829, 37281, 37282];
const STEP4_CYCLES_PAL: [u32; 6] = [8313, 16627, 24939, 33252, 33253, 33254];
const STEP5_CYCLES_PAL: [u32; 6] = [8313, 16627, 24939, 33253, 41565, 41566];
const FRAME_TYPE: [FrameType; 6] = [
FrameType::Quarter,
FrameType::Half,
FrameType::Quarter,
FrameType::None,
FrameType::Half,
FrameType::None,
];
pub const fn new(region: NesRegion) -> Self {
let mode = 0;
let step_cycles = Self::step_cycles(mode, region);
Self {
region,
step_cycles,
step: 0,
mode,
write_buffer: None,
write_delay: 0,
block_counter: 0,
cycle: 0,
inhibit_irq: false,
irq_pending: false,
}
}
pub const fn set_region(&mut self, region: NesRegion) {
self.region = region;
self.step_cycles = Self::step_cycles(self.mode, region);
}
const fn step_cycles(mode: u8, region: NesRegion) -> [u32; 6] {
match (mode, region) {
(0, NesRegion::Auto | NesRegion::Ntsc | NesRegion::Dendy) => Self::STEP4_CYCLES_NTSC,
(0, NesRegion::Pal) => Self::STEP4_CYCLES_PAL,
(_, NesRegion::Auto | NesRegion::Ntsc | NesRegion::Dendy) => Self::STEP5_CYCLES_NTSC,
(_, NesRegion::Pal) => Self::STEP5_CYCLES_PAL,
}
}
pub fn write(&mut self, val: u8, cycle: u32) {
self.write_buffer = Some(val);
self.write_delay = if cycle & 0x01 == 0x01 { 4 } else { 3 };
trace!("APU $4017 write delay cycles: {}", self.write_delay);
self.inhibit_irq = val & 0x40 == 0x40; if self.inhibit_irq {
trace!("APU Frame Counter IRQ inhibit");
self.irq_pending = false;
}
}
#[inline(always)]
pub const fn should_clock(&mut self, cycles: u32) -> bool {
self.block_counter > 0
|| self.write_buffer.is_some()
|| (self.cycle + cycles) >= (self.step_cycles[self.step] - 1)
}
pub fn clock_with(&mut self, cycles: u32, mut on_clock: impl FnMut(FrameType)) -> u32 {
let mut cycles_ran = 0;
let step_cycles = self.step_cycles[self.step];
if self.cycle + cycles >= step_cycles {
if !self.inhibit_irq && self.mode == 0 && self.step >= 3 {
trace!(
"APU Frame Counter IRQ pending - cycles: {} >= {step_cycles}",
self.cycle + cycles
);
self.irq_pending = true;
}
let ty = Self::FRAME_TYPE[self.step];
if ty != FrameType::None && self.block_counter == 0 {
on_clock(ty);
self.block_counter = 2;
}
if step_cycles >= self.cycle {
cycles_ran = step_cycles - self.cycle;
}
self.step += 1;
if self.step == 6 {
trace!(
"APU Frame Counter total cycles: {}",
self.cycle + cycles_ran
);
self.step = 0;
self.cycle = 0;
} else {
self.cycle += cycles_ran;
}
} else {
cycles_ran = cycles;
self.cycle += cycles_ran;
}
if let Some(val) = self.write_buffer {
self.write_delay -= 1;
if self.write_delay == 0 {
self.mode = if val & 0x80 == 0x80 { 1 } else { 0 };
self.step_cycles = Self::step_cycles(self.mode, self.region);
self.step = 0;
self.cycle = 0;
self.write_buffer = None;
if self.mode == 1 && self.block_counter == 0 {
on_clock(FrameType::Half);
self.block_counter = 2;
}
}
}
if self.block_counter > 0 {
self.block_counter -= 1;
}
cycles_ran
}
}
impl Reset for FrameCounter {
fn reset(&mut self, kind: ResetKind) {
self.cycle = 0;
if kind == ResetKind::Hard {
self.mode = 0;
self.step_cycles = Self::step_cycles(self.mode, self.region);
self.write(0x00, 0);
self.write_delay -= 1; }
self.step = 0;
self.block_counter = 0;
self.irq_pending = false;
}
}