From a9227af750cf8cd7fb760b1989609d4cf0a4c5ec Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Tue, 26 Nov 2019 19:11:51 -0600 Subject: [PATCH 01/36] starting on audio --- src/apu/dmc.rs | 7 ++++ src/apu/mod.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++ src/apu/noise.rs | 32 +++++++++++++++++ src/apu/square.rs | 13 +++++++ src/apu/triangle.rs | 9 +++++ src/cpu/mod.rs | 13 ++++--- src/main.rs | 28 ++++++++------- src/screen/mod.rs | 58 +++++++++++++++---------------- 8 files changed, 199 insertions(+), 45 deletions(-) create mode 100644 src/apu/dmc.rs create mode 100644 src/apu/mod.rs create mode 100644 src/apu/noise.rs create mode 100644 src/apu/square.rs create mode 100644 src/apu/triangle.rs diff --git a/src/apu/dmc.rs b/src/apu/dmc.rs new file mode 100644 index 0000000..e344902 --- /dev/null +++ b/src/apu/dmc.rs @@ -0,0 +1,7 @@ +impl super::DMC { + pub fn new() -> Self { + super::DMC { + + } + } +} \ No newline at end of file diff --git a/src/apu/mod.rs b/src/apu/mod.rs new file mode 100644 index 0000000..839d90a --- /dev/null +++ b/src/apu/mod.rs @@ -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), + } + } +} diff --git a/src/apu/noise.rs b/src/apu/noise.rs new file mode 100644 index 0000000..93b66b3 --- /dev/null +++ b/src/apu/noise.rs @@ -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). diff --git a/src/apu/square.rs b/src/apu/square.rs new file mode 100644 index 0000000..fe6fbf2 --- /dev/null +++ b/src/apu/square.rs @@ -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, + } + } +} \ No newline at end of file diff --git a/src/apu/triangle.rs b/src/apu/triangle.rs new file mode 100644 index 0000000..15b50c2 --- /dev/null +++ b/src/apu/triangle.rs @@ -0,0 +1,9 @@ +impl super::Triangle { + pub fn new() -> Self { + super::Triangle { + timer: 0, + length_counter: 0, + linear_counter: 0, + } + } +} \ No newline at end of file diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index e48e180..289ad2a 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -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) { diff --git a/src/main.rs b/src/main.rs index 27f5c26..15637ff 100644 --- a/src/main.rs +++ b/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 }, _ => (), } } diff --git a/src/screen/mod.rs b/src/screen/mod.rs index 06c7c2c..edf3696 100644 --- a/src/screen/mod.rs +++ b/src/screen/mod.rs @@ -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, TextureCreator), 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, 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, buffer: &Vec) -> 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(()) } From ac416cc72205af58f7aa5b494b00aa88dfca5e4e Mon Sep 17 00:00:00 2001 From: Theron Date: Tue, 26 Nov 2019 22:23:18 -0600 Subject: [PATCH 02/36] apu --- src/apu/mod.rs | 22 +++++++++++++++++++++ src/audio.rs | 34 ++++++++++++++++++++++++++++++++ src/{screen/mod.rs => screen.rs} | 0 3 files changed, 56 insertions(+) create mode 100644 src/audio.rs rename src/{screen/mod.rs => screen.rs} (100%) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 839d90a..3d21862 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -9,9 +9,13 @@ pub struct Apu { triangle: Triangle, noise: Noise, dmc: DMC, + + square_table: Vec, + tnd_table: Vec, } struct Square { + sample: u16, 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) @@ -24,6 +28,7 @@ struct Square { // $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 { + sample: u16, timer: usize, length_counter: usize, // (this bit is also the linear counter's control flag) linear_counter: usize, @@ -32,6 +37,7 @@ struct Triangle { // $400E M---.PPPP Mode and period (write) // bit 7 M--- ---- Mode flag struct Noise { + sample: u16, timer: usize, length_counter: usize, envelope: usize, @@ -45,12 +51,18 @@ struct DMC { impl Apu { fn new() -> Self { + let square_table = (0..31).map(|x| 95.52/(8128.0 / x as f32) + 100.0).collect(); + let tnd_table = (0..203).map(|x| 163.67/(24329.0 / x as f32) + 100.0).collect(); Apu { square1: Square::new(), square2: Square::new(), triangle: Triangle::new(), noise: Noise::new(), dmc: DMC::new(), + + square_table: square_table, + tnd_table: tnd_table, + } } @@ -81,4 +93,14 @@ impl Apu { 0x4017 => self.frame_counter(value), } } + + fn mix(&self) -> f32 { + let square_out = self.square_table[self.square1.sample + self.square2.sample as usize]; + let tnd_out = self.tnd_table[(3*self.triangle.sample)+(2*self.noise.sample)+self.dmc.sample as usize]; + square_out + tnd_out + } + + fn frame_counter(value: u8) { + + } } diff --git a/src/audio.rs b/src/audio.rs new file mode 100644 index 0000000..bc6b249 --- /dev/null +++ b/src/audio.rs @@ -0,0 +1,34 @@ +extern crate sdl2; + +use sdl2::audio::{AudioCallback, AudioSpecDesired}; + +pub struct Speaker { + buffer: [u8; 4096], +} + +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]; // get data from apu + } + } +} + +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 + }; + + audio_subsystem.open_playback(None, &desired_spec, |spec| { + // Show obtained AudioSpec + println!("{:?}", spec); + + // initialize the audio callback + Speaker{buffer: [0; 4096]} + }) +} \ No newline at end of file diff --git a/src/screen/mod.rs b/src/screen.rs similarity index 100% rename from src/screen/mod.rs rename to src/screen.rs From d0896707bd7c512c9f9ce573d740e48e1013c2e9 Mon Sep 17 00:00:00 2001 From: Theron Date: Wed, 4 Dec 2019 20:57:05 -0600 Subject: [PATCH 03/36] sketching, reading --- src/apu/dmc.rs | 19 ++++++++++++++++++- src/apu/mod.rs | 13 +++++++++---- src/apu/noise.rs | 11 +++++++++++ src/apu/square.rs | 21 +++++++++++++++++++++ src/apu/triangle.rs | 12 ++++++++++++ 5 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/apu/dmc.rs b/src/apu/dmc.rs index e344902..b700064 100644 --- a/src/apu/dmc.rs +++ b/src/apu/dmc.rs @@ -1,7 +1,24 @@ impl super::DMC { pub fn new() -> Self { super::DMC { - + sample: 0, } } + + + pub fn control(&mut self, value: u8) { + + } + + pub fn direct_load(&mut self, value: u8) { + + } + + pub fn sample_address(&mut self, value: u8) { + + } + + pub fn sample_length(&mut self, value: u8) { + + } } \ No newline at end of file diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 3d21862..eb5d53f 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -46,7 +46,7 @@ struct Noise { } struct DMC { - + sample: u16, } impl Apu { @@ -91,16 +91,21 @@ impl Apu { 0x4015 => self.control(value), 0x4016 => (), 0x4017 => self.frame_counter(value), + _ => panic!("bad address written: 0x{:X}", address), } } fn mix(&self) -> f32 { - let square_out = self.square_table[self.square1.sample + self.square2.sample as usize]; - let tnd_out = self.tnd_table[(3*self.triangle.sample)+(2*self.noise.sample)+self.dmc.sample as usize]; + let square_out = self.square_table[(self.square1.sample + self.square2.sample) as usize]; + let tnd_out = self.tnd_table[((3*self.triangle.sample)+(2*self.noise.sample) + self.dmc.sample) as usize]; square_out + tnd_out } - fn frame_counter(value: u8) { + fn frame_counter(&mut self, value: u8) { } + + fn control(&mut self, value: u8) { + + } } diff --git a/src/apu/noise.rs b/src/apu/noise.rs index 93b66b3..59b4e4d 100644 --- a/src/apu/noise.rs +++ b/src/apu/noise.rs @@ -19,6 +19,17 @@ impl super::Noise { self.linear_feedback_sr >>= 1; self.linear_feedback_sr |= feedback << 14; } + + pub fn envelope(&mut self, value: u8) { + + } + + pub fn loop_noise(&mut self, value: u8) { + + } + pub fn load_length_counter(&mut self, value: u8) { + + } } // When the timer clocks the shift register, the following actions occur in order: diff --git a/src/apu/square.rs b/src/apu/square.rs index fe6fbf2..77b0572 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -8,6 +8,27 @@ impl super::Square { length_counter: 0, envelope: 0, sweep: 0, + sample: 0, } } + + pub fn clock(&mut self) { + + } + + pub fn duty(&mut self, value: u8) { + + } + + pub fn sweep(&mut self, value: u8) { + + } + + pub fn timer_low(&mut self, value: u8) { + + } + + pub fn timer_high(&mut self, value: u8) { + + } } \ No newline at end of file diff --git a/src/apu/triangle.rs b/src/apu/triangle.rs index 15b50c2..202f21c 100644 --- a/src/apu/triangle.rs +++ b/src/apu/triangle.rs @@ -6,4 +6,16 @@ impl super::Triangle { linear_counter: 0, } } + + pub fn timer_low(&mut self, value: u8) { + + } + + pub fn timer_high(&mut self, value: u8) { + + } + + pub fn counter(&mut self, value: u8) { + + } } \ No newline at end of file From bbeb38475ca398e389dbce1cde452f29cecdd633 Mon Sep 17 00:00:00 2001 From: Theron Date: Sat, 7 Dec 2019 17:09:28 -0600 Subject: [PATCH 04/36] apu --- src/apu/mod.rs | 8 +++++++- src/apu/square.rs | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index eb5d53f..3acfb11 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -16,7 +16,7 @@ pub struct Apu { struct Square { sample: u16, - duty_cycle: usize, + duty_cycle: u8, 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, @@ -49,6 +49,12 @@ struct DMC { sample: u16, } +struct Envelope { + start_flag: bool, + divider: usize, + delay_level_counter: usize, +} + impl Apu { fn new() -> Self { let square_table = (0..31).map(|x| 95.52/(8128.0 / x as f32) + 100.0).collect(); diff --git a/src/apu/square.rs b/src/apu/square.rs index 77b0572..79609d0 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -17,7 +17,8 @@ impl super::Square { } pub fn duty(&mut self, value: u8) { - + self.duty_cycle = value >> 6; + self.length_counter_halt = } pub fn sweep(&mut self, value: u8) { From b6361738fef300181aebcafdf690ad1a3f7208b9 Mon Sep 17 00:00:00 2001 From: Theron Date: Mon, 9 Dec 2019 21:22:53 -0600 Subject: [PATCH 05/36] apu --- src/apu/mod.rs | 31 +++++++++++++++++++++++++++---- src/apu/square.rs | 22 +++++++++++++++++++++- src/cpu/mod.rs | 10 ++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 3acfb11..a90589a 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -12,6 +12,10 @@ pub struct Apu { square_table: Vec, tnd_table: Vec, + + frame_counter: u8, + mode: u8, + interrupt_inhibit: u8, } struct Square { @@ -55,8 +59,12 @@ struct Envelope { delay_level_counter: usize, } +struct FrameCounter { + +} + impl Apu { - fn new() -> Self { + pub fn new() -> Self { let square_table = (0..31).map(|x| 95.52/(8128.0 / x as f32) + 100.0).collect(); let tnd_table = (0..203).map(|x| 163.67/(24329.0 / x as f32) + 100.0).collect(); Apu { @@ -69,6 +77,9 @@ impl Apu { square_table: square_table, tnd_table: tnd_table, + frame_counter: 0, + mode: 0, + interrupt_inhibit: 0, } } @@ -96,7 +107,7 @@ impl Apu { 0x4014 => (), 0x4015 => self.control(value), 0x4016 => (), - 0x4017 => self.frame_counter(value), + 0x4017 => self.step_frame_counter(value), _ => panic!("bad address written: 0x{:X}", address), } } @@ -107,8 +118,20 @@ impl Apu { square_out + tnd_out } - fn frame_counter(&mut self, value: u8) { - + fn step_frame_counter(&mut self, value: u8) { + // 0 selects 4-step sequence, 1 selects 5-step sequence + if value & (1<<7) == 0 { + self.mode = 0; + self.frame_counter = 4; + } else { + self.mode = 1; + self.frame_counter = 5; + } + // If set, the frame interrupt flag is cleared, otherwise it is unaffected. + if value & (1<<6) != 0 { + self.interrupt_inhibit = 0; + } + } fn control(&mut self, value: u8) { diff --git a/src/apu/square.rs b/src/apu/square.rs index 79609d0..c0a817e 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -32,4 +32,24 @@ impl super::Square { pub fn timer_high(&mut self, value: u8) { } -} \ No newline at end of file +} + +struct EnvelopeGenerator { + +} + +struct SweepUnit { + +} + +struct Timer { + +} + +struct Sequencer { + +} + +struct LengthCounter { + +} diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 289ad2a..a4c6e66 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -78,6 +78,7 @@ pub struct Cpu { // apu apu: super::Apu, + even: bool, // keep track of whether we're on an even cycle for apu // controller pub strobe: u8, @@ -146,6 +147,13 @@ impl Cpu { } pub fn step(&mut self) -> u64 { + // for apu + if self.even { + self.even = false; + } else { + self.even = true; + } + // skip cycles from OAM DMA if necessary if self.delay > 0 { self.delay -= 1; @@ -181,6 +189,8 @@ 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 + if self. // return how many cycles it took self.clock - clock } From 1e59eace50608c5f56d54ec2dc295b48cceb1fc2 Mon Sep 17 00:00:00 2001 From: Theron Date: Tue, 10 Dec 2019 00:34:21 -0600 Subject: [PATCH 06/36] apu --- src/apu/mod.rs | 13 +++++++++++++ src/audio.rs | 2 +- src/cpu/mod.rs | 2 +- src/ppu/mod.rs | 4 ++-- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index a90589a..ba4ebed 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -3,6 +3,10 @@ mod square; mod triangle; mod dmc; +// APU clock ticks every other CPU cycle. +// Frame counter only ticks every 3728.5 APU ticks, and in audio frames of 4 or 5. +// Length counter controls note durations. + pub struct Apu { square1: Square, square2: Square, @@ -83,6 +87,10 @@ impl Apu { } } + pub fn clock(&mut self) { + + } + fn write_reg(&mut self, address: usize, value: u8) { match address { 0x4000 => self.square1.duty(value), @@ -118,6 +126,11 @@ 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 step_frame_counter(&mut self, value: u8) { // 0 selects 4-step sequence, 1 selects 5-step sequence if value & (1<<7) == 0 { diff --git a/src/audio.rs b/src/audio.rs index bc6b249..e1b9334 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: [u8; 4096], + buffer: [f32; 4096], } impl AudioCallback for Speaker { diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index a4c6e66..f11ffcc 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -190,7 +190,7 @@ impl Cpu { // look up instruction in table and execute self.opcode_table[opcode](self, address, mode); // maintain 1 apu cycle per 2 cpu cycles - if self. + self.even = if self.even { self.apu.step(); false } else { true }; // return how many cycles it took self.clock - clock } diff --git a/src/ppu/mod.rs b/src/ppu/mod.rs index d838de8..46e46c8 100644 --- a/src/ppu/mod.rs +++ b/src/ppu/mod.rs @@ -64,8 +64,8 @@ pub struct Ppu { grayscale: bool, show_background_left: bool, // 1: Show background in leftmost 8 pixels of screen, 0: Hide show_sprites_left: bool, // 1: Show sprites in leftmost 8 pixels of screen, 0: Hide - show_background: bool, // 1: Show background - show_sprites: bool, // 1: Show sprites + show_background: bool, // 1: Show background + show_sprites: bool, // 1: Show sprites emphasize_red: bool, // Emphasize red emphasize_green: bool, // Emphasize green emphasize_blue: bool, // Emphasize blue From 5855d6b251170f462babae95da3db302261cef03 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Thu, 12 Dec 2019 00:17:07 -0600 Subject: [PATCH 07/36] apu timing notes --- src/apu/mod.rs | 10 ++++++++++ src/audio.rs | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index ba4ebed..944c188 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -7,6 +7,16 @@ mod dmc; // Frame counter only ticks every 3728.5 APU ticks, and in audio frames of 4 or 5. // Length counter controls note durations. +// How to sync clock to audio? +// Measure time slept to see if it will be a problem. +// What if the APU kept a ring buffer of audio data way longer than the audio device's sample size, +// and that was in a struct with some markers, so the audio device can just consume what it needs during PPU's sleep and mark +// where it left off? But wouldn't it catch up and consume buffer? It won't catch up if it's big enough, and the APU can +// change the markers somehow as it needs to? Or audio callback truncates what it consumed and adjusts head? No, audio device doesn't +// need all samples, it needs one from the stream 44100 time per second. So just an if statement, if time has passed grab a sample. +// But then that won't be running during PPU 60Hz sleep... So run audio in its own thread... Except then it won't work on Windows because of SDL... +// So just run the console in its own thread and video/audio in the main thread... But that's annoying. + pub struct Apu { square1: Square, square2: Square, diff --git a/src/audio.rs b/src/audio.rs index e1b9334..92c3d7f 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -4,13 +4,14 @@ use sdl2::audio::{AudioCallback, AudioSpecDesired}; pub struct Speaker { buffer: [f32; 4096], + head: usize, } 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]; // get data from apu + *x = self.buffer[i+self.head..i+self.head+4096]; // get data from apu } } } From 1c19b9d10a7ed822ab07641fc56d58b767d87763 Mon Sep 17 00:00:00 2001 From: Theron Date: Sat, 14 Dec 2019 18:15:06 -0600 Subject: [PATCH 08/36] apu --- src/apu/dmc.rs | 4 ++ src/apu/mod.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++- src/apu/noise.rs | 2 + src/apu/square.rs | 3 +- src/apu/triangle.rs | 2 + src/cpu/mod.rs | 7 +--- 6 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/apu/dmc.rs b/src/apu/dmc.rs index b700064..504e182 100644 --- a/src/apu/dmc.rs +++ b/src/apu/dmc.rs @@ -2,6 +2,10 @@ impl super::DMC { pub fn new() -> Self { super::DMC { sample: 0, + enabled: false, + bytes_remaining: 0, + interrupt: false, + length_counter: 0, } } diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 944c188..be1e492 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -30,6 +30,7 @@ pub struct Apu { frame_counter: u8, mode: u8, interrupt_inhibit: u8, + frame_interrupt: bool, } struct Square { @@ -41,6 +42,7 @@ struct Square { length_counter: usize, envelope: usize, sweep: usize, + enabled: bool, } // $4008 Hlll.llll Triangle channel length counter halt and linear counter load (write) @@ -50,6 +52,7 @@ struct Triangle { timer: usize, length_counter: usize, // (this bit is also the linear counter's control flag) linear_counter: usize, + enabled: bool, } // $400E M---.PPPP Mode and period (write) @@ -61,10 +64,15 @@ struct Noise { envelope: usize, linear_feedback_sr: u16, mode: bool, // also called loop noise, bit 7 of $400E + enabled: bool, } struct DMC { sample: u16, + enabled: bool, + interrupt: bool, + length_counter: usize, + bytes_remaining: usize, } struct Envelope { @@ -94,14 +102,15 @@ impl Apu { frame_counter: 0, mode: 0, interrupt_inhibit: 0, + frame_interrupt: false, } } - pub fn clock(&mut self) { + pub fn step(&mut self) { } - fn write_reg(&mut self, address: usize, value: u8) { + pub fn write_reg(&mut self, address: usize, value: u8) { match address { 0x4000 => self.square1.duty(value), 0x4001 => self.square1.sweep(value), @@ -158,6 +167,85 @@ impl Apu { } fn control(&mut self, value: u8) { + // Writing to this register clears the DMC interrupt flag. + self.dmc.interrupt = false; + // Writing a zero to any of the channel enable bits will silence that channel and immediately set its length counter to 0. + if value & (1<<0) != 0 { + self.square1.enabled = true; + } else { + self.square1.enabled = false; + self.square1.length_counter = 0; + } + if value & (1<<1) != 0 { + self.square2.enabled = true; + } else { + self.square2.enabled = false; + self.square2.length_counter = 0; + + } + if value & (1<<2) != 0 { + self.triangle.enabled = true; + + } else { + self.triangle.enabled = false; + self.triangle.length_counter = 0; + + } + if value & (1<<3) != 0 { + self.noise.enabled = true; + + } else { + self.noise.enabled = false; + self.noise.length_counter = 0; + + } + if value & (1<<4) != 0 { + self.dmc.enabled = true; + // If the DMC bit is set, the DMC sample will be restarted only if its bytes remaining is 0. + // If there are bits remaining in the 1-byte sample buffer, these will finish playing before the next sample is fetched. + if self.dmc.bytes_remaining != 0 { + // TODO: how does dmc repeat? + } + } else { + self.dmc.enabled = false; + self.dmc.length_counter = 0; + // If the DMC bit is clear, the DMC bytes remaining will be set to 0 and the DMC will silence when it empties. + self.dmc.bytes_remaining = 0; + } + } + + pub fn read_status(&mut self) -> u8 { + // IF-D NT21: DMC interrupt (I), frame interrupt (F), DMC active (D), length counter > 0 (N/T/2/1) + let mut val = 0; + // N/T/2/1 will read as 1 if the corresponding length counter is greater than 0. For the triangle channel, the status of the linear counter is irrelevant. + if self.square1.length_counter != 0 { + val |= 1<<0; + } + if self.square2.length_counter != 0 { + val |= 1<<1; + } + if self.triangle.length_counter != 0 { + val |= 1<<2; + } + if self.noise.length_counter != 0 { + val |= 1<<3; + } + // D will read as 1 if the DMC bytes remaining is more than 0. + if self.dmc.bytes_remaining != 0 { + val |= 1<<4; + } + if self.frame_interrupt { + val |= 1<<6; + } + if self.dmc.interrupt { + val |= 1<<7; + } + + + // Reading this register clears the frame interrupt flag (but not the DMC interrupt flag). + self.frame_interrupt = false; + // TODO: If an interrupt flag was set at the same moment of the read, it will read back as 1 but it will not be cleared. + val } } diff --git a/src/apu/noise.rs b/src/apu/noise.rs index 59b4e4d..bc00cfa 100644 --- a/src/apu/noise.rs +++ b/src/apu/noise.rs @@ -6,6 +6,8 @@ impl super::Noise { 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 + sample: 0, + enabled: false, } } diff --git a/src/apu/square.rs b/src/apu/square.rs index c0a817e..40c8dda 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -9,6 +9,7 @@ impl super::Square { envelope: 0, sweep: 0, sample: 0, + enabled: false, } } @@ -18,7 +19,7 @@ impl super::Square { pub fn duty(&mut self, value: u8) { self.duty_cycle = value >> 6; - self.length_counter_halt = + // self.length_counter_halt = } pub fn sweep(&mut self, value: u8) { diff --git a/src/apu/triangle.rs b/src/apu/triangle.rs index 202f21c..5f156a7 100644 --- a/src/apu/triangle.rs +++ b/src/apu/triangle.rs @@ -4,6 +4,8 @@ impl super::Triangle { timer: 0, length_counter: 0, linear_counter: 0, + sample: 0, + enabled: false, } } diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index f11ffcc..507448e 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -100,6 +100,7 @@ impl Cpu { mapper_func: cart.cpu_mapper_func, ppu: ppu, apu: apu, + even: true, strobe: 0, button_states: 0, button_number: 0, @@ -148,11 +149,7 @@ impl Cpu { pub fn step(&mut self) -> u64 { // for apu - if self.even { - self.even = false; - } else { - self.even = true; - } + self.even = if self.even {false} else {true}; // skip cycles from OAM DMA if necessary if self.delay > 0 { From 8a8d45cbff371f0d8b3fcb086ffa160d2e96740a Mon Sep 17 00:00:00 2001 From: Theron Date: Mon, 16 Dec 2019 22:06:00 -0600 Subject: [PATCH 09/36] apu --- src/apu/dmc.rs | 12 ++++++++++-- src/apu/mod.rs | 47 +++++---------------------------------------- src/apu/noise.rs | 17 ++++++++++++++-- src/apu/square.rs | 33 ++++++++++++++++++++++++++----- src/apu/triangle.rs | 15 +++++++++++++-- 5 files changed, 71 insertions(+), 53 deletions(-) diff --git a/src/apu/dmc.rs b/src/apu/dmc.rs index 504e182..57e1b75 100644 --- a/src/apu/dmc.rs +++ b/src/apu/dmc.rs @@ -1,6 +1,14 @@ -impl super::DMC { +pub struct DMC { + pub sample: u16, + pub enabled: bool, + pub interrupt: bool, + pub length_counter: usize, + pub bytes_remaining: usize, +} + +impl DMC { pub fn new() -> Self { - super::DMC { + DMC { sample: 0, enabled: false, bytes_remaining: 0, diff --git a/src/apu/mod.rs b/src/apu/mod.rs index be1e492..9bfb431 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -3,6 +3,11 @@ mod square; mod triangle; mod dmc; +use noise::Noise; +use square::Square; +use triangle::Triangle; +use dmc::DMC; + // APU clock ticks every other CPU cycle. // Frame counter only ticks every 3728.5 APU ticks, and in audio frames of 4 or 5. // Length counter controls note durations. @@ -33,48 +38,6 @@ pub struct Apu { frame_interrupt: bool, } -struct Square { - sample: u16, - duty_cycle: u8, - 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, - enabled: bool, -} - -// $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 { - sample: u16, - timer: usize, - length_counter: usize, // (this bit is also the linear counter's control flag) - linear_counter: usize, - enabled: bool, -} - -// $400E M---.PPPP Mode and period (write) -// bit 7 M--- ---- Mode flag -struct Noise { - sample: u16, - timer: usize, - length_counter: usize, - envelope: usize, - linear_feedback_sr: u16, - mode: bool, // also called loop noise, bit 7 of $400E - enabled: bool, -} - -struct DMC { - sample: u16, - enabled: bool, - interrupt: bool, - length_counter: usize, - bytes_remaining: usize, -} - struct Envelope { start_flag: bool, divider: usize, diff --git a/src/apu/noise.rs b/src/apu/noise.rs index bc00cfa..a7a3870 100644 --- a/src/apu/noise.rs +++ b/src/apu/noise.rs @@ -1,6 +1,19 @@ -impl super::Noise { + +// $400E M---.PPPP Mode and period (write) +// bit 7 M--- ---- Mode flag +pub struct Noise { + pub sample: u16, + timer: usize, + pub length_counter: usize, + envelope: usize, + linear_feedback_sr: u16, + mode: bool, // also called loop noise, bit 7 of $400E + pub enabled: bool, +} + +impl Noise { pub fn new() -> Self { - super::Noise { + Noise { timer: 0, length_counter: 0, envelope: 0, diff --git a/src/apu/square.rs b/src/apu/square.rs index 40c8dda..699f5fc 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -1,7 +1,28 @@ -impl super::Square { +const duty_cycle_sequences: [[u8; 8]; 4] = [ + [0, 1, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 0, 0, 0], + [1, 0, 0, 1, 1, 1, 1, 1], +]; + +pub struct Square { + pub sample: u16, + duty_cycle: [u8; 8], + duty_counter: u8, + 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, + pub length_counter: usize, + envelope: usize, + sweep: usize, + pub enabled: bool, +} + +impl Square { pub fn new() -> Self { - super::Square { - duty_cycle: 0, + Square { + duty_cycle: duty_cycle_sequences[0], + duty_counter: 0, length_counter_halt: false, constant_volume_flag: false, timer: 0, @@ -18,8 +39,10 @@ impl super::Square { } pub fn duty(&mut self, value: u8) { - self.duty_cycle = value >> 6; - // self.length_counter_halt = + self.duty_cycle = duty_cycle_sequences[(value >> 6) as usize]; + self.length_counter_halt = value & (1<<5) != 0; + self.constant_volume_flag = value & (1<<4) != 0; + } pub fn sweep(&mut self, value: u8) { diff --git a/src/apu/triangle.rs b/src/apu/triangle.rs index 5f156a7..a7f050b 100644 --- a/src/apu/triangle.rs +++ b/src/apu/triangle.rs @@ -1,6 +1,17 @@ -impl super::Triangle { + +// $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) +pub struct Triangle { + pub sample: u16, + timer: usize, + pub length_counter: usize, // (this bit is also the linear counter's control flag) + linear_counter: usize, + pub enabled: bool, +} + +impl Triangle { pub fn new() -> Self { - super::Triangle { + Triangle { timer: 0, length_counter: 0, linear_counter: 0, From 20ded5f56c9ba6839b682e7b2783af6804a95331 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Mon, 16 Dec 2019 23:43:10 -0600 Subject: [PATCH 10/36] apu --- src/apu/mod.rs | 4 +++- src/apu/square.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 9bfb431..1cd7e42 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -21,6 +21,8 @@ use dmc::DMC; // need all samples, it needs one from the stream 44100 time per second. So just an if statement, if time has passed grab a sample. // But then that won't be running during PPU 60Hz sleep... So run audio in its own thread... Except then it won't work on Windows because of SDL... // So just run the console in its own thread and video/audio in the main thread... But that's annoying. +// No. Don't have to be concerned about the audio device, that's solved by the buffer, and the 44100 samples get fed in batches of 4096 from the large buffer, +// when the device needs them, which is accomplished just by calling .resume() before the main loop starts. So a large buffer really should allow for the 60Hz sleep lock. pub struct Apu { square1: Square, @@ -84,6 +86,7 @@ impl Apu { 0x4006 => self.square2.timer_low(value), 0x4007 => self.square2.timer_high(value), 0x4008 => self.triangle.counter(value), + 0x4009 => (), 0x400A => self.triangle.timer_low(value), 0x400B => self.triangle.timer_high(value), 0x400C => self.noise.envelope(value), @@ -204,7 +207,6 @@ impl Apu { if self.dmc.interrupt { val |= 1<<7; } - // Reading this register clears the frame interrupt flag (but not the DMC interrupt flag). self.frame_interrupt = false; diff --git a/src/apu/square.rs b/src/apu/square.rs index 699f5fc..779e504 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -16,6 +16,9 @@ pub struct Square { envelope: usize, sweep: usize, pub enabled: bool, + decay_counter: u8, + start: bool, + divider: usize, } impl Square { @@ -31,6 +34,9 @@ impl Square { sweep: 0, sample: 0, enabled: false, + decay_counter: 0, + start: false, + divider: 0, } } @@ -38,21 +44,42 @@ impl Square { } + pub fn clock_frame_counter(&mut self) { + + } + + pub fn clock_envelope(&mut self) { + if self.start { + self.envelope -= 1; + self.start = false; + } else { + + } + } + + // $4000/$4004 pub fn duty(&mut self, value: u8) { self.duty_cycle = duty_cycle_sequences[(value >> 6) as usize]; self.length_counter_halt = value & (1<<5) != 0; self.constant_volume_flag = value & (1<<4) != 0; - + if self.constant_volume_flag { + self.envelope = value & 0b1111; + } else { + self.envelope = self.decay_counter; + } } + // $4001/$4005 pub fn sweep(&mut self, value: u8) { } + // $4002/$4006 pub fn timer_low(&mut self, value: u8) { } + // $4003/$4007 pub fn timer_high(&mut self, value: u8) { } From 2cc8df766efda924ad24f307dceb8a21ab15441e Mon Sep 17 00:00:00 2001 From: Theron Date: Tue, 17 Dec 2019 20:38:15 -0600 Subject: [PATCH 11/36] apu --- src/apu/square.rs | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/apu/square.rs b/src/apu/square.rs index 779e504..5798834 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -1,4 +1,4 @@ -const duty_cycle_sequences: [[u8; 8]; 4] = [ +const DUTY_CYCLE_SEQUENCES: [[u8; 8]; 4] = [ [0, 1, 0, 0, 0, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 0, 0, 0], @@ -11,20 +11,20 @@ pub struct Square { duty_counter: u8, 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, - pub length_counter: usize, - envelope: usize, - sweep: usize, + timer: u16, + pub length_counter: u8, + envelope: u8, + sweep: u8, pub enabled: bool, decay_counter: u8, start: bool, - divider: usize, + divider: u8, } impl Square { pub fn new() -> Self { Square { - duty_cycle: duty_cycle_sequences[0], + duty_cycle: DUTY_CYCLE_SEQUENCES[0], duty_counter: 0, length_counter_halt: false, constant_volume_flag: false, @@ -49,17 +49,36 @@ impl Square { } pub fn clock_envelope(&mut self) { - if self.start { - self.envelope -= 1; - self.start = false; + // When clocked by the frame counter, one of two actions occurs: + // if the start flag is clear, the divider is clocked, + if !self.start { + self.clock_divider(); } else { + self.start = false; // otherwise the start flag is cleared, + self.decay_counter = 15; // the decay level counter is loaded with 15, + self.divider = self.envelope; // and the divider's period is immediately reloaded + } + } + fn clock_divider(&mut self) { + // When the divider is clocked while at 0, it is loaded with V and clocks the decay level counter. + if self.divider == 0 { + self.divider = self.envelope; + // Then one of two actions occurs: If the counter is non-zero, it is decremented, + if self.decay_counter != 0 { + self.decay_counter -= 1; + } else if self.length_counter_halt { + // otherwise if the loop flag is set, the decay level counter is loaded with 15. + self.decay_counter = 15; + } + } else { + self.divider -= 1; } } // $4000/$4004 pub fn duty(&mut self, value: u8) { - self.duty_cycle = duty_cycle_sequences[(value >> 6) as usize]; + self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize]; self.length_counter_halt = value & (1<<5) != 0; self.constant_volume_flag = value & (1<<4) != 0; if self.constant_volume_flag { From 6fb17d77c6980bdd81c3f0cd2912a1c79c5294a3 Mon Sep 17 00:00:00 2001 From: Theron Date: Tue, 17 Dec 2019 21:29:00 -0600 Subject: [PATCH 12/36] apu --- src/apu/mod.rs | 31 +++++++++++++++++++++++++++++-- src/apu/noise.rs | 4 ++++ src/apu/triangle.rs | 4 ++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 1cd7e42..3fa51f8 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -35,6 +35,7 @@ pub struct Apu { tnd_table: Vec, frame_counter: u8, + current_frame: u8, mode: u8, interrupt_inhibit: u8, frame_interrupt: bool, @@ -65,6 +66,7 @@ impl Apu { tnd_table: tnd_table, frame_counter: 0, + current_frame: 0, mode: 0, interrupt_inhibit: 0, frame_interrupt: false, @@ -100,7 +102,7 @@ impl Apu { 0x4014 => (), 0x4015 => self.control(value), 0x4016 => (), - 0x4017 => self.step_frame_counter(value), + 0x4017 => self.set_frame_counter(value), _ => panic!("bad address written: 0x{:X}", address), } } @@ -116,7 +118,7 @@ impl Apu { // - - - 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 step_frame_counter(&mut self, value: u8) { + fn set_frame_counter(&mut self, value: u8) { // 0 selects 4-step sequence, 1 selects 5-step sequence if value & (1<<7) == 0 { self.mode = 0; @@ -132,6 +134,27 @@ impl Apu { } + fn step_frame_counter(&mut self) { + match self.frame_counter { + 4 => { + self.square1.clock_envelope(); + self.square2.clock_envelope(); + self.triangle.clock_linear_counter(); + self.noise.clock_envelope(); + if self.current_frame == 1 || self.current_frame == 3 { + + } + if self.current_frame == 3 { + self.issue_irq(); + } + }, + 5 => { + + }, + _ => panic!("invalid frame counter value"), + } + } + fn control(&mut self, value: u8) { // Writing to this register clears the DMC interrupt flag. self.dmc.interrupt = false; @@ -213,4 +236,8 @@ impl Apu { // TODO: If an interrupt flag was set at the same moment of the read, it will read back as 1 but it will not be cleared. val } + + fn issue_irq(&mut self) { + + } } diff --git a/src/apu/noise.rs b/src/apu/noise.rs index a7a3870..290672c 100644 --- a/src/apu/noise.rs +++ b/src/apu/noise.rs @@ -39,6 +39,10 @@ impl Noise { } + pub fn clock_envelope(&mut self) { + + } + pub fn loop_noise(&mut self, value: u8) { } diff --git a/src/apu/triangle.rs b/src/apu/triangle.rs index a7f050b..d0589b7 100644 --- a/src/apu/triangle.rs +++ b/src/apu/triangle.rs @@ -31,4 +31,8 @@ impl Triangle { pub fn counter(&mut self, value: u8) { } + + pub fn clock_linear_counter(&mut self) { + + } } \ No newline at end of file From 359459f5ad6f216fc220ee329bdf053324bef91d Mon Sep 17 00:00:00 2001 From: Theron Date: Wed, 18 Dec 2019 23:35:04 -0600 Subject: [PATCH 13/36] apu --- src/apu/mod.rs | 69 +++++++++++++++++++++++++++++++-------------- src/apu/noise.rs | 4 +++ src/apu/square.rs | 4 +++ src/apu/triangle.rs | 4 +++ 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 3fa51f8..e968a39 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -24,6 +24,9 @@ use dmc::DMC; // No. Don't have to be concerned about the audio device, that's solved by the buffer, and the 44100 samples get fed in batches of 4096 from the large buffer, // when the device needs them, which is accomplished just by calling .resume() before the main loop starts. So a large buffer really should allow for the 60Hz sleep lock. +// 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,866.5 times per second. 894,866.5/44,100=20.29 APU clocks per audio sample. + pub struct Apu { square1: Square, square2: Square, @@ -39,6 +42,8 @@ pub struct Apu { mode: u8, interrupt_inhibit: u8, frame_interrupt: bool, + cycle: usize, + remainder: f64, // keep sample at 44100Hz } struct Envelope { @@ -47,9 +52,7 @@ struct Envelope { delay_level_counter: usize, } -struct FrameCounter { - -} +const FRAME_COUNTER_STEPS: [usize; 5] = [3728, 7456, 11185, 14914, 18640]; impl Apu { pub fn new() -> Self { @@ -70,11 +73,29 @@ impl Apu { mode: 0, interrupt_inhibit: 0, frame_interrupt: false, + cycle: 0, + remainder: 0, } } pub fn step(&mut self) { + 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_866.5/44_100 { // APU frequency over sample frequency + self.sample_audio(); + self.remainder -= 894_866.5/44_100; + } + self.remainder += 1; + + self.cycle += 1; + if (self.frame_counter == 4 && self.cycle == 14915) + || (self.frame_counter == 5 && self.cycle == 18641) { + self.cycle = 0; + } } pub fn write_reg(&mut self, address: usize, value: u8) { @@ -134,24 +155,30 @@ impl Apu { } - fn step_frame_counter(&mut self) { - match self.frame_counter { - 4 => { - self.square1.clock_envelope(); - self.square2.clock_envelope(); - self.triangle.clock_linear_counter(); - self.noise.clock_envelope(); - if self.current_frame == 1 || self.current_frame == 3 { - - } - if self.current_frame == 3 { - self.issue_irq(); - } - }, - 5 => { - - }, - _ => panic!("invalid frame counter value"), + fn clock_frame_counter(&mut self) { + if !(self.frame_counter == 5 && self.current_frame == 4) { + // step envelopes + self.square1.clock_envelope(); + self.square2.clock_envelope(); + self.triangle.clock_linear_counter(); + self.noise.clock_envelope(); + } + if (self.current_frame == 1) + || (self.frame_counter == 4 && self.current_frame == 3) + || (self.frame_counter == 5 && self.current_frame == 4) { + // step length counters and sweep units + self.square1.clock_length_counter(); + self.square2.clock_length_counter(); + self.triangle.clock_length_counter(); + self.noise.clock_length_counter(); + } + if self.frame_counter == 4 && self.current_frame == 3 { + self.issue_irq(); + } + // advance counter + self.current_frame += 1; + if self.current_frame == self.frame_counter { + self.current_frame = 0; } } diff --git a/src/apu/noise.rs b/src/apu/noise.rs index 290672c..5366f51 100644 --- a/src/apu/noise.rs +++ b/src/apu/noise.rs @@ -43,6 +43,10 @@ impl Noise { } + pub fn clock_length_counter(&mut self) { + + } + pub fn loop_noise(&mut self, value: u8) { } diff --git a/src/apu/square.rs b/src/apu/square.rs index 5798834..254566f 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -59,6 +59,10 @@ impl Square { self.divider = self.envelope; // and the divider's period is immediately reloaded } } + + pub fn clock_length_counter(&mut self) { + + } fn clock_divider(&mut self) { // When the divider is clocked while at 0, it is loaded with V and clocks the decay level counter. diff --git a/src/apu/triangle.rs b/src/apu/triangle.rs index d0589b7..52b1d2c 100644 --- a/src/apu/triangle.rs +++ b/src/apu/triangle.rs @@ -35,4 +35,8 @@ impl Triangle { pub fn clock_linear_counter(&mut self) { } + + pub fn clock_length_counter(&mut self) { + + } } \ No newline at end of file From c62c5b9ead3e8a888b3e48c765fe4f803b88f7e2 Mon Sep 17 00:00:00 2001 From: Theron Date: Wed, 18 Dec 2019 23:49:11 -0600 Subject: [PATCH 14/36] typo, todo --- src/apu/mod.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index e968a39..70ddbba 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -25,7 +25,10 @@ use dmc::DMC; // when the device needs them, which is accomplished just by calling .resume() before the main loop starts. So a large buffer really should allow for the 60Hz sleep lock. // 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,866.5 times per second. 894,866.5/44,100=20.29 APU clocks per audio sample. +// 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, @@ -85,9 +88,9 @@ impl Apu { } // push sample to buffer - if self.remainder > 894_866.5/44_100 { // APU frequency over sample frequency + if self.remainder > 894_886.5/44_100 { // APU frequency over sample frequency self.sample_audio(); - self.remainder -= 894_866.5/44_100; + self.remainder -= 894_886.5/44_100; } self.remainder += 1; From 444636f621da83ffc6329cb0aa04b5f2c98754e5 Mon Sep 17 00:00:00 2001 From: Theron Date: Thu, 19 Dec 2019 18:13:48 -0600 Subject: [PATCH 15/36] 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 { From 1e11c224934f8e8cc98ba44cd4735b714beabd1f Mon Sep 17 00:00:00 2001 From: Theron Date: Thu, 19 Dec 2019 18:35:14 -0600 Subject: [PATCH 16/36] apu --- src/apu/mod.rs | 2 +- src/audio.rs | 13 ++++++++++--- src/main.rs | 8 ++++++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 4d4640b..bda4666 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -81,7 +81,7 @@ impl Apu { } } - pub fn step(&mut self) -> Option { + pub fn clock(&mut self) -> Option { let mut sample = None; if (self.frame_counter == 4 && FRAME_COUNTER_STEPS[..4].contains(&self.cycle)) diff --git a/src/audio.rs b/src/audio.rs index 9c7d470..e986c72 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: Vec, + buffer: [f32; 4096*4], head: usize, } @@ -13,7 +13,14 @@ impl AudioCallback for Speaker { for (i, x) in out.iter_mut().enumerate() { *x = self.buffer[i+self.head]; // get data from apu } - self.buffer = self.buffer[4096..].to_vec(); + self.head = (self.head + 4096) % (4096*4) + } +} + +impl Speaker { + pub fn append(&mut self, sample: f32) { + self.buffer[self.head] = sample; + self.head = (self.head + 1) % (4096*4); } } @@ -31,6 +38,6 @@ pub fn initialize(context: &sdl2::Sdl) -> Result Result<(), String> { 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 audio_device = audio::initialize(&sdl_context).expect("Could not create audio device"); let mut half_cycle = false; + audio_device.resume(); // Initialize hardware components let cart = Cartridge::new(); @@ -64,7 +65,10 @@ fn main() -> Result<(), String> { } } for _ in 0..apu_cycles { - cpu.apu.step(); + match cpu.apu.clock() { + Some(sample) => audio_device.speaker.append(sample), + None => (), + } } // clock PPU three times for every CPU cycle for _ in 0..cpu_cycles * 3 { From 69dda36534f778857240cf060a8eb37e9b13c132 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Fri, 20 Dec 2019 00:44:25 -0600 Subject: [PATCH 17/36] apu --- src/audio.rs | 50 +++++++++++++++++++++++--------------------------- src/main.rs | 6 +++--- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index e986c72..f2dc71d 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -2,29 +2,29 @@ extern crate sdl2; use sdl2::audio::{AudioCallback, AudioSpecDesired}; -pub struct Speaker { - buffer: [f32; 4096*4], - head: usize, -} +// pub struct Speaker { +// buffer: [f32; 4096*4], +// head: usize, +// } -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]; // get data from apu - } - self.head = (self.head + 4096) % (4096*4) - } -} +// 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]; // get data from apu +// } +// self.head = (self.head + 4096) % (4096*4) +// } +// } -impl Speaker { - pub fn append(&mut self, sample: f32) { - self.buffer[self.head] = sample; - self.head = (self.head + 1) % (4096*4); - } -} +// impl Speaker { +// pub fn append(&mut self, sample: f32) { +// self.buffer[self.head] = sample; +// self.head = (self.head + 1) % (4096*4); +// } +// } -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 { @@ -33,11 +33,7 @@ pub fn initialize(context: &sdl2::Sdl) -> Result Result<(), String> { } for _ in 0..apu_cycles { match cpu.apu.clock() { - Some(sample) => audio_device.speaker.append(sample), - None => (), - } + Some(sample) => audio_device.queue(&wav), + None => false, + }; } // clock PPU three times for every CPU cycle for _ in 0..cpu_cycles * 3 { From 9c682571776ef47b63de022833cc024d259228be Mon Sep 17 00:00:00 2001 From: Theron Date: Sat, 21 Dec 2019 18:02:32 -0600 Subject: [PATCH 18/36] apu --- src/apu/dmc.rs | 6 +- src/apu/mod.rs | 42 +++++++------- src/apu/noise.rs | 6 +- src/apu/square.rs | 135 +++++++++++++++++++++++++++----------------- src/apu/triangle.rs | 6 +- src/audio.rs | 4 +- src/main.rs | 14 ++++- 7 files changed, 128 insertions(+), 85 deletions(-) diff --git a/src/apu/dmc.rs b/src/apu/dmc.rs index 57e1b75..efcee9d 100644 --- a/src/apu/dmc.rs +++ b/src/apu/dmc.rs @@ -18,7 +18,7 @@ impl DMC { } - pub fn control(&mut self, value: u8) { + pub fn write_control(&mut self, value: u8) { } @@ -26,11 +26,11 @@ impl DMC { } - pub fn sample_address(&mut self, value: u8) { + pub fn write_sample_address(&mut self, value: u8) { } - pub fn sample_length(&mut self, value: u8) { + pub fn write_sample_length(&mut self, value: u8) { } } \ No newline at end of file diff --git a/src/apu/mod.rs b/src/apu/mod.rs index bda4666..63d40eb 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -27,6 +27,8 @@ 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: organize APU structs + pub struct Apu { square1: Square, square2: Square, @@ -54,7 +56,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 +const CYCLES_PER_SAMPLE: f32 = 894_886.5/44_100.0; // APU frequency over sample frequency. May need to turn this down slightly as it's outputting less than 44_100Hz. impl Apu { pub fn new() -> Self { @@ -106,28 +108,28 @@ impl Apu { pub 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), + 0x4000 => self.square1.write_duty(value), + 0x4001 => self.square1.write_sweep(value), + 0x4002 => self.square1.write_timer_low(value), + 0x4003 => self.square1.write_timer_high(value), + 0x4004 => self.square2.write_duty(value), + 0x4005 => self.square2.write_sweep(value), + 0x4006 => self.square2.write_timer_low(value), + 0x4007 => self.square2.write_timer_high(value), + 0x4008 => self.triangle.write_counter(value), 0x4009 => (), - 0x400A => self.triangle.timer_low(value), - 0x400B => self.triangle.timer_high(value), - 0x400C => self.noise.envelope(value), + 0x400A => self.triangle.write_timer_low(value), + 0x400B => self.triangle.write_timer_high(value), + 0x400C => self.noise.write_envelope(value), 0x400D => (), - 0x400E => self.noise.loop_noise(value), - 0x400F => self.noise.load_length_counter(value), - 0x4010 => self.dmc.control(value), + 0x400E => self.noise.write_loop_noise(value), + 0x400F => self.noise.write_length_counter(value), + 0x4010 => self.dmc.write_control(value), 0x4011 => self.dmc.direct_load(value), - 0x4012 => self.dmc.sample_address(value), - 0x4013 => self.dmc.sample_length(value), + 0x4012 => self.dmc.write_sample_address(value), + 0x4013 => self.dmc.write_sample_length(value), 0x4014 => (), - 0x4015 => self.control(value), + 0x4015 => self.write_control(value), 0x4016 => (), 0x4017 => self.set_frame_counter(value), _ => panic!("bad address written: 0x{:X}", address), @@ -188,7 +190,7 @@ impl Apu { } } - fn control(&mut self, value: u8) { + fn write_control(&mut self, value: u8) { // Writing to this register clears the DMC interrupt flag. self.dmc.interrupt = false; // Writing a zero to any of the channel enable bits will silence that channel and immediately set its length counter to 0. diff --git a/src/apu/noise.rs b/src/apu/noise.rs index 5366f51..defe901 100644 --- a/src/apu/noise.rs +++ b/src/apu/noise.rs @@ -35,7 +35,7 @@ impl Noise { self.linear_feedback_sr |= feedback << 14; } - pub fn envelope(&mut self, value: u8) { + pub fn write_envelope(&mut self, value: u8) { } @@ -47,10 +47,10 @@ impl Noise { } - pub fn loop_noise(&mut self, value: u8) { + pub fn write_loop_noise(&mut self, value: u8) { } - pub fn load_length_counter(&mut self, value: u8) { + pub fn write_length_counter(&mut self, value: u8) { } } diff --git a/src/apu/square.rs b/src/apu/square.rs index 254566f..475bb72 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -7,45 +7,80 @@ const DUTY_CYCLE_SEQUENCES: [[u8; 8]; 4] = [ pub struct Square { pub sample: u16, - duty_cycle: [u8; 8], - duty_counter: u8, - 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: u16, - pub length_counter: u8, - envelope: u8, - sweep: u8, + divider: u16, pub enabled: bool, - decay_counter: u8, + + duty_cycle: [u8; 8], + duty_counter: usize, + + envelope: u16, start: bool, - divider: u8, + decay_counter: u16, + constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume) + + length_counter_halt: bool, // (this bit is also the envelope's loop flag) + pub length_counter: u8, + + timer: u16, + timer_period: u16, + sweep: u8, + sweep_divider: u8, + shift_count: u8, + sweep_adder_overflow: bool, + sweep_enabled: bool, + sweep_negate: bool, + sweep_reload: bool, } impl Square { pub fn new() -> Self { Square { + sample: 0, + divider: 0, + enabled: false, + duty_cycle: DUTY_CYCLE_SEQUENCES[0], duty_counter: 0, - length_counter_halt: false, - constant_volume_flag: false, - timer: 0, - length_counter: 0, + envelope: 0, - sweep: 0, - sample: 0, - enabled: false, - decay_counter: 0, start: false, - divider: 0, + decay_counter: 0, + constant_volume_flag: false, + + timer: 0, + timer_period: 0, + sweep: 0, + sweep_divider: 0, + shift_count: 0, + sweep_adder_overflow: false, + sweep_enabled: false, + sweep_negate: false, + sweep_reload: false, + + length_counter: 0, + length_counter_halt: false, } } pub fn clock(&mut self) { - - } - - pub fn clock_frame_counter(&mut self) { - + // The sequencer is clocked by an 11-bit timer. Given the timer value t = HHHLLLLLLLL formed by timer high and timer low, this timer is updated every APU cycle + // (i.e., every second CPU cycle), and counts t, t-1, ..., 0, t, t-1, ..., clocking the waveform generator when it goes from 0 to t. + if self.timer == 0 { + self.timer = self.timer_period; + self.duty_counter = (self.duty_counter + 1) % 8; + } else { + self.timer -= 1; + } + // Update volume for this channel + // The mixer receives the current envelope volume except when + self.sample = if self.duty_cycle[self.duty_counter] == 0 // The sequencer output is zero, or + || self.sweep_adder_overflow // overflow from the sweep unit's adder is silencing the channel, + || self.length_counter == 0 // the length counter is zero, or + || self.timer < 8 { // the timer has a value less than eight. + 0 + } else { + self.decay_counter + }; } pub fn clock_envelope(&mut self) { @@ -80,50 +115,46 @@ impl Square { } } + pub fn clock_sweep(&mut self) { + + } + // $4000/$4004 - pub fn duty(&mut self, value: u8) { + pub fn write_duty(&mut self, value: u8) { + // TODO: The duty cycle is changed (see table below), but the sequencer's current position isn't affected. self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize]; self.length_counter_halt = value & (1<<5) != 0; self.constant_volume_flag = value & (1<<4) != 0; if self.constant_volume_flag { - self.envelope = value & 0b1111; + self.envelope = value as u16 & 0b1111; } else { self.envelope = self.decay_counter; } } // $4001/$4005 - pub fn sweep(&mut self, value: u8) { - + pub fn write_sweep(&mut self, value: u8) { + self.sweep_enabled = value >> 7 == 1; + self.sweep_divider = value >> 4 & 0b111; + self.sweep_negate = value & 0b1000 != 0; + self.shift_count = value & 0b111; } // $4002/$4006 - pub fn timer_low(&mut self, value: u8) { - + pub fn write_timer_low(&mut self, value: u8) { + self.timer &= 0b11111111_00000000; + self.timer |= value as u16; } // $4003/$4007 - pub fn timer_high(&mut self, value: u8) { - + pub fn write_timer_high(&mut self, value: u8) { + // LLLL.Lttt Pulse channel 1 length counter load and timer (write) + self.length_counter = value >> 3; + let timer_high = value as u16 & 0b0000_0111; + self.timer &= 0b11111000_11111111; // mask off high 3 bits of 11-bit timer + self.timer |= timer_high << 8; // apply high timer bits in their place + // The sequencer is immediately restarted at the first value of the current sequence. The envelope is also restarted. The period divider is not reset. + self.duty_counter = 0; + self.start = true; } } - -struct EnvelopeGenerator { - -} - -struct SweepUnit { - -} - -struct Timer { - -} - -struct Sequencer { - -} - -struct LengthCounter { - -} diff --git a/src/apu/triangle.rs b/src/apu/triangle.rs index 52b1d2c..e267b21 100644 --- a/src/apu/triangle.rs +++ b/src/apu/triangle.rs @@ -20,15 +20,15 @@ impl Triangle { } } - pub fn timer_low(&mut self, value: u8) { + pub fn write_timer_low(&mut self, value: u8) { } - pub fn timer_high(&mut self, value: u8) { + pub fn write_timer_high(&mut self, value: u8) { } - pub fn counter(&mut self, value: u8) { + pub fn write_counter(&mut self, value: u8) { } diff --git a/src/audio.rs b/src/audio.rs index f2dc71d..236734a 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -29,8 +29,8 @@ pub fn initialize(context: &sdl2::Sdl) -> Result, S let desired_spec = AudioSpecDesired { freq: Some(44_100), - channels: Some(1), // mono - samples: Some(4096), // default sample size + channels: Some(1), // mono + samples: None, // default sample size }; audio_subsystem.open_queue(None, &desired_spec) diff --git a/src/main.rs b/src/main.rs index 69727e5..52ddc7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,7 @@ fn main() -> Result<(), String> { // Set up audio let mut audio_device = audio::initialize(&sdl_context).expect("Could not create audio device"); let mut half_cycle = false; + let mut audio_buffer = Vec::::new(); audio_device.resume(); // Initialize hardware components @@ -49,6 +50,7 @@ fn main() -> Result<(), String> { let mut timer = Instant::now(); let mut fps_timer = Instant::now(); let mut fps = 0; + let mut sps = 0; // PROFILER.lock().unwrap().start("./main.profile").unwrap(); 'running: loop { @@ -66,10 +68,14 @@ fn main() -> Result<(), String> { } for _ in 0..apu_cycles { match cpu.apu.clock() { - Some(sample) => audio_device.queue(&wav), - None => false, + Some(sample) => {sps += 1; audio_buffer.push(sample)}, + None => (), }; } + if audio_buffer.len() == 44_100 { + audio_device.queue(&audio_buffer); + audio_buffer = vec![]; + } // clock PPU three times for every CPU cycle for _ in 0..cpu_cycles * 3 { let (pixel, end_of_frame) = cpu.ppu.clock(); @@ -107,6 +113,10 @@ fn main() -> Result<(), String> { println!("fps: {}", fps); fps = 0; fps_timer = now; + + println!("samples per second: {}", sps); + sps = 0; + } } // PROFILER.lock().unwrap().stop().unwrap(); From 25cf5fceae90aece1b63bb9596a621d45182c361 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Sat, 21 Dec 2019 22:14:47 -0600 Subject: [PATCH 19/36] apu --- src/apu/mod.rs | 6 +++-- src/apu/square.rs | 56 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 63d40eb..d8f131b 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -63,8 +63,8 @@ impl Apu { let square_table = (0..31).map(|x| 95.52/(8128.0 / x as f32) + 100.0).collect(); let tnd_table = (0..203).map(|x| 163.67/(24329.0 / x as f32) + 100.0).collect(); Apu { - square1: Square::new(), - square2: Square::new(), + square1: Square::new(false), + square2: Square::new(true), triangle: Triangle::new(), noise: Noise::new(), dmc: DMC::new(), @@ -175,6 +175,8 @@ impl Apu { || (self.frame_counter == 4 && self.current_frame == 3) || (self.frame_counter == 5 && self.current_frame == 4) { // step length counters and sweep units + self.square1.clock_sweep(); + self.square2.clock_sweep(); self.square1.clock_length_counter(); self.square2.clock_length_counter(); self.triangle.clock_length_counter(); diff --git a/src/apu/square.rs b/src/apu/square.rs index 475bb72..d4459c1 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -7,58 +7,61 @@ const DUTY_CYCLE_SEQUENCES: [[u8; 8]; 4] = [ pub struct Square { pub sample: u16, - divider: u16, pub enabled: bool, duty_cycle: [u8; 8], duty_counter: usize, envelope: u16, - start: bool, + divider: u16, decay_counter: u16, constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume) + start: bool, length_counter_halt: bool, // (this bit is also the envelope's loop flag) pub length_counter: u8, timer: u16, timer_period: u16, - sweep: u8, - sweep_divider: u8, + sweep_divider: u8, // Period, P shift_count: u8, - sweep_adder_overflow: bool, + sweep_counter: u8, sweep_enabled: bool, sweep_negate: bool, sweep_reload: bool, + + second_channel: bool, // hack to detect timing difference in clock_sweep() } impl Square { - pub fn new() -> Self { + pub fn new(second_channel: bool) -> Self { Square { sample: 0, - divider: 0, enabled: false, duty_cycle: DUTY_CYCLE_SEQUENCES[0], duty_counter: 0, envelope: 0, - start: false, + divider: 0, decay_counter: 0, constant_volume_flag: false, + start: false, timer: 0, timer_period: 0, - sweep: 0, sweep_divider: 0, shift_count: 0, - sweep_adder_overflow: false, + sweep_period: 0, + sweep_counter: 0, sweep_enabled: false, sweep_negate: false, sweep_reload: false, length_counter: 0, length_counter_halt: false, + + second_channel: second_channel, } } @@ -74,7 +77,7 @@ impl Square { // Update volume for this channel // The mixer receives the current envelope volume except when self.sample = if self.duty_cycle[self.duty_counter] == 0 // The sequencer output is zero, or - || self.sweep_adder_overflow // overflow from the sweep unit's adder is silencing the channel, + || self.sweep_period > 0x7FF // overflow from the sweep unit's adder is silencing the channel, || self.length_counter == 0 // the length counter is zero, or || self.timer < 8 { // the timer has a value less than eight. 0 @@ -116,7 +119,25 @@ impl Square { } pub fn clock_sweep(&mut self) { + // If the divider's counter is zero, the sweep is enabled, and the sweep unit is not muting the channel: The pulse's period is adjusted. + if self.sweep_counter == 0 { + self.sweep_enabled == true; + } + } + fn adjust_sweep(&mut self) { + let change = self.timer_period >> self.shift_count; + if self.sweep_negate { + self.timer_period -= change; + if self.second_channel { + self.timer_period -= 1; + } + } else { + self.timer_period += change; + } + if self.sweep_counter == 0 { + self.sweep_enabled = true; + } } // $4000/$4004 @@ -125,24 +146,25 @@ impl Square { self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize]; self.length_counter_halt = value & (1<<5) != 0; self.constant_volume_flag = value & (1<<4) != 0; - if self.constant_volume_flag { - self.envelope = value as u16 & 0b1111; + self.envelope = if self.constant_volume_flag { + value as u16 & 0b1111 } else { - self.envelope = self.decay_counter; - } + self.decay_counter + }; } // $4001/$4005 pub fn write_sweep(&mut self, value: u8) { self.sweep_enabled = value >> 7 == 1; - self.sweep_divider = value >> 4 & 0b111; + self.sweep_divider = ((value >> 4) & 0b111) + 1; self.sweep_negate = value & 0b1000 != 0; self.shift_count = value & 0b111; + self.sweep_reload = true; } // $4002/$4006 pub fn write_timer_low(&mut self, value: u8) { - self.timer &= 0b11111111_00000000; + self.timer &= 0b00000111_00000000; self.timer |= value as u16; } From b128ad485ef421004e49536d0765f122383f98d7 Mon Sep 17 00:00:00 2001 From: Theron Date: Mon, 23 Dec 2019 17:46:14 -0600 Subject: [PATCH 20/36] apu --- src/apu/mod.rs | 68 +++++++++++++++++++-------------------- src/apu/square.rs | 81 +++++++++++++++++++++++++++-------------------- src/main.rs | 1 + 3 files changed, 82 insertions(+), 68 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index d8f131b..38f24ff 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -29,6 +29,13 @@ use dmc::DMC; // TODO: organize APU structs +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. May need to turn this down slightly as it's outputting less than 44_100Hz. +const LENGTH_COUNTER_TABLE: [u8; 32] = [ + 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, + 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30, +]; + pub struct Apu { square1: Square, square2: Square, @@ -41,7 +48,6 @@ pub struct Apu { frame_counter: u8, current_frame: u8, - mode: u8, interrupt_inhibit: bool, frame_interrupt: bool, cycle: usize, @@ -49,15 +55,6 @@ pub struct Apu { pub trigger_irq: bool, } -struct Envelope { - start_flag: bool, - divider: usize, - delay_level_counter: usize, -} - -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. May need to turn this down slightly as it's outputting less than 44_100Hz. - impl Apu { pub fn new() -> Self { let square_table = (0..31).map(|x| 95.52/(8128.0 / x as f32) + 100.0).collect(); @@ -74,7 +71,6 @@ impl Apu { frame_counter: 0, current_frame: 0, - mode: 0, interrupt_inhibit: false, frame_interrupt: false, cycle: 0, @@ -131,7 +127,7 @@ impl Apu { 0x4014 => (), 0x4015 => self.write_control(value), 0x4016 => (), - 0x4017 => self.set_frame_counter(value), + 0x4017 => self.write_frame_counter(value), _ => panic!("bad address written: 0x{:X}", address), } } @@ -139,25 +135,10 @@ impl Apu { fn mix(&self) -> f32 { let square_out = self.square_table[(self.square1.sample + self.square2.sample) as usize]; let tnd_out = self.tnd_table[((3*self.triangle.sample)+(2*self.noise.sample) + self.dmc.sample) as usize]; + // println!("square: {}, tnd: {}", square_out, tnd_out); square_out + tnd_out } - fn set_frame_counter(&mut self, value: u8) { - // 0 selects 4-step sequence, 1 selects 5-step sequence - if value & (1<<7) == 0 { - self.mode = 0; - self.frame_counter = 4; - } else { - self.mode = 1; - self.frame_counter = 5; - } - // If set, the frame interrupt flag is cleared, otherwise it is unaffected. - if value & (1<<6) != 0 { - self.interrupt_inhibit = false; - } - - } - // mode 0: mode 1: function // --------- ----------- ----------------------------- // - - - f - - - - - IRQ (if bit 6 is clear) @@ -192,6 +173,7 @@ impl Apu { } } + // CPU writes to $4015 fn write_control(&mut self, value: u8) { // Writing to this register clears the DMC interrupt flag. self.dmc.interrupt = false; @@ -204,27 +186,21 @@ impl Apu { } if value & (1<<1) != 0 { self.square2.enabled = true; - } else { self.square2.enabled = false; self.square2.length_counter = 0; - } if value & (1<<2) != 0 { self.triangle.enabled = true; - } else { self.triangle.enabled = false; self.triangle.length_counter = 0; - } if value & (1<<3) != 0 { self.noise.enabled = true; - } else { self.noise.enabled = false; self.noise.length_counter = 0; - } if value & (1<<4) != 0 { self.dmc.enabled = true; @@ -241,6 +217,7 @@ impl Apu { } } + // CPU reads from $4015 pub fn read_status(&mut self) -> u8 { // IF-D NT21: DMC interrupt (I), frame interrupt (F), DMC active (D), length counter > 0 (N/T/2/1) let mut val = 0; @@ -274,4 +251,27 @@ impl Apu { val } + // $4017 + fn write_frame_counter(&mut self, value: u8) { + // 0 selects 4-step sequence, 1 selects 5-step sequence + self.frame_counter = if value & (1<<7) == 0 { 4 } else { 5 }; + // If set, the frame interrupt flag is cleared, otherwise it is unaffected. + if value & (1<<6) != 0 { + self.interrupt_inhibit = false; + } + // If the mode flag is set, then both "quarter frame" and "half frame" signals are also generated. + if self.frame_counter == 5 { + // Clock envelopes, length counters, and sweep units + self.square1.clock_envelope(); + self.square1.clock_sweep(); + self.square1.clock_length_counter(); + self.square2.clock_envelope(); + self.square2.clock_sweep(); + self.square2.clock_length_counter(); + self.triangle.clock_linear_counter(); + self.triangle.clock_length_counter(); + self.noise.clock_envelope(); + self.noise.clock_length_counter(); + } + } } diff --git a/src/apu/square.rs b/src/apu/square.rs index d4459c1..a67ed6c 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -13,7 +13,7 @@ pub struct Square { duty_counter: usize, envelope: u16, - divider: u16, + envelope_divider: u16, decay_counter: u16, constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume) start: bool, @@ -23,9 +23,9 @@ pub struct Square { timer: u16, timer_period: u16, - sweep_divider: u8, // Period, P + sweep_divider: u16, // Period, P + sweep_counter: u16, shift_count: u8, - sweep_counter: u8, sweep_enabled: bool, sweep_negate: bool, sweep_reload: bool, @@ -43,7 +43,7 @@ impl Square { duty_counter: 0, envelope: 0, - divider: 0, + envelope_divider: 0, decay_counter: 0, constant_volume_flag: false, start: false, @@ -51,9 +51,8 @@ impl Square { timer: 0, timer_period: 0, sweep_divider: 0, - shift_count: 0, - sweep_period: 0, sweep_counter: 0, + shift_count: 0, sweep_enabled: false, sweep_negate: false, sweep_reload: false, @@ -77,9 +76,9 @@ impl Square { // Update volume for this channel // The mixer receives the current envelope volume except when self.sample = if self.duty_cycle[self.duty_counter] == 0 // The sequencer output is zero, or - || self.sweep_period > 0x7FF // overflow from the sweep unit's adder is silencing the channel, - || self.length_counter == 0 // the length counter is zero, or - || self.timer < 8 { // the timer has a value less than eight. + || self.sweep_divider > 0x7FF // overflow from the sweep unit's adder is silencing the channel, + || self.length_counter == 0 // the length counter is zero, or + || self.timer < 8 { // the timer has a value less than eight. 0 } else { self.decay_counter @@ -90,22 +89,24 @@ impl Square { // When clocked by the frame counter, one of two actions occurs: // if the start flag is clear, the divider is clocked, if !self.start { - self.clock_divider(); + self.clock_envelope_divider(); } else { self.start = false; // otherwise the start flag is cleared, self.decay_counter = 15; // the decay level counter is loaded with 15, - self.divider = self.envelope; // and the divider's period is immediately reloaded + self.envelope_divider = self.envelope; // and the divider's period is immediately reloaded } } pub fn clock_length_counter(&mut self) { - + if !(self.length_counter == 0 || self.length_counter_halt) { + self.length_counter -= 1; + } } - fn clock_divider(&mut self) { + fn clock_envelope_divider(&mut self) { // When the divider is clocked while at 0, it is loaded with V and clocks the decay level counter. - if self.divider == 0 { - self.divider = self.envelope; + if self.envelope_divider == 0 { + self.envelope_divider = self.envelope; // Then one of two actions occurs: If the counter is non-zero, it is decremented, if self.decay_counter != 0 { self.decay_counter -= 1; @@ -114,30 +115,39 @@ impl Square { self.decay_counter = 15; } } else { - self.divider -= 1; + self.envelope_divider -= 1; } } pub fn clock_sweep(&mut self) { + // The sweep unit continuously calculates each channel's target period in this way: + // A barrel shifter shifts the channel's 11-bit raw timer period right by the shift count, producing the change amount. + let change = self.timer_period >> self.shift_count; + // If the negate flag is true, the change amount is made negative. + // The target period is the sum of the current period and the change amount. + if self.sweep_negate { + self.sweep_divider = self.timer_period - change; + if self.second_channel { + self.sweep_divider -= 1; + } + } else { + self.sweep_divider = self.timer_period + change; + } // If the divider's counter is zero, the sweep is enabled, and the sweep unit is not muting the channel: The pulse's period is adjusted. - if self.sweep_counter == 0 { - self.sweep_enabled == true; + if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer < 8 || self.sweep_divider > 0x7FF) { + self.adjust_sweep(); + } + // If the divider's counter is zero or the reload flag is true: The counter is set to P and the reload flag is cleared. Otherwise, the counter is decremented. + if self.sweep_counter == 0 || self.sweep_reload { + self.sweep_counter = self.sweep_divider; + self.sweep_reload = false; + } else { + self.sweep_counter -= 1; } } fn adjust_sweep(&mut self) { - let change = self.timer_period >> self.shift_count; - if self.sweep_negate { - self.timer_period -= change; - if self.second_channel { - self.timer_period -= 1; - } - } else { - self.timer_period += change; - } - if self.sweep_counter == 0 { - self.sweep_enabled = true; - } + self.timer_period = self.sweep_divider; } // $4000/$4004 @@ -156,7 +166,7 @@ impl Square { // $4001/$4005 pub fn write_sweep(&mut self, value: u8) { self.sweep_enabled = value >> 7 == 1; - self.sweep_divider = ((value >> 4) & 0b111) + 1; + self.sweep_divider = ((value as u16 >> 4) & 0b111) + 1; self.sweep_negate = value & 0b1000 != 0; self.shift_count = value & 0b111; self.sweep_reload = true; @@ -164,19 +174,22 @@ impl Square { // $4002/$4006 pub fn write_timer_low(&mut self, value: u8) { - self.timer &= 0b00000111_00000000; - self.timer |= value as u16; + self.timer &= 0b00000111_00000000; // mask off everything but high 3 bits of 11-bit timer + self.timer |= value as u16; // apply low 8 bits } // $4003/$4007 pub fn write_timer_high(&mut self, value: u8) { // LLLL.Lttt Pulse channel 1 length counter load and timer (write) - self.length_counter = value >> 3; + if self.enabled { + self.length_counter = super::LENGTH_COUNTER_TABLE[value as usize >> 3]; + } let timer_high = value as u16 & 0b0000_0111; self.timer &= 0b11111000_11111111; // mask off high 3 bits of 11-bit timer self.timer |= timer_high << 8; // apply high timer bits in their place // The sequencer is immediately restarted at the first value of the current sequence. The envelope is also restarted. The period divider is not reset. self.duty_counter = 0; self.start = true; + self.duty_counter = 0; } } diff --git a/src/main.rs b/src/main.rs index 52ddc7b..ca98a16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -73,6 +73,7 @@ fn main() -> Result<(), String> { }; } if audio_buffer.len() == 44_100 { + println!("queueing: {:?}", &audio_buffer[..32]); audio_device.queue(&audio_buffer); audio_buffer = vec![]; } From bf2317dc28b77ef291c685b5acaf254424e370fb Mon Sep 17 00:00:00 2001 From: Theron Date: Wed, 25 Dec 2019 21:51:54 -0600 Subject: [PATCH 21/36] apu --- src/apu/dmc.rs | 3 +++ src/apu/mod.rs | 12 ++++++++++-- src/apu/square.rs | 1 + src/apu/triangle.rs | 4 ++++ src/main.rs | 10 ++++++++-- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/apu/dmc.rs b/src/apu/dmc.rs index efcee9d..a181f7f 100644 --- a/src/apu/dmc.rs +++ b/src/apu/dmc.rs @@ -17,6 +17,9 @@ impl DMC { } } + pub fn clock(&mut self) { + + } pub fn write_control(&mut self, value: u8) { diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 38f24ff..0332dd5 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -57,8 +57,10 @@ pub struct Apu { impl Apu { pub fn new() -> Self { - let square_table = (0..31).map(|x| 95.52/(8128.0 / x as f32) + 100.0).collect(); - let tnd_table = (0..203).map(|x| 163.67/(24329.0 / x as f32) + 100.0).collect(); + let square_table = (0..31).map(|x| 95.52/((8128.0 / x as f32) + 100.0)).collect(); + let tnd_table = (0..203).map(|x| 163.67/((24329.0 / x as f32) + 100.0)).collect(); + println!("square_table: {:?}", square_table); + println!("tnd_table: {:?}", tnd_table); Apu { square1: Square::new(false), square2: Square::new(true), @@ -82,6 +84,12 @@ impl Apu { pub fn clock(&mut self) -> Option { let mut sample = None; + self.square1.clock(); + self.square2.clock(); + self.triangle.clock(); + self.noise.clock(); + self.dmc.clock(); + 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(); diff --git a/src/apu/square.rs b/src/apu/square.rs index a67ed6c..0063ce0 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -83,6 +83,7 @@ impl Square { } else { self.decay_counter }; + println!("{}", self.sample); } pub fn clock_envelope(&mut self) { diff --git a/src/apu/triangle.rs b/src/apu/triangle.rs index e267b21..a245772 100644 --- a/src/apu/triangle.rs +++ b/src/apu/triangle.rs @@ -32,6 +32,10 @@ impl Triangle { } + pub fn clock(&mut self) { + + } + pub fn clock_linear_counter(&mut self) { } diff --git a/src/main.rs b/src/main.rs index ca98a16..a70b7e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,12 +68,18 @@ fn main() -> Result<(), String> { } for _ in 0..apu_cycles { match cpu.apu.clock() { - Some(sample) => {sps += 1; audio_buffer.push(sample)}, + Some(sample) => { + sps += 1; + // if sample != 0.0 { + // println!("sample {}", sample); + // } + audio_buffer.push(sample) + }, None => (), }; } if audio_buffer.len() == 44_100 { - println!("queueing: {:?}", &audio_buffer[..32]); + // println!("queueing: {:?}", &audio_buffer[..32]); audio_device.queue(&audio_buffer); audio_buffer = vec![]; } From 686d45f8d3c8e8fbc11af4452e02e6c6e5458aa2 Mon Sep 17 00:00:00 2001 From: Theron Date: Mon, 30 Dec 2019 20:10:27 -0600 Subject: [PATCH 22/36] fixed timer_period bug, cleaned up. square channels kind of working. need to figure out how to sample properly. --- src/apu/mod.rs | 4 +++- src/apu/square.rs | 21 ++++++++------------- src/audio.rs | 24 ------------------------ src/main.rs | 23 +++++++++++------------ 4 files changed, 22 insertions(+), 50 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 0332dd5..c2ab8e2 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -30,7 +30,8 @@ use dmc::DMC; // TODO: organize APU structs 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. May need to turn this down slightly as it's outputting less than 44_100Hz. +// const CYCLES_PER_SAMPLE: f32 = 894_886.5/44_100.0; // APU frequency over sample frequency. May need to turn this down slightly as it's outputting less than 44_100Hz. +const CYCLES_PER_SAMPLE: f32 = 19.65; const LENGTH_COUNTER_TABLE: [u8; 32] = [ 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30, @@ -111,6 +112,7 @@ impl Apu { } pub fn write_reg(&mut self, address: usize, value: u8) { + // println!("writing 0b{:08b} to 0x{:X}", value, address); match address { 0x4000 => self.square1.write_duty(value), 0x4001 => self.square1.write_sweep(value), diff --git a/src/apu/square.rs b/src/apu/square.rs index 0063ce0..1ccfe81 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -75,7 +75,7 @@ impl Square { } // Update volume for this channel // The mixer receives the current envelope volume except when - self.sample = if self.duty_cycle[self.duty_counter] == 0 // The sequencer output is zero, or + self.sample = if self.duty_cycle[self.duty_counter] == 0 // the sequencer output is zero, or || self.sweep_divider > 0x7FF // overflow from the sweep unit's adder is silencing the channel, || self.length_counter == 0 // the length counter is zero, or || self.timer < 8 { // the timer has a value less than eight. @@ -83,7 +83,6 @@ impl Square { } else { self.decay_counter }; - println!("{}", self.sample); } pub fn clock_envelope(&mut self) { @@ -101,7 +100,7 @@ impl Square { pub fn clock_length_counter(&mut self) { if !(self.length_counter == 0 || self.length_counter_halt) { self.length_counter -= 1; - } + } } fn clock_envelope_divider(&mut self) { @@ -136,7 +135,7 @@ impl Square { } // If the divider's counter is zero, the sweep is enabled, and the sweep unit is not muting the channel: The pulse's period is adjusted. if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer < 8 || self.sweep_divider > 0x7FF) { - self.adjust_sweep(); + self.timer_period = self.sweep_divider; } // If the divider's counter is zero or the reload flag is true: The counter is set to P and the reload flag is cleared. Otherwise, the counter is decremented. if self.sweep_counter == 0 || self.sweep_reload { @@ -147,13 +146,9 @@ impl Square { } } - fn adjust_sweep(&mut self) { - self.timer_period = self.sweep_divider; - } - // $4000/$4004 pub fn write_duty(&mut self, value: u8) { - // TODO: The duty cycle is changed (see table below), but the sequencer's current position isn't affected. + // The duty cycle is changed (see table below), but the sequencer's current position isn't affected. self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize]; self.length_counter_halt = value & (1<<5) != 0; self.constant_volume_flag = value & (1<<4) != 0; @@ -175,8 +170,8 @@ impl Square { // $4002/$4006 pub fn write_timer_low(&mut self, value: u8) { - self.timer &= 0b00000111_00000000; // mask off everything but high 3 bits of 11-bit timer - self.timer |= value as u16; // apply low 8 bits + self.timer_period &= 0b00000111_00000000; // mask off everything but high 3 bits of 11-bit timer + self.timer_period |= value as u16; // apply low 8 bits } // $4003/$4007 @@ -186,8 +181,8 @@ impl Square { self.length_counter = super::LENGTH_COUNTER_TABLE[value as usize >> 3]; } let timer_high = value as u16 & 0b0000_0111; - self.timer &= 0b11111000_11111111; // mask off high 3 bits of 11-bit timer - self.timer |= timer_high << 8; // apply high timer bits in their place + self.timer_period &= 0b11111000_11111111; // mask off high 3 bits of 11-bit timer + self.timer_period |= timer_high << 8; // apply high timer bits in their place // The sequencer is immediately restarted at the first value of the current sequence. The envelope is also restarted. The period divider is not reset. self.duty_counter = 0; self.start = true; diff --git a/src/audio.rs b/src/audio.rs index 236734a..1fde837 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -2,28 +2,6 @@ extern crate sdl2; use sdl2::audio::{AudioCallback, AudioSpecDesired}; -// pub struct Speaker { -// buffer: [f32; 4096*4], -// head: usize, -// } - -// 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]; // get data from apu -// } -// self.head = (self.head + 4096) % (4096*4) -// } -// } - -// impl Speaker { -// pub fn append(&mut self, sample: f32) { -// self.buffer[self.head] = sample; -// self.head = (self.head + 1) % (4096*4); -// } -// } - pub fn initialize(context: &sdl2::Sdl) -> Result, String> { let audio_subsystem = context.audio()?; @@ -35,5 +13,3 @@ pub fn initialize(context: &sdl2::Sdl) -> Result, S audio_subsystem.open_queue(None, &desired_spec) } - -// problem is: how to get data into callback from outside? can't change its signature. can't sneak it in through struct as struct is consumed by the audio device diff --git a/src/main.rs b/src/main.rs index a70b7e8..342f6a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,6 @@ fn main() -> Result<(), String> { // Set up audio let mut audio_device = audio::initialize(&sdl_context).expect("Could not create audio device"); let mut half_cycle = false; - let mut audio_buffer = Vec::::new(); audio_device.resume(); // Initialize hardware components @@ -70,19 +69,11 @@ fn main() -> Result<(), String> { match cpu.apu.clock() { Some(sample) => { sps += 1; - // if sample != 0.0 { - // println!("sample {}", sample); - // } - audio_buffer.push(sample) + audio_device.queue(&vec![sample]); }, None => (), }; } - if audio_buffer.len() == 44_100 { - // println!("queueing: {:?}", &audio_buffer[..32]); - audio_device.queue(&audio_buffer); - audio_buffer = vec![]; - } // clock PPU three times for every CPU cycle for _ in 0..cpu_cycles * 3 { let (pixel, end_of_frame) = cpu.ppu.clock(); @@ -130,5 +121,13 @@ fn main() -> Result<(), String> { Ok(()) } -// TODO: reset function? -// TODO: save/load functionality +/* +TODO: +- remaining APU channels +- common mappers +- battery-backed RAM solution +- GUI? drag and drop ROMs? +- reset function +- save/load/pause functionality + +*/ From f936258310491e3134d2939f238120a386052d90 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Tue, 31 Dec 2019 12:57:02 -0600 Subject: [PATCH 23/36] troubleshooting sweep and envelope units, hopefully --- src/apu/square.rs | 60 ++++++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/apu/square.rs b/src/apu/square.rs index 1ccfe81..0b359cd 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -23,6 +23,8 @@ pub struct Square { timer: u16, timer_period: u16, + + target_period: u16, sweep_divider: u16, // Period, P sweep_counter: u16, shift_count: u8, @@ -50,6 +52,8 @@ impl Square { timer: 0, timer_period: 0, + + target_period: 0, sweep_divider: 0, sweep_counter: 0, shift_count: 0, @@ -76,10 +80,12 @@ impl Square { // Update volume for this channel // The mixer receives the current envelope volume except when self.sample = if self.duty_cycle[self.duty_counter] == 0 // the sequencer output is zero, or - || self.sweep_divider > 0x7FF // overflow from the sweep unit's adder is silencing the channel, + || self.target_period > 0x7FF // overflow from the sweep unit's adder is silencing the channel, || self.length_counter == 0 // the length counter is zero, or - || self.timer < 8 { // the timer has a value less than eight. + || self.timer_period < 8 { // the timer has a value less than eight. 0 + } else if self.constant_volume_flag { + self.envelope } else { self.decay_counter }; @@ -96,12 +102,6 @@ impl Square { self.envelope_divider = self.envelope; // and the divider's period is immediately reloaded } } - - pub fn clock_length_counter(&mut self) { - if !(self.length_counter == 0 || self.length_counter_halt) { - self.length_counter -= 1; - } - } fn clock_envelope_divider(&mut self) { // When the divider is clocked while at 0, it is loaded with V and clocks the decay level counter. @@ -119,23 +119,18 @@ impl Square { } } - pub fn clock_sweep(&mut self) { - // The sweep unit continuously calculates each channel's target period in this way: - // A barrel shifter shifts the channel's 11-bit raw timer period right by the shift count, producing the change amount. - let change = self.timer_period >> self.shift_count; - // If the negate flag is true, the change amount is made negative. - // The target period is the sum of the current period and the change amount. - if self.sweep_negate { - self.sweep_divider = self.timer_period - change; - if self.second_channel { - self.sweep_divider -= 1; - } - } else { - self.sweep_divider = self.timer_period + change; + pub fn clock_length_counter(&mut self) { + if !(self.length_counter == 0 || self.length_counter_halt) { + self.length_counter -= 1; } + } + + pub fn clock_sweep(&mut self) { + // When the frame counter sends a half-frame clock (at 120 or 96 Hz), two things happen. // If the divider's counter is zero, the sweep is enabled, and the sweep unit is not muting the channel: The pulse's period is adjusted. - if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer < 8 || self.sweep_divider > 0x7FF) { - self.timer_period = self.sweep_divider; + if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer_period < 8 || self.target_period > 0x7FF) { + self.timer_period = self.target_period; + println!("timer period adjusted to {}", self.timer_period); } // If the divider's counter is zero or the reload flag is true: The counter is set to P and the reload flag is cleared. Otherwise, the counter is decremented. if self.sweep_counter == 0 || self.sweep_reload { @@ -144,6 +139,24 @@ impl Square { } else { self.sweep_counter -= 1; } + + // The sweep unit continuously calculates each channel's target period in this way: + // A barrel shifter shifts the channel's 11-bit raw timer period right by the shift count, producing the change amount. + let change = self.timer_period >> self.shift_count; + // If the negate flag is true, the change amount is made negative. + // The target period is the sum of the current period and the change amount. + if self.sweep_negate { + self.target_period = self.timer_period - change; + // The two pulse channels have their adders' carry inputs wired differently, + // which produces different results when each channel's change amount is made negative: + // Pulse 1 adds the ones' complement (−c − 1). Making 20 negative produces a change amount of −21. + // Pulse 2 adds the two's complement (−c). Making 20 negative produces a change amount of −20. + if !self.second_channel { + self.target_period -= 1; + } + } else { + self.target_period = self.timer_period + change; + } } // $4000/$4004 @@ -186,6 +199,5 @@ impl Square { // The sequencer is immediately restarted at the first value of the current sequence. The envelope is also restarted. The period divider is not reset. self.duty_counter = 0; self.start = true; - self.duty_counter = 0; } } From 8f3ab6e751fcc6f380e46e625ce6351dce8da071 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Tue, 31 Dec 2019 17:22:44 -0600 Subject: [PATCH 24/36] troubleshooting, experimenting --- src/apu/mod.rs | 2 -- src/apu/square.rs | 12 ++++++++++-- src/main.rs | 8 +++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index c2ab8e2..c87eab9 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -60,8 +60,6 @@ impl Apu { pub fn new() -> Self { let square_table = (0..31).map(|x| 95.52/((8128.0 / x as f32) + 100.0)).collect(); let tnd_table = (0..203).map(|x| 163.67/((24329.0 / x as f32) + 100.0)).collect(); - println!("square_table: {:?}", square_table); - println!("tnd_table: {:?}", tnd_table); Apu { square1: Square::new(false), square2: Square::new(true), diff --git a/src/apu/square.rs b/src/apu/square.rs index 0b359cd..21569fe 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -80,7 +80,7 @@ impl Square { // Update volume for this channel // The mixer receives the current envelope volume except when self.sample = if self.duty_cycle[self.duty_counter] == 0 // the sequencer output is zero, or - || self.target_period > 0x7FF // overflow from the sweep unit's adder is silencing the channel, + || self.timer_period > 0x7FF // overflow from the sweep unit's adder is silencing the channel, || self.length_counter == 0 // the length counter is zero, or || self.timer_period < 8 { // the timer has a value less than eight. 0 @@ -126,9 +126,10 @@ impl Square { } pub fn clock_sweep(&mut self) { + self.calculate_target_period(); // When the frame counter sends a half-frame clock (at 120 or 96 Hz), two things happen. // If the divider's counter is zero, the sweep is enabled, and the sweep unit is not muting the channel: The pulse's period is adjusted. - if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer_period < 8 || self.target_period > 0x7FF) { + if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer_period < 8 || self.timer_period > 0x7FF) { self.timer_period = self.target_period; println!("timer period adjusted to {}", self.timer_period); } @@ -139,7 +140,10 @@ impl Square { } else { self.sweep_counter -= 1; } + } + // Whenever the current period changes for any reason, whether by $400x writes or by sweep, the target period also changes. + pub fn calculate_target_period(&mut self) { // The sweep unit continuously calculates each channel's target period in this way: // A barrel shifter shifts the channel's 11-bit raw timer period right by the shift count, producing the change amount. let change = self.timer_period >> self.shift_count; @@ -185,17 +189,21 @@ impl Square { pub fn write_timer_low(&mut self, value: u8) { self.timer_period &= 0b00000111_00000000; // mask off everything but high 3 bits of 11-bit timer self.timer_period |= value as u16; // apply low 8 bits + self.calculate_target_period(); } // $4003/$4007 pub fn write_timer_high(&mut self, value: u8) { // LLLL.Lttt Pulse channel 1 length counter load and timer (write) + // TODO: thought the below meant that the length counter was only set if the channel was enabled, but apparently not as not having it fixes start game noise in DK. + // When the enabled bit is cleared (via $4015), the length counter is forced to 0 and cannot be changed until enabled is set again (the length counter's previous value is lost). if self.enabled { self.length_counter = super::LENGTH_COUNTER_TABLE[value as usize >> 3]; } let timer_high = value as u16 & 0b0000_0111; self.timer_period &= 0b11111000_11111111; // mask off high 3 bits of 11-bit timer self.timer_period |= timer_high << 8; // apply high timer bits in their place + self.calculate_target_period(); // The sequencer is immediately restarted at the first value of the current sequence. The envelope is also restarted. The period divider is not reset. self.duty_counter = 0; self.start = true; diff --git a/src/main.rs b/src/main.rs index 342f6a5..fcaa57d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,7 +69,7 @@ fn main() -> Result<(), String> { match cpu.apu.clock() { Some(sample) => { sps += 1; - audio_device.queue(&vec![sample]); + if sps < 44100 {audio_device.queue(&vec![sample]);} }, None => (), }; @@ -126,8 +126,14 @@ TODO: - remaining APU channels - common mappers - battery-backed RAM solution +- fix mysterious Mario pipe non-locations - GUI? drag and drop ROMs? - reset function - save/load/pause functionality + +Timing notes: +The PPU is throttled to 60Hz by sleeping in the main loop. This locks the CPU to roughly its intended speed, 1.789773MHz NTSC. The APU runs at half that. +The SDL audio device samples/outputs at 44,100Hz, so as long as the APU queues up 44,100 samples per second, it works. + */ From 24be0586e60084c5d43f3edbf20e05918331b734 Mon Sep 17 00:00:00 2001 From: Theron Date: Tue, 31 Dec 2019 19:03:29 -0600 Subject: [PATCH 25/36] troubleshooting --- src/apu/mod.rs | 19 +++---------------- src/apu/square.rs | 1 + 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index c87eab9..c5725ea 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -12,26 +12,14 @@ use dmc::DMC; // Frame counter only ticks every 3728.5 APU ticks, and in audio frames of 4 or 5. // Length counter controls note durations. -// How to sync clock to audio? -// Measure time slept to see if it will be a problem. -// What if the APU kept a ring buffer of audio data way longer than the audio device's sample size, -// and that was in a struct with some markers, so the audio device can just consume what it needs during PPU's sleep and mark -// where it left off? But wouldn't it catch up and consume buffer? It won't catch up if it's big enough, and the APU can -// change the markers somehow as it needs to? Or audio callback truncates what it consumed and adjusts head? No, audio device doesn't -// need all samples, it needs one from the stream 44100 time per second. So just an if statement, if time has passed grab a sample. -// But then that won't be running during PPU 60Hz sleep... So run audio in its own thread... Except then it won't work on Windows because of SDL... -// So just run the console in its own thread and video/audio in the main thread... But that's annoying. -// No. Don't have to be concerned about the audio device, that's solved by the buffer, and the 44100 samples get fed in batches of 4096 from the large buffer, -// when the device needs them, which is accomplished just by calling .resume() before the main loop starts. So a large buffer really should allow for the 60Hz sleep lock. - // 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: organize APU structs 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. May need to turn this down slightly as it's outputting less than 44_100Hz. -const CYCLES_PER_SAMPLE: f32 = 19.65; +const CYCLES_PER_SAMPLE: f32 = 894_886.5/44_100.0; // APU frequency over sample frequency. May need to turn this down slightly as it's outputting less than 44_100Hz. +// const CYCLES_PER_SAMPLE: f32 = 20.0; const LENGTH_COUNTER_TABLE: [u8; 32] = [ 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30, @@ -96,7 +84,7 @@ impl Apu { if self.remainder > CYCLES_PER_SAMPLE { // send sample to buffer sample = Some(self.mix()); - self.remainder -= CYCLES_PER_SAMPLE; + self.remainder -= 20.0; } self.remainder += 1.0; @@ -143,7 +131,6 @@ impl Apu { fn mix(&self) -> f32 { let square_out = self.square_table[(self.square1.sample + self.square2.sample) as usize]; let tnd_out = self.tnd_table[((3*self.triangle.sample)+(2*self.noise.sample) + self.dmc.sample) as usize]; - // println!("square: {}, tnd: {}", square_out, tnd_out); square_out + tnd_out } diff --git a/src/apu/square.rs b/src/apu/square.rs index 21569fe..538d16b 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -199,6 +199,7 @@ impl Square { // When the enabled bit is cleared (via $4015), the length counter is forced to 0 and cannot be changed until enabled is set again (the length counter's previous value is lost). if self.enabled { self.length_counter = super::LENGTH_COUNTER_TABLE[value as usize >> 3]; + println!("val: 0b{:08b}, wrote length_counter {}", value, self.length_counter); } let timer_high = value as u16 & 0b0000_0111; self.timer_period &= 0b11111000_11111111; // mask off high 3 bits of 11-bit timer From 73549d6c2d0a47f63b80dc588aff8cbe7f663994 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Tue, 31 Dec 2019 19:32:00 -0600 Subject: [PATCH 26/36] troubleshooting, maybe fixed something --- src/apu/square.rs | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/apu/square.rs b/src/apu/square.rs index 538d16b..56f1234 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -9,14 +9,14 @@ pub struct Square { pub sample: u16, pub enabled: bool, - duty_cycle: [u8; 8], - duty_counter: usize, + duty_cycle: [u8; 8], // "sequencer", set to one of the lines in DUTY_CYCLE_SEQUENCES + duty_counter: usize, // current index within the duty_cycle - envelope: u16, - envelope_divider: u16, - decay_counter: u16, + envelope: u16, // constant volume/envelope period. reflects what was last written to $4000/$4004 + envelope_divider: u16, // + decay_counter: u16, // remainder of envelope divider constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume) - start: bool, + start: bool, // restarts envelope length_counter_halt: bool, // (this bit is also the envelope's loop flag) pub length_counter: u8, @@ -107,16 +107,17 @@ impl Square { // When the divider is clocked while at 0, it is loaded with V and clocks the decay level counter. if self.envelope_divider == 0 { self.envelope_divider = self.envelope; - // Then one of two actions occurs: If the counter is non-zero, it is decremented, - if self.decay_counter != 0 { - self.decay_counter -= 1; - } else if self.length_counter_halt { - // otherwise if the loop flag is set, the decay level counter is loaded with 15. - self.decay_counter = 15; - } } else { self.envelope_divider -= 1; } + // Then one of two actions occurs: If the counter is non-zero, it is decremented, + if self.decay_counter != 0 { + self.decay_counter -= 1; + // println!("decaying to {}", self.decay_counter); + } else if self.length_counter_halt { + // otherwise if the loop flag is set, the decay level counter is loaded with 15. + self.decay_counter = 15; + } } pub fn clock_length_counter(&mut self) { @@ -169,11 +170,12 @@ impl Square { self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize]; self.length_counter_halt = value & (1<<5) != 0; self.constant_volume_flag = value & (1<<4) != 0; - self.envelope = if self.constant_volume_flag { - value as u16 & 0b1111 - } else { - self.decay_counter - }; + self.envelope = value as u16 & 0b1111; + // if self.constant_volume_flag { + // value as u16 & 0b1111 + // } else { + // self.decay_counter + // }; } // $4001/$4005 @@ -199,7 +201,7 @@ impl Square { // When the enabled bit is cleared (via $4015), the length counter is forced to 0 and cannot be changed until enabled is set again (the length counter's previous value is lost). if self.enabled { self.length_counter = super::LENGTH_COUNTER_TABLE[value as usize >> 3]; - println!("val: 0b{:08b}, wrote length_counter {}", value, self.length_counter); + // println!("val: 0b{:08b}, wrote length_counter {}", value, self.length_counter); } let timer_high = value as u16 & 0b0000_0111; self.timer_period &= 0b11111000_11111111; // mask off high 3 bits of 11-bit timer From 73b0a280347520e443229bb37ce1ea3cfd140f02 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Wed, 1 Jan 2020 15:28:28 -0600 Subject: [PATCH 27/36] fixed DK walking sound though not sure why exactly. --- src/apu/mod.rs | 14 +++++++------- src/apu/square.rs | 22 +++++++++++++--------- src/main.rs | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index c5725ea..40d1f44 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -97,6 +97,12 @@ impl Apu { sample } + fn mix(&self) -> f32 { + let square_out = self.square_table[(self.square1.sample + self.square2.sample) as usize]; + let tnd_out = self.tnd_table[((3*self.triangle.sample)+(2*self.noise.sample) + self.dmc.sample) as usize]; + square_out + tnd_out + } + pub fn write_reg(&mut self, address: usize, value: u8) { // println!("writing 0b{:08b} to 0x{:X}", value, address); match address { @@ -128,13 +134,7 @@ impl Apu { } } - fn mix(&self) -> f32 { - let square_out = self.square_table[(self.square1.sample + self.square2.sample) as usize]; - let tnd_out = self.tnd_table[((3*self.triangle.sample)+(2*self.noise.sample) + self.dmc.sample) as usize]; - square_out + tnd_out - } - - // mode 0: mode 1: function + // mode 0: mode 1: function // --------- ----------- ----------------------------- // - - - f - - - - - IRQ (if bit 6 is clear) // - l - l - l - - l Length counter and sweep diff --git a/src/apu/square.rs b/src/apu/square.rs index 56f1234..4ab3e78 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -107,17 +107,18 @@ impl Square { // When the divider is clocked while at 0, it is loaded with V and clocks the decay level counter. if self.envelope_divider == 0 { self.envelope_divider = self.envelope; + // Then one of two actions occurs: If the counter is non-zero, it is decremented, + if self.decay_counter != 0 { + self.decay_counter -= 1; + // println!("decaying to {}", self.decay_counter); + } else if self.length_counter_halt { + // otherwise if the loop flag is set, the decay level counter is loaded with 15. + println!("looping decay counter"); + self.decay_counter = 15; + } } else { self.envelope_divider -= 1; } - // Then one of two actions occurs: If the counter is non-zero, it is decremented, - if self.decay_counter != 0 { - self.decay_counter -= 1; - // println!("decaying to {}", self.decay_counter); - } else if self.length_counter_halt { - // otherwise if the loop flag is set, the decay level counter is loaded with 15. - self.decay_counter = 15; - } } pub fn clock_length_counter(&mut self) { @@ -130,7 +131,7 @@ impl Square { self.calculate_target_period(); // When the frame counter sends a half-frame clock (at 120 or 96 Hz), two things happen. // If the divider's counter is zero, the sweep is enabled, and the sweep unit is not muting the channel: The pulse's period is adjusted. - if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer_period < 8 || self.timer_period > 0x7FF) { + if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer_period < 8 || self.target_period > 0x7FF) { self.timer_period = self.target_period; println!("timer period adjusted to {}", self.timer_period); } @@ -138,6 +139,7 @@ impl Square { if self.sweep_counter == 0 || self.sweep_reload { self.sweep_counter = self.sweep_divider; self.sweep_reload = false; + if self.sweep_enabled { self.timer_period = self.target_period; } // This fixes the DK walking sound. Why? Not reflected in documentation. } else { self.sweep_counter -= 1; } @@ -170,6 +172,7 @@ impl Square { self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize]; self.length_counter_halt = value & (1<<5) != 0; self.constant_volume_flag = value & (1<<4) != 0; + // println!("using envelope volume: {}", !self.constant_volume_flag); self.envelope = value as u16 & 0b1111; // if self.constant_volume_flag { // value as u16 & 0b1111 @@ -180,6 +183,7 @@ impl Square { // $4001/$4005 pub fn write_sweep(&mut self, value: u8) { + // println!("writing sweep 0b{:08b}", value); self.sweep_enabled = value >> 7 == 1; self.sweep_divider = ((value as u16 >> 4) & 0b111) + 1; self.sweep_negate = value & 0b1000 != 0; diff --git a/src/main.rs b/src/main.rs index fcaa57d..504cc79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,7 +69,7 @@ fn main() -> Result<(), String> { match cpu.apu.clock() { Some(sample) => { sps += 1; - if sps < 44100 {audio_device.queue(&vec![sample]);} + if sps < 44_100 {audio_device.queue(&vec![sample]);} }, None => (), }; From 42b3544648b789f011674394b26b2081bab81ba6 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Wed, 1 Jan 2020 15:55:50 -0600 Subject: [PATCH 28/36] hopefully a positive breakthrough. was skipping reset of apu cycles and thus missing frame counter clocks when frame counter had been switched to 4-mode but current cycle was already beyond 14915. --- src/apu/mod.rs | 9 +++++---- src/main.rs | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 40d1f44..96dca50 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -77,8 +77,9 @@ impl Apu { self.noise.clock(); self.dmc.clock(); - if (self.frame_counter == 4 && FRAME_COUNTER_STEPS[..4].contains(&self.cycle)) - || (self.frame_counter == 5 && FRAME_COUNTER_STEPS.contains(&self.cycle)) { + // if (self.frame_counter == 4 && FRAME_COUNTER_STEPS[..4].contains(&self.cycle)) + // || (self.frame_counter == 5 && FRAME_COUNTER_STEPS.contains(&self.cycle)) { + if FRAME_COUNTER_STEPS.contains(&self.cycle) { self.clock_frame_counter(); } if self.remainder > CYCLES_PER_SAMPLE { @@ -89,8 +90,7 @@ impl Apu { self.remainder += 1.0; self.cycle += 1; - if (self.frame_counter == 4 && self.cycle == 14915) - || (self.frame_counter == 5 && self.cycle == 18641) { + if (self.frame_counter == 4 && self.cycle == 14915) || self.cycle == 18641 { self.cycle = 0; } @@ -267,6 +267,7 @@ impl Apu { self.triangle.clock_length_counter(); self.noise.clock_envelope(); self.noise.clock_length_counter(); + self.clock_frame_counter(); } } } diff --git a/src/main.rs b/src/main.rs index 504cc79..ed6e8cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,7 +69,8 @@ fn main() -> Result<(), String> { match cpu.apu.clock() { Some(sample) => { sps += 1; - if sps < 44_100 {audio_device.queue(&vec![sample]);} + if sps < 44_100 {audio_device.queue(&vec![sample]);} // TODO: fix this + // audio_device.queue(&vec![sample]); }, None => (), }; From 352b48ba415cc9210524e12d770c99660aefd8c5 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Wed, 1 Jan 2020 15:56:55 -0600 Subject: [PATCH 29/36] removing unnecessary frame counter clock when writing to 017 --- src/apu/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 96dca50..32aee94 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -267,7 +267,7 @@ impl Apu { self.triangle.clock_length_counter(); self.noise.clock_envelope(); self.noise.clock_length_counter(); - self.clock_frame_counter(); + // self.clock_frame_counter(); } } } From bcbf0d3acb55646c95f6bb41643c1508f8f4476b Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Wed, 1 Jan 2020 20:51:08 -0600 Subject: [PATCH 30/36] cleanup --- src/apu/mod.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 32aee94..879bcb3 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -71,24 +71,27 @@ impl Apu { pub fn clock(&mut self) -> Option { let mut sample = None; + // Clock each channel self.square1.clock(); self.square2.clock(); self.triangle.clock(); self.noise.clock(); self.dmc.clock(); - // if (self.frame_counter == 4 && FRAME_COUNTER_STEPS[..4].contains(&self.cycle)) - // || (self.frame_counter == 5 && FRAME_COUNTER_STEPS.contains(&self.cycle)) { - if FRAME_COUNTER_STEPS.contains(&self.cycle) { - self.clock_frame_counter(); - } + // Send sample to buffer if necessary if self.remainder > CYCLES_PER_SAMPLE { - // send sample to buffer sample = Some(self.mix()); self.remainder -= 20.0; } self.remainder += 1.0; + + // Step frame counter if necessary + // if (self.frame_counter == 4 && FRAME_COUNTER_STEPS[..4].contains(&self.cycle)) + // || (self.frame_counter == 5 && FRAME_COUNTER_STEPS.contains(&self.cycle)) { + if FRAME_COUNTER_STEPS.contains(&self.cycle) { + self.clock_frame_counter(); + } self.cycle += 1; if (self.frame_counter == 4 && self.cycle == 14915) || self.cycle == 18641 { self.cycle = 0; @@ -239,7 +242,6 @@ impl Apu { if self.dmc.interrupt { val |= 1<<7; } - // Reading this register clears the frame interrupt flag (but not the DMC interrupt flag). self.frame_interrupt = false; // TODO: If an interrupt flag was set at the same moment of the read, it will read back as 1 but it will not be cleared. @@ -267,7 +269,6 @@ impl Apu { self.triangle.clock_length_counter(); self.noise.clock_envelope(); self.noise.clock_length_counter(); - // self.clock_frame_counter(); } } } From 0ea8ac64710765707e33773eaee6ba2b89a5c441 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Wed, 1 Jan 2020 21:20:47 -0600 Subject: [PATCH 31/36] cleanup --- src/apu/square.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/apu/square.rs b/src/apu/square.rs index 4ab3e78..9f4883f 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -70,7 +70,7 @@ impl Square { pub fn clock(&mut self) { // The sequencer is clocked by an 11-bit timer. Given the timer value t = HHHLLLLLLLL formed by timer high and timer low, this timer is updated every APU cycle - // (i.e., every second CPU cycle), and counts t, t-1, ..., 0, t, t-1, ..., clocking the waveform generator when it goes from 0 to t. + // (i.e., every second CPU cycle), and counts t, t-1, ..., 0, t, t-1, ..., clocking the waveform generator when it goes from 0 to t. if self.timer == 0 { self.timer = self.timer_period; self.duty_counter = (self.duty_counter + 1) % 8; @@ -82,7 +82,8 @@ impl Square { self.sample = if self.duty_cycle[self.duty_counter] == 0 // the sequencer output is zero, or || self.timer_period > 0x7FF // overflow from the sweep unit's adder is silencing the channel, || self.length_counter == 0 // the length counter is zero, or - || self.timer_period < 8 { // the timer has a value less than eight. + || self.timer_period < 8 // the timer has a value less than eight. + { 0 } else if self.constant_volume_flag { self.envelope @@ -172,13 +173,7 @@ impl Square { self.duty_cycle = DUTY_CYCLE_SEQUENCES[(value >> 6) as usize]; self.length_counter_halt = value & (1<<5) != 0; self.constant_volume_flag = value & (1<<4) != 0; - // println!("using envelope volume: {}", !self.constant_volume_flag); self.envelope = value as u16 & 0b1111; - // if self.constant_volume_flag { - // value as u16 & 0b1111 - // } else { - // self.decay_counter - // }; } // $4001/$4005 From f4673188d22088c3d8d2847ab65a662ba15ca58d Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Wed, 1 Jan 2020 21:50:20 -0600 Subject: [PATCH 32/36] triangle --- src/apu/mod.rs | 3 ++- src/apu/square.rs | 1 - src/apu/triangle.rs | 63 ++++++++++++++++++++++++++++----------------- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 879bcb3..2a01cb4 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -152,7 +152,8 @@ impl Apu { } if (self.current_frame == 1) || (self.frame_counter == 4 && self.current_frame == 3) - || (self.frame_counter == 5 && self.current_frame == 4) { + || (self.frame_counter == 5 && self.current_frame == 4) + { // step length counters and sweep units self.square1.clock_sweep(); self.square2.clock_sweep(); diff --git a/src/apu/square.rs b/src/apu/square.rs index 9f4883f..fc149fe 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -200,7 +200,6 @@ impl Square { // When the enabled bit is cleared (via $4015), the length counter is forced to 0 and cannot be changed until enabled is set again (the length counter's previous value is lost). if self.enabled { self.length_counter = super::LENGTH_COUNTER_TABLE[value as usize >> 3]; - // println!("val: 0b{:08b}, wrote length_counter {}", value, self.length_counter); } let timer_high = value as u16 & 0b0000_0111; self.timer_period &= 0b11111000_11111111; // mask off high 3 bits of 11-bit timer diff --git a/src/apu/triangle.rs b/src/apu/triangle.rs index a245772..21cb3fc 100644 --- a/src/apu/triangle.rs +++ b/src/apu/triangle.rs @@ -1,37 +1,30 @@ - -// $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) pub struct Triangle { - pub sample: u16, - timer: usize, - pub length_counter: usize, // (this bit is also the linear counter's control flag) - linear_counter: usize, pub enabled: bool, + pub sample: u16, + + timer: u16, + timer_period: u16, + + pub length_counter: usize, + length_counter_halt: false, // (this bit is also the linear counter's control flag) / (this bit is also the length counter halt flag) + + linear_counter: usize, } impl Triangle { pub fn new() -> Self { Triangle { - timer: 0, - length_counter: 0, - linear_counter: 0, - sample: 0, enabled: false, + sample: 0, + timer: 0, + timer_period: 0, + length_counter: 0, + length_counter_halt: false, + linear_counter: 0, + linear_counter_reload: false, } } - pub fn write_timer_low(&mut self, value: u8) { - - } - - pub fn write_timer_high(&mut self, value: u8) { - - } - - pub fn write_counter(&mut self, value: u8) { - - } - pub fn clock(&mut self) { } @@ -43,4 +36,28 @@ impl Triangle { pub fn clock_length_counter(&mut self) { } + + // $4008 + pub fn write_counter(&mut self, value: u8) { + self.length_counter_halt = value >> 7 as bool; + self.counter_reload_value = (value << 1) >> 1; + } + + // $400A + pub fn write_timer_low(&mut self, value: u8) { + self.timer_period &= 0b00000111_00000000; + self.timer_period |= value; + } + + // $400B + pub fn write_timer_high(&mut self, value: u8) { + if self.enabled { + self.length_counter = super::LENGTH_COUNTER_TABLE[value as usize >> 3]; + } + self.timer_period &= 0b00000000_11111111; + let timer_high = value & 0b0000_0111; + self.timer_period |= timer_high << 8; + self.linear_counter_reload = true; + } + } \ No newline at end of file From 611723ed79a05aefc56fd6565787457741d130a4 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Wed, 1 Jan 2020 23:55:41 -0600 Subject: [PATCH 33/36] think triangle channel is working! --- src/apu/mod.rs | 1 + src/apu/triangle.rs | 51 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index 2a01cb4..dd72f4c 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -75,6 +75,7 @@ impl Apu { self.square1.clock(); self.square2.clock(); self.triangle.clock(); + self.triangle.clock(); // TODO: hacky. clocking triangle twice because it runs every CPU cycle self.noise.clock(); self.dmc.clock(); diff --git a/src/apu/triangle.rs b/src/apu/triangle.rs index 21cb3fc..c89fbc9 100644 --- a/src/apu/triangle.rs +++ b/src/apu/triangle.rs @@ -1,14 +1,22 @@ +const WAVEFORM: [u16; 32] = [ + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +]; + pub struct Triangle { pub enabled: bool, pub sample: u16, timer: u16, timer_period: u16, + waveform_counter: usize, - pub length_counter: usize, - length_counter_halt: false, // (this bit is also the linear counter's control flag) / (this bit is also the length counter halt flag) + pub length_counter: u8, + length_counter_halt: bool, // (this bit is also the linear counter's control flag) / (this bit is also the length counter halt flag) - linear_counter: usize, + linear_counter: u8, + counter_reload_value: u8, + linear_counter_reload: bool, } impl Triangle { @@ -18,35 +26,58 @@ impl Triangle { sample: 0, timer: 0, timer_period: 0, + waveform_counter: 0, length_counter: 0, length_counter_halt: false, linear_counter: 0, + counter_reload_value: 0, linear_counter_reload: false, } } pub fn clock(&mut self) { - + if self.timer == 0 { + self.timer = self.timer_period; + // The sequencer is clocked by the timer as long as both the linear counter and the length counter are nonzero. + if self.linear_counter != 0 && self.length_counter != 0 { + self.waveform_counter = (self.waveform_counter + 1) % 32; + } + } else { + self.timer -= 1; + } + self.sample = WAVEFORM[self.waveform_counter]; } pub fn clock_linear_counter(&mut self) { - + // When the frame counter generates a linear counter clock, the following actions occur in order: + // If the linear counter reload flag is set, the linear counter is reloaded with the counter reload value, + if self.linear_counter_reload { + self.linear_counter = self.counter_reload_value; + } else if self.linear_counter != 0 { // otherwise if the linear counter is non-zero, it is decremented. + self.linear_counter -= 1; + } + // If the control flag is clear, the linear counter reload flag is cleared. + if !self.length_counter_halt { + self.linear_counter_reload = false; + } } pub fn clock_length_counter(&mut self) { - + if !(self.length_counter == 0 || self.length_counter_halt) { + self.length_counter -= 1; + } } // $4008 pub fn write_counter(&mut self, value: u8) { - self.length_counter_halt = value >> 7 as bool; + self.length_counter_halt = value >> 7 != 0; self.counter_reload_value = (value << 1) >> 1; } // $400A pub fn write_timer_low(&mut self, value: u8) { self.timer_period &= 0b00000111_00000000; - self.timer_period |= value; + self.timer_period |= value as u16; } // $400B @@ -56,8 +87,8 @@ impl Triangle { } self.timer_period &= 0b00000000_11111111; let timer_high = value & 0b0000_0111; - self.timer_period |= timer_high << 8; + self.timer_period |= (timer_high as u16) << 8; self.linear_counter_reload = true; } -} \ No newline at end of file +} From 4bbb6f6984c95e2c72fcc7b077c775a7d5c3dd9f Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Thu, 2 Jan 2020 17:41:52 -0600 Subject: [PATCH 34/36] noise channel working, I think --- src/apu/noise.rs | 114 +++++++++++++++++++++++++++++++++++++--------- src/apu/square.rs | 8 +--- src/main.rs | 6 ++- 3 files changed, 99 insertions(+), 29 deletions(-) diff --git a/src/apu/noise.rs b/src/apu/noise.rs index defe901..525675a 100644 --- a/src/apu/noise.rs +++ b/src/apu/noise.rs @@ -1,57 +1,129 @@ +const NOISE_TABLE: [u16; 16] = [4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068]; + // $400E M---.PPPP Mode and period (write) // bit 7 M--- ---- Mode flag pub struct Noise { pub sample: u16, - timer: usize, - pub length_counter: usize, - envelope: usize, + pub enabled: bool, + + envelope: u16, // constant volume/envelope period. reflects what was last written to $4000/$4004 + envelope_divider: u16, + decay_counter: u16, // remainder of envelope divider + constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume) + start: bool, // restarts envelope + + timer: u16, + timer_period: u16, + + pub length_counter: u8, + length_counter_halt: bool, + linear_feedback_sr: u16, mode: bool, // also called loop noise, bit 7 of $400E - pub enabled: bool, } impl Noise { pub fn new() -> Self { 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 sample: 0, enabled: false, + envelope: 0, + envelope_divider: 0, + decay_counter: 0, + constant_volume_flag: false, + start: false, + timer: 0, + timer_period: 0, + length_counter: 0, + length_counter_halt: false, + 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, + if self.timer == 0 { + self.clock_linear_counter(); + } else { + self.timer -= 1; + } + // The mixer receives the current envelope volume except when + // Bit 0 of the shift register is set, or the length counter is zero + self.sample = if self.linear_feedback_sr & 1 == 1 || self.length_counter == 0 { + 0 + } else if self.constant_volume_flag { + self.envelope + } else { + self.decay_counter }; + } + + pub fn clock_linear_counter(&mut self) { + // When the timer clocks the shift register, the following actions occur in order: + // Feedback is calculated as the exclusive-OR of bit 0 + let bit0 = self.linear_feedback_sr & (1 << 0); + // and one other bit: bit 6 if Mode flag is set, otherwise bit 1. + let bit_num = if self.mode { 6 } else { 1 }; + let other_bit = (self.linear_feedback_sr & (1 << bit_num)) >> bit_num; let feedback = bit0 ^ other_bit; + // The shift register is shifted right by one bit. self.linear_feedback_sr >>= 1; + // Bit 14, the leftmost bit, is set to the feedback calculated earlier. self.linear_feedback_sr |= feedback << 14; } - pub fn write_envelope(&mut self, value: u8) { - + pub fn clock_envelope(&mut self) { + // When clocked by the frame counter, one of two actions occurs: + // if the start flag is clear, the divider is clocked, + if !self.start { + self.clock_envelope_divider(); + } else { + self.start = false; // otherwise the start flag is cleared, + self.decay_counter = 15; // the decay level counter is loaded with 15, + self.envelope_divider = self.envelope; // and the divider's period is immediately reloaded + } } - pub fn clock_envelope(&mut self) { - + fn clock_envelope_divider(&mut self) { + // When the divider is clocked while at 0, it is loaded with V and clocks the decay level counter. + if self.envelope_divider == 0 { + self.envelope_divider = self.envelope; + // Then one of two actions occurs: If the counter is non-zero, it is decremented, + if self.decay_counter != 0 { + self.decay_counter -= 1; + } else if self.length_counter_halt { + // otherwise if the loop flag is set, the decay level counter is loaded with 15. + self.decay_counter = 15; + } + } else { + self.envelope_divider -= 1; + } } pub fn clock_length_counter(&mut self) { - + if !(self.length_counter == 0 || self.length_counter_halt) { + self.length_counter -= 1; + } } - pub fn write_loop_noise(&mut self, value: u8) { - + // $400C + pub fn write_envelope(&mut self, value: u8) { + self.length_counter_halt = (value >> 5) & 1 == 1; + self.constant_volume_flag = (value >> 4) & 1 == 1; + self.envelope = value as u16 & 0b1111; } + + // $400E + pub fn write_loop_noise(&mut self, value: u8) { + self.mode = value >> 7 == 1; + self.timer_period = NOISE_TABLE[(value & 0b1111) as usize]; + } + + // $400F pub fn write_length_counter(&mut self, value: u8) { - + self.length_counter = value >> 3; + self.start = true; } } diff --git a/src/apu/square.rs b/src/apu/square.rs index fc149fe..6591de8 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -13,7 +13,7 @@ pub struct Square { duty_counter: usize, // current index within the duty_cycle envelope: u16, // constant volume/envelope period. reflects what was last written to $4000/$4004 - envelope_divider: u16, // + envelope_divider: u16, decay_counter: u16, // remainder of envelope divider constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume) start: bool, // restarts envelope @@ -111,10 +111,8 @@ impl Square { // Then one of two actions occurs: If the counter is non-zero, it is decremented, if self.decay_counter != 0 { self.decay_counter -= 1; - // println!("decaying to {}", self.decay_counter); } else if self.length_counter_halt { - // otherwise if the loop flag is set, the decay level counter is loaded with 15. - println!("looping decay counter"); + // otherwise if the loop flag is set, the decay level counter is loaded with 15. self.decay_counter = 15; } } else { @@ -134,7 +132,6 @@ impl Square { // If the divider's counter is zero, the sweep is enabled, and the sweep unit is not muting the channel: The pulse's period is adjusted. if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer_period < 8 || self.target_period > 0x7FF) { self.timer_period = self.target_period; - println!("timer period adjusted to {}", self.timer_period); } // If the divider's counter is zero or the reload flag is true: The counter is set to P and the reload flag is cleared. Otherwise, the counter is decremented. if self.sweep_counter == 0 || self.sweep_reload { @@ -178,7 +175,6 @@ impl Square { // $4001/$4005 pub fn write_sweep(&mut self, value: u8) { - // println!("writing sweep 0b{:08b}", value); self.sweep_enabled = value >> 7 == 1; self.sweep_divider = ((value as u16 >> 4) & 0b111) + 1; self.sweep_negate = value & 0b1000 != 0; diff --git a/src/main.rs b/src/main.rs index ed6e8cb..9397bf5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -124,7 +124,8 @@ fn main() -> Result<(), String> { /* TODO: -- remaining APU channels +- DMC audio channel, high- and low-pass filters, refactor envelope +- name audio variables (dividers, counters, etc.) more consistently - common mappers - battery-backed RAM solution - fix mysterious Mario pipe non-locations @@ -136,5 +137,6 @@ TODO: Timing notes: The PPU is throttled to 60Hz by sleeping in the main loop. This locks the CPU to roughly its intended speed, 1.789773MHz NTSC. The APU runs at half that. The SDL audio device samples/outputs at 44,100Hz, so as long as the APU queues up 44,100 samples per second, it works. - +But it's not doing so evenly. If PPU runs faster than 60Hz, audio will get skipped, and if slower, audio will pop/have gaps. +Need to probably lock everything to the APU but worried about checking time that often. Can do for some division of 44_100. */ From 63f550da4f89b3f82b727fba955077602b7b1779 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Thu, 2 Jan 2020 17:50:46 -0600 Subject: [PATCH 35/36] readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d27f038..8546037 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ Thanks to Michael Fogleman's https://github.com/fogleman/nes for getting me unst - More mappers (only NROM/mapper 0 implemented so far) -- Audio +- DMC audio channel, high- and low-pass filters, APU cleanup/timing fix + +- Save/load functionality and battery-backed RAM solution - Player 2 controller? From d599e60e6596bcd7974aa85889a62ffc41fde7c7 Mon Sep 17 00:00:00 2001 From: Theron Spiegl Date: Thu, 2 Jan 2020 18:00:18 -0600 Subject: [PATCH 36/36] readme --- README.md | 2 ++ pics/smb.png | Bin 0 -> 60516 bytes 2 files changed, 2 insertions(+) create mode 100644 pics/smb.png diff --git a/README.md b/README.md index 8546037..464231e 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ This is an NES emulator and a work in progress. The CPU and PPU work, though the - NTSC timing + + ## Controls: ``` Button | Key diff --git a/pics/smb.png b/pics/smb.png new file mode 100644 index 0000000000000000000000000000000000000000..9b90b9b8e19fe170f31d46d822939a3a0c5a5d8a GIT binary patch literal 60516 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 aX@5IXw?SZrO%#xbsph%$NUxmkX;_Y>fzUlC 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