starting on audio

This commit is contained in:
Theron Spiegl 2019-11-26 19:11:51 -06:00
parent b35add1999
commit a9227af750
8 changed files with 199 additions and 45 deletions

7
src/apu/dmc.rs Normal file
View File

@ -0,0 +1,7 @@
impl super::DMC {
pub fn new() -> Self {
super::DMC {
}
}
}

84
src/apu/mod.rs Normal file
View File

@ -0,0 +1,84 @@
mod noise;
mod square;
mod triangle;
mod dmc;
pub struct Apu {
square1: Square,
square2: Square,
triangle: Triangle,
noise: Noise,
dmc: DMC,
}
struct Square {
duty_cycle: usize,
length_counter_halt: bool, // (this bit is also the envelope's loop flag)
constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume)
timer: usize,
length_counter: usize,
envelope: usize,
sweep: usize,
}
// $4008 Hlll.llll Triangle channel length counter halt and linear counter load (write)
// bit 7 H--- ---- Halt length counter (this bit is also the linear counter's control flag)
struct Triangle {
timer: usize,
length_counter: usize, // (this bit is also the linear counter's control flag)
linear_counter: usize,
}
// $400E M---.PPPP Mode and period (write)
// bit 7 M--- ---- Mode flag
struct Noise {
timer: usize,
length_counter: usize,
envelope: usize,
linear_feedback_sr: u16,
mode: bool, // also called loop noise, bit 7 of $400E
}
struct DMC {
}
impl Apu {
fn new() -> Self {
Apu {
square1: Square::new(),
square2: Square::new(),
triangle: Triangle::new(),
noise: Noise::new(),
dmc: DMC::new(),
}
}
fn write_reg(&mut self, address: usize, value: u8) {
match address {
0x4000 => self.square1.duty(value),
0x4001 => self.square1.sweep(value),
0x4002 => self.square1.timer_low(value),
0x4003 => self.square1.timer_high(value),
0x4004 => self.square2.duty(value),
0x4005 => self.square2.sweep(value),
0x4006 => self.square2.timer_low(value),
0x4007 => self.square2.timer_high(value),
0x4008 => self.triangle.counter(value),
0x400A => self.triangle.timer_low(value),
0x400B => self.triangle.timer_high(value),
0x400C => self.noise.envelope(value),
0x400D => (),
0x400E => self.noise.loop_noise(value),
0x400F => self.noise.load_length_counter(value),
0x4010 => self.dmc.control(value),
0x4011 => self.dmc.direct_load(value),
0x4012 => self.dmc.sample_address(value),
0x4013 => self.dmc.sample_length(value),
0x4014 => (),
0x4015 => self.control(value),
0x4016 => (),
0x4017 => self.frame_counter(value),
}
}
}

32
src/apu/noise.rs Normal file
View File

@ -0,0 +1,32 @@
impl super::Noise {
pub fn new() -> Self {
super::Noise {
timer: 0,
length_counter: 0,
envelope: 0,
linear_feedback_sr: 1, // On power-up, the shift register is loaded with the value 1.
mode: false, // also called loop noise, bit 7 of $400E
}
}
pub fn clock(&mut self) {
let bit0 = self.linear_feedback_sr & (1 << 0);
let other_bit = match self.mode {
false => (self.linear_feedback_sr & (1 << 1)) >> 1,
true => (self.linear_feedback_sr & (1 << 6)) >> 6,
};
let feedback = bit0 ^ other_bit;
self.linear_feedback_sr >>= 1;
self.linear_feedback_sr |= feedback << 14;
}
}
// When the timer clocks the shift register, the following actions occur in order:
// 1. Feedback is calculated as the exclusive-OR of bit 0 and one other bit: bit 6 if Mode flag is set, otherwise bit 1.
// 2. The shift register is shifted right by one bit.
// 3. Bit 14, the leftmost bit, is set to the feedback calculated earlier.
// This results in a pseudo-random bit sequence, 32767 steps long when Mode flag is clear,
// and randomly 93 or 31 steps long otherwise. (The particular 31- or 93-step sequence depends
// on where in the 32767-step sequence the shift register was when Mode flag was set).

13
src/apu/square.rs Normal file
View File

@ -0,0 +1,13 @@
impl super::Square {
pub fn new() -> Self {
super::Square {
duty_cycle: 0,
length_counter_halt: false,
constant_volume_flag: false,
timer: 0,
length_counter: 0,
envelope: 0,
sweep: 0,
}
}
}

9
src/apu/triangle.rs Normal file
View File

@ -0,0 +1,9 @@
impl super::Triangle {
pub fn new() -> Self {
super::Triangle {
timer: 0,
length_counter: 0,
linear_counter: 0,
}
}
}

View File

@ -76,6 +76,9 @@ pub struct Cpu {
// ppu
pub ppu: super::Ppu,
// apu
apu: super::Apu,
// controller
pub strobe: u8,
pub button_states: u8, // Player 1 controller
@ -83,7 +86,7 @@ pub struct Cpu {
}
impl Cpu {
pub fn new(cart: &super::Cartridge, ppu: super::Ppu) -> Self {
pub fn new(cart: &super::Cartridge, ppu: super::Ppu, apu: super::Apu) -> Self {
let mut cpu = Cpu{
mem: vec![0; 0x2000],
A: 0, X: 0, Y: 0,
@ -95,6 +98,7 @@ impl Cpu {
prg_rom: cart.prg_rom.clone(),
mapper_func: cart.cpu_mapper_func,
ppu: ppu,
apu: apu,
strobe: 0,
button_states: 0,
button_number: 0,
@ -187,11 +191,12 @@ impl Cpu {
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, // APU stuff
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() // unwraping because mapper funcs won't return None for reads.
*(self.mapper_func)(self, address, false).unwrap() // unwrapping because mapper funcs won't return None for reads.
},
_ => panic!("invalid read from 0x{:02x}", address),
};
@ -205,7 +210,7 @@ impl Cpu {
0x2000...0x3FFF => self.write_ppu_reg(address % 8, val),
0x4014 => self.write_ppu_reg(8, val),
0x4016 => self.write_controller(val),
0x4000...0x4017 => (), // APU stuff
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) {

View File

@ -2,15 +2,17 @@ use std::time::{Instant, Duration};
mod cpu;
mod ppu;
mod apu;
mod cartridge;
mod screen;
mod input;
mod screen;
use cpu::Cpu;
use ppu::Ppu;
use apu::Apu;
use cartridge::Cartridge;
use screen::{init_window, draw_pixel, draw_to_window};
use input::poll_buttons;
use screen::{init_window, draw_pixel, draw_to_window};
use sdl2::keyboard::Keycode;
use sdl2::event::Event;
@ -20,12 +22,12 @@ use sdl2::pixels::PixelFormatEnum;
fn main() -> Result<(), String> {
// Set up screen
let sdl_context = sdl2::init()?;
let mut event_pump = sdl_context.event_pump()?;
let sdl_context = sdl2::init()?;
let mut event_pump = sdl_context.event_pump()?;
let (mut canvas, texture_creator) = init_window(&sdl_context).expect("Could not create window");
let mut texture = texture_creator.create_texture_streaming(
PixelFormatEnum::RGB24, 256*screen::SCALE_FACTOR as u32, 240*screen::SCALE_FACTOR as u32)
.map_err(|e| e.to_string())?;
.map_err(|e| e.to_string())?;
let byte_width = 256 * 3 * screen::SCALE_FACTOR; // 256 NES pixels, 3 bytes for each pixel (RGB 24-bit), and NES-to-SDL scale factor
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
@ -33,7 +35,8 @@ fn main() -> Result<(), String> {
// Initialize hardware components
let cart = Cartridge::new();
let ppu = Ppu::new(&cart);
let mut cpu = Cpu::new(&cart, ppu);
let apu = Apu::new();
let mut cpu = Cpu::new(&cart, ppu, apu);
// For throttling to 60 FPS
let mut timer = Instant::now();
@ -45,25 +48,26 @@ fn main() -> Result<(), String> {
// 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 _i in 0..num_cycles * 3 {
for _ in 0..num_cycles * 3 {
let (pixel, end_of_frame) = cpu.ppu.step();
match pixel {
Some((x, y, color)) => draw_pixel(&mut screen_buffer, x, y, color),
None => (),
};
if end_of_frame {
fps += 1;
draw_to_window(&mut texture, &mut canvas, &screen_buffer)?;
fps += 1; // keep track of how many frames we've rendered this second
draw_to_window(&mut texture, &mut canvas, &screen_buffer)?; // draw the buffer to the window with SDL
let now = Instant::now();
// if we're running faster than 60Hz, kill time
if now < timer + Duration::from_millis(1000/60) {
std::thread::sleep(timer + Duration::from_millis(1000/60) - now);
}
timer = Instant::now();
// listen for Esc or window close. TODO: does this prevent keyboard events from being handled?
for event in event_pump.poll_iter() {
match event {
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
break 'running
},
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. }
=> { break 'running },
_ => (),
}
}

View File

@ -12,40 +12,40 @@ const BYTES_IN_ROW: usize = BYTES_IN_COL * 256; // 256 = screen width in NES pix
type RGBColor = (u8, u8, u8);
pub fn init_window(context: &Sdl) -> Result<(Canvas<Window>, TextureCreator<WindowContext>), String> {
let video_subsystem = context.video()?;
let window = video_subsystem.window("NESTUR", (256 * SCALE_FACTOR) as u32, (240 * SCALE_FACTOR) as u32)
.position_centered()
.opengl()
.build()
.map_err(|e| e.to_string())?;
let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?;
let texture_creator = canvas.texture_creator();
canvas.set_draw_color(Color::RGB(0, 0, 0));
canvas.clear();
canvas.present();
Ok((canvas, texture_creator))
let video_subsystem = context.video()?;
let window = video_subsystem.window("NESTUR", (256 * SCALE_FACTOR) as u32, (240 * SCALE_FACTOR) as u32)
.position_centered()
.opengl()
.build()
.map_err(|e| e.to_string())?;
let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?;
let texture_creator = canvas.texture_creator();
canvas.set_draw_color(Color::RGB(0, 0, 0));
canvas.clear();
canvas.present();
Ok((canvas, texture_creator))
}
pub fn draw_pixel(buffer: &mut Vec<u8>, x: usize, y: usize, color: RGBColor) {
let (r, g, b) = color;
let nes_y_offset = y * BYTES_IN_ROW * SCALE_FACTOR; // find offset for thick, SCALE_FACTOR-pixel tall row
for sdl_row_num in 0..SCALE_FACTOR { // looping over one-pixel tall rows up to SCALE_FACTOR
let row_offset = nes_y_offset + (sdl_row_num * BYTES_IN_ROW); // row_offset is the offset within buffer of the thin row we're on
let nes_x_offset = x * BYTES_IN_COL; // find horizontal offset within row (in byte terms) of NES x-coordinate
for sdl_col_num in 0..SCALE_FACTOR { // for pixels up to SCALE_FACTOR, moving horizontally
let col_offset = nes_x_offset + (sdl_col_num * 3); // skip 3 bytes at a time, R/G/B for each pixel
let offset = row_offset + col_offset;
buffer[offset + 0] = r;
buffer[offset + 1] = g;
buffer[offset + 2] = b;
}
}
let (r, g, b) = color;
let nes_y_offset = y * BYTES_IN_ROW * SCALE_FACTOR; // find offset for thick, SCALE_FACTOR-pixel tall row
for sdl_row_num in 0..SCALE_FACTOR { // looping over one-pixel tall rows up to SCALE_FACTOR
let row_offset = nes_y_offset + (sdl_row_num * BYTES_IN_ROW); // row_offset is the offset within buffer of the thin row we're on
let nes_x_offset = x * BYTES_IN_COL; // find horizontal offset within row (in byte terms) of NES x-coordinate
for sdl_col_num in 0..SCALE_FACTOR { // for pixels up to SCALE_FACTOR, moving horizontally
let col_offset = nes_x_offset + (sdl_col_num * 3); // skip 3 bytes at a time, R/G/B for each pixel
let offset = row_offset + col_offset;
buffer[offset + 0] = r;
buffer[offset + 1] = g;
buffer[offset + 2] = b;
}
}
}
pub fn draw_to_window(texture: &mut Texture, canvas: &mut Canvas<Window>, buffer: &Vec<u8>) -> Result<(), String> {
texture.update(None, buffer, 256*3*SCALE_FACTOR)
.map_err(|e| e.to_string())?;
texture.update(None, buffer, 256*3*SCALE_FACTOR)
.map_err(|e| e.to_string())?;
canvas.copy(&texture, None, None)?;
canvas.present();
Ok(())
canvas.present();
Ok(())
}