From b128ad485ef421004e49536d0765f122383f98d7 Mon Sep 17 00:00:00 2001 From: Theron Date: Mon, 23 Dec 2019 17:46:14 -0600 Subject: [PATCH] apu --- src/apu/mod.rs | 68 +++++++++++++++++++-------------------- src/apu/square.rs | 81 +++++++++++++++++++++++++++-------------------- src/main.rs | 1 + 3 files changed, 82 insertions(+), 68 deletions(-) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index d8f131b..38f24ff 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -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(); + } + } } diff --git a/src/apu/square.rs b/src/apu/square.rs index d4459c1..a67ed6c 100644 --- a/src/apu/square.rs +++ b/src/apu/square.rs @@ -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; } } diff --git a/src/main.rs b/src/main.rs index 52ddc7b..ca98a16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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![]; }