dmc.rsannotateddmc.rssource237 lines · 7.1 KB · raw

The APU's delta modulation channel (DMC) which can play back delta-encoded PCM samples read from memory, one byte at a time.

Channel output values are between 0 and 127 (inclusive).

This channel can optionally generate IRQs when the current sample has been completely read from memory.

9use crate::api::NesEmulatorConfig;
10use crate::bus::CpuBus;
11use bincode::{Decode, Encode};
12use jgenesis_common::frontend::TimingMode;
13use jgenesis_common::num::GetBit;

From https://www.nesdev.org/wiki/APU_DMC

16const NTSC_PERIOD_LOOKUP_TABLE: [u16; 16] =
17    [428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54];
18const PAL_PERIOD_LOOKUP_TABLE: [u16; 16] =
19    [398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50];
21#[derive(Debug, Clone, Encode, Decode)]
22struct DmcOutputUnit {
23    output_level: u8,
24    shift_register: u8,
25    bits_remaining: u8,
26    silence_flag: bool,
27}
28
29impl DmcOutputUnit {
30    fn new() -> Self {
31        Self { output_level: 0, shift_register: 0, bits_remaining: 8, silence_flag: true }
32    }
33
34    fn clock(&mut self, sample_buffer: &mut Option<u8>) {
35        if !self.silence_flag {
36            let new_output_level = if self.shift_register.bit(0) {
37                self.output_level + 2
38            } else {
39                self.output_level.wrapping_sub(2)
40            };
41            if new_output_level < 128 {
42                self.output_level = new_output_level;
43            }
44        }
45
46        self.shift_register >>= 1;
47        self.bits_remaining -= 1;
48
49        if self.bits_remaining == 0 {
50            self.bits_remaining = 8;
51            match sample_buffer.take() {
52                Some(sample_bits) => {
53                    self.shift_register = sample_bits;
54                    self.silence_flag = false;
55                }
56                None => {
57                    self.silence_flag = true;
58                }
59            }
60        }
61    }
62
63    fn sample(&self) -> u8 {
64        self.output_level
65    }
66}
67
68#[derive(Debug, Clone, Encode, Decode)]
69pub struct DeltaModulationChannel {
70    enabled: bool,
71    timer_counter: u16,
72    timer_period: u16,
73    sample_buffer: Option<u8>,
74    output_unit: DmcOutputUnit,
75    sample_address: u16,
76    current_sample_address: u16,
77    sample_length: u16,
78    sample_bytes_remaining: u16,
79    loop_flag: bool,
80    irq_enabled: bool,
81    interrupt_flag: bool,
82    dma_initial_load: bool,
83    dma_start_latency: u8,
84    period_lookup_table: [u16; 16],
85}
86
87impl DeltaModulationChannel {
88    pub fn new(timing_mode: TimingMode) -> Self {
89        let period_lookup_table = match timing_mode {
90            TimingMode::Ntsc => NTSC_PERIOD_LOOKUP_TABLE,
91            TimingMode::Pal => PAL_PERIOD_LOOKUP_TABLE,
92        };
93
94        Self {
95            enabled: false,
96            timer_counter: period_lookup_table[0] - 1,
97            timer_period: period_lookup_table[0],
98            sample_buffer: None,
99            output_unit: DmcOutputUnit::new(),
100            sample_address: 0xC000,
101            current_sample_address: 0xC000,
102            sample_length: 1,
103            sample_bytes_remaining: 0,
104            loop_flag: false,
105            irq_enabled: false,
106            interrupt_flag: false,
107            dma_initial_load: false,
108            dma_start_latency: 0,
109            period_lookup_table,
110        }
111    }
112
113    pub fn process_dmc_freq_update(&mut self, dmc_freq_value: u8) {
114        self.irq_enabled = dmc_freq_value.bit(7);
115        self.loop_flag = dmc_freq_value.bit(6);
116        self.timer_period = self.period_lookup_table[(dmc_freq_value & 0x0F) as usize];
117
118        if !self.irq_enabled {
119            self.interrupt_flag = false;
120        }
121    }
122
123    pub fn process_dmc_raw_update(&mut self, dmc_raw_value: u8) {
124        self.output_unit.output_level = dmc_raw_value & 0x7F;
125    }
126
127    pub fn process_dmc_start_update(&mut self, dmc_start_value: u8) {
128        self.sample_address = 0xC000 | (u16::from(dmc_start_value) << 6);
129    }
130
131    pub fn process_dmc_len_update(&mut self, dmc_len_value: u8) {
132        self.sample_length = (u16::from(dmc_len_value) << 4) + 1;
133    }
134
135    pub fn process_snd_chn_update(&mut self, snd_chn_value: u8, frame_counter_ticks: u16) {
136        log::trace!("SND_CHN write: {snd_chn_value:02X} (enabled = {})", snd_chn_value.bit(4));
137
138        self.interrupt_flag = false;
139
140        self.enabled = snd_chn_value.bit(4);
141        if self.enabled && self.sample_bytes_remaining == 0 {
142            self.restart();
143            self.dma_initial_load = true;
144            // Load DMAs begin in 3 cycles (if SND_CHN written on put) or 4 cycles (if written on get)
145            self.dma_start_latency = 2 + (frame_counter_ticks & 1) as u8;
146        } else if !self.enabled {
147            self.sample_bytes_remaining = 0;
148            self.sample_buffer = None;
149        }
150    }
151
152    fn restart(&mut self) {
153        self.current_sample_address = self.sample_address;
154        self.sample_bytes_remaining = self.sample_length;
155    }
156
157    pub fn needs_dma(&self) -> bool {
158        self.enabled
159            && self.sample_bytes_remaining != 0
160            && self.sample_buffer.is_none()
161            && self.dma_start_latency == 0
162    }
163
164    pub fn dma_initial_load(&self) -> bool {
165        self.dma_initial_load
166    }
167
168    pub fn dma_read(
169        &mut self,
170        bus: &mut CpuBus<'_>,
171        halted_cpu_address: u16,
172        config: &NesEmulatorConfig,
173    ) {
174        log::trace!(
175            "DMA read from address {:04X}, remaining {}",
176            self.current_sample_address,
177            self.sample_bytes_remaining
178        );
179
180        self.sample_buffer =
181            Some(bus.dmc_dma_read(self.current_sample_address, halted_cpu_address, config));
182
183        // Sample address overflows to $8000 rather than $C000
184        self.current_sample_address = 0x8000 | self.current_sample_address.wrapping_add(1);
185        self.sample_bytes_remaining -= 1;
186
187        if self.sample_bytes_remaining == 0 {
188            if self.loop_flag {
189                self.restart();
190            } else if self.irq_enabled {
191                self.interrupt_flag = true;
192            }
193        }
194    }
195
196    pub fn tick_cpu(&mut self) {
197        if self.timer_counter == 0 {
198            self.clock();
199            self.timer_counter = self.timer_period - 1;
200        } else {
201            self.timer_counter -= 1;
202        }
203
204        self.dma_start_latency = self.dma_start_latency.saturating_sub(1);
205    }
206
207    fn clock(&mut self) {
208        let sample_buffer_was_full = self.sample_buffer.is_some();
209        self.output_unit.clock(&mut self.sample_buffer);
210
211        if self.enabled
212            && sample_buffer_was_full
213            && self.sample_buffer.is_none()
214            && self.sample_bytes_remaining != 0
215        {
216            self.dma_initial_load = false;
217            // Reload DMAs begin in 2 cycles (if emptied on get) or 3 cycles (if emptied on put)
218            self.dma_start_latency = 2;
219        }
220    }
221
222    pub fn sample(&self) -> u8 {
223        self.output_unit.sample()
224    }
225
226    pub fn sample_bytes_remaining(&self) -> u16 {
227        self.sample_bytes_remaining
228    }
229
230    pub fn interrupt_flag(&self) -> bool {
231        self.interrupt_flag
232    }
233
234    pub fn reset(&mut self) {
235        self.output_unit.output_level &= 0x01;
236    }
237}