This commit is contained in:
Theron 2019-12-23 17:46:14 -06:00
parent 25cf5fceae
commit b128ad485e
3 changed files with 82 additions and 68 deletions

View File

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

View File

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

View File

@ -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![];
}