use crate::tables::{ENV_DATA, SHAPE_TO_ENV};
pub const NUM_CHANNELS: usize = 3;
#[derive(Clone, Debug, Default)]
pub struct ToneGenerator {
counter: u32,
period: u32,
edge_bits: u32,
pending_reset: bool,
}
impl ToneGenerator {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn set_period(&mut self, period: u32) {
self.period = period;
}
#[cfg(test)]
pub fn period(&self) -> u32 {
self.period
}
#[inline]
pub fn is_half_amplitude(&self) -> bool {
self.period <= 1
}
#[inline]
pub fn mark_pending_reset(&mut self) {
self.pending_reset = true;
}
#[inline]
pub fn apply_pending_reset(&mut self, channel_shift: u32) {
if self.pending_reset {
self.edge_bits ^= 0x1f << channel_shift;
self.counter = 0;
self.pending_reset = false;
}
}
#[inline]
pub fn tick(&mut self, channel_shift: u32) -> u32 {
self.counter += 1;
if self.counter >= self.period {
self.edge_bits ^= 0x1f << channel_shift;
self.counter = 0;
}
self.edge_bits
}
#[inline]
pub fn set_edge_bits(&mut self, bits: u32) {
self.edge_bits = bits;
}
pub fn reset(&mut self) {
self.counter = 0;
self.period = 0;
self.pending_reset = false;
}
}
#[derive(Clone, Debug)]
pub struct NoiseGenerator {
counter: u32,
period: u32,
lfsr: u32,
output_mask: u32,
half_tick: bool,
}
impl NoiseGenerator {
pub fn new() -> Self {
Self {
counter: 0,
period: 0,
lfsr: 1, output_mask: 0,
half_tick: false,
}
}
#[inline]
pub fn set_period(&mut self, period: u32) {
self.period = period;
}
#[inline]
pub fn tick(&mut self) -> u32 {
self.half_tick = !self.half_tick;
if self.half_tick {
self.counter += 1;
let effective_period = self.period.max(1);
if self.counter >= effective_period {
let lsb = self.lfsr & 1;
self.lfsr >>= 1;
if lsb != 0 {
self.lfsr ^= 0x12000; }
self.output_mask = if lsb != 0 { !0 } else { 0 };
self.counter = 0;
}
}
self.output_mask
}
#[cfg(test)]
pub fn output_mask(&self) -> u32 {
self.output_mask
}
pub fn reset(&mut self) {
self.counter = 0;
self.lfsr = 1;
self.output_mask = 0;
self.half_tick = false;
}
}
impl Default for NoiseGenerator {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug, Default)]
pub struct EnvelopeGenerator {
counter: u32,
period: u32,
position: i32,
data_offset: usize,
}
impl EnvelopeGenerator {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn set_period(&mut self, period: u32) {
self.period = period;
}
#[inline]
pub fn set_shape(&mut self, shape: u8) {
let shape_index = (shape & 0x0f) as usize;
self.data_offset = SHAPE_TO_ENV[shape_index] as usize * 32 * 4;
debug_assert!(self.data_offset <= 9 * 32 * 4, "data_offset out of range");
self.position = -64;
self.counter = 0;
}
#[inline]
pub fn trigger(&mut self) {
self.position = -64;
self.counter = 0;
}
#[inline]
pub fn tick(&mut self) {
self.counter += 1;
if self.counter >= self.period {
self.position += 1;
if self.position > 0 {
self.position &= 63;
}
self.counter = 0;
}
}
#[inline]
pub fn level(&self) -> u32 {
let index = self.data_offset + (self.position + 64) as usize;
ENV_DATA.get(index).copied().unwrap_or(0) as u32
}
pub fn reset(&mut self) {
self.counter = 0;
self.position = 0;
self.data_offset = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tone_generator_period() {
let mut tone = ToneGenerator::new();
tone.set_period(100);
assert_eq!(tone.period(), 100);
assert!(!tone.is_half_amplitude());
tone.set_period(1);
assert!(tone.is_half_amplitude());
}
#[test]
fn test_noise_generator_lfsr() {
let mut noise = NoiseGenerator::new();
let mut outputs = Vec::new();
for _ in 0..100 {
noise.tick();
outputs.push(noise.output_mask());
}
let has_variation = outputs.windows(2).any(|w| w[0] != w[1]);
assert!(
has_variation,
"Noise generator should produce varying output"
);
}
#[test]
fn test_envelope_trigger() {
let mut envelope = EnvelopeGenerator::new();
envelope.set_period(10);
envelope.set_shape(0);
assert_eq!(envelope.position, -64);
for _ in 0..50 {
envelope.tick();
}
envelope.trigger();
assert_eq!(envelope.position, -64);
}
}