apu
This commit is contained in:
parent
25cf5fceae
commit
b128ad485e
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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![];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue