use super::envelope::Envelope;
use super::length_counter::LengthCounter;
use crate::nes::apu::envelope::EnvelopeState;
use crate::trace_apu;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PulseState {
pub timer: u16,
pub timer_period: u16,
pub length_counter: u8,
pub length_counter_enabled: bool,
pub length_counter_halt: bool,
pub length_counter_pending_halt: Option<bool>,
pub length_counter_reload_value: u8,
pub length_counter_previous_value: u8,
pub duty: u8,
pub duty_position: u8,
pub envelope: EnvelopeState,
pub sweep_enabled: bool,
pub sweep_period: u8,
pub sweep_negate: bool,
pub sweep_shift: u8,
pub sweep_reload: bool,
pub sweep_divider: u8,
}
pub struct Pulse {
is_pulse1: bool,
timer_period: u16,
timer_counter: u16,
duty_mode: u8,
sequence_position: u8,
envelope: Envelope,
length_counter: LengthCounter,
sweep_enabled: bool,
sweep_divider_period: u8,
sweep_negate: bool,
sweep_shift: u8,
sweep_reload: bool,
sweep_divider: u8,
}
const DUTY_SEQUENCES: [[u8; 8]; 4] = [
[0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 0, 0], ];
impl Default for Pulse {
fn default() -> Self {
Self::new(true) }
}
impl Pulse {
pub fn new(is_pulse1: bool) -> Self {
Self {
is_pulse1,
timer_period: 0,
timer_counter: 0,
duty_mode: 0,
sequence_position: 0,
envelope: Envelope::new(),
length_counter: LengthCounter::new(),
sweep_enabled: false,
sweep_divider_period: 0,
sweep_negate: false,
sweep_shift: 0,
sweep_reload: false,
sweep_divider: 0,
}
}
pub fn reset(&mut self) {
trace_apu!(2; "{} reset", if self.is_pulse1 { "pulse1" } else { "pulse2" });
self.timer_period = 0;
self.timer_counter = 0;
self.duty_mode = 0;
self.sequence_position = 0;
self.envelope.reset();
self.length_counter.reset();
self.sweep_enabled = false;
self.sweep_divider_period = 0;
self.sweep_negate = false;
self.sweep_shift = 0;
self.sweep_reload = false;
self.sweep_divider = 0;
}
pub fn write_timer_low(&mut self, value: u8) {
self.timer_period = (self.timer_period & 0x0700) | (value as u16);
trace_apu!(4; "{} write_timer_low value=0x{:02X} period=0x{:03X}", if self.is_pulse1 { "pulse1" } else { "pulse2" }, value, self.timer_period);
}
pub fn write_timer_high(&mut self, value: u8) {
self.timer_period = (self.timer_period & 0x00FF) | (((value & 0x07) as u16) << 8);
trace_apu!(4; "{} write_timer_high value=0x{:02X} period=0x{:03X}", if self.is_pulse1 { "pulse1" } else { "pulse2" }, value, self.timer_period);
}
#[cfg(test)]
pub fn get_timer_period(&self) -> u16 {
self.timer_period
}
pub fn clock_timer(&mut self) {
if self.timer_counter == 0 {
self.timer_counter = self.timer_period;
self.clock_sequencer();
trace_apu!(5; "{} clock_timer reload period=0x{:03X} seq_pos={}", if self.is_pulse1 { "pulse1" } else { "pulse2" }, self.timer_period, self.sequence_position);
} else {
self.timer_counter -= 1;
}
}
fn clock_sequencer(&mut self) {
self.sequence_position = if self.sequence_position == 0 {
7
} else {
self.sequence_position - 1
};
}
pub fn get_sequencer_output(&self) -> u8 {
DUTY_SEQUENCES[self.duty_mode as usize][self.sequence_position as usize]
}
#[cfg(test)]
pub fn write_duty(&mut self, duty: u8) {
self.duty_mode = duty & 0x03;
}
pub fn write_control(&mut self, value: u8) {
self.duty_mode = (value >> 6) & 0x03;
self.length_counter.set_halt((value & 0x20) != 0); self.envelope.write_control(value);
trace_apu!(3; "{} write_control value=0x{:02X} duty={} halt={}", if self.is_pulse1 { "pulse1" } else { "pulse2" }, value, self.duty_mode, (value & 0x20) != 0);
}
pub fn write_length_counter_timer_high(&mut self, value: u8) {
self.write_timer_high(value);
self.sequence_position = 0;
self.timer_counter = self.timer_period;
self.envelope.restart();
let index = value >> 3;
self.length_counter.load_from_index(index);
trace_apu!(3; "{} write_length_counter_timer_high value=0x{:02X} length_index={}", if self.is_pulse1 { "pulse1" } else { "pulse2" }, value, index);
}
pub fn clock_envelope(&mut self) {
self.envelope.clock();
}
pub fn get_envelope_volume(&self) -> u8 {
self.envelope.volume()
}
pub fn clock_length_counter(&mut self) {
trace_apu!(
3; "{} length_clock halt={} value={}",
if self.is_pulse1 { "pulse1" } else { "pulse2" },
self.length_counter.is_halted(),
self.length_counter.value()
);
self.length_counter.clock();
}
pub fn apply_pending_length_reload(&mut self) {
self.length_counter.reload_counter();
}
pub fn apply_pending_length_halt(&mut self) {
self.length_counter.apply_pending_halt();
}
pub fn get_length_counter(&self) -> u8 {
self.length_counter.value()
}
pub fn clear_length_counter(&mut self) {
self.length_counter.clear();
}
#[cfg(test)]
pub fn get_envelope_start_flag(&self) -> bool {
self.envelope.debug_start_flag()
}
#[cfg(test)]
pub fn get_sweep_reload(&self) -> bool {
self.sweep_reload
}
#[cfg(test)]
pub fn debug_length_counter_halt(&self) -> bool {
self.length_counter.is_halted()
}
#[cfg(test)]
pub fn debug_length_counter_pending_halt(&self) -> Option<bool> {
self.length_counter.pending_halt()
}
pub fn set_length_counter_enabled(&mut self, enabled: bool) {
self.length_counter.set_enabled(enabled);
trace_apu!(2; "{} set_length_counter_enabled {}", if self.is_pulse1 { "pulse1" } else { "pulse2" }, enabled);
}
pub fn is_length_counter_enabled(&self) -> bool {
self.length_counter.is_enabled()
}
pub fn write_sweep(&mut self, value: u8) {
self.sweep_enabled = (value & 0x80) != 0;
self.sweep_divider_period = ((value >> 4) & 0x07) + 1;
self.sweep_negate = (value & 0x08) != 0;
self.sweep_shift = value & 0x07;
self.sweep_reload = true;
trace_apu!(3; "{} write_sweep value=0x{:02X} enabled={} period={} negate={} shift={}", if self.is_pulse1 { "pulse1" } else { "pulse2" }, value, self.sweep_enabled, self.sweep_divider_period, self.sweep_negate, self.sweep_shift);
}
pub fn get_sweep_target_period(&self) -> u16 {
let change = (self.timer_period >> self.sweep_shift) as i32;
let current = self.timer_period as i32;
let delta = if self.sweep_negate {
if self.is_pulse1 {
-change - 1
} else {
-change
}
} else {
change
};
let target = current + delta;
if target < 0 { 0 } else { target as u16 }
}
#[cfg(test)]
pub fn is_sweep_muting(&self) -> bool {
self.timer_period < 8 || (!self.sweep_negate && self.get_sweep_target_period() > 0x7FF)
}
pub fn clock_sweep(&mut self) {
trace_apu!(1; "{} sweep_clock divider={} reload={} enabled={} shift={} period=0x{:03X}", if self.is_pulse1 { "pulse1" } else { "pulse2" }, self.sweep_divider, self.sweep_reload, self.sweep_enabled, self.sweep_shift, self.timer_period);
let target_period = self.get_sweep_target_period();
let reload_value = self.sweep_divider_reload_value();
trace_apu!(2; "{} sweep_state curr=0x{:03X} target=0x{:03X} divider={} reload={} enabled={} shift={}", if self.is_pulse1 { "pulse1" } else { "pulse2" }, self.timer_period, target_period, self.sweep_divider, self.sweep_reload, self.sweep_enabled, self.sweep_shift);
self.sweep_divider = self.sweep_divider.wrapping_sub(1);
if self.sweep_divider == 0 {
if !self.sweep_enabled {
trace_apu!(1; "{} sweep_skip disabled", if self.is_pulse1 { "pulse1" } else { "pulse2" });
} else if self.sweep_shift == 0 {
trace_apu!(1; "{} sweep_skip shift=0", if self.is_pulse1 { "pulse1" } else { "pulse2" });
} else if self.timer_period < 8 || target_period > 0x7FF {
trace_apu!(1; "{} sweep_mute curr=0x{:03X} target=0x{:03X}", if self.is_pulse1 { "pulse1" } else { "pulse2" }, self.timer_period, target_period);
} else {
self.timer_period = target_period;
self.timer_counter = self.timer_period;
trace_apu!(5; "{} sweep_update period=0x{:03X}", if self.is_pulse1 { "pulse1" } else { "pulse2" }, self.timer_period);
}
self.sweep_divider = reload_value;
}
if self.sweep_reload {
self.sweep_divider = reload_value;
self.sweep_reload = false;
}
}
fn sweep_divider_reload_value(&self) -> u8 {
self.sweep_divider_period
}
pub fn output(&self) -> u8 {
let target_period = self.get_sweep_target_period();
if self.get_sequencer_output() == 0
|| !self.length_counter.is_enabled() || self.length_counter.value() == 0
|| self.timer_period < 8
|| (!self.sweep_negate && target_period > 0x7FF)
{
0
} else {
self.get_envelope_volume()
}
}
pub fn capture_state(&self) -> PulseState {
PulseState {
timer: self.timer_counter,
timer_period: self.timer_period,
length_counter: self.length_counter.value(),
length_counter_enabled: self.length_counter.is_enabled(),
length_counter_halt: self.length_counter.is_halted(),
length_counter_pending_halt: self.length_counter.pending_halt(),
length_counter_reload_value: self.length_counter.reload_value(),
length_counter_previous_value: self.length_counter.previous_value(),
duty: self.duty_mode,
duty_position: self.sequence_position,
envelope: self.envelope.capture_state(),
sweep_enabled: self.sweep_enabled,
sweep_period: self.sweep_divider_period,
sweep_negate: self.sweep_negate,
sweep_shift: self.sweep_shift,
sweep_reload: self.sweep_reload,
sweep_divider: self.sweep_divider,
}
}
pub fn restore_state(&mut self, state: &PulseState) {
self.timer_counter = state.timer;
self.timer_period = state.timer_period;
self.length_counter.set_value(state.length_counter);
if state.length_counter_enabled {
self.length_counter.enable();
} else {
self.length_counter.disable();
}
self.length_counter
.set_halt_state(state.length_counter_halt, state.length_counter_pending_halt);
self.length_counter.set_reload_state(
state.length_counter_reload_value,
state.length_counter_previous_value,
);
self.duty_mode = state.duty;
self.sequence_position = state.duty_position;
self.envelope.restore_state(&state.envelope);
self.sweep_enabled = state.sweep_enabled;
self.sweep_divider_period = state.sweep_period;
self.sweep_negate = state.sweep_negate;
self.sweep_shift = state.sweep_shift;
self.sweep_reload = state.sweep_reload;
self.sweep_divider = state.sweep_divider;
}
}
#[cfg(test)]
#[allow(clippy::unusual_byte_groupings)]
mod tests {
use super::*;
fn write_length(pulse: &mut Pulse, value: u8) {
pulse.write_length_counter_timer_high(value);
pulse.apply_pending_length_reload();
}
#[test]
fn test_pulse_new() {
let pulse = Pulse::default();
assert_eq!(pulse.timer_period, 0);
assert_eq!(pulse.sequence_position, 0);
}
#[test]
fn test_write_timer_low() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0xAB);
assert_eq!(pulse.timer_period, 0xAB);
}
#[test]
fn test_write_timer_high() {
let mut pulse = Pulse::default();
pulse.write_timer_high(0x05);
assert_eq!(pulse.timer_period, 0x0500);
}
#[test]
fn test_write_timer_combined() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0xCD);
pulse.write_timer_high(0x07);
assert_eq!(pulse.timer_period, 0x07CD);
}
#[test]
fn test_timer_high_masks_upper_bits() {
let mut pulse = Pulse::default();
pulse.write_timer_high(0xFF); assert_eq!(pulse.timer_period, 0x0700);
}
#[test]
fn test_sequencer_starts_at_zero() {
let pulse = Pulse::default();
assert_eq!(pulse.sequence_position, 0);
}
#[test]
fn test_sequencer_counts_down() {
let mut pulse = Pulse {
timer_period: 0,
..Default::default()
};
pulse.clock_timer();
assert_eq!(pulse.sequence_position, 7);
pulse.clock_timer();
assert_eq!(pulse.sequence_position, 6);
pulse.clock_timer();
assert_eq!(pulse.sequence_position, 5);
pulse.clock_timer();
assert_eq!(pulse.sequence_position, 4);
pulse.clock_timer();
assert_eq!(pulse.sequence_position, 3);
pulse.clock_timer();
assert_eq!(pulse.sequence_position, 2);
pulse.clock_timer();
assert_eq!(pulse.sequence_position, 1);
pulse.clock_timer();
assert_eq!(pulse.sequence_position, 0);
pulse.clock_timer();
assert_eq!(pulse.sequence_position, 7);
}
#[test]
fn test_timer_countdown() {
let mut pulse = Pulse::default();
pulse.write_timer_low(3);
assert_eq!(pulse.timer_counter, 0);
pulse.clock_timer();
assert_eq!(pulse.timer_counter, 3);
assert_eq!(pulse.sequence_position, 7);
pulse.clock_timer();
assert_eq!(pulse.timer_counter, 2);
assert_eq!(pulse.sequence_position, 7);
pulse.clock_timer();
assert_eq!(pulse.timer_counter, 1);
pulse.clock_timer();
assert_eq!(pulse.timer_counter, 0);
pulse.clock_timer();
assert_eq!(pulse.timer_counter, 3);
assert_eq!(pulse.sequence_position, 6);
}
#[test]
fn test_duty_cycle_0_12_5_percent() {
let mut pulse = Pulse::default();
pulse.write_duty(0);
assert_eq!(pulse.get_sequencer_output(), 0);
pulse.sequence_position = 7;
assert_eq!(pulse.get_sequencer_output(), 1);
pulse.sequence_position = 6;
assert_eq!(pulse.get_sequencer_output(), 0);
pulse.sequence_position = 1;
assert_eq!(pulse.get_sequencer_output(), 0); }
#[test]
fn test_duty_cycle_1_25_percent() {
let mut pulse = Pulse::default();
pulse.write_duty(1);
pulse.sequence_position = 7;
assert_eq!(pulse.get_sequencer_output(), 1);
pulse.sequence_position = 6;
assert_eq!(pulse.get_sequencer_output(), 1);
pulse.sequence_position = 5;
assert_eq!(pulse.get_sequencer_output(), 0); }
#[test]
fn test_duty_cycle_2_50_percent() {
let mut pulse = Pulse::default();
pulse.write_duty(2);
pulse.sequence_position = 7;
assert_eq!(pulse.get_sequencer_output(), 1);
pulse.sequence_position = 4;
assert_eq!(pulse.get_sequencer_output(), 1);
pulse.sequence_position = 3;
assert_eq!(pulse.get_sequencer_output(), 0);
pulse.sequence_position = 0;
assert_eq!(pulse.get_sequencer_output(), 0);
}
#[test]
fn test_duty_cycle_3_25_percent_negated() {
let mut pulse = Pulse::default();
pulse.write_duty(3);
pulse.sequence_position = 0;
assert_eq!(pulse.get_sequencer_output(), 1);
pulse.sequence_position = 5;
assert_eq!(pulse.get_sequencer_output(), 1);
pulse.sequence_position = 6;
assert_eq!(pulse.get_sequencer_output(), 0);
pulse.sequence_position = 7;
assert_eq!(pulse.get_sequencer_output(), 0);
}
#[test]
fn test_duty_write_masks_upper_bits() {
let mut pulse = Pulse::default();
pulse.write_duty(0xFF); assert_eq!(pulse.duty_mode, 3);
}
#[test]
fn test_envelope_constant_volume_mode() {
let mut pulse = Pulse::default();
pulse.write_control(0b0001_1010); assert_eq!(pulse.get_envelope_volume(), 10);
pulse.clock_envelope();
assert_eq!(pulse.get_envelope_volume(), 10);
}
#[test]
fn test_envelope_decay_mode() {
let mut pulse = Pulse::default();
pulse.write_control(0b0000_0000);
assert_eq!(pulse.get_envelope_volume(), 0);
pulse.envelope.restart();
pulse.clock_envelope();
assert_eq!(pulse.get_envelope_volume(), 15);
pulse.clock_envelope();
assert_eq!(pulse.get_envelope_volume(), 14);
pulse.clock_envelope();
assert_eq!(pulse.get_envelope_volume(), 13);
}
#[test]
fn test_envelope_start_flag_on_write() {
let mut pulse = Pulse::default();
pulse.write_control(0b0000_0000);
assert!(!pulse.get_envelope_start_flag());
write_length(&mut pulse, 0x00);
assert!(pulse.get_envelope_start_flag());
}
#[test]
fn test_envelope_divider_period() {
let mut pulse = Pulse::default();
pulse.write_control(0b0000_0010); pulse.envelope.restart();
pulse.clock_envelope();
assert_eq!(pulse.envelope.debug_counter(), 15);
assert_eq!(pulse.envelope.debug_divider(), 2);
pulse.clock_envelope();
assert_eq!(pulse.envelope.debug_counter(), 15);
assert_eq!(pulse.envelope.debug_divider(), 1);
pulse.clock_envelope();
assert_eq!(pulse.envelope.debug_counter(), 15);
assert_eq!(pulse.envelope.debug_divider(), 0);
pulse.clock_envelope();
assert_eq!(pulse.envelope.debug_counter(), 14);
assert_eq!(pulse.envelope.debug_divider(), 2);
}
#[test]
fn test_envelope_loop_flag() {
let mut pulse = Pulse::default();
pulse.write_control(0b0010_0000); pulse.envelope.restart();
pulse.clock_envelope();
for expected in (0..=15).rev() {
assert_eq!(pulse.envelope.debug_counter(), expected);
pulse.clock_envelope();
}
assert_eq!(pulse.envelope.debug_counter(), 15);
}
#[test]
fn test_envelope_no_loop_stays_at_zero() {
let mut pulse = Pulse::default();
pulse.write_control(0b0000_0000); pulse.envelope.restart();
pulse.clock_envelope();
for _ in 0..15 {
pulse.clock_envelope();
}
assert_eq!(pulse.envelope.debug_counter(), 0);
pulse.clock_envelope();
assert_eq!(pulse.envelope.debug_counter(), 0);
pulse.clock_envelope();
assert_eq!(pulse.envelope.debug_counter(), 0);
}
#[test]
fn test_write_control_parses_all_fields() {
let mut pulse = Pulse::default();
pulse.write_control(0b1111_1010);
assert_eq!(pulse.duty_mode, 3);
assert!(pulse.envelope.debug_loop_flag());
assert!(pulse.envelope.debug_disable_flag());
assert_eq!(pulse.envelope.debug_n(), 10);
}
#[test]
fn test_envelope_constant_volume_still_updates_decay() {
let mut pulse = Pulse::default();
pulse.write_control(0b0001_0101); pulse.envelope.restart();
pulse.clock_envelope();
assert_eq!(pulse.envelope.debug_counter(), 15);
assert_eq!(pulse.get_envelope_volume(), 5);
for _ in 0..6 {
pulse.clock_envelope();
}
assert_eq!(pulse.envelope.debug_counter(), 14);
assert_eq!(pulse.get_envelope_volume(), 5); }
#[test]
fn test_length_counter_load_values() {
let mut pulse = Pulse::default();
pulse.set_length_counter_enabled(true);
let expected = [
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,
];
for (i, &expected_value) in expected.iter().enumerate() {
let value = (i as u8) << 3; write_length(&mut pulse, value);
assert_eq!(
pulse.get_length_counter(),
expected_value,
"Failed for index {}",
i
);
}
}
#[test]
fn test_length_counter_decrements() {
let mut pulse = Pulse::default();
pulse.set_length_counter_enabled(true);
write_length(&mut pulse, 0b00001_000); assert_eq!(pulse.get_length_counter(), 254);
pulse.clock_length_counter();
assert_eq!(pulse.get_length_counter(), 253);
pulse.clock_length_counter();
assert_eq!(pulse.get_length_counter(), 252);
}
#[test]
fn test_length_counter_halt_flag() {
let mut pulse = Pulse::default();
pulse.write_control(0b0010_0000); pulse.apply_pending_length_halt();
pulse.set_length_counter_enabled(true);
write_length(&mut pulse, 0b00010_000);
assert_eq!(pulse.get_length_counter(), 20);
pulse.clock_length_counter();
assert_eq!(pulse.get_length_counter(), 20);
pulse.clock_length_counter();
assert_eq!(pulse.get_length_counter(), 20);
}
#[test]
fn test_length_counter_stops_at_zero() {
let mut pulse = Pulse::default();
pulse.write_control(0b0000_0000); pulse.set_length_counter_enabled(true);
write_length(&mut pulse, 0b00011_000);
assert_eq!(pulse.get_length_counter(), 2);
pulse.clock_length_counter();
assert_eq!(pulse.get_length_counter(), 1);
pulse.clock_length_counter();
assert_eq!(pulse.get_length_counter(), 0);
pulse.clock_length_counter();
assert_eq!(pulse.get_length_counter(), 0);
}
#[test]
fn test_length_counter_can_be_reloaded() {
let mut pulse = Pulse::default();
pulse.set_length_counter_enabled(true);
write_length(&mut pulse, 0b00100_000); assert_eq!(pulse.get_length_counter(), 40);
pulse.clock_length_counter();
pulse.clock_length_counter();
assert_eq!(pulse.get_length_counter(), 38);
write_length(&mut pulse, 0b00000_000); assert_eq!(pulse.get_length_counter(), 10);
}
#[test]
fn test_length_counter_halt_flag_shared_with_envelope_loop() {
let mut pulse = Pulse::default();
pulse.write_control(0b0010_0000); pulse.apply_pending_length_halt();
assert!(pulse.length_counter.is_halted());
assert!(pulse.envelope.debug_loop_flag());
pulse.write_control(0b0000_0000); pulse.apply_pending_length_halt();
assert!(!pulse.length_counter.is_halted());
assert!(!pulse.envelope.debug_loop_flag());
}
#[test]
fn test_set_length_counter_enabled() {
let mut pulse = Pulse::default();
pulse.set_length_counter_enabled(true);
write_length(&mut pulse, 0b01010_000); assert_eq!(pulse.get_length_counter(), 60);
pulse.set_length_counter_enabled(false);
assert_eq!(pulse.get_length_counter(), 0);
pulse.set_length_counter_enabled(true);
assert_eq!(pulse.get_length_counter(), 0);
write_length(&mut pulse, 0b00100_000); assert_eq!(pulse.get_length_counter(), 40);
}
#[test]
fn test_length_counter_with_halt_then_unhalt() {
let mut pulse = Pulse::default();
pulse.write_control(0b0010_0000); pulse.apply_pending_length_halt();
pulse.set_length_counter_enabled(true);
write_length(&mut pulse, 0b00000_000);
pulse.clock_length_counter();
assert_eq!(pulse.get_length_counter(), 10);
pulse.write_control(0b0000_0000);
pulse.apply_pending_length_halt();
pulse.clock_length_counter();
assert_eq!(pulse.get_length_counter(), 9); }
#[test]
fn test_sweep_initial_state() {
let pulse = Pulse::default();
assert!(pulse.is_sweep_muting());
}
#[test]
fn test_sweep_write_register() {
let mut pulse = Pulse::default();
pulse.write_sweep(0b1011_1010);
pulse.clock_sweep();
}
#[test]
fn test_sweep_reload_defers_first_update() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0x10);
pulse.write_timer_high(0x00); pulse.write_sweep(0b1011_0001);
assert_eq!(pulse.get_sweep_target_period(), 24);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 16);
assert_eq!(pulse.sweep_divider, 4);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 16);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 16);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 16);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 24);
}
#[test]
fn test_sweep_target_period_no_negate() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0x04);
pulse.write_timer_high(0x00); pulse.write_sweep(0b1000_0001);
assert_eq!(pulse.get_sweep_target_period(), 6);
}
#[test]
fn test_sweep_target_period_with_negate_ones_complement() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0x14);
pulse.write_timer_high(0x00); pulse.write_sweep(0b1000_1001);
assert_eq!(pulse.get_sweep_target_period(), 9);
}
#[test]
fn test_sweep_target_period_negate_with_shift_0() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0x14);
pulse.write_timer_high(0x00); pulse.write_sweep(0b1000_1000);
assert_eq!(pulse.get_sweep_target_period(), 0);
}
#[test]
fn test_sweep_muting_period_less_than_8() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0x07);
pulse.write_timer_high(0x00);
assert!(pulse.is_sweep_muting());
pulse.write_timer_low(0x08);
pulse.write_timer_high(0x00); assert!(!pulse.is_sweep_muting()); }
#[test]
fn test_sweep_muting_target_greater_than_7ff() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0xFF);
pulse.write_timer_high(0b000_11111); pulse.write_sweep(0b1000_0001);
assert!(pulse.is_sweep_muting());
}
#[test]
fn test_sweep_divider_reload() {
let mut pulse = Pulse::default();
pulse.write_sweep(0b1011_0000);
pulse.clock_sweep();
pulse.clock_sweep(); pulse.clock_sweep(); pulse.clock_sweep();
}
#[test]
fn test_sweep_divider_period_is_p_plus_one() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0x10);
pulse.write_timer_high(0x00); pulse.write_sweep(0b1001_0001);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 16);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 16);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 24);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 24);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 36);
}
#[test]
fn test_write_length_does_not_reset_sweep_divider() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0x10);
pulse.write_timer_high(0x00); pulse.write_sweep(0b1001_0001);
pulse.clock_sweep();
pulse.clock_sweep();
pulse.write_length_counter_timer_high(0x00);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 24);
}
#[test]
fn test_sweep_period_update_when_enabled() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0x10);
pulse.write_timer_high(0x00); pulse.write_sweep(0b1000_0001);
assert_eq!(pulse.get_sweep_target_period(), 24);
pulse.clock_sweep();
assert_eq!(
pulse.get_timer_period(),
16,
"Period should not update on first clock"
);
pulse.clock_sweep();
assert_eq!(
pulse.get_timer_period(),
24,
"Period should update to sweep target on second clock"
);
pulse.clock_sweep();
assert_eq!(
pulse.get_timer_period(),
36,
"Period should update to sweep target on third clock"
);
}
#[test]
fn test_sweep_no_update_when_disabled() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0x10);
pulse.write_timer_high(0x00); pulse.write_sweep(0b0000_0001);
pulse.clock_sweep();
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 16);
}
#[test]
fn test_sweep_no_update_when_shift_zero() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0x10);
pulse.write_timer_high(0x00); pulse.write_sweep(0b1000_0000);
pulse.clock_sweep();
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 16);
}
#[test]
fn test_sweep_no_update_when_muting() {
let mut pulse = Pulse::default();
pulse.write_timer_low(0x07);
pulse.write_timer_high(0x00); pulse.write_sweep(0b1000_0001);
assert!(pulse.is_sweep_muting());
pulse.clock_sweep();
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 7);
}
#[test]
fn test_output_when_all_conditions_met() {
let mut pulse = Pulse::default();
pulse.write_control(0b1011_1010); pulse.write_timer_low(0x64); pulse.write_timer_high(0x00);
write_length(&mut pulse, 0b00000_000);
for _ in 0..=100 {
pulse.clock_timer();
}
let output = pulse.output();
assert!(output == 10 || output == 0); }
#[test]
fn test_output_silenced_when_sequencer_zero() {
let mut pulse = Pulse::default();
pulse.write_control(0b0001_1111); pulse.write_timer_low(0x64);
pulse.write_timer_high(0x00);
write_length(&mut pulse, 0b00000_000);
for _ in 0..102 {
pulse.clock_timer();
}
let mut found_zero = false;
for _ in 0..8 {
if pulse.output() == 0 {
found_zero = true;
break;
}
pulse.clock_timer();
}
assert!(found_zero);
}
#[test]
fn test_output_silenced_when_length_counter_zero() {
let mut pulse = Pulse::default();
pulse.write_control(0b1011_1111); pulse.write_timer_low(0x64);
pulse.write_timer_high(0x00);
assert_eq!(pulse.output(), 0);
}
#[test]
fn test_output_silenced_when_timer_period_less_than_8() {
let mut pulse = Pulse::default();
pulse.write_control(0b1011_1111); pulse.write_timer_low(0x07); pulse.write_timer_high(0x00);
write_length(&mut pulse, 0b00000_000);
assert_eq!(pulse.output(), 0);
}
#[test]
fn test_output_silenced_when_sweep_overflow() {
let mut pulse = Pulse::default();
pulse.write_control(0b1011_1111); write_length(&mut pulse, 0b00000_111); pulse.write_timer_low(0xFF); pulse.write_sweep(0b1000_0001);
assert!(pulse.is_sweep_muting());
assert_eq!(pulse.output(), 0);
}
#[test]
fn test_output_uses_envelope_volume() {
let mut pulse = Pulse::default();
pulse.write_control(0b1000_0101); pulse.write_timer_low(0x64);
pulse.write_timer_high(0x00);
write_length(&mut pulse, 0b00000_000);
pulse.clock_envelope();
assert_eq!(pulse.get_envelope_volume(), 15);
let output = pulse.output();
assert!(output == 15 || output == 0);
}
#[test]
fn test_integration_full_waveform_cycle() {
let mut pulse = Pulse::default();
pulse.write_control(0b1001_1000); pulse.write_timer_low(0x0A); pulse.write_timer_high(0x00);
pulse.set_length_counter_enabled(true);
write_length(&mut pulse, 0b00000_000);
let mut outputs = Vec::new();
for _ in 0..8 {
outputs.push(pulse.output());
for _ in 0..=10 {
pulse.clock_timer();
}
}
let has_volume = outputs.contains(&8);
let has_silence = outputs.contains(&0);
assert!(has_volume && has_silence);
}
#[test]
fn test_integration_length_counter_silences_channel() {
let mut pulse = Pulse::default();
pulse.write_control(0b1001_1111); pulse.write_timer_low(0x64);
pulse.write_timer_high(0x00);
pulse.set_length_counter_enabled(true);
write_length(&mut pulse, 0b00000_000);
assert_eq!(pulse.get_length_counter(), 10);
for _ in 0..10 {
pulse.clock_length_counter();
}
assert_eq!(pulse.get_length_counter(), 0);
assert_eq!(pulse.output(), 0); }
#[test]
fn test_integration_sweep_changes_period_over_time() {
let mut pulse = Pulse::default();
pulse.write_control(0b1011_1111); pulse.write_timer_low(0x10); pulse.write_timer_high(0x00);
write_length(&mut pulse, 0b00000_000); pulse.write_sweep(0b1000_0001);
let initial_period = pulse.get_timer_period();
assert_eq!(initial_period, 16);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 16);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 24);
pulse.clock_sweep();
assert_eq!(pulse.get_timer_period(), 36);
}
#[test]
fn test_output_all_muting_conditions_independent() {
let mut pulse = Pulse::default();
pulse.write_control(0b1011_1111); pulse.write_timer_low(0x64); pulse.write_timer_high(0x00);
write_length(&mut pulse, 0b00000_000);
pulse.set_length_counter_enabled(false);
assert_eq!(pulse.output(), 0);
write_length(&mut pulse, 0b00000_000);
pulse.write_timer_low(0x07);
pulse.write_timer_high(0x00);
assert_eq!(pulse.output(), 0);
pulse.write_timer_low(0x64);
pulse.write_timer_high(0x00);
write_length(&mut pulse, 0b00000_111); pulse.write_timer_low(0xFF); pulse.write_sweep(0b1000_0001); assert_eq!(pulse.output(), 0);
}
#[test]
fn reset_restores_pulse_to_initial_state_preserving_identity() {
let mut pulse1 = Pulse::new(true);
let mut pulse2 = Pulse::new(false);
pulse1.write_control(0b1011_1111); pulse1.write_timer_low(0x64);
pulse1.write_timer_high(0x03);
pulse1.write_sweep(0b1000_0001);
pulse1.set_length_counter_enabled(true);
write_length(&mut pulse1, 0b00000_000);
for _ in 0..100 {
pulse1.clock_timer();
}
assert!(pulse1.get_timer_period() > 0);
assert!(pulse1.get_length_counter() > 0);
pulse1.reset();
pulse2.reset();
assert_eq!(pulse1.get_timer_period(), 0);
assert_eq!(pulse1.get_length_counter(), 0);
assert_eq!(pulse1.output(), 0);
pulse1.write_timer_low(0x10);
pulse1.write_timer_high(0x00);
pulse1.write_sweep(0b1000_1001); pulse2.write_timer_low(0x10);
pulse2.write_timer_high(0x00);
pulse2.write_sweep(0b1000_1001);
pulse1.clock_sweep();
pulse2.clock_sweep();
assert_eq!(pulse1.get_timer_period(), 16);
assert_eq!(pulse2.get_timer_period(), 16);
pulse1.clock_sweep();
pulse2.clock_sweep();
assert_eq!(pulse1.get_timer_period(), 7);
assert_eq!(pulse2.get_timer_period(), 8);
}
}