use crate::core_types::LPC_ORDER;
#[derive(Debug, Clone)]
pub struct SynthesisFilter {
state: [f32; LPC_ORDER],
}
impl SynthesisFilter {
pub fn new() -> Self {
Self {
state: [0.0; LPC_ORDER],
}
}
#[inline]
pub fn process_sample(&mut self, excitation: f32, a: &[f32; LPC_ORDER]) -> f32 {
let mut y = excitation;
for i in 0..LPC_ORDER {
y -= a[i] * self.state[i];
}
for i in (1..LPC_ORDER).rev() {
self.state[i] = self.state[i - 1];
}
self.state[0] = y;
y
}
pub fn process_buffer(
&mut self,
excitation: &[f32],
a: &[f32; LPC_ORDER],
output: &mut [f32],
) {
let n = excitation.len().min(output.len());
for i in 0..n {
output[i] = self.process_sample(excitation[i], a);
}
}
pub fn process_buffer_interp(
&mut self,
excitation: &[f32],
a_start: &[f32; LPC_ORDER],
a_end: &[f32; LPC_ORDER],
output: &mut [f32],
) {
let n = excitation.len().min(output.len());
if n == 0 {
return;
}
let mut a_interp = [0.0f32; LPC_ORDER];
for i in 0..n {
let t = i as f32 / (n - 1).max(1) as f32;
for k in 0..LPC_ORDER {
a_interp[k] = (1.0 - t) * a_start[k] + t * a_end[k];
}
output[i] = self.process_sample(excitation[i], &a_interp);
}
}
pub fn reset(&mut self) {
self.state = [0.0; LPC_ORDER];
}
pub fn state(&self) -> &[f32; LPC_ORDER] {
&self.state
}
}
#[derive(Debug, Clone)]
pub struct DeEmphasis {
prev: f32,
coeff: f32,
}
impl DeEmphasis {
pub fn new(coeff: f32) -> Self {
Self { prev: 0.0, coeff }
}
#[inline]
pub fn process_sample(&mut self, x: f32) -> f32 {
let y = x + self.coeff * self.prev;
self.prev = y;
y
}
pub fn process_inplace(&mut self, buf: &mut [f32]) {
for sample in buf.iter_mut() {
*sample = self.process_sample(*sample);
}
}
pub fn reset(&mut self) {
self.prev = 0.0;
}
}
use crate::core_types::{FrameParams, FRAME_SAMPLES, NUM_LSF};
use crate::lpc::lsf_to_lpc;
use crate::math::{powf, log10f, fabsf, fmaxf};
pub const PRE_EMPHASIS_COEFF: f32 = 0.97;
pub struct SynthesisFrameProcessor {
filter: SynthesisFilter,
deemph: DeEmphasis,
prev_lpc: [f32; LPC_ORDER],
}
impl SynthesisFrameProcessor {
pub fn new() -> Self {
Self {
filter: SynthesisFilter::new(),
deemph: DeEmphasis::new(PRE_EMPHASIS_COEFF),
prev_lpc: [0.0; LPC_ORDER],
}
}
pub fn process_frame(
&mut self,
params: &FrameParams,
excitation: &[f32],
output: &mut [f32],
) {
let n = excitation.len().min(output.len()).min(FRAME_SAMPLES);
let lpc = lsf_to_lpc(¶ms.lsf);
let gain_linear = db_to_linear(params.gain);
let mut scaled_excitation = [0.0f32; FRAME_SAMPLES];
for i in 0..n {
scaled_excitation[i] = excitation[i] * gain_linear;
}
self.filter.process_buffer_interp(
&scaled_excitation[..n],
&self.prev_lpc,
&lpc,
&mut output[..n],
);
self.deemph.process_inplace(&mut output[..n]);
self.prev_lpc = lpc;
}
pub fn process_frame_no_interp(
&mut self,
params: &FrameParams,
excitation: &[f32],
output: &mut [f32],
) {
let n = excitation.len().min(output.len()).min(FRAME_SAMPLES);
let lpc = lsf_to_lpc(¶ms.lsf);
let gain_linear = db_to_linear(params.gain);
let mut scaled_excitation = [0.0f32; FRAME_SAMPLES];
for i in 0..n {
scaled_excitation[i] = excitation[i] * gain_linear;
}
self.filter.process_buffer(
&scaled_excitation[..n],
&lpc,
&mut output[..n],
);
self.deemph.process_inplace(&mut output[..n]);
self.prev_lpc = lpc;
}
pub fn reset(&mut self) {
self.filter.reset();
self.deemph.reset();
self.prev_lpc = [0.0; LPC_ORDER];
}
pub fn prev_lpc(&self) -> &[f32; LPC_ORDER] {
&self.prev_lpc
}
}
#[inline]
pub fn db_to_linear(db: f32) -> f32 {
powf(10.0, db / 20.0)
}
#[inline]
pub fn linear_to_db(linear: f32) -> f32 {
20.0 * log10f(fmaxf(fabsf(linear), 1e-10))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_identity_filter() {
let mut filt = SynthesisFilter::new();
let a = [0.0f32; LPC_ORDER];
let input = [1.0, 2.0, 3.0, -1.0, 0.5];
for &x in &input {
let y = filt.process_sample(x, &a);
assert!(
(y - x).abs() < 1e-6,
"Identity filter: in={}, out={}",
x, y
);
}
}
#[test]
fn test_impulse_response() {
let mut filt = SynthesisFilter::new();
let mut a = [0.0f32; LPC_ORDER];
a[0] = 0.9;
let y0 = filt.process_sample(1.0, &a);
assert!((y0 - 1.0).abs() < 1e-6);
let y1 = filt.process_sample(0.0, &a);
assert!((y1 - (-0.9)).abs() < 1e-6, "y[1] should be -0.9, got {}", y1);
let y2 = filt.process_sample(0.0, &a);
assert!((y2 - 0.81).abs() < 1e-5, "y[2] should be 0.81, got {}", y2);
let y3 = filt.process_sample(0.0, &a);
assert!((y3 - (-0.729)).abs() < 1e-4, "y[3] should be -0.729, got {}", y3);
}
#[test]
fn test_impulse_response_decays() {
let mut filt = SynthesisFilter::new();
let mut a = [0.0f32; LPC_ORDER];
a[0] = 0.5;
a[1] = 0.2;
let _y0 = filt.process_sample(1.0, &a);
let mut decayed = false;
for _ in 0..100 {
let y = filt.process_sample(0.0, &a);
if y.abs() < 0.001 {
decayed = true;
break;
}
}
assert!(decayed, "Stable filter impulse response should decay");
}
#[test]
fn test_process_buffer() {
let mut filt1 = SynthesisFilter::new();
let mut filt2 = SynthesisFilter::new();
let a = [0.0f32; LPC_ORDER];
let excitation = [1.0, 2.0, 3.0, 4.0, 5.0];
let mut output = [0.0f32; 5];
filt1.process_buffer(&excitation, &a, &mut output);
for i in 0..5 {
let y = filt2.process_sample(excitation[i], &a);
assert!(
(output[i] - y).abs() < 1e-6,
"Buffer vs sample mismatch at [{}]",
i
);
}
}
#[test]
fn test_state_carries_across_calls() {
let mut filt = SynthesisFilter::new();
let mut a = [0.0f32; LPC_ORDER];
a[0] = 0.5;
let y0 = filt.process_sample(1.0, &a);
let y1 = filt.process_sample(0.0, &a);
assert!(
(filt.state()[0] - y1).abs() < 1e-6,
"State[0] should be last output"
);
filt.reset();
assert!(
filt.state().iter().all(|&s| s == 0.0),
"State should be zero after reset"
);
}
#[test]
fn test_dc_gain() {
let mut filt = SynthesisFilter::new();
let mut a = [0.0f32; LPC_ORDER];
a[0] = 0.5;
let mut y = 0.0f32;
for _ in 0..200 {
y = filt.process_sample(1.0, &a);
}
let expected_dc = 1.0 / (1.0 + 0.5);
assert!(
(y - expected_dc).abs() < 0.01,
"DC gain: expected {}, got {}",
expected_dc, y
);
}
#[test]
fn test_interp_same_coefficients() {
let mut filt1 = SynthesisFilter::new();
let mut filt2 = SynthesisFilter::new();
let mut a = [0.0f32; LPC_ORDER];
a[0] = 0.7;
a[1] = -0.2;
let excitation = [1.0, 0.5, -0.3, 0.8, 0.0, -0.5, 0.2, 0.1];
let mut out1 = [0.0f32; 8];
let mut out2 = [0.0f32; 8];
filt1.process_buffer(&excitation, &a, &mut out1);
filt2.process_buffer_interp(&excitation, &a, &a, &mut out2);
for i in 0..8 {
assert!(
(out1[i] - out2[i]).abs() < 1e-5,
"Interp with same coeffs should match fixed at [{}]: {} vs {}",
i, out1[i], out2[i]
);
}
}
#[test]
fn test_interp_produces_output() {
let mut filt = SynthesisFilter::new();
let mut a_start = [0.0f32; LPC_ORDER];
let mut a_end = [0.0f32; LPC_ORDER];
a_start[0] = 0.3;
a_end[0] = 0.8;
let excitation = [1.0; 32];
let mut output = [0.0f32; 32];
filt.process_buffer_interp(&excitation, &a_start, &a_end, &mut output);
let energy: f32 = output.iter().map(|x| x * x).sum::<f32>() / 32.0;
assert!(energy > 0.1, "Interpolated output should have energy: {}", energy);
}
#[test]
fn test_deemphasis_identity() {
let mut de = DeEmphasis::new(0.0);
assert!((de.process_sample(1.0) - 1.0).abs() < 1e-6);
assert!((de.process_sample(-0.5) - (-0.5)).abs() < 1e-6);
}
#[test]
fn test_deemphasis_accumulates() {
let mut de = DeEmphasis::new(0.97);
let y0 = de.process_sample(1.0);
assert!((y0 - 1.0).abs() < 1e-6);
let y1 = de.process_sample(0.0);
assert!((y1 - 0.97).abs() < 1e-6, "y[1] should be 0.97, got {}", y1);
let y2 = de.process_sample(0.0);
assert!((y2 - 0.9409).abs() < 1e-4, "y[2] should be ~0.9409, got {}", y2);
}
#[test]
fn test_pre_emphasis_deemphasis_inverse() {
let original = [0.5, 0.8, -0.3, 0.1, 0.9, -0.7, 0.4, 0.2];
let coeff = 0.97;
let mut pre = [0.0f32; 8];
pre[0] = original[0]; for i in 1..8 {
pre[i] = original[i] - coeff * original[i - 1];
}
let mut de = DeEmphasis::new(coeff);
let mut recovered = [0.0f32; 8];
for i in 0..8 {
recovered[i] = de.process_sample(pre[i]);
}
for i in 0..8 {
assert!(
(recovered[i] - original[i]).abs() < 1e-5,
"Pre/de-emphasis inverse failed at [{}]: {} vs {}",
i, original[i], recovered[i]
);
}
}
#[test]
fn test_deemphasis_inplace() {
let mut de1 = DeEmphasis::new(0.97);
let mut de2 = DeEmphasis::new(0.97);
let input = [1.0, 0.5, -0.3, 0.8];
let mut expected = [0.0f32; 4];
for i in 0..4 {
expected[i] = de1.process_sample(input[i]);
}
let mut buf = input;
de2.process_inplace(&mut buf);
for i in 0..4 {
assert!(
(buf[i] - expected[i]).abs() < 1e-6,
"Inplace mismatch at [{}]",
i
);
}
}
#[test]
fn test_db_to_linear() {
assert!((db_to_linear(0.0) - 1.0).abs() < 1e-6);
assert!((db_to_linear(-20.0) - 0.1).abs() < 1e-4);
assert!((db_to_linear(-6.0) - 0.5012).abs() < 0.01);
assert!((db_to_linear(-60.0) - 0.001).abs() < 1e-4);
}
#[test]
fn test_linear_to_db_roundtrip() {
let values = [-3.0, -10.0, -20.0, -40.0, 0.0];
for &db in &values {
let lin = db_to_linear(db);
let recovered = linear_to_db(lin);
assert!(
(recovered - db).abs() < 0.01,
"dB roundtrip failed: {} → {} → {}",
db, lin, recovered
);
}
}
#[test]
fn test_frame_processor_silence() {
let mut proc = SynthesisFrameProcessor::new();
let params = FrameParams::default(); let excitation = [0.0f32; FRAME_SAMPLES];
let mut output = [0.0f32; FRAME_SAMPLES];
proc.process_frame(¶ms, &excitation, &mut output);
let peak = output.iter().fold(0.0f32, |m, &s| m.max(s.abs()));
assert!(peak < 0.001, "Silence in should give silence out, peak={}", peak);
}
#[test]
fn test_frame_processor_produces_output() {
let mut proc = SynthesisFrameProcessor::new();
let mut params = FrameParams::default();
params.gain = -10.0;
let mut excitation = [0.0f32; FRAME_SAMPLES];
excitation[0] = 1.0;
excitation[80] = 1.0;
let mut output = [0.0f32; FRAME_SAMPLES];
proc.process_frame(¶ms, &excitation, &mut output);
let energy: f32 = output.iter().map(|x| x * x).sum::<f32>() / FRAME_SAMPLES as f32;
assert!(energy > 1e-6, "Should produce non-trivial output, energy={}", energy);
}
#[test]
fn test_frame_processor_gain_scaling() {
let excitation = [0.1f32; FRAME_SAMPLES];
let mut proc_quiet = SynthesisFrameProcessor::new();
let mut params_quiet = FrameParams::default();
params_quiet.gain = -40.0;
let mut out_quiet = [0.0f32; FRAME_SAMPLES];
proc_quiet.process_frame_no_interp(¶ms_quiet, &excitation, &mut out_quiet);
let mut proc_loud = SynthesisFrameProcessor::new();
let mut params_loud = FrameParams::default();
params_loud.gain = -10.0;
let mut out_loud = [0.0f32; FRAME_SAMPLES];
proc_loud.process_frame_no_interp(¶ms_loud, &excitation, &mut out_loud);
let energy_quiet: f32 = out_quiet.iter().map(|x| x * x).sum();
let energy_loud: f32 = out_loud.iter().map(|x| x * x).sum();
assert!(
energy_loud > energy_quiet,
"Louder gain should produce more energy: loud={}, quiet={}",
energy_loud, energy_quiet
);
}
#[test]
fn test_frame_processor_stores_prev_lpc() {
let mut proc = SynthesisFrameProcessor::new();
assert!(proc.prev_lpc().iter().all(|&x| x == 0.0));
let params = FrameParams::default();
let excitation = [0.0f32; FRAME_SAMPLES];
let mut output = [0.0f32; FRAME_SAMPLES];
proc.process_frame(¶ms, &excitation, &mut output);
let lpc = lsf_to_lpc(¶ms.lsf);
for i in 0..LPC_ORDER {
assert!(
(proc.prev_lpc()[i] - lpc[i]).abs() < 1e-5,
"prev_lpc[{}] mismatch: {} vs {}",
i, proc.prev_lpc()[i], lpc[i]
);
}
}
#[test]
fn test_frame_processor_reset() {
let mut proc = SynthesisFrameProcessor::new();
let mut params = FrameParams::default();
params.gain = -10.0;
let excitation = [0.5f32; FRAME_SAMPLES];
let mut output = [0.0f32; FRAME_SAMPLES];
proc.process_frame(¶ms, &excitation, &mut output);
proc.reset();
assert!(proc.prev_lpc().iter().all(|&x| x == 0.0));
}
#[test]
fn test_frame_processor_output_finite() {
let mut proc = SynthesisFrameProcessor::new();
let mut params = FrameParams::default();
params.gain = -6.0;
let excitation = [0.2f32; FRAME_SAMPLES];
let mut output = [0.0f32; FRAME_SAMPLES];
for _ in 0..10 {
proc.process_frame(¶ms, &excitation, &mut output);
assert!(
output.iter().all(|x| x.is_finite()),
"Output must be finite after multiple frames"
);
}
}
#[test]
fn test_no_interp_vs_interp_first_frame() {
let mut proc1 = SynthesisFrameProcessor::new();
let mut proc2 = SynthesisFrameProcessor::new();
let mut params = FrameParams::default();
params.gain = -10.0;
let excitation = [0.3f32; FRAME_SAMPLES];
let mut out1 = [0.0f32; FRAME_SAMPLES];
let mut out2 = [0.0f32; FRAME_SAMPLES];
proc1.process_frame_no_interp(¶ms, &excitation, &mut out1);
proc2.process_frame(¶ms, &excitation, &mut out2);
let e1: f32 = out1.iter().map(|x| x * x).sum();
let e2: f32 = out2.iter().map(|x| x * x).sum();
assert!(e1 > 1e-6, "no_interp should produce output");
assert!(e2 > 1e-6, "interp should produce output");
for i in 0..LPC_ORDER {
assert!(
(proc1.prev_lpc()[i] - proc2.prev_lpc()[i]).abs() < 1e-5,
"prev_lpc should match after first frame"
);
}
}
}