apu
This commit is contained in:
parent
25cf5fceae
commit
b128ad485e
|
@ -29,6 +29,13 @@ use dmc::DMC;
|
||||||
|
|
||||||
// TODO: organize APU structs
|
// 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 {
|
pub struct Apu {
|
||||||
square1: Square,
|
square1: Square,
|
||||||
square2: Square,
|
square2: Square,
|
||||||
|
@ -41,7 +48,6 @@ pub struct Apu {
|
||||||
|
|
||||||
frame_counter: u8,
|
frame_counter: u8,
|
||||||
current_frame: u8,
|
current_frame: u8,
|
||||||
mode: u8,
|
|
||||||
interrupt_inhibit: bool,
|
interrupt_inhibit: bool,
|
||||||
frame_interrupt: bool,
|
frame_interrupt: bool,
|
||||||
cycle: usize,
|
cycle: usize,
|
||||||
|
@ -49,15 +55,6 @@ pub struct Apu {
|
||||||
pub trigger_irq: bool,
|
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 {
|
impl Apu {
|
||||||
pub 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 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,
|
frame_counter: 0,
|
||||||
current_frame: 0,
|
current_frame: 0,
|
||||||
mode: 0,
|
|
||||||
interrupt_inhibit: false,
|
interrupt_inhibit: false,
|
||||||
frame_interrupt: false,
|
frame_interrupt: false,
|
||||||
cycle: 0,
|
cycle: 0,
|
||||||
|
@ -131,7 +127,7 @@ impl Apu {
|
||||||
0x4014 => (),
|
0x4014 => (),
|
||||||
0x4015 => self.write_control(value),
|
0x4015 => self.write_control(value),
|
||||||
0x4016 => (),
|
0x4016 => (),
|
||||||
0x4017 => self.set_frame_counter(value),
|
0x4017 => self.write_frame_counter(value),
|
||||||
_ => panic!("bad address written: 0x{:X}", address),
|
_ => panic!("bad address written: 0x{:X}", address),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,25 +135,10 @@ impl Apu {
|
||||||
fn mix(&self) -> f32 {
|
fn mix(&self) -> f32 {
|
||||||
let square_out = self.square_table[(self.square1.sample + self.square2.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];
|
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
|
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
|
// mode 0: mode 1: function
|
||||||
// --------- ----------- -----------------------------
|
// --------- ----------- -----------------------------
|
||||||
// - - - f - - - - - IRQ (if bit 6 is clear)
|
// - - - f - - - - - IRQ (if bit 6 is clear)
|
||||||
|
@ -192,6 +173,7 @@ impl Apu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CPU writes to $4015
|
||||||
fn write_control(&mut self, value: u8) {
|
fn write_control(&mut self, value: u8) {
|
||||||
// Writing to this register clears the DMC interrupt flag.
|
// Writing to this register clears the DMC interrupt flag.
|
||||||
self.dmc.interrupt = false;
|
self.dmc.interrupt = false;
|
||||||
|
@ -204,27 +186,21 @@ impl Apu {
|
||||||
}
|
}
|
||||||
if value & (1<<1) != 0 {
|
if value & (1<<1) != 0 {
|
||||||
self.square2.enabled = true;
|
self.square2.enabled = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
self.square2.enabled = false;
|
self.square2.enabled = false;
|
||||||
self.square2.length_counter = 0;
|
self.square2.length_counter = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
if value & (1<<2) != 0 {
|
if value & (1<<2) != 0 {
|
||||||
self.triangle.enabled = true;
|
self.triangle.enabled = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
self.triangle.enabled = false;
|
self.triangle.enabled = false;
|
||||||
self.triangle.length_counter = 0;
|
self.triangle.length_counter = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
if value & (1<<3) != 0 {
|
if value & (1<<3) != 0 {
|
||||||
self.noise.enabled = true;
|
self.noise.enabled = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
self.noise.enabled = false;
|
self.noise.enabled = false;
|
||||||
self.noise.length_counter = 0;
|
self.noise.length_counter = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
if value & (1<<4) != 0 {
|
if value & (1<<4) != 0 {
|
||||||
self.dmc.enabled = true;
|
self.dmc.enabled = true;
|
||||||
|
@ -241,6 +217,7 @@ impl Apu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CPU reads from $4015
|
||||||
pub fn read_status(&mut self) -> u8 {
|
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)
|
// IF-D NT21: DMC interrupt (I), frame interrupt (F), DMC active (D), length counter > 0 (N/T/2/1)
|
||||||
let mut val = 0;
|
let mut val = 0;
|
||||||
|
@ -274,4 +251,27 @@ impl Apu {
|
||||||
val
|
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,
|
duty_counter: usize,
|
||||||
|
|
||||||
envelope: u16,
|
envelope: u16,
|
||||||
divider: u16,
|
envelope_divider: u16,
|
||||||
decay_counter: u16,
|
decay_counter: u16,
|
||||||
constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume)
|
constant_volume_flag: bool, // (0: use volume from envelope; 1: use constant volume)
|
||||||
start: bool,
|
start: bool,
|
||||||
|
@ -23,9 +23,9 @@ pub struct Square {
|
||||||
|
|
||||||
timer: u16,
|
timer: u16,
|
||||||
timer_period: u16,
|
timer_period: u16,
|
||||||
sweep_divider: u8, // Period, P
|
sweep_divider: u16, // Period, P
|
||||||
|
sweep_counter: u16,
|
||||||
shift_count: u8,
|
shift_count: u8,
|
||||||
sweep_counter: u8,
|
|
||||||
sweep_enabled: bool,
|
sweep_enabled: bool,
|
||||||
sweep_negate: bool,
|
sweep_negate: bool,
|
||||||
sweep_reload: bool,
|
sweep_reload: bool,
|
||||||
|
@ -43,7 +43,7 @@ impl Square {
|
||||||
duty_counter: 0,
|
duty_counter: 0,
|
||||||
|
|
||||||
envelope: 0,
|
envelope: 0,
|
||||||
divider: 0,
|
envelope_divider: 0,
|
||||||
decay_counter: 0,
|
decay_counter: 0,
|
||||||
constant_volume_flag: false,
|
constant_volume_flag: false,
|
||||||
start: false,
|
start: false,
|
||||||
|
@ -51,9 +51,8 @@ impl Square {
|
||||||
timer: 0,
|
timer: 0,
|
||||||
timer_period: 0,
|
timer_period: 0,
|
||||||
sweep_divider: 0,
|
sweep_divider: 0,
|
||||||
shift_count: 0,
|
|
||||||
sweep_period: 0,
|
|
||||||
sweep_counter: 0,
|
sweep_counter: 0,
|
||||||
|
shift_count: 0,
|
||||||
sweep_enabled: false,
|
sweep_enabled: false,
|
||||||
sweep_negate: false,
|
sweep_negate: false,
|
||||||
sweep_reload: false,
|
sweep_reload: false,
|
||||||
|
@ -77,9 +76,9 @@ impl Square {
|
||||||
// Update volume for this channel
|
// Update volume for this channel
|
||||||
// The mixer receives the current envelope volume except when
|
// 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_period > 0x7FF // overflow from the sweep unit's adder is silencing the channel,
|
|| 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.length_counter == 0 // the length counter is zero, or
|
||||||
|| self.timer < 8 { // the timer has a value less than eight.
|
|| self.timer < 8 { // the timer has a value less than eight.
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
self.decay_counter
|
self.decay_counter
|
||||||
|
@ -90,22 +89,24 @@ impl Square {
|
||||||
// When clocked by the frame counter, one of two actions occurs:
|
// When clocked by the frame counter, one of two actions occurs:
|
||||||
// if the start flag is clear, the divider is clocked,
|
// if the start flag is clear, the divider is clocked,
|
||||||
if !self.start {
|
if !self.start {
|
||||||
self.clock_divider();
|
self.clock_envelope_divider();
|
||||||
} else {
|
} else {
|
||||||
self.start = false; // otherwise the start flag is cleared,
|
self.start = false; // otherwise the start flag is cleared,
|
||||||
self.decay_counter = 15; // the decay level counter is loaded with 15,
|
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) {
|
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.
|
// When the divider is clocked while at 0, it is loaded with V and clocks the decay level counter.
|
||||||
if self.divider == 0 {
|
if self.envelope_divider == 0 {
|
||||||
self.divider = self.envelope;
|
self.envelope_divider = self.envelope;
|
||||||
// Then one of two actions occurs: If the counter is non-zero, it is decremented,
|
// Then one of two actions occurs: If the counter is non-zero, it is decremented,
|
||||||
if self.decay_counter != 0 {
|
if self.decay_counter != 0 {
|
||||||
self.decay_counter -= 1;
|
self.decay_counter -= 1;
|
||||||
|
@ -114,30 +115,39 @@ impl Square {
|
||||||
self.decay_counter = 15;
|
self.decay_counter = 15;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.divider -= 1;
|
self.envelope_divider -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clock_sweep(&mut self) {
|
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 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 {
|
if self.sweep_counter == 0 && self.sweep_enabled && !(self.timer < 8 || self.sweep_divider > 0x7FF) {
|
||||||
self.sweep_enabled == true;
|
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) {
|
fn adjust_sweep(&mut self) {
|
||||||
let change = self.timer_period >> self.shift_count;
|
self.timer_period = self.sweep_divider;
|
||||||
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
|
// $4000/$4004
|
||||||
|
@ -156,7 +166,7 @@ impl Square {
|
||||||
// $4001/$4005
|
// $4001/$4005
|
||||||
pub fn write_sweep(&mut self, value: u8) {
|
pub fn write_sweep(&mut self, value: u8) {
|
||||||
self.sweep_enabled = value >> 7 == 1;
|
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.sweep_negate = value & 0b1000 != 0;
|
||||||
self.shift_count = value & 0b111;
|
self.shift_count = value & 0b111;
|
||||||
self.sweep_reload = true;
|
self.sweep_reload = true;
|
||||||
|
@ -164,19 +174,22 @@ impl Square {
|
||||||
|
|
||||||
// $4002/$4006
|
// $4002/$4006
|
||||||
pub fn write_timer_low(&mut self, value: u8) {
|
pub fn write_timer_low(&mut self, value: u8) {
|
||||||
self.timer &= 0b00000111_00000000;
|
self.timer &= 0b00000111_00000000; // mask off everything but high 3 bits of 11-bit timer
|
||||||
self.timer |= value as u16;
|
self.timer |= value as u16; // apply low 8 bits
|
||||||
}
|
}
|
||||||
|
|
||||||
// $4003/$4007
|
// $4003/$4007
|
||||||
pub fn write_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)
|
// 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;
|
let timer_high = value as u16 & 0b0000_0111;
|
||||||
self.timer &= 0b11111000_11111111; // mask off high 3 bits of 11-bit timer
|
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 |= 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.
|
// 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.duty_counter = 0;
|
||||||
self.start = true;
|
self.start = true;
|
||||||
|
self.duty_counter = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ fn main() -> Result<(), String> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if audio_buffer.len() == 44_100 {
|
if audio_buffer.len() == 44_100 {
|
||||||
|
println!("queueing: {:?}", &audio_buffer[..32]);
|
||||||
audio_device.queue(&audio_buffer);
|
audio_device.queue(&audio_buffer);
|
||||||
audio_buffer = vec![];
|
audio_buffer = vec![];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue