use super::envelope::Envelope;
use super::length_counter::LengthCounter;
use crate::apu::envelope::EnvelopeState;
use crate::console::TimingMode;
use crate::trace_apu;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NoiseState {
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 envelope: EnvelopeState,
pub mode_flag: bool,
pub shift_register: u16,
}
const NOISE_PERIOD_TABLE_NTSC: [u16; 16] = [
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
];
const NOISE_PERIOD_TABLE_PAL: [u16; 16] = [
4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,
];
pub struct Noise {
tv_system: TimingMode,
shift_register: u16,
mode: bool,
timer: u16,
timer_period: u16,
envelope: Envelope,
length_counter: LengthCounter,
}
impl Default for Noise {
fn default() -> Self {
Self::new()
}
}
impl Noise {
pub fn new() -> Self {
Self::new_with_tv_system(TimingMode::Ntsc)
}
pub fn new_with_tv_system(tv_system: TimingMode) -> Self {
let timer_period = match tv_system {
TimingMode::Ntsc => NOISE_PERIOD_TABLE_NTSC[0],
TimingMode::Pal => NOISE_PERIOD_TABLE_PAL[0],
TimingMode::MultiRegion | TimingMode::Dendy | TimingMode::Unknown(_) => {
NOISE_PERIOD_TABLE_NTSC[0]
}
};
Noise {
tv_system,
shift_register: 1, mode: false,
timer: 0,
timer_period,
envelope: Envelope::new(),
length_counter: LengthCounter::new(),
}
}
pub fn reset(&mut self) {
trace_apu!(2; "noise reset");
let tv_system = self.tv_system;
*self = Self::new_with_tv_system(tv_system);
}
pub fn clock_timer(&mut self) {
if self.timer == 0 {
self.timer = self.timer_period;
self.clock_shift_register();
trace_apu!(5; "noise clock_timer reload period={} shift=0x{:04X}", self.timer_period, self.shift_register);
} else {
self.timer -= 1;
}
}
fn clock_shift_register(&mut self) {
let bit0 = self.shift_register & 1;
let other_bit = if self.mode {
(self.shift_register >> 6) & 1
} else {
(self.shift_register >> 1) & 1
};
let feedback = bit0 ^ other_bit;
self.shift_register >>= 1;
self.shift_register = (self.shift_register & 0x3FFF) | (feedback << 14);
trace_apu!(5; "noise shift_register mode={} feedback={} value=0x{:04X}", self.mode, feedback, self.shift_register);
}
pub fn clock_envelope(&mut self) {
self.envelope.clock();
}
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 write_envelope(&mut self, value: u8) {
let halt = (value >> 5) & 1 == 1;
self.length_counter.set_halt(halt);
self.envelope.write_control(value);
trace_apu!(3; "noise write_envelope value=0x{:02X} halt={}", value, halt);
}
pub fn write_period(&mut self, value: u8) {
self.mode = (value >> 7) & 1 == 1;
let period_index = (value & 0x0F) as usize;
self.timer_period = match self.tv_system {
TimingMode::Ntsc => NOISE_PERIOD_TABLE_NTSC[period_index],
TimingMode::Pal => NOISE_PERIOD_TABLE_PAL[period_index],
TimingMode::MultiRegion | TimingMode::Dendy | TimingMode::Unknown(_) => {
NOISE_PERIOD_TABLE_NTSC[period_index]
}
};
trace_apu!(3; "noise write_period value=0x{:02X} mode={} period_index={} period={}", value, self.mode, period_index, self.timer_period);
}
pub fn write_length(&mut self, value: u8) {
let length_index = (value >> 3) & 0x1F;
self.length_counter.load_from_index(length_index);
self.envelope.restart();
trace_apu!(3; "noise write_length value=0x{:02X} length_index={}", value, length_index);
}
pub fn output(&self) -> u8 {
if !self.length_counter.is_enabled()
|| self.length_counter.value() == 0
|| (self.shift_register & 1) == 1
{
return 0;
}
self.envelope.volume()
}
pub fn set_length_counter_enabled(&mut self, enabled: bool) {
self.length_counter.set_enabled(enabled);
trace_apu!(2; "noise set_length_counter_enabled {}", enabled);
}
pub fn is_length_counter_enabled(&self) -> bool {
self.length_counter.is_enabled()
}
pub fn get_length_counter(&self) -> u8 {
self.length_counter.value()
}
pub fn clear_length_counter(&mut self) {
self.length_counter.clear();
}
pub fn capture_state(&self) -> NoiseState {
NoiseState {
timer: self.timer,
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(),
envelope: self.envelope.capture_state(),
mode_flag: self.mode,
shift_register: self.shift_register,
}
}
pub fn restore_state(&mut self, state: &NoiseState) {
self.timer = 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.envelope.restore_state(&state.envelope);
self.mode = state.mode_flag;
self.shift_register = state.shift_register;
}
}
#[cfg(test)]
#[allow(clippy::unusual_byte_groupings)]
mod tests {
use super::*;
use crate::console::TimingMode;
fn write_length(noise: &mut Noise, value: u8) {
noise.write_length(value);
noise.apply_pending_length_reload();
}
#[test]
fn test_noise_new() {
let noise = Noise::new();
assert_eq!(noise.shift_register, 1);
assert!(!noise.mode);
assert_eq!(noise.timer_period, 4);
}
#[test]
fn test_noise_pal_period_table() {
let mut noise = Noise::new_with_tv_system(TimingMode::Pal);
assert_eq!(noise.timer_period, 4);
noise.write_period(0b0000_0010);
assert_eq!(noise.timer_period, 14);
}
#[test]
fn test_lfsr_mode_0_feedback() {
let mut noise = Noise::new();
noise.shift_register = 0b0000_0000_0000_0011;
noise.clock_shift_register();
assert_eq!(noise.shift_register, 0b0000_0000_0000_0001);
}
#[test]
fn test_timer_clocks_shift_register() {
let mut noise = Noise::new();
noise.shift_register = 0b0000_0000_0000_0001; noise.timer_period = 4; noise.timer = 4;
noise.clock_timer(); noise.clock_timer(); noise.clock_timer(); assert_eq!(noise.shift_register, 0b0000_0000_0000_0001);
noise.clock_timer(); assert_eq!(noise.shift_register, 0b0000_0000_0000_0001);
noise.clock_timer();
assert_eq!(noise.shift_register, 0b0100_0000_0000_0000);
}
#[test]
fn test_lfsr_mode_1_feedback() {
let mut noise = Noise::new();
noise.mode = true;
noise.shift_register = 0b0000_0000_0100_0001;
noise.clock_shift_register();
assert_eq!(noise.shift_register, 0b0000_0000_0010_0000);
}
#[test]
fn test_envelope_decay_mode() {
let mut noise = Noise::new();
noise.envelope.write_control(0b0000_0010); noise.envelope.restart();
noise.clock_envelope();
assert_eq!(noise.envelope.debug_counter(), 15);
assert_eq!(noise.envelope.debug_divider(), 2);
noise.clock_envelope();
assert_eq!(noise.envelope.debug_counter(), 15); assert_eq!(noise.envelope.debug_divider(), 1);
noise.clock_envelope();
assert_eq!(noise.envelope.debug_counter(), 15); assert_eq!(noise.envelope.debug_divider(), 0);
noise.clock_envelope();
assert_eq!(noise.envelope.debug_counter(), 14);
assert_eq!(noise.envelope.debug_divider(), 2); }
#[test]
fn test_length_counter_clocking() {
let mut noise = Noise::new();
noise.set_length_counter_enabled(true);
noise.length_counter.load_from_index(0); noise.length_counter.reload_counter();
noise.length_counter.set_halt(false);
noise.length_counter.apply_pending_halt();
noise.clock_length_counter();
assert_eq!(noise.get_length_counter(), 9);
noise.clock_length_counter();
assert_eq!(noise.get_length_counter(), 8);
}
#[test]
fn test_length_counter_halt() {
let mut noise = Noise::new();
noise.set_length_counter_enabled(true);
noise.length_counter.load_from_index(0); noise.length_counter.reload_counter();
noise.length_counter.set_halt(true);
noise.length_counter.apply_pending_halt();
noise.clock_length_counter();
assert_eq!(noise.get_length_counter(), 10); }
#[test]
fn test_write_envelope_register() {
let mut noise = Noise::new();
noise.write_envelope(0b0001_0101);
assert!(!noise.length_counter.is_halted());
assert!(!noise.envelope.debug_loop_flag());
assert!(noise.envelope.debug_disable_flag());
assert_eq!(noise.envelope.debug_n(), 5);
}
#[test]
fn test_write_period_register() {
let mut noise = Noise::new();
noise.write_period(0b1000_1010);
assert!(noise.mode);
assert_eq!(noise.timer_period, NOISE_PERIOD_TABLE_NTSC[10]);
}
#[test]
fn test_write_length_register() {
let mut noise = Noise::new();
noise.set_length_counter_enabled(true);
write_length(&mut noise, 0b10110_000);
assert_eq!(noise.get_length_counter(), LengthCounter::lookup(22));
assert!(noise.envelope.debug_start_flag()); }
#[test]
fn test_output_muted_when_length_zero() {
let mut noise = Noise::new();
noise.length_counter.clear();
noise.write_envelope(0b0001_1010);
assert_eq!(noise.output(), 0);
}
#[test]
fn test_output_muted_when_shift_register_bit_0_set() {
let mut noise = Noise::new();
noise.set_length_counter_enabled(true);
noise.length_counter.load_from_index(0); noise.write_envelope(0b0001_1010); noise.shift_register = 0b0000_0000_0000_0001;
assert_eq!(noise.output(), 0);
}
#[test]
fn test_output_uses_envelope_volume() {
let mut noise = Noise::new();
noise.set_length_counter_enabled(true); noise.length_counter.load_from_index(0); noise.write_envelope(0b0000_0000); write_length(&mut noise, 0b00000_000);
noise.clock_envelope();
for _ in 0..8 {
noise.clock_envelope();
}
assert_eq!(noise.envelope.debug_counter(), 7);
noise.shift_register = 0b0000_0000_0000_0010;
assert_eq!(noise.output(), 7);
}
#[test]
fn test_output_uses_constant_volume() {
let mut noise = Noise::new();
noise.set_length_counter_enabled(true); noise.length_counter.load_from_index(0); noise.length_counter.reload_counter();
noise.write_envelope(0b0001_1100); noise.shift_register = 0b0000_0000_0000_0010;
assert_eq!(noise.output(), 12);
}
#[test]
fn test_set_length_counter_enabled() {
let mut noise = Noise::new();
noise.set_length_counter_enabled(true);
noise.length_counter.load_from_index(0);
noise.length_counter.reload_counter();
assert_eq!(noise.get_length_counter(), 10);
noise.set_length_counter_enabled(false);
assert_eq!(noise.get_length_counter(), 0);
noise.set_length_counter_enabled(true);
assert_eq!(noise.get_length_counter(), 0);
}
#[test]
fn reset_restores_noise_to_initial_state() {
let mut noise = Noise::new();
noise.write_period(0b1000_1111); noise.write_envelope(0b0011_1010); noise.set_length_counter_enabled(true);
write_length(&mut noise, 0b00000_000); for _ in 0..1000 {
noise.clock_timer();
}
assert!(noise.mode); assert_eq!(noise.get_length_counter(), 10);
assert_ne!(noise.shift_register, 1);
noise.reset();
assert_eq!(noise.shift_register, 1); assert!(!noise.mode);
assert_eq!(noise.get_length_counter(), 0);
assert!(!noise.is_length_counter_enabled());
}
}