This commit is contained in:
Theron 2019-12-19 18:13:48 -06:00
parent c62c5b9ead
commit 444636f621
6 changed files with 85 additions and 56 deletions

View File

@ -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<f32> {
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) {
}
}

View File

@ -3,7 +3,7 @@ extern crate sdl2;
use sdl2::audio::{AudioCallback, AudioSpecDesired};
pub struct Speaker {
buffer: [f32; 4096],
buffer: Vec<f32>,
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<sdl2::audio::AudioDevice<SquareWave>, String> {
pub fn initialize(context: &sdl2::Sdl) -> Result<sdl2::audio::AudioDevice<Speaker>, 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<sdl2::audio::AudioDevice<Square
println!("{:?}", spec);
// initialize the audio callback
Speaker{buffer: [0; 4096]}
Speaker{buffer: vec![], head: 0}
})
}

View File

@ -77,8 +77,7 @@ pub struct Cpu {
pub ppu: super::Ppu,
// apu
apu: super::Apu,
even: bool, // keep track of whether we're on an even cycle for apu
pub apu: super::Apu,
// controller
pub strobe: u8,
@ -100,7 +99,6 @@ impl Cpu {
mapper_func: cart.cpu_mapper_func,
ppu: ppu,
apu: apu,
even: true,
strobe: 0,
button_states: 0,
button_number: 0,
@ -148,8 +146,6 @@ impl Cpu {
}
pub fn step(&mut self) -> 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 => (),

View File

@ -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;
}
}

View File

@ -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 => (),

View File

@ -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 {