APU (audio processing unit) emulation code.
The APU runs at the same speed as the CPU, although some APU functionality only clocks every other CPU cycle.
The APU generates a 1.789773MHz audio signal by mixing samples from its 5 audio channels: 2 square wave generators, a triangle wave generator, a pseudo-random noise generator, and a DMC (delta modulation channel).
Some APU functionality is clocked by a 240Hz frame counter which divides CPU clocks. Envelopes and the triangle wave generator's linear counter are clocked every quarter-frame, and length counters and the square wave generators' sweep units are clocked every half-frame. The frame counter can also optionally generate an IRQ roughly once per frame.
21use crate::api::NesEmulatorConfig; 22use crate::apu::dmc::DeltaModulationChannel; 23use crate::apu::noise::NoiseChannel; 24use crate::apu::pulse::{PulseChannel, SweepStatus}; 25use crate::apu::triangle::TriangleChannel; 26use crate::bus::{CpuBus, IoRegister, IrqSource}; 27use bincode::{Decode, Encode}; 28use jgenesis_common::frontend::TimingMode; 29use jgenesis_common::num::GetBit; 30use std::array; 31use std::ops::Range; 32use std::sync::LazyLock; 33 34#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 35enum FrameCounterMode { 36 FourStep, 37 FiveStep, 38} 39 40#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 41enum FrameCounterResetState { 42 Joy2Updated, 43 PendingReset, 44 None, 45} 46 47#[derive(Debug, Clone, Encode, Decode)] 48pub struct FrameCounter { 49 steps: [u16; 5], 50 four_step_reset: u16, 51 five_step_reset: u16, 52 interrupt_range: Range<u16>, 53 cpu_ticks: u16, 54 mode: FrameCounterMode, 55 interrupt_inhibit_flag: bool, 56 reset_state: FrameCounterResetState, 57} 58 59impl FrameCounter { 60 const NTSC_STEPS: [u16; 5] = [7456, 14912, 22370, 29828, 37280]; 61 const PAL_STEPS: [u16; 5] = [8312, 16626, 24938, 33252, 41564]; 62 63 pub fn new(timing_mode: TimingMode) -> Self { 64 let steps = match timing_mode { 65 TimingMode::Ntsc => Self::NTSC_STEPS, 66 TimingMode::Pal => Self::PAL_STEPS, 67 }; 68 69 let four_step_reset = steps[3] + 2; 70 let five_step_reset = steps[4] + 2; 71 let interrupt_range = steps[3]..(steps[3] + 2); 72 73 Self { 74 steps: steps.map(|step| step + 1), 75 four_step_reset, 76 five_step_reset, 77 interrupt_range, 78 cpu_ticks: 0, 79 mode: FrameCounterMode::FourStep, 80 interrupt_inhibit_flag: false, 81 reset_state: FrameCounterResetState::None, 82 } 83 } 84 85 fn process_joy2_update(&mut self, joy2_value: u8) { 86 self.mode = 87 if joy2_value.bit(7) { FrameCounterMode::FiveStep } else { FrameCounterMode::FourStep }; 88 self.interrupt_inhibit_flag = joy2_value.bit(6); 89 90 self.reset_state = FrameCounterResetState::Joy2Updated; 91 } 92 93 pub fn tick(&mut self) { 94 match self.reset_state { 95 FrameCounterResetState::Joy2Updated => { 96 if !self.cpu_ticks.bit(0) { 97 self.reset_state = FrameCounterResetState::PendingReset; 98 } 99 } 100 FrameCounterResetState::PendingReset => { 101 self.cpu_ticks = 0; 102 self.reset_state = FrameCounterResetState::None; 103 return; 104 } 105 FrameCounterResetState::None => {} 106 } 107 108 if (self.cpu_ticks == self.four_step_reset && self.mode == FrameCounterMode::FourStep) 109 || self.cpu_ticks == self.five_step_reset 110 { 111 self.cpu_ticks = 0; 112 } 113 114 self.cpu_ticks += 1; 115 } 116 117 fn five_step_reset_clock(&self) -> bool { 118 self.mode == FrameCounterMode::FiveStep 119 && self.reset_state == FrameCounterResetState::PendingReset 120 } 121 122 pub fn generate_quarter_frame_clock(&self) -> bool { 123 (self.cpu_ticks == self.steps[0] 124 || self.cpu_ticks == self.steps[1] 125 || self.cpu_ticks == self.steps[2] 126 || (self.cpu_ticks == self.steps[3] && self.mode == FrameCounterMode::FourStep) 127 || self.cpu_ticks == self.steps[4]) 128 || self.five_step_reset_clock() 129 } 130 131 pub fn generate_half_frame_clock(&self) -> bool { 132 (self.cpu_ticks == self.steps[1] 133 || (self.cpu_ticks == self.steps[3] && self.mode == FrameCounterMode::FourStep) 134 || self.cpu_ticks == self.steps[4]) 135 || self.five_step_reset_clock() 136 } 137 138 fn should_set_interrupt_flag(&self) -> bool { 139 self.mode == FrameCounterMode::FourStep && self.interrupt_range.contains(&self.cpu_ticks) 140 } 141} 142 143#[derive(Debug, Clone, Encode, Decode)] 144pub struct ApuState { 145 pulse_channel_1: PulseChannel, 146 pulse_channel_2: PulseChannel, 147 triangle_channel: TriangleChannel, 148 noise_channel: NoiseChannel, 149 dmc: DeltaModulationChannel, 150 frame_counter: FrameCounter, 151 frame_counter_interrupt_flag: bool, 152 frame_counter_interrupt_clear_pending: bool, 153} 154 155impl ApuState { 156 pub fn new(timing_mode: TimingMode) -> Self { 157 Self { 158 pulse_channel_1: PulseChannel::new_channel_1(SweepStatus::Enabled), 159 pulse_channel_2: PulseChannel::new_channel_2(SweepStatus::Enabled), 160 triangle_channel: TriangleChannel::new(), 161 noise_channel: NoiseChannel::new(), 162 dmc: DeltaModulationChannel::new(timing_mode), 163 frame_counter: FrameCounter::new(timing_mode), 164 frame_counter_interrupt_flag: false, 165 frame_counter_interrupt_clear_pending: false, 166 } 167 } 168 169 pub fn is_active_cycle(&self) -> bool { 170 self.frame_counter.cpu_ticks.bit(0) 171 } 172 173 pub fn needs_dmc_dma(&self) -> bool { 174 self.dmc.needs_dma() 175 } 176 177 pub fn dmc_dma_initial_load(&self) -> bool { 178 self.dmc.dma_initial_load() 179 } 180 181 pub fn dmc_dma_read( 182 &mut self, 183 bus: &mut CpuBus<'_>, 184 halted_cpu_address: u16, 185 config: &NesEmulatorConfig, 186 ) { 187 self.dmc.dma_read(bus, halted_cpu_address, config); 188 } 189 190 fn process_register_update(&mut self, register: IoRegister, value: u8) { 191 match register { 192 IoRegister::SQ1_VOL => { 193 self.pulse_channel_1.process_vol_update(value); 194 } 195 IoRegister::SQ1_SWEEP => { 196 self.pulse_channel_1.process_sweep_update(value); 197 } 198 IoRegister::SQ1_LO => { 199 self.pulse_channel_1.process_lo_update(value); 200 } 201 IoRegister::SQ1_HI => { 202 self.pulse_channel_1.process_hi_update(value); 203 } 204 IoRegister::SQ2_VOL => { 205 self.pulse_channel_2.process_vol_update(value); 206 } 207 IoRegister::SQ2_SWEEP => { 208 self.pulse_channel_2.process_sweep_update(value); 209 } 210 IoRegister::SQ2_LO => { 211 self.pulse_channel_2.process_lo_update(value); 212 } 213 IoRegister::SQ2_HI => { 214 self.pulse_channel_2.process_hi_update(value); 215 } 216 IoRegister::TRI_LINEAR => { 217 self.triangle_channel.process_tri_linear_update(value); 218 } 219 IoRegister::TRI_LO => { 220 self.triangle_channel.process_lo_update(value); 221 } 222 IoRegister::TRI_HI => { 223 self.triangle_channel.process_hi_update(value); 224 } 225 IoRegister::NOISE_VOL => { 226 self.noise_channel.process_vol_update(value); 227 } 228 IoRegister::NOISE_LO => { 229 self.noise_channel.process_lo_update(value); 230 } 231 IoRegister::NOISE_HI => { 232 self.noise_channel.process_hi_update(value); 233 } 234 IoRegister::DMC_FREQ => { 235 self.dmc.process_dmc_freq_update(value); 236 } 237 IoRegister::DMC_RAW => { 238 self.dmc.process_dmc_raw_update(value); 239 } 240 IoRegister::DMC_START => { 241 self.dmc.process_dmc_start_update(value); 242 } 243 IoRegister::DMC_LEN => { 244 self.dmc.process_dmc_len_update(value); 245 } 246 IoRegister::SND_CHN => { 247 self.pulse_channel_1.process_snd_chn_update(value); 248 self.pulse_channel_2.process_snd_chn_update(value); 249 self.triangle_channel.process_snd_chn_update(value); 250 self.noise_channel.process_snd_chn_update(value); 251 self.dmc.process_snd_chn_update(value, self.frame_counter.cpu_ticks); 252 } 253 IoRegister::JOY2 => { 254 self.frame_counter.process_joy2_update(value); 255 } 256 _ => {} 257 } 258 } 259 260 fn tick_cpu(&mut self, bus: &mut CpuBus<'_>, config: &NesEmulatorConfig) { 261 self.pulse_channel_1.tick_cpu(); 262 self.pulse_channel_2.tick_cpu(); 263 self.triangle_channel.tick_cpu(config.silence_ultrasonic_triangle_output); 264 self.noise_channel.tick_cpu(); 265 self.dmc.tick_cpu(); 266 self.frame_counter.tick(); 267 268 if self.frame_counter.generate_quarter_frame_clock() { 269 self.pulse_channel_1.clock_quarter_frame(); 270 self.pulse_channel_2.clock_quarter_frame(); 271 self.triangle_channel.clock_quarter_frame(); 272 self.noise_channel.clock_quarter_frame(); 273 } 274 275 if self.frame_counter.generate_half_frame_clock() { 276 self.pulse_channel_1.clock_half_frame(); 277 self.pulse_channel_2.clock_half_frame(); 278 self.triangle_channel.clock_half_frame(); 279 self.noise_channel.clock_half_frame(); 280 } 281 282 if self.frame_counter.should_set_interrupt_flag() { 283 self.frame_counter_interrupt_flag = true; 284 } else if self.frame_counter.interrupt_inhibit_flag { 285 self.frame_counter_interrupt_flag = false; 286 } 287 288 bus.interrupt_lines().set_irq_low_pull( 289 IrqSource::ApuFrameCounter, 290 self.frame_counter_interrupt_flag && !self.frame_counter.interrupt_inhibit_flag, 291 ); 292 293 bus.interrupt_lines().set_irq_low_pull(IrqSource::ApuDmc, self.dmc.interrupt_flag()); 294 } 295 296 fn get_apu_status(&self) -> u8 { 297 (u8::from(self.dmc.interrupt_flag()) << 7) 298 | (u8::from(self.frame_counter_interrupt_flag) << 6) 299 | (u8::from(self.dmc.sample_bytes_remaining() > 0) << 4) 300 | (u8::from(self.noise_channel.length_counter() > 0) << 3) 301 | (u8::from(self.triangle_channel.length_counter() > 0) << 2) 302 | (u8::from(self.pulse_channel_2.length_counter() > 0) << 1) 303 | u8::from(self.pulse_channel_1.length_counter() > 0) 304 } 305 306 fn mix_samples(&self) -> f64 { 307 let pulse1_sample = self.pulse_channel_1.sample(); 308 let pulse2_sample = self.pulse_channel_2.sample(); 309 let triangle_sample = self.triangle_channel.sample(); 310 let noise_sample = self.noise_channel.sample(); 311 let dmc_sample = self.dmc.sample(); 312 313 let pulse_mix = mix_pulse_samples(pulse1_sample, pulse2_sample); 314 let tnd_mix = mix_tnd_samples(triangle_sample, noise_sample, dmc_sample); 315 316 pulse_mix + tnd_mix 317 }
Retrieve the current audio sample being generated by the APU, in the range 0 to 1.
325pub fn mix_pulse_samples(pulse1_sample: u8, pulse2_sample: u8) -> f64 { 326 static PULSE_AUDIO_LOOKUP_TABLE: LazyLock<[f64; 31]> = LazyLock::new(|| { 327 array::from_fn(|pulse_sum| { 328 if pulse_sum == 0 { 329 return 0.0; 330 } 331 332 // Formula from https://www.nesdev.org/wiki/APU_Mixer 333 let pulse_sum = pulse_sum as f64; 334 95.88 / (8128.0 / pulse_sum + 100.0) 335 }) 336 }); 337 338 PULSE_AUDIO_LOOKUP_TABLE[(pulse1_sample + pulse2_sample) as usize] 339} 340 341fn mix_tnd_samples(triangle_sample: u8, noise_sample: u8, dmc_sample: u8) -> f64 { 342 static TND_AUDIO_LOOKUP_TABLE: LazyLock<Box<[[[f64; 16]; 16]; 128]>> = LazyLock::new(|| { 343 let mut lookup_table = Box::new([[[0.0; 16]; 16]; 128]); 344 345 for (dmc_sample, dmc_row) in lookup_table.iter_mut().enumerate() { 346 for (triangle_sample, triangle_row) in dmc_row.iter_mut().enumerate() { 347 for (noise_sample, value) in triangle_row.iter_mut().enumerate() { 348 if triangle_sample > 0 || noise_sample > 0 || dmc_sample > 0 { 349 // Formula from https://www.nesdev.org/wiki/APU_Mixer 350 *value = 159.79 351 / (1.0 352 / (triangle_sample as f64 / 8227.0 353 + noise_sample as f64 / 12241.0 354 + dmc_sample as f64 / 22638.0) 355 + 100.0); 356 } 357 } 358 } 359 } 360 361 lookup_table 362 }); 363 364 TND_AUDIO_LOOKUP_TABLE[dmc_sample as usize][triangle_sample as usize][noise_sample as usize] 365}
Tick the APU for one CPU cycle.
This function only updates internal state. It does not directly output audio samples anywhere.
To retrieve the current audio sample, call ApuState::sample.
371pub fn tick(state: &mut ApuState, bus: &mut CpuBus<'_>, config: &NesEmulatorConfig) { 372 log::trace!("APU: Frame counter state: {:?}", state.frame_counter); 373 log::trace!("APU: Pulse 1 state: {:?}", state.pulse_channel_1); 374 log::trace!("APU: Pulse 2 state: {:?}", state.pulse_channel_2); 375 log::trace!("APU: DMC state: {:?}", state.dmc); 376 377 if bus.get_io_registers_mut().get_and_clear_snd_chn_read() { 378 state.frame_counter_interrupt_clear_pending = true; 379 } 380 381 // Frame interrupt flag clears only take effect on odd cycles 382 if state.frame_counter_interrupt_clear_pending && state.frame_counter.cpu_ticks.bit(0) { 383 state.frame_counter_interrupt_clear_pending = false; 384 385 // Interrupt flag clear is suppressed if it happens on the same cycle that the flag is set 386 if state.frame_counter.cpu_ticks != state.frame_counter.interrupt_range.end - 1 { 387 state.frame_counter_interrupt_flag = false; 388 } 389 } 390 391 if let Some((dirty_register, value)) = bus.get_io_registers_mut().take_dirty_register() { 392 state.process_register_update(dirty_register, value); 393 } 394 395 state.tick_cpu(bus, config); 396 397 bus.get_io_registers_mut().set_apu_status(state.get_apu_status()); 398 log::trace!("APU: Status set to {:02X}", state.get_apu_status()); 399}
Reset the APU, as if the console's reset button was pressed.
This does not completely re-initialize all state, but it does silence the APU, reset the frame counter, and reset some triangle wave generator and DMC state.
405pub fn reset(state: &mut ApuState) { 406 // Silence the APU by simulating a SND_CHN=$00 write 407 state.process_register_update(IoRegister::SND_CHN, 0x00); 408 409 state.frame_counter.reset_state = FrameCounterResetState::Joy2Updated; 410 state.frame_counter_interrupt_flag = false; 411 412 state.triangle_channel.reset(); 413 state.dmc.reset(); 414}