1//! The APU's delta modulation channel (DMC) which can play back delta-encoded PCM samples read 2//! from memory, one byte at a time. 3//! 4//! Channel output values are between 0 and 127 (inclusive). 5//! 6//! This channel can optionally generate IRQs when the current sample has been completely read 7//! from memory. 8 9use crate::api::NesEmulatorConfig; 10use crate::bus::CpuBus; 11use bincode::{Decode, Encode}; 12use jgenesis_common::frontend::TimingMode; 13use jgenesis_common::num::GetBit; 14 15// 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]; 20 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}