use std::f32::consts::PI;
const MAX_LPC_ORDER: usize = 12;
const ENERGY_TABLE: [f32; 96] = [
0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001,
0.0001, 0.0001, 0.0002, 0.0002, 0.0002, 0.0003, 0.0003, 0.0004,
0.0005, 0.0006, 0.0007, 0.0008, 0.001, 0.0012, 0.0015, 0.0018,
0.0022, 0.0027, 0.0032, 0.0039, 0.0047, 0.0056, 0.0068, 0.0081,
0.0097, 0.0116, 0.0139, 0.0166, 0.0198, 0.0237, 0.0283, 0.0337,
0.0402, 0.0480, 0.0573, 0.0683, 0.0815, 0.0972, 0.1159, 0.1382,
0.1648, 0.1966, 0.2344, 0.2796, 0.3335, 0.3977, 0.4739, 0.5642,
0.6310, 0.7079, 0.7943, 0.8913, 1.0, 1.122, 1.259, 1.413, 1.585,
1.778, 1.995, 2.239, 2.512, 2.818, 3.162, 3.548, 3.981, 4.467,
5.012, 5.623, 6.310, 7.079, 7.943, 8.913, 10.0, 11.22, 12.59,
14.13, 15.85, 17.78, 19.95, 22.39, 25.12, 28.18, 31.62, 35.48,
39.81, 44.67, 50.12, 56.23
];
#[derive(Debug, Clone)]
pub struct SidPacket {
pub energy_level: u8,
pub lpc_coeffs: Vec<u8>,
}
impl SidPacket {
pub fn from_bytes(data: &[u8]) -> Option<Self> {
if data.is_empty() {
return None;
}
let energy_level = data[0].min(95); let lpc_coeffs = if data.len() > 1 {
data[1..].to_vec()
} else {
Vec::new()
};
Some(SidPacket {
energy_level,
lpc_coeffs,
})
}
pub fn get_energy(&self) -> f32 {
ENERGY_TABLE[self.energy_level as usize]
}
pub fn get_reflection_coefficients(&self) -> Vec<f32> {
self.lpc_coeffs
.iter()
.take(MAX_LPC_ORDER)
.map(|&coeff| {
if coeff == 255 {
(coeff as f32) / 128.0 - 1.0
} else {
(coeff as f32 - 127.0) / 128.0
}
})
.collect()
}
}
pub struct CngDecoder {
target_energy: f32,
current_energy: f32,
target_reflection_coeffs: [f32; MAX_LPC_ORDER],
current_reflection_coeffs: [f32; MAX_LPC_ORDER],
filter_memory: [f32; MAX_LPC_ORDER],
random_seed: u32,
random_spare: Option<f32>,
samples_since_update: usize,
first_generation: bool,
lpc_order: usize,
energy_beta: f32,
coeff_beta: f32,
}
impl CngDecoder {
pub fn new() -> Self {
Self {
target_energy: 0.001, current_energy: 0.001,
target_reflection_coeffs: [0.0; MAX_LPC_ORDER],
current_reflection_coeffs: [0.0; MAX_LPC_ORDER],
filter_memory: [0.0; MAX_LPC_ORDER],
random_seed: 7777, random_spare: None,
samples_since_update: 0,
first_generation: true,
lpc_order: 5, energy_beta: 0.95, coeff_beta: 0.9, }
}
pub fn update_parameters(&mut self, sid: &SidPacket) {
self.target_energy = sid.get_energy() * 0.75;
let new_coeffs = sid.get_reflection_coefficients();
self.lpc_order = new_coeffs.len().min(MAX_LPC_ORDER);
self.target_reflection_coeffs.fill(0.0);
for (i, &coeff) in new_coeffs.iter().enumerate() {
if i < MAX_LPC_ORDER {
self.target_reflection_coeffs[i] = coeff.clamp(-0.99, 0.99); }
}
log::debug!(
"CNG: Updated parameters - energy={:.6}, lpc_order={}, coeffs={:?}",
self.target_energy,
self.lpc_order,
&self.target_reflection_coeffs[..self.lpc_order]
);
}
pub fn generate(&mut self, samples: &mut [f32]) -> Result<(), &'static str> {
if samples.is_empty() {
return Ok(());
}
self.interpolate_parameters();
let lpc_coeffs = self.reflection_to_lpc();
for sample in samples.iter_mut() {
let noise = self.generate_gaussian_noise();
let scaled_noise = noise * self.current_energy.sqrt();
*sample = self.apply_lpc_filter(scaled_noise, &lpc_coeffs);
}
self.samples_since_update += samples.len();
self.first_generation = false;
Ok(())
}
pub fn reset(&mut self) {
self.current_energy = 0.001;
self.current_reflection_coeffs.fill(0.0);
self.filter_memory.fill(0.0);
self.samples_since_update = 0;
self.first_generation = true;
self.random_spare = None;
log::debug!("CNG: Reset decoder state");
}
fn interpolate_parameters(&mut self) {
self.current_energy = self.energy_beta * self.current_energy +
(1.0 - self.energy_beta) * self.target_energy;
for i in 0..self.lpc_order {
self.current_reflection_coeffs[i] =
self.coeff_beta * self.current_reflection_coeffs[i] +
(1.0 - self.coeff_beta) * self.target_reflection_coeffs[i];
}
}
fn reflection_to_lpc(&self) -> Vec<f32> {
let mut lpc = vec![0.0; self.lpc_order + 1];
lpc[0] = 1.0;
if self.lpc_order == 0 {
return lpc;
}
let mut temp = vec![0.0; self.lpc_order];
for m in 1..=self.lpc_order {
let k = self.current_reflection_coeffs[m - 1];
lpc[m] = k;
for i in 1..m {
temp[i] = lpc[i] + k * lpc[m - i];
}
for i in 1..m {
lpc[i] = temp[i];
}
}
lpc
}
fn generate_gaussian_noise(&mut self) -> f32 {
if let Some(spare) = self.random_spare.take() {
return spare;
}
let u1 = self.uniform_random();
let u2 = self.uniform_random();
let magnitude = (-2.0 * u1.ln()).sqrt();
let angle = 2.0 * PI * u2;
let z0 = magnitude * angle.cos();
let z1 = magnitude * angle.sin();
self.random_spare = Some(z1);
z0
}
fn uniform_random(&mut self) -> f32 {
self.random_seed = self.random_seed.wrapping_mul(1103515245).wrapping_add(12345);
(self.random_seed as f32) / (u32::MAX as f32)
}
fn apply_lpc_filter(&mut self, input: f32, lpc_coeffs: &[f32]) -> f32 {
if self.lpc_order == 0 || lpc_coeffs.len() <= 1 {
return input;
}
let mut output = input;
for i in 1..lpc_coeffs.len().min(self.lpc_order + 1) {
if i <= self.filter_memory.len() {
output -= lpc_coeffs[i] * self.filter_memory[i - 1];
}
}
if self.lpc_order > 0 {
for i in (1..self.lpc_order).rev() {
self.filter_memory[i] = self.filter_memory[i - 1];
}
self.filter_memory[0] = output;
}
output
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sid_packet_parsing() {
let data = [30, 100, 150, 200]; let sid = SidPacket::from_bytes(&data).unwrap();
assert_eq!(sid.energy_level, 30);
assert_eq!(sid.lpc_coeffs.len(), 3);
assert!(sid.get_energy() > 0.0);
}
#[test]
fn test_reflection_coefficient_conversion() {
let data = [10, 127, 200, 50]; let sid = SidPacket::from_bytes(&data).unwrap();
let coeffs = sid.get_reflection_coefficients();
for coeff in coeffs {
assert!(coeff >= -1.0 && coeff <= 1.0);
}
}
#[test]
fn test_cng_decoder_basic() {
let mut decoder = CngDecoder::new();
let mut samples = vec![0.0; 160];
let result = decoder.generate(&mut samples);
assert!(result.is_ok());
let has_non_zero = samples.iter().any(|&s| s.abs() > 1e-10);
assert!(has_non_zero);
}
#[test]
fn test_parameter_update() {
let mut decoder = CngDecoder::new();
let initial_energy = decoder.current_energy;
let sid_data = [50, 100, 150]; let sid = SidPacket::from_bytes(&sid_data).unwrap();
decoder.update_parameters(&sid);
assert!(decoder.target_energy > initial_energy);
}
}