use crate::{
apu::timer::{Timer, TimerCycle},
common::{Clock, NesRegion, Regional, Reset, ResetKind, Sample},
};
use serde::{Deserialize, Serialize};
use tracing::trace;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[must_use]
pub struct Dmc {
pub region: NesRegion,
pub timer: Timer,
pub force_silent: bool,
pub irq_enabled: bool,
pub irq_pending: bool,
pub dma_pending: bool,
pub loops: bool,
pub addr: u16,
pub sample_addr: u16,
pub bytes_remaining: u16,
pub sample_length: u16,
pub sample_buffer: u8,
pub buffer_empty: bool,
pub init: u8,
pub output_level: u8,
pub bits_remaining: u8,
pub shift: u8,
pub silence: bool,
pub should_clock: bool,
}
impl Default for Dmc {
fn default() -> Self {
Self::new(NesRegion::default())
}
}
impl Dmc {
const PERIOD_TABLE_NTSC: [u16; 16] = [
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
];
const PERIOD_TABLE_PAL: [u16; 16] = [
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
];
pub const fn new(region: NesRegion) -> Self {
Self {
region,
timer: Timer::preload(Self::period(region, 0)),
force_silent: false,
irq_enabled: false,
irq_pending: false,
dma_pending: false,
loops: false,
addr: 0xC000,
sample_addr: 0x0000,
bytes_remaining: 0x0000,
sample_length: 0x0001,
sample_buffer: 0x00,
buffer_empty: true,
init: 0,
output_level: 0x00,
bits_remaining: 0x08,
shift: 0x00,
silence: true,
should_clock: false,
}
}
#[must_use]
pub const fn silent(&self) -> bool {
self.force_silent
}
pub const fn set_silent(&mut self, silent: bool) {
self.force_silent = silent;
}
#[cold]
#[must_use]
pub fn irq_pending_in(&self, cycles_to_run: u32) -> bool {
if self.irq_enabled && self.bytes_remaining > 0 {
let cycles_to_empty = (u16::from(self.bits_remaining) + (self.bytes_remaining - 1) * 8)
* self.timer.period;
cycles_to_run >= u32::from(cycles_to_empty)
} else {
false
}
}
#[must_use]
pub const fn dma_addr(&self) -> u16 {
self.addr
}
fn init_sample(&mut self) {
self.addr = self.sample_addr;
self.bytes_remaining = self.sample_length;
trace!(
"APU DMC sample started. bytes remaining: {}",
self.bytes_remaining
);
self.should_clock = self.bytes_remaining > 0;
}
pub fn load_buffer(&mut self, val: u8) {
if self.bytes_remaining > 0 {
self.sample_buffer = val;
self.buffer_empty = false;
if self.addr == 0xFFFF {
self.addr = 0x8000;
} else {
self.addr += 1;
}
self.bytes_remaining -= 1;
trace!("APU DMC bytes remaining: {}", self.bytes_remaining);
if self.bytes_remaining == 0 {
self.should_clock = false;
if self.loops {
self.init_sample();
} else if self.irq_enabled {
self.irq_pending = true;
}
}
}
}
const fn period(region: NesRegion, val: u8) -> u16 {
let index = (val & 0x0F) as usize;
match region {
NesRegion::Auto | NesRegion::Ntsc | NesRegion::Dendy => {
Self::PERIOD_TABLE_NTSC[index] - 1
}
NesRegion::Pal => Self::PERIOD_TABLE_PAL[index] - 1,
}
}
pub const fn write_timer(&mut self, val: u8) {
self.irq_enabled = val & 0x80 == 0x80;
self.loops = val & 0x40 == 0x40;
self.timer.period = Self::period(self.region, val);
if !self.irq_enabled {
self.irq_pending = false;
}
}
pub const fn write_output(&mut self, val: u8) {
self.output_level = val & 0x7F;
}
pub fn write_addr(&mut self, val: u8) {
self.sample_addr = 0xC000 | (u16::from(val) << 6);
}
pub fn write_length(&mut self, val: u8) {
self.sample_length = (u16::from(val) << 4) | 1;
}
pub fn set_enabled(&mut self, enabled: bool, cycle: u32) {
if !enabled {
self.bytes_remaining = 0;
self.should_clock = false;
} else if self.bytes_remaining == 0 {
self.init_sample();
self.init = if cycle & 0x01 == 0x00 { 2 } else { 3 };
}
}
#[inline(always)]
pub fn should_clock(&mut self) -> bool {
if self.init > 0 {
self.init -= 1;
if self.init == 0 && self.buffer_empty && self.bytes_remaining > 0 {
trace!("APU DMC DMA pending");
self.dma_pending = true;
}
}
self.should_clock
}
}
impl Sample for Dmc {
fn output(&self) -> f32 {
if self.silent() {
0.0
} else {
f32::from(self.output_level)
}
}
}
impl TimerCycle for Dmc {
fn cycle(&self) -> u32 {
self.timer.cycle
}
}
impl Clock for Dmc {
fn clock(&mut self) {
if self.timer.tick() {
if !self.silence {
if self.shift & 0x01 == 0x01 {
if self.output_level <= 125 {
self.output_level += 2;
}
} else if self.output_level >= 2 {
self.output_level -= 2;
}
self.shift >>= 1;
}
if self.bits_remaining > 0 {
self.bits_remaining -= 1;
}
trace!("APU DMC bits remaining: {}", self.bits_remaining);
if self.bits_remaining == 0 {
self.bits_remaining = 8;
self.silence = self.buffer_empty;
if !self.buffer_empty {
self.shift = self.sample_buffer;
self.buffer_empty = true;
if self.bytes_remaining > 0 {
trace!("APU DMC DMA pending");
self.dma_pending = true;
}
}
}
}
}
}
impl Regional for Dmc {
fn region(&self) -> NesRegion {
self.region
}
fn set_region(&mut self, region: NesRegion) {
self.region = region;
self.timer.period = Self::period(region, 0);
}
}
impl Reset for Dmc {
fn reset(&mut self, kind: ResetKind) {
self.timer.reset(kind);
self.timer.period = Self::period(self.region, 0);
self.timer.reload();
self.timer.cycle += 1; if let ResetKind::Hard = kind {
self.sample_addr = 0xC000;
self.sample_length = 1;
}
self.irq_enabled = false;
self.irq_pending = false;
self.dma_pending = false;
self.loops = false;
self.addr = 0x0000;
self.bytes_remaining = 0;
self.sample_buffer = 0x00;
self.buffer_empty = true;
self.output_level = 0x00;
self.bits_remaining = 0x08;
self.shift = 0x00;
self.silence = true;
self.should_clock = false;
}
}