apu.rsannotatedapu.rssource414 lines · 14.9 KB · raw

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.

15mod dmc;
16mod noise;
17pub mod pulse;
18mod triangle;
19pub mod units;
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.

320    pub fn sample(&self) -> f64 {
321        self.mix_samples()
322    }
323}
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}