apu
This commit is contained in:
parent
c62c5b9ead
commit
444636f621
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
11
src/audio.rs
11
src/audio.rs
|
@ -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}
|
||||
})
|
||||
}
|
|
@ -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 => (),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
29
src/main.rs
29
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 => (),
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue