1use 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#[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 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 pub const fn write_output(&mut self, val: u8) {
154 self.output_level = val & 0x7F;
155 }
156
157 pub fn write_addr(&mut self, val: u8) {
159 self.sample_addr = 0xC000 | (u16::from(val) << 6);
160 }
161
162 pub fn write_length(&mut self, val: u8) {
164 self.sample_length = (u16::from(val) << 4) | 1;
165 }
166
167 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 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 fn clock(&mut self) -> u64 {
213 if self.timer.clock() > 0 {
214 if !self.silence {
215 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; 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}