Skip to main content

rustynes_apu/
noise.rs

1//! Noise channel: 15-bit LFSR + envelope + length counter.
2//!
3//! Per `docs/apu-2a03.md` §Behavior and NESdev wiki "APU Noise" page.
4//!
5//! - Mode 0 (long): feedback = bit 0 XOR bit 1, 15-bit period.
6//! - Mode 1 (short): feedback = bit 0 XOR bit 6, 93-bit period.
7
8use crate::Region;
9use crate::envelope::Envelope;
10use crate::length::LengthCounter;
11
12/// 16-entry NTSC noise period table (NESdev wiki).  Index = bits 0-3 of `$400E`.
13pub const NTSC_NOISE_PERIODS: [u16; 16] = [
14    4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
15];
16
17/// 16-entry PAL noise period table (NESdev wiki).
18pub const PAL_NOISE_PERIODS: [u16; 16] = [
19    4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,
20];
21
22/// Noise channel state.
23#[derive(Debug, Clone, Copy)]
24pub struct Noise {
25    /// LFSR (initialized to 1 on power-up; only bottom 15 bits used).
26    pub(crate) lfsr: u16,
27    /// Mode (false = long / 15-bit, true = short / 6-bit).
28    pub(crate) mode: bool,
29    /// Timer reload (from period table).
30    pub(crate) timer_period: u16,
31    /// Internal countdown timer.
32    pub(crate) timer: u16,
33    /// Envelope generator.
34    pub envelope: Envelope,
35    /// Length counter.
36    pub length: LengthCounter,
37    /// Region (selects period table).
38    pub(crate) region: Region,
39}
40
41impl Noise {
42    /// New noise channel.
43    #[must_use]
44    pub const fn new(region: Region) -> Self {
45        Self {
46            lfsr: 1,
47            mode: false,
48            timer_period: 4,
49            timer: 0,
50            envelope: Envelope {
51                start: false,
52                loop_flag: false,
53                constant: false,
54                volume_or_period: 0,
55                divider: 0,
56                decay: 0,
57            },
58            length: LengthCounter {
59                count: 0,
60                halt: false,
61                new_halt: false,
62                enabled: false,
63                reload_val: 0,
64                previous_count: 0,
65            },
66            region,
67        }
68    }
69
70    /// `$400C` write.
71    pub fn write_ctrl(&mut self, value: u8) {
72        let halt = (value & 0x20) != 0;
73        // Length-halt is deferred (applied after the same-cycle half-frame
74        // clock, per `LengthCounter::reload`); the envelope loop flag is not.
75        self.length.set_halt(halt);
76        self.envelope.loop_flag = halt;
77        self.envelope.constant = (value & 0x10) != 0;
78        self.envelope.volume_or_period = value & 0x0F;
79    }
80
81    /// `$400E` write: mode + period index.
82    pub fn write_period(&mut self, value: u8) {
83        self.mode = (value & 0x80) != 0;
84        let idx = (value & 0x0F) as usize;
85        self.timer_period = match self.region {
86            Region::Pal => PAL_NOISE_PERIODS[idx],
87            _ => NTSC_NOISE_PERIODS[idx],
88        };
89    }
90
91    /// `$400F` write: length load + envelope restart.
92    pub fn write_length(&mut self, value: u8) {
93        self.length.load(value);
94        self.envelope.start = true;
95    }
96
97    /// One APU clock.
98    pub fn clock_timer(&mut self) {
99        if self.timer == 0 {
100            self.timer = self.timer_period;
101            // LFSR step.
102            let bit_a = self.lfsr & 1;
103            let bit_b = if self.mode {
104                (self.lfsr >> 6) & 1
105            } else {
106                (self.lfsr >> 1) & 1
107            };
108            let feedback = bit_a ^ bit_b;
109            self.lfsr = (self.lfsr >> 1) | (feedback << 14);
110        } else {
111            self.timer -= 1;
112        }
113    }
114
115    /// Half-frame clock: length.
116    pub fn clock_half_frame(&mut self) {
117        self.length.clock();
118    }
119
120    /// Quarter-frame clock: envelope.
121    pub fn clock_quarter_frame(&mut self) {
122        self.envelope.clock();
123    }
124
125    /// Per-cycle output (0..=15).
126    #[must_use]
127    pub fn output(&self) -> u8 {
128        if self.length.count == 0 || (self.lfsr & 1) != 0 {
129            0
130        } else {
131            self.envelope.output()
132        }
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn lfsr_long_mode_taps_bit1() {
142        let mut n = Noise::new(Region::Ntsc);
143        n.timer = 0;
144        n.timer_period = 0;
145        n.mode = false;
146        n.lfsr = 1;
147        n.clock_timer();
148        // bit0=1 ^ bit1=0 = 1 -> shift right yields 0 with feedback in bit14.
149        assert_eq!(n.lfsr, 0x4000);
150    }
151
152    #[test]
153    fn lfsr_short_mode_taps_bit6() {
154        let mut n = Noise::new(Region::Ntsc);
155        n.timer = 0;
156        n.timer_period = 0;
157        n.mode = true;
158        n.lfsr = 1;
159        n.clock_timer();
160        // bit0=1 ^ bit6=0 = 1.
161        assert_eq!(n.lfsr, 0x4000);
162    }
163}