use super::length_counter::LengthCounter;
use crate::trace_apu;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TriangleState {
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 linear_counter: u8,
pub linear_counter_reload: u8,
pub linear_counter_reload_flag: bool,
pub control_flag: bool,
pub sequence_position: u8,
}
pub struct Triangle {
timer_period: u16,
timer_counter: u16,
sequence_position: u8,
linear_counter: u8,
linear_counter_reload_value: u8,
linear_counter_reload_flag: bool,
control_flag: bool,
length_counter: LengthCounter,
}
const TRIANGLE_SEQUENCE_LENGTH: u8 = 32;
const TRIANGLE_SEQUENCE: [u8; TRIANGLE_SEQUENCE_LENGTH as usize] = [
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15,
];
impl Default for Triangle {
fn default() -> Self {
Self::new()
}
}
impl Triangle {
pub fn new() -> Self {
Self {
timer_period: 0,
timer_counter: 0,
sequence_position: 0,
linear_counter: 0,
linear_counter_reload_value: 0,
linear_counter_reload_flag: false,
control_flag: false,
length_counter: LengthCounter::new(),
}
}
pub fn reset(&mut self) {
trace_apu!(2; "triangle reset");
self.length_counter.set_enabled(false);
}
pub fn clock_timer(&mut self) {
if self.timer_counter == 0 {
self.timer_counter = self.timer_period;
if self.linear_counter != 0 && self.length_counter.value() != 0 {
self.clock_sequencer();
trace_apu!(5; "triangle clock_timer advance seq_pos={} timer_period=0x{:03X}", self.sequence_position, self.timer_period);
}
} else {
self.timer_counter -= 1;
}
}
fn clock_sequencer(&mut self) {
self.sequence_position = (self.sequence_position + 1) % TRIANGLE_SEQUENCE_LENGTH;
}
#[cfg(test)]
pub fn debug_sequence_position(&self) -> u8 {
self.sequence_position
}
pub fn output(&self) -> u8 {
if self.linear_counter == 0 || self.length_counter.value() == 0 {
return 0;
}
TRIANGLE_SEQUENCE[self.sequence_position as usize]
}
pub fn write_linear_counter(&mut self, value: u8) {
self.control_flag = (value & 0x80) != 0;
self.length_counter.set_halt(self.control_flag);
self.linear_counter_reload_value = value & 0x7F;
trace_apu!(3; "triangle write_linear_counter value=0x{:02X} control={} reload={}", value, self.control_flag, self.linear_counter_reload_value);
}
pub fn write_timer_low(&mut self, value: u8) {
self.timer_period = (self.timer_period & 0xFF00) | (value as u16);
trace_apu!(4; "triangle write_timer_low value=0x{:02X} period=0x{:03X}", value, self.timer_period);
}
pub fn write_length_counter_timer_high(&mut self, value: u8) {
let timer_high = ((value & 0x07) as u16) << 8;
self.timer_period = (self.timer_period & 0x00FF) | timer_high;
let length_index = value >> 3;
self.load_length_counter(length_index);
self.linear_counter_reload_flag = true;
trace_apu!(3; "triangle write_length_counter_timer_high value=0x{:02X} length_index={} period=0x{:03X}", value, length_index, self.timer_period);
}
#[cfg(test)]
pub fn set_linear_counter_reload(&mut self, value: u8) {
self.linear_counter_reload_value = value;
}
#[cfg(test)]
pub fn trigger_linear_counter_reload(&mut self) {
self.linear_counter = self.linear_counter_reload_value;
}
#[cfg(test)]
pub fn clock_linear_counter(&mut self) {
if self.linear_counter > 0 {
self.linear_counter -= 1;
}
}
pub fn clock_linear_counter_with_reload(&mut self) {
if self.linear_counter_reload_flag {
self.linear_counter = self.linear_counter_reload_value;
trace_apu!(5; "triangle linear_counter reload value={}", self.linear_counter);
} else if self.linear_counter > 0 {
self.linear_counter -= 1;
trace_apu!(5; "triangle linear_counter decrement value={}", self.linear_counter);
}
if !self.control_flag {
self.linear_counter_reload_flag = false;
}
}
#[cfg(test)]
pub fn set_linear_counter_reload_flag(&mut self) {
self.linear_counter_reload_flag = true;
}
#[cfg(test)]
pub fn is_linear_counter_reload_flag_set(&self) -> bool {
self.linear_counter_reload_flag
}
#[cfg(test)]
pub fn set_control_flag(&mut self, value: bool) {
self.control_flag = value;
self.length_counter.set_halt(value);
}
pub fn load_length_counter(&mut self, index: u8) {
self.length_counter.load_from_index(index);
trace_apu!(3; "triangle load_length_counter index={}", index);
}
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_linear_counter(&self) -> u8 {
self.linear_counter
}
pub fn clock_length_counter(&mut self) {
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 set_length_counter_enabled(&mut self, enabled: bool) {
self.length_counter.set_enabled(enabled);
trace_apu!(2; "triangle set_length_counter_enabled {}", enabled);
}
pub fn is_length_counter_enabled(&self) -> bool {
self.length_counter.is_enabled()
}
pub fn capture_state(&self) -> TriangleState {
TriangleState {
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(),
linear_counter: self.linear_counter,
linear_counter_reload: self.linear_counter_reload_value,
linear_counter_reload_flag: self.linear_counter_reload_flag,
control_flag: self.control_flag,
sequence_position: self.sequence_position,
}
}
pub fn restore_state(&mut self, state: &TriangleState) {
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.linear_counter = state.linear_counter;
self.linear_counter_reload_value = state.linear_counter_reload;
self.linear_counter_reload_flag = state.linear_counter_reload_flag;
self.control_flag = state.control_flag;
self.sequence_position = state.sequence_position;
}
}
#[cfg(test)]
#[allow(clippy::unusual_byte_groupings)]
mod tests {
use super::*;
fn load_length(triangle: &mut Triangle, index: u8) {
triangle.load_length_counter(index);
triangle.apply_pending_length_reload();
}
fn write_length_high(triangle: &mut Triangle, value: u8) {
triangle.write_length_counter_timer_high(value);
triangle.apply_pending_length_reload();
}
fn enabled_triangle_with_counters() -> Triangle {
let mut triangle = Triangle::new();
triangle.set_length_counter_enabled(true);
load_length(&mut triangle, 1); triangle.write_linear_counter(0x7F); triangle.trigger_linear_counter_reload();
triangle
}
#[test]
fn test_triangle_new() {
let triangle = Triangle::new();
assert_eq!(triangle.timer_period, 0);
assert_eq!(triangle.timer_counter, 0);
assert_eq!(triangle.sequence_position, 0);
assert_eq!(triangle.linear_counter, 0);
assert_eq!(triangle.get_length_counter(), 0);
}
#[test]
fn test_triangle_32_step_sequence() {
let mut triangle = Triangle::new();
triangle.timer_period = 0;
triangle.linear_counter = 1;
triangle.set_length_counter_enabled(true);
load_length(&mut triangle, 0);
let expected_sequence = vec![
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, ];
for expected_value in expected_sequence {
assert_eq!(
triangle.output(),
expected_value,
"Expected {} at position {}",
expected_value,
triangle.sequence_position
);
triangle.clock_timer();
}
assert_eq!(triangle.sequence_position, 0);
assert_eq!(triangle.output(), 15);
}
#[test]
fn test_linear_counter_clocking() {
let mut triangle = Triangle::new();
triangle.set_linear_counter_reload(5);
triangle.trigger_linear_counter_reload();
assert_eq!(triangle.linear_counter, 5);
triangle.clock_linear_counter();
assert_eq!(triangle.linear_counter, 4);
triangle.clock_linear_counter();
assert_eq!(triangle.linear_counter, 3);
triangle.clock_linear_counter();
assert_eq!(triangle.linear_counter, 2);
triangle.clock_linear_counter();
assert_eq!(triangle.linear_counter, 1);
triangle.clock_linear_counter();
assert_eq!(triangle.linear_counter, 0);
triangle.clock_linear_counter();
assert_eq!(triangle.linear_counter, 0);
}
#[test]
fn test_linear_counter_reload_flag() {
let mut triangle = Triangle::new();
triangle.set_linear_counter_reload(10);
assert!(!triangle.is_linear_counter_reload_flag_set());
triangle.set_linear_counter_reload_flag();
assert!(triangle.is_linear_counter_reload_flag_set());
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.linear_counter, 10);
assert!(!triangle.is_linear_counter_reload_flag_set());
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.linear_counter, 9);
triangle.set_control_flag(true);
triangle.set_linear_counter_reload_flag();
assert!(triangle.is_linear_counter_reload_flag_set());
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.linear_counter, 10);
assert!(triangle.is_linear_counter_reload_flag_set());
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.linear_counter, 10); }
#[test]
fn test_linear_counter_reload_timing_requires_reload_flag_and_clears_when_control_clear() {
let mut triangle = Triangle::new();
triangle.set_length_counter_enabled(true);
triangle.write_linear_counter(0b0000_0111);
for _ in 0..20 {
triangle.clock_linear_counter_with_reload();
}
assert_eq!(triangle.get_linear_counter(), 0);
assert!(!triangle.is_linear_counter_reload_flag_set());
write_length_high(&mut triangle, 0b0000_0000);
assert!(triangle.is_linear_counter_reload_flag_set());
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.get_linear_counter(), 7);
assert!(!triangle.is_linear_counter_reload_flag_set());
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.get_linear_counter(), 6);
}
#[test]
fn test_linear_counter_halt_control_flag_stops_decrement_and_resumes_when_cleared() {
let mut triangle = Triangle::new();
triangle.set_length_counter_enabled(true);
triangle.write_linear_counter(0b1000_0111);
write_length_high(&mut triangle, 0b0000_0000);
assert!(triangle.is_linear_counter_reload_flag_set());
for _ in 0..5 {
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.get_linear_counter(), 7);
assert!(triangle.is_linear_counter_reload_flag_set());
}
triangle.write_linear_counter(0b0000_0111);
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.get_linear_counter(), 7);
assert!(!triangle.is_linear_counter_reload_flag_set());
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.get_linear_counter(), 6);
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.get_linear_counter(), 5);
}
#[test]
fn test_length_counter_clocking() {
let mut triangle = Triangle::new();
triangle.set_length_counter_enabled(true);
load_length(&mut triangle, 5);
assert_eq!(triangle.get_length_counter(), 4);
triangle.clock_length_counter();
assert_eq!(triangle.get_length_counter(), 3);
triangle.clock_length_counter();
assert_eq!(triangle.get_length_counter(), 2);
triangle.clock_length_counter();
assert_eq!(triangle.get_length_counter(), 1);
triangle.clock_length_counter();
assert_eq!(triangle.get_length_counter(), 0);
triangle.clock_length_counter();
assert_eq!(triangle.get_length_counter(), 0);
}
#[test]
fn test_length_counter_zero_mutes_output_but_linear_counter_still_clocks() {
let mut triangle = Triangle::new();
triangle.set_length_counter_enabled(true);
load_length(&mut triangle, 3);
assert_eq!(triangle.get_length_counter(), 2);
triangle.write_linear_counter(0b0000_0011);
triangle.set_linear_counter_reload_flag();
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.get_linear_counter(), 3);
assert_ne!(triangle.output(), 0);
triangle.clock_length_counter();
assert_eq!(triangle.get_length_counter(), 1);
assert_ne!(triangle.output(), 0);
triangle.clock_length_counter();
assert_eq!(triangle.get_length_counter(), 0);
assert_eq!(triangle.output(), 0);
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.get_linear_counter(), 2);
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.get_linear_counter(), 1);
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.get_linear_counter(), 0);
triangle.set_linear_counter_reload_flag();
triangle.clock_linear_counter_with_reload();
assert_eq!(triangle.get_linear_counter(), 3);
assert_eq!(triangle.output(), 0);
}
#[test]
fn test_length_counter_halt() {
let mut triangle = Triangle::new();
triangle.set_length_counter_enabled(true);
load_length(&mut triangle, 5);
assert_eq!(triangle.get_length_counter(), 4);
triangle.set_control_flag(true);
triangle.apply_pending_length_halt();
triangle.clock_length_counter();
assert_eq!(triangle.get_length_counter(), 4);
triangle.clock_length_counter();
assert_eq!(triangle.get_length_counter(), 4);
triangle.set_control_flag(false);
triangle.apply_pending_length_halt();
triangle.clock_length_counter();
assert_eq!(triangle.get_length_counter(), 3);
triangle.clock_length_counter();
assert_eq!(triangle.get_length_counter(), 2);
}
#[test]
fn test_write_linear_counter_register() {
let mut triangle = Triangle::new();
triangle.write_linear_counter(0b1010_1010);
assert_eq!(triangle.linear_counter_reload_value, 42);
assert!(triangle.control_flag);
triangle.write_linear_counter(0b0011_1111);
assert_eq!(triangle.linear_counter_reload_value, 63);
assert!(!triangle.control_flag);
triangle.write_linear_counter(0b1111_1111);
assert_eq!(triangle.linear_counter_reload_value, 127);
assert!(triangle.control_flag);
}
#[test]
fn test_write_timer_low_register() {
let mut triangle = Triangle::new();
triangle.write_timer_low(0xAB);
assert_eq!(triangle.timer_period & 0xFF, 0xAB);
triangle.write_timer_low(0x12);
assert_eq!(triangle.timer_period & 0xFF, 0x12);
}
#[test]
fn test_write_length_counter_timer_high_register() {
let mut triangle = Triangle::new();
triangle.set_length_counter_enabled(true);
triangle.write_timer_low(0xFF); write_length_high(&mut triangle, 0b1010_1101);
assert_eq!(triangle.get_length_counter(), 20);
let expected_timer = (0b101 << 8) | 0xFF;
assert_eq!(triangle.timer_period, expected_timer);
assert!(triangle.is_linear_counter_reload_flag_set());
}
#[test]
fn test_output_muted_when_counters_zero() {
let mut triangle = Triangle::new();
triangle.timer_period = 0;
triangle.set_length_counter_enabled(true);
triangle.set_linear_counter_reload(0);
triangle.trigger_linear_counter_reload();
load_length(&mut triangle, 1); assert_eq!(triangle.output(), 0);
triangle.set_linear_counter_reload(10);
triangle.trigger_linear_counter_reload();
triangle.clear_length_counter();
assert_eq!(triangle.output(), 0);
triangle.linear_counter = 5;
load_length(&mut triangle, 1); assert_eq!(triangle.output(), 15); }
#[test]
fn test_output_mutes_immediately_when_linear_counter_zero_and_recovers() {
let mut triangle = Triangle::new();
triangle.set_length_counter_enabled(true);
load_length(&mut triangle, 1);
triangle.linear_counter = 5;
assert_ne!(triangle.output(), 0);
triangle.linear_counter = 0;
assert_eq!(triangle.output(), 0);
triangle.linear_counter = 5;
assert_ne!(triangle.output(), 0);
}
#[test]
fn test_output_mutes_immediately_when_length_counter_zero_and_recovers() {
let mut triangle = Triangle::new();
triangle.set_length_counter_enabled(true);
triangle.linear_counter = 5;
load_length(&mut triangle, 3); assert_ne!(triangle.output(), 0);
triangle.clear_length_counter();
assert_eq!(triangle.output(), 0);
load_length(&mut triangle, 3);
assert_ne!(triangle.output(), 0);
}
#[test]
fn test_set_length_counter_enabled() {
let mut triangle = Triangle::new();
triangle.set_length_counter_enabled(true);
load_length(&mut triangle, 5);
assert_eq!(triangle.get_length_counter(), 4);
triangle.set_length_counter_enabled(false);
assert_eq!(triangle.get_length_counter(), 0);
load_length(&mut triangle, 10);
assert_eq!(triangle.get_length_counter(), 0);
triangle.set_length_counter_enabled(true);
assert_eq!(triangle.get_length_counter(), 0);
load_length(&mut triangle, 11);
assert_eq!(triangle.get_length_counter(), 10); }
#[test]
fn test_triangle_timer_period_sweep_no_skipping_steps() {
let mut triangle = enabled_triangle_with_counters();
let periods = [0u16, 1, 2, 3, 7, 15, 31, 63];
for &period in &periods {
triangle.timer_period = period;
triangle.timer_counter = period;
let mut prev_pos = triangle.sequence_position;
let mut steps_seen = 0u32;
let mut clocks_since_step = 0u16;
while steps_seen < 64 {
triangle.clock_timer();
clocks_since_step += 1;
if triangle.sequence_position != prev_pos {
let expected = (prev_pos + 1) % TRIANGLE_SEQUENCE_LENGTH;
assert_eq!(
triangle.sequence_position, expected,
"period={}: step skipped: prev={}, got={}, expected={}",
period, prev_pos, triangle.sequence_position, expected
);
assert_eq!(
clocks_since_step,
period + 1,
"period={}: step interval mismatch",
period
);
prev_pos = triangle.sequence_position;
clocks_since_step = 0;
steps_seen += 1;
}
}
}
}
#[test]
fn test_triangle_timer_period_change_does_not_affect_current_countdown() {
let mut triangle = enabled_triangle_with_counters();
triangle.timer_period = 5;
triangle.timer_counter = 3;
let start_pos = triangle.sequence_position;
triangle.timer_period = 10;
for _ in 0..3 {
triangle.clock_timer();
assert_eq!(triangle.sequence_position, start_pos);
}
triangle.clock_timer();
assert_eq!(
triangle.sequence_position,
(start_pos + 1) % TRIANGLE_SEQUENCE_LENGTH
);
assert_eq!(triangle.timer_counter, 10);
}
#[test]
fn test_triangle_sequencer_does_not_advance_when_linear_counter_zero() {
let mut triangle = enabled_triangle_with_counters();
triangle.linear_counter = 0;
triangle.timer_period = 0;
triangle.timer_counter = 0;
let start_pos = triangle.sequence_position;
for _ in 0..100 {
triangle.clock_timer();
}
assert_eq!(
triangle.sequence_position, start_pos,
"Sequencer should not advance when linear counter is zero"
);
}
#[test]
fn test_triangle_sequencer_does_not_advance_when_length_counter_zero() {
let mut triangle = enabled_triangle_with_counters();
triangle.clear_length_counter();
triangle.timer_period = 0;
triangle.timer_counter = 0;
let start_pos = triangle.sequence_position;
for _ in 0..100 {
triangle.clock_timer();
}
assert_eq!(
triangle.sequence_position, start_pos,
"Sequencer should not advance when length counter is zero"
);
}
#[test]
fn reset_disables_length_counter_but_preserves_other_state() {
let mut triangle = Triangle::new();
triangle.write_linear_counter(0b1000_0111); triangle.write_timer_low(0x50);
write_length_high(&mut triangle, 0b00001_010); triangle.set_length_counter_enabled(true);
triangle.linear_counter = 5;
triangle.sequence_position = 10;
triangle.timer_counter = 20;
assert!(triangle.is_length_counter_enabled());
assert_eq!(triangle.linear_counter, 5);
assert_eq!(triangle.sequence_position, 10);
triangle.reset();
assert!(!triangle.is_length_counter_enabled());
assert_eq!(triangle.linear_counter, 5);
assert_eq!(triangle.sequence_position, 10);
}
}