noise channel working, I think

This commit is contained in:
Theron Spiegl 2020-01-02 17:41:52 -06:00
parent 611723ed79
commit 4bbb6f6984
3 changed files with 99 additions and 29 deletions

View File

@ -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;
}
}

View File

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

View File

@ -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.
*/