tetanes_core/apu/
dmc.rs

1//! APU DMC (Delta Modulation Channel) implementation.
2//!
3//! See: <https://www.nesdev.org/wiki/APU_DMC>
4
5use crate::{
6    apu::timer::{Timer, TimerCycle},
7    common::{Clock, NesRegion, Regional, Reset, ResetKind, Sample},
8    cpu::{Cpu, Irq},
9};
10use serde::{Deserialize, Serialize};
11use tracing::trace;
12
13/// APU DMC (Delta Modulation Channel) provides sample playback.
14///
15/// See: <https://www.nesdev.org/wiki/APU_DMC>
16#[derive(Debug, Clone, Serialize, Deserialize)]
17#[must_use]
18pub struct Dmc {
19    pub region: NesRegion,
20    pub timer: Timer,
21    pub force_silent: bool,
22    pub irq_enabled: bool,
23    pub loops: bool,
24    pub addr: u16,
25    pub sample_addr: u16,
26    pub bytes_remaining: u16,
27    pub sample_length: u16,
28    pub sample_buffer: u8,
29    pub buffer_empty: bool,
30    pub init: u8,
31    pub output_level: u8,
32    pub bits_remaining: u8,
33    pub shift: u8,
34    pub silence: bool,
35    pub should_clock: bool,
36}
37
38impl Default for Dmc {
39    fn default() -> Self {
40        Self::new(NesRegion::Ntsc)
41    }
42}
43
44impl Dmc {
45    const PERIOD_TABLE_NTSC: [u64; 16] = [
46        428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
47    ];
48    const PERIOD_TABLE_PAL: [u64; 16] = [
49        398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
50    ];
51
52    pub const fn new(region: NesRegion) -> Self {
53        Self {
54            region,
55            timer: Timer::preload(Self::period(region, 0)),
56            force_silent: false,
57            irq_enabled: false,
58            loops: false,
59            addr: 0xC000,
60            sample_addr: 0x0000,
61            bytes_remaining: 0x0000,
62            sample_length: 0x0001,
63            sample_buffer: 0x00,
64            buffer_empty: true,
65            init: 0,
66            output_level: 0x00,
67            bits_remaining: 0x08,
68            shift: 0x00,
69            silence: true,
70            should_clock: false,
71        }
72    }
73
74    #[must_use]
75    pub const fn silent(&self) -> bool {
76        self.force_silent
77    }
78
79    pub const fn set_silent(&mut self, silent: bool) {
80        self.force_silent = silent;
81    }
82
83    #[must_use]
84    pub fn irq_pending_in(&self, cycles_to_run: u64) -> bool {
85        if self.irq_enabled && self.bytes_remaining > 0 {
86            let cycles_to_empty = (u64::from(self.bits_remaining)
87                + u64::from(self.bytes_remaining - 1) * 8)
88                * self.timer.period;
89            cycles_to_run >= cycles_to_empty
90        } else {
91            false
92        }
93    }
94
95    #[must_use]
96    pub const fn dma_addr(&self) -> u16 {
97        self.addr
98    }
99
100    fn init_sample(&mut self) {
101        self.addr = self.sample_addr;
102        self.bytes_remaining = self.sample_length;
103        trace!(
104            "APU DMC sample started. bytes remaining: {}",
105            self.bytes_remaining
106        );
107        self.should_clock = self.bytes_remaining > 0;
108    }
109
110    pub fn load_buffer(&mut self, val: u8) {
111        if self.bytes_remaining > 0 {
112            self.sample_buffer = val;
113            self.buffer_empty = false;
114            if self.addr == 0xFFFF {
115                self.addr = 0x8000;
116            } else {
117                self.addr += 1;
118            }
119            self.bytes_remaining -= 1;
120            trace!("APU DMC bytes remaining: {}", self.bytes_remaining);
121            if self.bytes_remaining == 0 {
122                self.should_clock = false;
123                if self.loops {
124                    self.init_sample();
125                } else if self.irq_enabled {
126                    Cpu::set_irq(Irq::DMC);
127                }
128            }
129        }
130    }
131
132    const fn period(region: NesRegion, val: u8) -> u64 {
133        let index = (val & 0x0F) as usize;
134        match region {
135            NesRegion::Auto | NesRegion::Ntsc | NesRegion::Dendy => {
136                Self::PERIOD_TABLE_NTSC[index] - 1
137            }
138            NesRegion::Pal => Self::PERIOD_TABLE_PAL[index] - 1,
139        }
140    }
141
142    /// $4010 DMC timer
143    pub fn write_timer(&mut self, val: u8) {
144        self.irq_enabled = val & 0x80 == 0x80;
145        self.loops = val & 0x40 == 0x40;
146        self.timer.period = Self::period(self.region, val);
147        if !self.irq_enabled {
148            Cpu::clear_irq(Irq::DMC);
149        }
150    }
151
152    /// $4011 DMC output
153    pub const fn write_output(&mut self, val: u8) {
154        self.output_level = val & 0x7F;
155    }
156
157    /// $4012 DMC addr load
158    pub fn write_addr(&mut self, val: u8) {
159        self.sample_addr = 0xC000 | (u16::from(val) << 6);
160    }
161
162    /// $4013 DMC length
163    pub fn write_length(&mut self, val: u8) {
164        self.sample_length = (u16::from(val) << 4) | 1;
165    }
166
167    /// $4015 WRITE
168    pub fn set_enabled(&mut self, enabled: bool, cycle: u64) {
169        if !enabled {
170            self.bytes_remaining = 0;
171            self.should_clock = false;
172        } else if self.bytes_remaining == 0 {
173            self.init_sample();
174            // Delay a number of cycles based on even/odd cycle
175            self.init = if cycle & 0x01 == 0x00 { 2 } else { 3 };
176        }
177    }
178
179    pub fn should_clock(&mut self) -> bool {
180        if self.init > 0 {
181            self.init -= 1;
182            if self.init == 0 && self.buffer_empty && self.bytes_remaining > 0 {
183                trace!("APU DMC DMA pending");
184                Cpu::start_dmc_dma();
185            }
186        }
187        self.should_clock
188    }
189}
190
191impl Sample for Dmc {
192    fn output(&self) -> f32 {
193        if self.silent() {
194            0.0
195        } else {
196            f32::from(self.output_level)
197        }
198    }
199}
200
201impl TimerCycle for Dmc {
202    fn cycle(&self) -> u64 {
203        self.timer.cycle
204    }
205}
206
207impl Clock for Dmc {
208    //                          Timer
209    //                            |
210    //                            v
211    // Reader ---> Buffer ---> Shifter ---> Output level ---> (to the mixer)
212    fn clock(&mut self) -> u64 {
213        if self.timer.clock() > 0 {
214            if !self.silence {
215                // Update output level but clamp to 0..=127 range
216                if self.shift & 0x01 == 0x01 {
217                    if self.output_level <= 125 {
218                        self.output_level += 2;
219                    }
220                } else if self.output_level >= 2 {
221                    self.output_level -= 2;
222                }
223                self.shift >>= 1;
224            }
225
226            if self.bits_remaining > 0 {
227                self.bits_remaining -= 1;
228            }
229            trace!("APU DMC bits remaining: {}", self.bits_remaining);
230
231            if self.bits_remaining == 0 {
232                self.bits_remaining = 8;
233                self.silence = self.buffer_empty;
234                if !self.buffer_empty {
235                    self.shift = self.sample_buffer;
236                    self.buffer_empty = true;
237                    if self.bytes_remaining > 0 {
238                        trace!("APU DMC DMA pending");
239                        Cpu::start_dmc_dma();
240                    }
241                }
242            }
243            1
244        } else {
245            0
246        }
247    }
248}
249
250impl Regional for Dmc {
251    fn region(&self) -> NesRegion {
252        self.region
253    }
254
255    fn set_region(&mut self, region: NesRegion) {
256        self.region = region;
257        self.timer.period = Self::period(region, 0);
258    }
259}
260
261impl Reset for Dmc {
262    fn reset(&mut self, kind: ResetKind) {
263        self.timer.reset(kind);
264        self.timer.period = Self::period(self.region, 0);
265        self.timer.reload();
266        self.timer.cycle += 1; // FIXME: Startup timing is slightly wrong, DMA tests fail with the
267        // default
268        if let ResetKind::Hard = kind {
269            self.sample_addr = 0xC000;
270            self.sample_length = 1;
271        }
272        self.irq_enabled = false;
273        self.loops = false;
274        self.addr = 0x0000;
275        self.bytes_remaining = 0;
276        self.sample_buffer = 0x00;
277        self.buffer_empty = true;
278        self.output_level = 0x00;
279        self.bits_remaining = 0x08;
280        self.shift = 0x00;
281        self.silence = true;
282        self.should_clock = false;
283    }
284}