Merge pull request #1 from spieglt/apu

Apu
This commit is contained in:
spieglt 2020-01-02 18:04:14 -06:00 committed by GitHub
commit d4fffa7c46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 934 additions and 87 deletions

View File

@ -8,6 +8,8 @@ This is an NES emulator and a work in progress. The CPU and PPU work, though the
- NTSC timing
<img src="pics/smb.png" width=600>
## Controls:
```
Button | Key
@ -30,6 +32,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?

BIN
pics/smb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

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

@ -0,0 +1,39 @@
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 {
DMC {
sample: 0,
enabled: false,
bytes_remaining: 0,
interrupt: false,
length_counter: 0,
}
}
pub fn clock(&mut self) {
}
pub fn write_control(&mut self, value: u8) {
}
pub fn direct_load(&mut self, value: u8) {
}
pub fn write_sample_address(&mut self, value: u8) {
}
pub fn write_sample_length(&mut self, value: u8) {
}
}

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

@ -0,0 +1,276 @@
mod noise;
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.
// 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 = 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,
];
pub struct Apu {
square1: Square,
square2: Square,
triangle: Triangle,
noise: Noise,
dmc: DMC,
square_table: Vec<f32>,
tnd_table: Vec<f32>,
frame_counter: u8,
current_frame: u8,
interrupt_inhibit: bool,
frame_interrupt: bool,
cycle: usize,
remainder: f32, // keep sample at 44100Hz
pub trigger_irq: bool,
}
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();
Apu {
square1: Square::new(false),
square2: Square::new(true),
triangle: Triangle::new(),
noise: Noise::new(),
dmc: DMC::new(),
square_table: square_table,
tnd_table: tnd_table,
frame_counter: 0,
current_frame: 0,
interrupt_inhibit: false,
frame_interrupt: false,
cycle: 0,
remainder: 0_f32,
trigger_irq: false,
}
}
pub fn clock(&mut self) -> Option<f32> {
let mut sample = None;
// Clock each channel
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();
// Send sample to buffer if necessary
if self.remainder > CYCLES_PER_SAMPLE {
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;
}
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 {
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.write_timer_low(value),
0x400B => self.triangle.write_timer_high(value),
0x400C => self.noise.write_envelope(value),
0x400D => (),
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.write_sample_address(value),
0x4013 => self.dmc.write_sample_length(value),
0x4014 => (),
0x4015 => self.write_control(value),
0x4016 => (),
0x4017 => self.write_frame_counter(value),
_ => panic!("bad address written: 0x{:X}", address),
}
}
// 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 == 3) {
// 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_sweep();
self.square2.clock_sweep();
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.interrupt_inhibit {
self.trigger_irq = true;
}
// advance counter
self.current_frame += 1;
if self.current_frame == self.frame_counter {
self.current_frame = 0;
}
}
// CPU writes to $4015
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.
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;
}
}
// 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;
// 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
}
// $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();
}
}
}

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

@ -0,0 +1,138 @@
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,
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
}
impl Noise {
pub fn new() -> Self {
Noise {
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) {
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 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
}
}
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;
}
}
// $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;
}
}
// 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).

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

@ -0,0 +1,208 @@
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,
pub enabled: bool,
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, // 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
length_counter_halt: bool, // (this bit is also the envelope's loop flag)
pub length_counter: u8,
timer: u16,
timer_period: u16,
target_period: u16,
sweep_divider: u16, // Period, P
sweep_counter: u16,
shift_count: 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(second_channel: bool) -> Self {
Square {
sample: 0,
enabled: false,
duty_cycle: DUTY_CYCLE_SEQUENCES[0],
duty_counter: 0,
envelope: 0,
envelope_divider: 0,
decay_counter: 0,
constant_volume_flag: false,
start: false,
timer: 0,
timer_period: 0,
target_period: 0,
sweep_divider: 0,
sweep_counter: 0,
shift_count: 0,
sweep_enabled: false,
sweep_negate: false,
sweep_reload: false,
length_counter: 0,
length_counter_halt: false,
second_channel: second_channel,
}
}
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.
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.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
} else if self.constant_volume_flag {
self.envelope
} else {
self.decay_counter
};
}
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
}
}
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 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) {
self.timer_period = self.target_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 {
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;
}
}
// 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;
// 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
pub fn write_duty(&mut self, value: u8) {
// 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;
self.envelope = value as u16 & 0b1111;
}
// $4001/$4005
pub fn write_sweep(&mut self, value: u8) {
self.sweep_enabled = value >> 7 == 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;
}
// $4002/$4006
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;
}
}

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

@ -0,0 +1,94 @@
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: 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: u8,
counter_reload_value: u8,
linear_counter_reload: bool,
}
impl Triangle {
pub fn new() -> Self {
Triangle {
enabled: false,
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 != 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 as u16;
}
// $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 as u16) << 8;
self.linear_counter_reload = true;
}
}

15
src/audio.rs Normal file
View File

@ -0,0 +1,15 @@
extern crate sdl2;
use sdl2::audio::{AudioCallback, AudioSpecDesired};
pub fn initialize(context: &sdl2::Sdl) -> Result<sdl2::audio::AudioQueue<f32>, String> {
let audio_subsystem = context.audio()?;
let desired_spec = AudioSpecDesired {
freq: Some(44_100),
channels: Some(1), // mono
samples: None, // default sample size
};
audio_subsystem.open_queue(None, &desired_spec)
}

View File

@ -76,6 +76,9 @@ pub struct Cpu {
// ppu
pub ppu: super::Ppu,
// apu
pub 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,
@ -142,16 +146,22 @@ impl Cpu {
}
pub fn step(&mut self) -> u64 {
// skip cycles from OAM DMA if necessary
if self.delay > 0 {
self.delay -= 1;
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;
@ -184,14 +194,15 @@ 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, // APU stuff
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.
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),
};
@ -201,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 => (), // APU stuff
0x4018...0x401F => (), // APU and I/O functionality that is normally disabled. See CPU Test Mode.
0x4020...0xFFFF => { // Cartridge space: PRG ROM, PRG RAM, and mapper registers
0x4000..=0x4017 => self.apu.write_reg(address, val), // APU stuff
0x4018..=0x401F => (), // APU and I/O functionality that is normally disabled. See CPU Test Mode.
0x4020..=0xFFFF => { // Cartridge space: PRG ROM, PRG RAM, and mapper registers
match (self.mapper_func)(self, address, true) {
Some(loc) => *loc = val,
None => (),

View File

@ -123,7 +123,7 @@ impl super::Cpu {
self.push((self.PC & 0xFF) as u8); // push low byte
self.push(self.P | 0b00110000); // push status register with break bits set
self.P |= INTERRUPT_DISABLE_FLAG; // set interrupt disable flag
self.PC = ((self.read(IRQ_VECTOR + 1) as usize) << 8) // set program counter to IRQ/BRK vector, taking high _byte
self.PC = ((self.read(IRQ_VECTOR + 1) as usize) << 8) // set program counter to IRQ/BRK vector, taking high byte
+ (self.read(IRQ_VECTOR) as usize); // and low byte
self.clock += 5; // total of 7 cycles, 2 come from implied()
}
@ -527,9 +527,19 @@ impl super::Cpu {
self.push((self.PC & 0xFF) as u8); // push low byte
self.push(self.P | 0b00110000); // push status register with break bits set
self.P |= INTERRUPT_DISABLE_FLAG; // set interrupt disable flag
self.PC = ((self.read(NMI_VECTOR + 1) as usize) << 8) // set program counter to IRQ/BRK vector, taking high _byte
self.PC = ((self.read(NMI_VECTOR + 1) as usize) << 8) // set program counter to NMI vector, taking high byte
+ (self.read(NMI_VECTOR) as usize); // and low byte
self.clock += 7;
}
pub fn irq(&mut self) {
self.push((self.PC >> 8) as u8); // push high byte
self.push((self.PC & 0xFF) as u8); // push low byte
self.push(self.P & 0b11001111); // push status register with break bits cleared
self.P |= INTERRUPT_DISABLE_FLAG; // set interrupt disable flag
self.PC = ((self.read(IRQ_VECTOR + 1) as usize) << 8) // set program counter to IRQ/BRK vector, taking high byte
+ (self.read(IRQ_VECTOR) as usize); // and low byte
self.clock += 7;
}
}

View File

@ -2,15 +2,19 @@ use std::time::{Instant, Duration};
mod cpu;
mod ppu;
mod apu;
mod cartridge;
mod screen;
mod input;
mod screen;
mod audio;
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 audio::initialize;
use sdl2::keyboard::Keycode;
use sdl2::event::Event;
@ -20,50 +24,78 @@ 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
// Set up audio
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();
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();
let mut fps_timer = Instant::now();
let mut fps = 0;
let mut sps = 0;
// 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 _i 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 {
match cpu.apu.clock() {
Some(sample) => {
sps += 1;
if sps < 44_100 {audio_device.queue(&vec![sample]);} // TODO: fix this
// audio_device.queue(&vec![sample]);
},
None => (),
};
}
// 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 => (),
};
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 },
_ => (),
}
}
@ -80,11 +112,31 @@ fn main() -> Result<(), String> {
println!("fps: {}", fps);
fps = 0;
fps_timer = now;
println!("samples per second: {}", sps);
sps = 0;
}
}
// PROFILER.lock().unwrap().stop().unwrap();
Ok(())
}
// TODO: reset function?
// TODO: save/load functionality
/*
TODO:
- 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
- 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.
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.
*/

View File

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

51
src/screen.rs Normal file
View File

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

View File

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