From 444636f621da83ffc6329cb0aa04b5f2c98754e5 Mon Sep 17 00:00:00 2001 From: Theron Date: Thu, 19 Dec 2019 18:13:48 -0600 Subject: [PATCH] apu --- src/apu/mod.rs | 52 +++++++++++++++++++++++----------------------- src/audio.rs | 11 +++++----- src/cpu/mod.rs | 33 ++++++++++++++--------------- src/cpu/opcodes.rs | 14 +++++++++++-- src/main.rs | 29 +++++++++++++++++++++----- src/ppu/mod.rs | 2 +- 6 files changed, 85 insertions(+), 56 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 70ddbba..4d4640b 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -27,9 +27,6 @@ use dmc::DMC; // We need to take a sample 44100 times per second. The CPU clocks (not steps) at 1.789773 MHz. Meaning the APU, going half as fast, // clocks 894,886.5 times per second. 894,886.5/44,100=20.29 APU clocks per audio sample. -// TODO: APU runs every other CPU clock, not step. Need to tear APU out of CPU (or at least step it from outside CPU's step function) -// and connect it to audio module. - pub struct Apu { square1: Square, square2: Square, @@ -43,10 +40,11 @@ pub struct Apu { frame_counter: u8, current_frame: u8, mode: u8, - interrupt_inhibit: u8, + interrupt_inhibit: bool, frame_interrupt: bool, cycle: usize, - remainder: f64, // keep sample at 44100Hz + remainder: f32, // keep sample at 44100Hz + pub trigger_irq: bool, } struct Envelope { @@ -56,6 +54,7 @@ struct Envelope { } const FRAME_COUNTER_STEPS: [usize; 5] = [3728, 7456, 11185, 14914, 18640]; +const CYCLES_PER_SAMPLE: f32 = 894_886.5/44_100.0; // APU frequency over sample frequency impl Apu { pub fn new() -> Self { @@ -74,31 +73,35 @@ impl Apu { frame_counter: 0, current_frame: 0, mode: 0, - interrupt_inhibit: 0, + interrupt_inhibit: false, frame_interrupt: false, cycle: 0, - remainder: 0, + remainder: 0_f32, + trigger_irq: false, } } - pub fn step(&mut self) { + pub fn step(&mut self) -> Option { + let mut sample = None; + if (self.frame_counter == 4 && FRAME_COUNTER_STEPS[..4].contains(&self.cycle)) || (self.frame_counter == 5 && FRAME_COUNTER_STEPS.contains(&self.cycle)) { self.clock_frame_counter(); } - - // push sample to buffer - if self.remainder > 894_886.5/44_100 { // APU frequency over sample frequency - self.sample_audio(); - self.remainder -= 894_886.5/44_100; + if self.remainder > CYCLES_PER_SAMPLE { + // send sample to buffer + sample = Some(self.mix()); + self.remainder -= CYCLES_PER_SAMPLE; } - self.remainder += 1; + self.remainder += 1.0; self.cycle += 1; if (self.frame_counter == 4 && self.cycle == 14915) || (self.frame_counter == 5 && self.cycle == 18641) { self.cycle = 0; } + + sample } pub fn write_reg(&mut self, address: usize, value: u8) { @@ -137,11 +140,6 @@ impl Apu { square_out + tnd_out } - // mode 0: mode 1: function - // --------- ----------- ----------------------------- - // - - - f - - - - - IRQ (if bit 6 is clear) - // - l - l - l - - l Length counter and sweep - // e e e e e e e - e Envelope and linear counter fn set_frame_counter(&mut self, value: u8) { // 0 selects 4-step sequence, 1 selects 5-step sequence if value & (1<<7) == 0 { @@ -153,13 +151,18 @@ impl Apu { } // If set, the frame interrupt flag is cleared, otherwise it is unaffected. if value & (1<<6) != 0 { - self.interrupt_inhibit = 0; + self.interrupt_inhibit = false; } } + // mode 0: mode 1: function + // --------- ----------- ----------------------------- + // - - - f - - - - - IRQ (if bit 6 is clear) + // - l - l - l - - l Length counter and sweep + // e e e e e e e - e Envelope and linear counter fn clock_frame_counter(&mut self) { - if !(self.frame_counter == 5 && self.current_frame == 4) { + if !(self.frame_counter == 5 && self.current_frame == 3) { // step envelopes self.square1.clock_envelope(); self.square2.clock_envelope(); @@ -175,8 +178,8 @@ impl Apu { self.triangle.clock_length_counter(); self.noise.clock_length_counter(); } - if self.frame_counter == 4 && self.current_frame == 3 { - self.issue_irq(); + if self.frame_counter == 4 && self.current_frame == 3 && !self.interrupt_inhibit { + self.trigger_irq = true; } // advance counter self.current_frame += 1; @@ -267,7 +270,4 @@ impl Apu { val } - fn issue_irq(&mut self) { - - } } diff --git a/src/audio.rs b/src/audio.rs index 92c3d7f..9c7d470 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -3,7 +3,7 @@ extern crate sdl2; use sdl2::audio::{AudioCallback, AudioSpecDesired}; pub struct Speaker { - buffer: [f32; 4096], + buffer: Vec, head: usize, } @@ -11,18 +11,19 @@ impl AudioCallback for Speaker { type Channel = f32; fn callback(&mut self, out: &mut [f32]) { for (i, x) in out.iter_mut().enumerate() { - *x = self.buffer[i+self.head..i+self.head+4096]; // get data from apu + *x = self.buffer[i+self.head]; // get data from apu } + self.buffer = self.buffer[4096..].to_vec(); } } -pub fn initialize(context: &sdl2::Sdl) -> Result, String> { +pub fn initialize(context: &sdl2::Sdl) -> Result, String> { let audio_subsystem = context.audio()?; let desired_spec = AudioSpecDesired { freq: Some(44_100), channels: Some(1), // mono - samples: 4096, // default sample size + samples: Some(4096), // default sample size }; audio_subsystem.open_playback(None, &desired_spec, |spec| { @@ -30,6 +31,6 @@ pub fn initialize(context: &sdl2::Sdl) -> Result u64 { - // for apu - self.even = if self.even {false} else {true}; // skip cycles from OAM DMA if necessary if self.delay > 0 { @@ -157,10 +153,15 @@ impl Cpu { return 1; } + // handle interrupts if self.ppu.trigger_nmi { self.nmi(); } self.ppu.trigger_nmi = false; + if self.apu.trigger_irq && (self.P & INTERRUPT_DISABLE_FLAG == 0) { + self.irq(); + } + self.apu.trigger_irq = false; // back up clock so we know how many cycles we complete let clock = self.clock; @@ -186,8 +187,6 @@ impl Cpu { self.advance_pc(mode); // look up instruction in table and execute self.opcode_table[opcode](self, address, mode); - // maintain 1 apu cycle per 2 cpu cycles - self.even = if self.even { self.apu.step(); false } else { true }; // return how many cycles it took self.clock - clock } @@ -195,14 +194,14 @@ impl Cpu { // memory interface fn read(&mut self, address: usize) -> u8 { let val = match address { - 0x0000...0x1FFF => self.mem[address % 0x0800], - 0x2000...0x3FFF => self.read_ppu_reg(address % 8), + 0x0000..=0x1FFF => self.mem[address % 0x0800], + 0x2000..=0x3FFF => self.read_ppu_reg(address % 8), 0x4014 => self.read_ppu_reg(8), 0x4015 => self.apu.read_status(), 0x4016 => self.read_controller(), - 0x4000...0x4017 => 0, // can't read from these APU registers - 0x4018...0x401F => 0, // APU and I/O functionality that is normally disabled. See CPU Test Mode. - 0x4020...0xFFFF => { // Cartridge space: PRG ROM, PRG RAM, and mapper registers + 0x4000..=0x4017 => 0, // can't read from these APU registers + 0x4018..=0x401F => 0, // APU and I/O functionality that is normally disabled. See CPU Test Mode. + 0x4020..=0xFFFF => { // Cartridge space: PRG ROM, PRG RAM, and mapper registers *(self.mapper_func)(self, address, false).unwrap() // unwrapping because mapper funcs won't return None for reads. }, _ => panic!("invalid read from 0x{:02x}", address), @@ -213,13 +212,13 @@ impl Cpu { // memory interface fn write(&mut self, address: usize, val: u8) { match address { - 0x0000...0x1FFF => self.mem[address % 0x0800] = val, - 0x2000...0x3FFF => self.write_ppu_reg(address % 8, val), + 0x0000..=0x1FFF => self.mem[address % 0x0800] = val, + 0x2000..=0x3FFF => self.write_ppu_reg(address % 8, val), 0x4014 => self.write_ppu_reg(8, val), 0x4016 => self.write_controller(val), - 0x4000...0x4017 => self.apu.write_reg(address, val), // APU stuff - 0x4018...0x401F => (), // APU and I/O functionality that is normally disabled. See CPU Test Mode. - 0x4020...0xFFFF => { // Cartridge space: PRG ROM, PRG RAM, and mapper registers + 0x4000..=0x4017 => self.apu.write_reg(address, val), // APU stuff + 0x4018..=0x401F => (), // APU and I/O functionality that is normally disabled. See CPU Test Mode. + 0x4020..=0xFFFF => { // Cartridge space: PRG ROM, PRG RAM, and mapper registers match (self.mapper_func)(self, address, true) { Some(loc) => *loc = val, None => (), diff --git a/src/cpu/opcodes.rs b/src/cpu/opcodes.rs index 24b17f2..076dbd3 100644 --- a/src/cpu/opcodes.rs +++ b/src/cpu/opcodes.rs @@ -123,7 +123,7 @@ impl super::Cpu { self.push((self.PC & 0xFF) as u8); // push low byte self.push(self.P | 0b00110000); // push status register with break bits set self.P |= INTERRUPT_DISABLE_FLAG; // set interrupt disable flag - self.PC = ((self.read(IRQ_VECTOR + 1) as usize) << 8) // set program counter to IRQ/BRK vector, taking high _byte + self.PC = ((self.read(IRQ_VECTOR + 1) as usize) << 8) // set program counter to IRQ/BRK vector, taking high byte + (self.read(IRQ_VECTOR) as usize); // and low byte self.clock += 5; // total of 7 cycles, 2 come from implied() } @@ -527,9 +527,19 @@ impl super::Cpu { self.push((self.PC & 0xFF) as u8); // push low byte self.push(self.P | 0b00110000); // push status register with break bits set self.P |= INTERRUPT_DISABLE_FLAG; // set interrupt disable flag - self.PC = ((self.read(NMI_VECTOR + 1) as usize) << 8) // set program counter to IRQ/BRK vector, taking high _byte + self.PC = ((self.read(NMI_VECTOR + 1) as usize) << 8) // set program counter to NMI vector, taking high byte + (self.read(NMI_VECTOR) as usize); // and low byte self.clock += 7; } + pub fn irq(&mut self) { + self.push((self.PC >> 8) as u8); // push high byte + self.push((self.PC & 0xFF) as u8); // push low byte + self.push(self.P & 0b11001111); // push status register with break bits cleared + self.P |= INTERRUPT_DISABLE_FLAG; // set interrupt disable flag + self.PC = ((self.read(IRQ_VECTOR + 1) as usize) << 8) // set program counter to IRQ/BRK vector, taking high byte + + (self.read(IRQ_VECTOR) as usize); // and low byte + self.clock += 7; + } + } diff --git a/src/main.rs b/src/main.rs index 15637ff..8f5d8ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod apu; mod cartridge; mod input; mod screen; +mod audio; use cpu::Cpu; use ppu::Ppu; @@ -13,6 +14,7 @@ use apu::Apu; use cartridge::Cartridge; use input::poll_buttons; use screen::{init_window, draw_pixel, draw_to_window}; +use audio::initialize; use sdl2::keyboard::Keycode; use sdl2::event::Event; @@ -32,6 +34,10 @@ fn main() -> Result<(), String> { let byte_height = 240 * screen::SCALE_FACTOR; // NES image is 240 pixels tall, multiply by scale factor for total number of rows needed let mut screen_buffer = vec![0; byte_width * byte_height]; // contains raw RGB data for the screen + // Set up audio + let mut speaker = audio::initialize(&sdl_context).expect("Could not create audio device"); + let mut half_cycle = false; + // Initialize hardware components let cart = Cartridge::new(); let ppu = Ppu::new(&cart); @@ -45,11 +51,24 @@ fn main() -> Result<(), String> { // PROFILER.lock().unwrap().start("./main.profile").unwrap(); 'running: loop { - // perform 1 cpu instruction, getting back number of clock cycles it took - let num_cycles = cpu.step(); - // maintain ratio of 3 ppu cycles for 1 cpu step - for _ in 0..num_cycles * 3 { - let (pixel, end_of_frame) = cpu.ppu.step(); + // step CPU: perform 1 cpu instruction, getting back number of clock cycles it took + let cpu_cycles = cpu.step(); + // clock APU every other CPU cycle + let mut apu_cycles = cpu_cycles / 2; + if cpu_cycles & 1 == 1 { // if cpu step took an odd number of cycles + if half_cycle { // and we have a half-cycle stored + apu_cycles += 1; // use it + half_cycle = false; + } else { + half_cycle = true; // or save it for next odd cpu step + } + } + for _ in 0..apu_cycles { + cpu.apu.step(); + } + // clock PPU three times for every CPU cycle + for _ in 0..cpu_cycles * 3 { + let (pixel, end_of_frame) = cpu.ppu.clock(); match pixel { Some((x, y, color)) => draw_pixel(&mut screen_buffer, x, y, color), None => (), diff --git a/src/ppu/mod.rs b/src/ppu/mod.rs index 46e46c8..db7dc86 100644 --- a/src/ppu/mod.rs +++ b/src/ppu/mod.rs @@ -143,7 +143,7 @@ impl Ppu { } } - pub fn step(&mut self) -> (Option<(usize, usize, (u8, u8, u8))>, bool) { + pub fn clock(&mut self) -> (Option<(usize, usize, (u8, u8, u8))>, bool) { if self.nmi_delay > 0 { self.nmi_delay -= 1; if self.nmi_delay == 0 && self.should_generate_nmi && self.vertical_blank {