starting on audio
This commit is contained in:
parent
b35add1999
commit
a9227af750
|
@ -0,0 +1,7 @@
|
|||
impl super::DMC {
|
||||
pub fn new() -> Self {
|
||||
super::DMC {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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).
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
impl super::Triangle {
|
||||
pub fn new() -> Self {
|
||||
super::Triangle {
|
||||
timer: 0,
|
||||
length_counter: 0,
|
||||
linear_counter: 0,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
28
src/main.rs
28
src/main.rs
|
@ -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 },
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue