32X PWM sound chip
3mod debug;
53.693175 MHz * 3 / 7 / (1047 - 1) ~= 22 KHz
15const TWENTY_TWO_KHZ_CYCLE_REGISTER: u16 = 1047;
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 18pub enum OutputDirection { 19 #[default] 20 Off = 0, 21 Same = 1, 22 Opposite = 2, 23 Prohibited = 3, 24} 25 26impl OutputDirection { 27 fn from_value(value: u16) -> Self { 28 match value & 3 { 29 0 => Self::Off, 30 1 => Self::Same, 31 2 => Self::Opposite, 32 3 => Self::Prohibited, 33 _ => unreachable!("value & 3 is always <= 3"), 34 } 35 } 36 37 fn is_off(self) -> bool { 38 matches!(self, Self::Off | Self::Prohibited) 39 } 40} 41 42impl Display for OutputDirection { 43 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 44 match self { 45 Self::Off => write!(f, "Off"), 46 Self::Same => write!(f, "Same side"), 47 Self::Opposite => write!(f, "Opposite side"), 48 Self::Prohibited => write!(f, "Prohibited"), 49 } 50 } 51} 52 53#[derive(Debug, Clone, Copy, Encode, Decode)] 54pub struct PwmControl { 55 pub timer_interval: u16, 56 pub dreq1_enabled: bool, 57 pub l_out: OutputDirection, 58 pub r_out: OutputDirection, 59} 60 61impl PwmControl { 62 fn new() -> Self { 63 Self { 64 timer_interval: 0, 65 dreq1_enabled: false, 66 l_out: OutputDirection::default(), 67 r_out: OutputDirection::default(), 68 } 69 } 70 71 fn effective_timer_interval(self) -> u16 { 72 if self.timer_interval == 0 { 16 } else { self.timer_interval } 73 } 74 75 // 68000: $A15130 76 // SH-2: $4030 77 fn read(self) -> u16 { 78 (self.timer_interval << 8) 79 | (u16::from(self.dreq1_enabled) << 7) 80 | ((self.r_out as u16) << 2) 81 | (self.l_out as u16) 82 } 83 84 // 68000: $A15130 85 fn m68k_write(&mut self, value: u16) { 86 self.r_out = OutputDirection::from_value(value >> 2); 87 self.l_out = OutputDirection::from_value(value); 88 // M68K cannot change timer interval or RTP / DREQ1 enable 89 90 log::debug!("PWM control write: {value:04X}"); 91 log::debug!(" L channel output direction: {:?}", self.l_out); 92 log::debug!(" R channel output direction: {:?}", self.r_out); 93 } 94 95 // SH-2: $4030 96 fn sh2_write(&mut self, value: u16) { 97 self.timer_interval = (value >> 8) & 0xF; 98 self.dreq1_enabled = value.bit(7); 99 self.r_out = OutputDirection::from_value(value >> 2); 100 self.l_out = OutputDirection::from_value(value); 101 102 log::debug!("PWM control write: {value:04X}"); 103 log::debug!(" Effective timer interval: {}", self.effective_timer_interval()); 104 log::debug!(" DREQ1 enabled: {}", self.dreq1_enabled); 105 log::debug!(" L channel output direction: {:?}", self.l_out); 106 log::debug!(" R channel output direction: {:?}", self.r_out); 107 } 108} 109 110const FIFO_LEN: usize = 3; 111 112#[derive(Debug, Clone, Encode, Decode)] 113pub struct PwmFifo(VecDeque<u16>); 114 115impl PwmFifo { 116 pub fn new() -> Self { 117 Self(VecDeque::with_capacity(FIFO_LEN)) 118 } 119 120 pub fn push(&mut self, sample: u16) { 121 if self.0.len() == FIFO_LEN { 122 self.0.pop_front(); 123 } 124 self.0.push_back(sample); 125 } 126 127 fn pop(&mut self) -> Option<u16> { 128 self.0.pop_front() 129 } 130 131 fn is_empty(&self) -> bool { 132 self.0.is_empty() 133 } 134 135 fn is_full(&self) -> bool { 136 self.0.len() == FIFO_LEN 137 } 138} 139 140#[derive(Debug, Clone, Encode, Decode)] 141pub struct PwmChip { 142 pub control: PwmControl, 143 pub cycle_register: u16, 144 l_fifo: PwmFifo, 145 r_fifo: PwmFifo, 146 l_output: u16, 147 r_output: u16, 148 cycle_counter: u64, 149 off_cycle_counter: u64, 150 timer_counter: u16, 151 dreq1: bool, 152 genesis_mclk_frequency: f64, 153}
Cycle register and pulse width are unsigned 12-bit values
156const U12_MASK: u16 = (1 << 12) - 1;
158impl PwmChip { 159 pub fn new(timing_mode: TimingMode) -> Self { 160 Self { 161 control: PwmControl::new(), 162 cycle_register: 0, 163 l_fifo: PwmFifo::new(), 164 r_fifo: PwmFifo::new(), 165 l_output: 0, 166 r_output: 0, 167 cycle_counter: U12_MASK.into(), 168 off_cycle_counter: U12_MASK.into(), 169 timer_counter: 16, 170 dreq1: false, 171 genesis_mclk_frequency: match timing_mode { 172 TimingMode::Ntsc => genesis_components::NTSC_GENESIS_MCLK_FREQUENCY, 173 TimingMode::Pal => genesis_components::PAL_GENESIS_MCLK_FREQUENCY, 174 }, 175 } 176 } 177 178 pub fn tick( 179 &mut self, 180 mut sh2_cycles: u64, 181 system_registers: &mut SystemRegisters, 182 audio_output: &mut impl Sega32XAudioOutput, 183 ) { 184 if (self.control.l_out.is_off() && self.control.r_out.is_off()) || self.cycle_register == 1 185 { 186 // PWM counters are stopped when both channels are off 187 // Output 0 samples at ~22 KHz 188 audio_output.update_pwm_source_frequency(compute_sample_rate( 189 self.genesis_mclk_frequency, 190 TWENTY_TWO_KHZ_CYCLE_REGISTER, 191 )); 192 193 while sh2_cycles != 0 { 194 let prev_cycle_counter = self.off_cycle_counter; 195 self.off_cycle_counter = self.off_cycle_counter.saturating_sub(sh2_cycles); 196 sh2_cycles -= prev_cycle_counter - self.off_cycle_counter; 197 198 if self.off_cycle_counter == 0 { 199 self.off_cycle_counter = (TWENTY_TWO_KHZ_CYCLE_REGISTER - 1).into(); 200 audio_output.collect_pwm((0.0, 0.0)); 201 } 202 } 203 204 return; 205 } 206 207 audio_output.update_pwm_source_frequency(compute_sample_rate( 208 self.genesis_mclk_frequency, 209 self.cycle_register, 210 )); 211 212 while sh2_cycles != 0 { 213 let prev_cycle_counter = self.cycle_counter; 214 self.cycle_counter = self.cycle_counter.saturating_sub(sh2_cycles); 215 sh2_cycles -= prev_cycle_counter - self.cycle_counter; 216 217 if self.cycle_counter == 0 { 218 // Cycle counter is always set to (register - 1), wrapping from 0 to 4095 219 let cycle_register = self.cycle_register; 220 self.cycle_counter = (cycle_register.wrapping_sub(1) & U12_MASK).into(); 221 222 self.l_output = self.l_fifo.pop().unwrap_or(self.l_output); 223 self.r_output = self.r_fifo.pop().unwrap_or(self.r_output); 224 225 let sample_l = match self.control.l_out { 226 OutputDirection::Same => pulse_width_to_f64(self.l_output, cycle_register), 227 OutputDirection::Opposite => pulse_width_to_f64(self.r_output, cycle_register), 228 _ => 0.0, 229 }; 230 let sample_r = match self.control.r_out { 231 OutputDirection::Same => pulse_width_to_f64(self.r_output, cycle_register), 232 OutputDirection::Opposite => pulse_width_to_f64(self.l_output, cycle_register), 233 _ => 0.0, 234 }; 235 audio_output.collect_pwm((sample_l, sample_r)); 236 237 self.timer_counter -= 1; 238 if self.timer_counter == 0 { 239 self.timer_counter = self.control.effective_timer_interval(); 240 241 log::trace!("Generating PWM interrupt"); 242 system_registers.notify_pwm_timer(); 243 244 self.dreq1 |= self.control.dreq1_enabled; 245 } 246 } 247 } 248 } 249 250 pub fn read_register(&self, address: u32) -> u16 { 251 log::trace!("PWM register read {address:08X}"); 252 253 match address & 0xF { 254 0x0 => self.control.read(), 255 0x2 => self.cycle_register, 256 0x4 => self.read_l_fifo_status(), 257 0x6 => self.read_r_fifo_status(), 258 0x8 => self.read_mono_fifo_status(), 259 _ => { 260 log::warn!("Invalid PWM register read {address:08X}"); 261 0 262 } 263 } 264 } 265 266 fn write_register( 267 &mut self, 268 address: u32, 269 value: u16, 270 control_write_fn: impl FnOnce(&mut PwmControl, u16), 271 ) { 272 match address & 0xF { 273 0x0 => { 274 control_write_fn(&mut self.control, value); 275 self.dreq1 &= self.control.dreq1_enabled; 276 } 277 0x2 => self.write_cycle_register(value), 278 0x4 => self.write_l_fifo(value), 279 0x6 => self.write_r_fifo(value), 280 0x8 => self.write_mono_fifo(value), 281 _ => { 282 // BC Racers frequently writes to $403A for some reason 283 log::debug!("Invalid PWM register write: {address:08X} {value:04X}"); 284 } 285 } 286 } 287 288 pub fn m68k_write_register(&mut self, address: u32, value: u16) { 289 self.write_register(address, value, PwmControl::m68k_write); 290 } 291 292 pub fn sh2_write_register(&mut self, address: u32, value: u16) { 293 self.write_register(address, value, PwmControl::sh2_write); 294 } 295 296 // 68000: $A15132 297 // SH-2: $4032 298 fn write_cycle_register(&mut self, value: u16) { 299 self.cycle_register = value & U12_MASK; 300 301 log::debug!("Cycle register write: {value:04X}"); 302 log::debug!( 303 " Effective sample rate: {} Hz", 304 53_693_175.0 * 3.0 / 7.0 / f64::from(self.cycle_register.wrapping_sub(1) & U12_MASK) 305 ); 306 } 307 308 // 68000: $A15134 309 // SH-2: $4034 310 fn read_l_fifo_status(&self) -> u16 { 311 (u16::from(self.l_fifo.is_full()) << 15) | (u16::from(self.l_fifo.is_empty()) << 14) 312 } 313 314 // 68000: $A15136 315 // SH-2: $4036 316 fn read_r_fifo_status(&self) -> u16 { 317 (u16::from(self.r_fifo.is_full()) << 15) | (u16::from(self.r_fifo.is_empty()) << 14) 318 } 319 320 // 68000: $A15138 321 // SH-2: $4038 322 fn read_mono_fifo_status(&self) -> u16 { 323 // TODO is this right? 324 let full = self.l_fifo.is_full() || self.r_fifo.is_full(); 325 let empty = self.l_fifo.is_empty() && self.r_fifo.is_empty(); 326 (u16::from(full) << 15) | (u16::from(empty) << 14) 327 } 328 329 // 68000: $A15134 330 // SH-2: $4034 331 fn write_l_fifo(&mut self, value: u16) { 332 let sample = value & U12_MASK; 333 self.l_fifo.push(sample); 334 335 log::trace!("L pulse width FIFO write: {value:04X}"); 336 log::trace!(" Effective wave height: {sample}"); 337 } 338 339 // 68000: $A15136 340 // SH-2: $4036 341 fn write_r_fifo(&mut self, value: u16) { 342 let sample = value & U12_MASK; 343 self.r_fifo.push(sample); 344 345 log::trace!("R pulse width FIFO write: {value:04X}"); 346 log::trace!(" Effective wave height: {sample}"); 347 } 348 349 // 68000: $A15138 350 // SH-2: $4038 351 fn write_mono_fifo(&mut self, value: u16) { 352 let sample = value & U12_MASK; 353 self.l_fifo.push(sample); 354 self.r_fifo.push(sample); 355 356 log::trace!("Mono pulse width FIFO write: {value:04X}"); 357 log::trace!(" Effective wave height: {sample}"); 358 } 359 360 pub fn dma_request_1(&self) -> bool { 361 self.dreq1 362 } 363 364 pub fn acknowledge_dreq_1(&mut self) { 365 self.dreq1 = false; 366 } 367} 368 369fn compute_sample_rate(genesis_mclk_frequency: f64, cycle_register: u16) -> f64 { 370 genesis_mclk_frequency * 3.0 / 7.0 / f64::from(cycle_register.wrapping_sub(1) & U12_MASK) 371} 372 373fn pulse_width_to_f64(sample: u16, cycle_register: u16) -> f64 { 374 if cycle_register == 1 { 375 return 0.0; 376 } 377 378 // Treat the pulse width as a sample on a scale from 0 to (cycle_register - 1) and map that to [0, 1] 379 let max_width = cycle_register.wrapping_sub(1) & U12_MASK; 380 let clamped_width = cmp::min(sample, max_width); 381 382 // TODO this is wrong - should treat PWM output as unsigned and maybe high-pass filter to shift the center to 0 383 let divisor = 0.5 * f64::from(max_width); 384 (f64::from(clamped_width) - divisor) / divisor 385}