Skip to main content

nescore/apu/
apu.rs

1//
2// apu.rs
3//
4// @author Natesh Narain <nnaraindev@gmail.com>
5// @date Mar 31 2020
6//
7use super::seq::{FrameSequencer, Event};
8use super::chnl::{SoundChannel, Pulse, Triangle, Noise, Dmc, LengthCounterUnit, EnvelopeUnit, NegateAddMode};
9
10use crate::common::{IoAccess, IoAccessRef, Clockable, Register, Interrupt};
11
12pub type Sample = f32;
13pub const APU_OUTPUT_RATE: f32 = 895_000.0;
14
15#[cfg(feature="events")]
16use std::sync::mpsc::Sender;
17
18#[cfg(feature="events")]
19pub mod events {
20    #[derive(Debug)]
21    pub struct ApuEvent {
22        pub pulse1: f32,
23        pub pulse2: f32,
24        pub triangle: f32,
25        pub noise: f32,
26        pub dmc: f32,
27        pub mixer: f32,
28    }
29}
30
31/// NES APU
32pub struct Apu {
33    pulse1: Pulse,
34    pulse2: Pulse,
35    triangle: Triangle,
36    noise: Noise,
37    dmc: Dmc,
38
39    sequencer: FrameSequencer,
40
41    pulse_table: [f32; 31],
42    tnd_table: [f32; 203],
43
44    bus: Option<IoAccessRef>,
45
46    // Event logging
47    #[cfg(feature="events")]
48    logger: Option<Sender<events::ApuEvent>>,
49}
50
51impl Default for Apu {
52    fn default() -> Self {
53        // TODO: Would be nice if this could be done inline with (0..31).map(|n| ...)
54        // Or use const fn?
55
56        // Pulse look up table
57        let mut pulse_table = [0f32; 31];
58        for (n, e) in pulse_table.iter_mut().enumerate() {
59            *e = 95.52 / (8128.0 / (n as f32) + 100f32);
60        }
61
62        // tnd look up table
63        let mut tnd_table = [0f32; 203];
64        for (n, e) in tnd_table.iter_mut().enumerate() {
65            *e = 163.67 / (24329.0 / (n as f32) + 100f32);
66        }
67
68        Apu {
69            pulse1: Pulse::default(),
70            pulse2: Pulse::default().add_mode(NegateAddMode::TwosComplement),
71            triangle: Triangle::default(),
72            noise: Noise::default(),
73            dmc: Dmc::default(),
74
75            sequencer: FrameSequencer::default(),
76
77            // Mixer lookup tables
78            pulse_table,
79            tnd_table,
80
81            bus: None,
82
83            #[cfg(feature="events")]
84            logger: None,
85        }
86    }
87}
88
89impl Clockable<Sample> for Apu {
90    fn tick(&mut self) -> Sample {
91        // Clock the frame sequencer to generate low frequency clock events and process them
92        for event in self.sequencer.tick().iter() {
93            match event {
94                Event::EnvelopAndLinear => {
95                    self.clock_envelope();
96                    self.triangle.clock_linear();
97                },
98                Event::LengthAndSweep => {
99                    self.clock_length();
100                    self.clock_sweep();
101                },
102                Event::Irq => {
103                    if let Some(ref mut bus) = self.bus {
104                        bus.borrow_mut().raise_interrupt(Interrupt::Irq);
105                    }
106                },
107                Event::None => {}
108            }
109        }
110
111        // Clock the pulse channels every APU cycle
112        self.pulse1.tick();
113        self.pulse2.tick();
114
115        // The triangle channel is clocked at twice the rate of the APU
116        self.triangle.tick();
117        self.triangle.tick();
118
119        // Clock noise channel
120        self.noise.tick();
121
122        // Clock DMC
123        self.dmc.tick();
124
125        // Mix channel outputs into final sample
126        self.mix()
127    }
128}
129
130impl IoAccess for Apu {
131    fn read_byte(&self, addr: u16) -> u8 {
132        match addr {
133            0x4000..=0x4003 => self.pulse1.read_byte(addr - 0x4000),
134            0x4004..=0x4007 => self.pulse2.read_byte(addr - 0x4004),
135            0x4008..=0x400B => self.triangle.read_byte(addr - 0x4008),
136            0x400C..=0x400F => self.noise.read_byte(addr - 0x400C),
137            0x4010..=0x4013 => self.dmc.read_byte(addr - 0x4010),
138            0x4015          => self.status(),
139            0x4017          => self.sequencer.value(),
140            _ => panic!("Invalid address for APU: ${:04X}", addr),
141        }
142    }
143
144    fn write_byte(&mut self, addr: u16, data: u8) {
145        match addr {
146            0x4000..=0x4003 => self.pulse1.write_byte(addr - 0x4000, data),
147            0x4004..=0x4007 => self.pulse2.write_byte(addr - 0x4004, data),
148            0x4008..=0x400B => self.triangle.write_byte(addr - 0x4008, data),
149            0x400C..=0x400F => self.noise.write_byte(addr - 0x400C, data),
150            0x4010..=0x4013 => self.dmc.write_byte(addr - 0x4010, data),
151            0x4015 => {
152                self.pulse1.enable_length(bit_is_set!(data, 0));
153                self.pulse2.enable_length(bit_is_set!(data, 1));
154                self.triangle.enable_length(bit_is_set!(data, 2));
155                self.noise.enable_length(bit_is_set!(data, 3));
156                self.dmc.set_enable(bit_is_set!(data, 4));
157            },
158            0x4017 => {
159                self.sequencer.load(data);
160
161                if bit_is_set!(data, 7) {
162                    // Immediately clock length units
163                    self.pulse1.clock_length();
164                    self.pulse2.clock_length();
165                    self.triangle.clock_length();
166                    self.noise.clock_length();
167                }
168            },
169            _ => panic!("Invalid address for APU: ${:04X}", addr),
170        }
171    }
172}
173
174impl Apu {
175    fn mix(&self) -> Sample {
176        let pulse1 = self.pulse1.output() as f32;
177        let pulse2 = self.pulse2.output() as f32;
178        let triangle = self.triangle.output() as f32;
179        let noise = self.noise.output() as f32;
180        let dmc = self.dmc.output() as f32;
181
182        let pulse_out = self.pulse_table[(pulse1 + pulse2) as usize];
183
184        let tnd_out = self.tnd_table[(3.0 * triangle + 2.0 * noise + dmc) as usize];
185
186        let mixed = pulse_out + tnd_out;
187
188        #[cfg(feature="events")]
189        {
190            let data = events::ApuEvent {
191                pulse1,
192                pulse2,
193                triangle,
194                noise,
195                dmc,
196                mixer: mixed,
197            };
198
199            if let Some(ref logger) = self.logger {
200                if logger.send(data).is_ok() {}
201            }
202        }
203
204        mixed
205    }
206
207    fn status(&self) -> u8 {
208        (self.pulse1.length_status() as u8)
209        | (self.pulse2.length_status() as u8) << 1
210        | (self.triangle.length_status() as u8) << 2
211        | (self.noise.length_status() as u8) << 3
212        | (self.dmc.status() as u8) << 4
213        | (self.sequencer.irq_status() as u8) << 6
214    }
215
216    fn clock_length(&mut self) {
217        self.pulse1.clock_length();
218        self.pulse2.clock_length();
219        self.triangle.clock_length();
220        self.noise.clock_length();
221    }
222
223    fn clock_envelope(&mut self) {
224        self.pulse1.clock_envelope();
225        self.pulse2.clock_envelope();
226        self.noise.clock_envelope();
227    }
228
229    fn clock_sweep(&mut self) {
230        self.pulse1.clock_sweep();
231        self.pulse2.clock_sweep();
232    }
233
234    pub fn load_bus(&mut self, bus: IoAccessRef) {
235        self.dmc.load_bus(bus.clone());
236        self.bus = Some(bus);
237    }
238
239    #[cfg(feature="events")]
240    pub fn set_event_sender(&mut self, sender: Sender<events::ApuEvent>) {
241        self.logger = Some(sender);
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    use std::rc::Rc;
250    use std::cell::RefCell;
251
252    #[test]
253    fn pulse_lenctr() {
254        let mut apu = init_apu();
255
256        // Mode Step4
257        apu.write_byte(0x4017, 0x00);
258
259        // Enable length counters
260        apu.write_byte(0x4015, 0x03);
261        // Set length counter to ten ticks
262        apu.write_byte(0x4003, 0x00);
263        apu.write_byte(0x4007, 0x00);
264
265        // Check the status and ensure the length counters report active
266        let status = apu.read_byte(0x4015);
267        assert!(bit_is_set!(status, 0));
268        assert!(bit_is_set!(status, 1));
269
270        // The length counter clock twice per frame
271        // Run for 4 frames
272        for _ in 0..4 {
273            run_for_step4_frame(&mut apu);
274        }
275
276        // Ensure the length counters are still active
277        let status = apu.read_byte(0x4015);
278        assert!(bit_is_set!(status, 0));
279        assert!(bit_is_set!(status, 1));
280
281        // Run for another frame
282        // The length counter should be reported as inactive
283        run_for_step4_frame(&mut apu);
284
285        let status = apu.read_byte(0x4015);
286        assert!(bit_is_clear!(status, 0));
287        assert!(bit_is_clear!(status, 1));
288    }
289
290    fn run_for_step4_frame(apu: &mut dyn Clockable<Sample>) {
291        for _ in 0..14915 {
292            apu.tick();
293        }
294    }
295
296    struct FakeBus {
297        vram: [u8; 0x4000],
298    }
299
300    impl Default for FakeBus {
301        fn default() -> Self {
302            FakeBus {
303                vram: [0; 0x4000],
304            }
305        }
306    }
307
308    impl IoAccess for FakeBus {
309        fn read_byte(&self, addr: u16) -> u8 {
310            self.vram[addr as usize]
311        }
312        fn write_byte(&mut self, addr: u16, value: u8) {
313            self.vram[addr as usize] = value;
314        }
315    }
316
317    fn init_apu() -> Apu {
318        let mut apu: Apu = Apu::default();
319        apu.load_bus(Rc::new(RefCell::new(FakeBus::default())));
320
321        apu
322    }
323}