use core::f64::consts::PI as PI64;
use crate::mbe_params::{L_MAX, MbeParams};
pub mod analysis;
pub mod phase_regen;
include!(concat!(env!("OUT_DIR"), "/annex_i_synth_window.rs"));
#[inline]
pub fn synth_window(n: i32) -> f32 {
if !(-105..=105).contains(&n) {
return 0.0;
}
IMBE_SYNTH_WINDOW[(n + 105) as usize]
}
pub const INIT_S_E: f64 = 75000.0;
pub const S_E_FLOOR: f64 = 10000.0;
pub fn enhance_spectral_amplitudes(
m_tilde: &[f32],
omega_0: f32,
s_e_prev: f64,
) -> ([f32; L_MAX as usize], f64) {
let l = m_tilde.len();
debug_assert!(l > 0 && l <= L_MAX as usize);
let omega_0 = f64::from(omega_0);
let mut r_m0 = 0.0f64;
let mut r_m1 = 0.0f64;
for (i, &m) in m_tilde.iter().enumerate() {
let l_one = (i + 1) as f64;
let m2 = f64::from(m) * f64::from(m);
r_m0 += m2;
r_m1 += m2 * (omega_0 * l_one).cos();
}
let mut m_bar_f64 = [0f64; L_MAX as usize];
let denom = omega_0 * r_m0 * (r_m0 - r_m1);
if r_m0 <= 0.0 || denom.abs() < 1e-30 {
for (i, &m) in m_tilde.iter().enumerate() {
m_bar_f64[i] = f64::from(m);
}
} else {
for (i, &m) in m_tilde.iter().enumerate() {
let l_one = (i + 1) as f64;
let bar = if 8 * (i + 1) <= l {
f64::from(m) } else {
let num = r_m0 * r_m0 + r_m1 * r_m1
- 2.0 * r_m0 * r_m1 * (omega_0 * l_one).cos();
if num <= 0.0 {
f64::from(m) } else {
let w_l = f64::from(m).sqrt() * (0.96 * num / denom).powf(0.25);
if w_l > 1.2 {
1.2 * f64::from(m)
} else if w_l < 0.5 {
0.5 * f64::from(m)
} else {
w_l * f64::from(m)
}
}
};
m_bar_f64[i] = bar;
}
let mut sum_sq = 0.0f64;
for &v in m_bar_f64.iter().take(l) {
sum_sq += v * v;
}
if sum_sq > 1e-30 {
let gamma = (r_m0 / sum_sq).sqrt();
for v in m_bar_f64.iter_mut().take(l) {
*v *= gamma;
}
}
}
let s_e = (0.95 * s_e_prev + 0.05 * r_m0).max(S_E_FLOOR);
let mut m_bar = [0f32; L_MAX as usize];
for (i, &v) in m_bar_f64.iter().take(l).enumerate() {
m_bar[i] = v as f32;
}
(m_bar, s_e)
}
pub const INIT_TAU_M: f64 = 20480.0;
pub const MUTE_EPSILON_R_THRESHOLD: f64 = 0.0875;
pub const MUTE_EPSILON_R_THRESHOLD_HALFRATE: f64 = 0.096;
const MUTE_NOISE_GAIN: f64 = 0.0065;
const LCG_MEAN: f64 = 26562.0;
const DEFAULT_OMEGA_0: f32 = 0.937_544_4;
#[derive(Clone, Copy, Debug, Default)]
pub struct FrameErrorContext {
pub epsilon_0: u8,
pub epsilon_4: u8,
pub epsilon_t: u8,
pub bad_pitch: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FrameDisposition {
Use,
Repeat,
Mute,
}
pub fn frame_disposition(
err: &FrameErrorContext,
epsilon_r_prev: f64,
) -> (FrameDisposition, f64) {
let epsilon_r =
0.95 * epsilon_r_prev + 0.05 * (f64::from(err.epsilon_t) / 144.0);
let disp = if epsilon_r > MUTE_EPSILON_R_THRESHOLD {
FrameDisposition::Mute
} else if err.bad_pitch
|| (err.epsilon_0 >= 2
&& f64::from(err.epsilon_t) >= 10.0 + 40.0 * epsilon_r)
{
FrameDisposition::Repeat
} else {
FrameDisposition::Use
};
(disp, epsilon_r)
}
pub fn frame_disposition_halfrate(
err: &FrameErrorContext,
epsilon_r_prev: f64,
) -> (FrameDisposition, f64) {
let epsilon_r = 0.95 * epsilon_r_prev + 0.001064 * f64::from(err.epsilon_t);
let disp = if epsilon_r > MUTE_EPSILON_R_THRESHOLD_HALFRATE {
FrameDisposition::Mute
} else if err.bad_pitch
|| err.epsilon_0 >= 4
|| (err.epsilon_0 >= 2 && err.epsilon_t >= 6)
{
FrameDisposition::Repeat
} else {
FrameDisposition::Use
};
(disp, epsilon_r)
}
#[derive(Clone, Debug)]
pub struct SmoothedFrame {
pub m_bar: [f32; L_MAX as usize],
pub v_bar: [bool; L_MAX as usize],
pub tau_m: f64,
}
pub fn apply_smoothing(
m_bar: &[f32],
v_tilde: &[bool],
s_e: f64,
epsilon_r: f64,
epsilon_t: u8,
epsilon_4: u8,
tau_m_prev: f64,
) -> SmoothedFrame {
let l = m_bar.len();
debug_assert_eq!(v_tilde.len(), l);
let v_m: f64 = if epsilon_r <= 0.005 && epsilon_t <= 4 {
f64::INFINITY
} else if epsilon_r <= 0.0125 && epsilon_4 == 0 {
45.255 * s_e.powf(0.375) * (-277.26 * epsilon_r).exp()
} else {
1.414 * s_e.powf(0.375)
};
let mut v_bar = [false; L_MAX as usize];
for (i, &m) in m_bar.iter().enumerate() {
v_bar[i] = if f64::from(m) > v_m { true } else { v_tilde[i] };
}
let a_m: f64 = m_bar.iter().take(l).map(|&v| f64::from(v)).sum();
let tau_m = if epsilon_r <= 0.005 && epsilon_t <= 6 {
20480.0
} else {
6000.0 - 300.0 * f64::from(epsilon_t) + tau_m_prev
};
let gamma_m: f64 = if tau_m > a_m { 1.0 } else if a_m > 0.0 { tau_m / a_m } else { 1.0 };
let mut m_bar_out = [0f32; L_MAX as usize];
for (i, &m) in m_bar.iter().enumerate() {
m_bar_out[i] = ((f64::from(m)) * gamma_m) as f32;
}
SmoothedFrame { m_bar: m_bar_out, v_bar, tau_m }
}
pub const FRAME_SAMPLES: usize = 160;
pub const NOISE_INIT: u32 = 3147;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum UnvoicedNoiseGen {
SpecLcg,
ChipLcg,
}
impl UnvoicedNoiseGen {
pub const fn params(self) -> (u32, u32, u32, u32) {
match self {
UnvoicedNoiseGen::SpecLcg => (171, 11213, 53125, 3147),
UnvoicedNoiseGen::ChipLcg => (173, 13849, 65536, 60584),
}
}
}
fn default_lcg_gen() -> UnvoicedNoiseGen {
match std::env::var("BLIP25_LCG").ok().as_deref() {
Some("spec") | Some("Spec") => UnvoicedNoiseGen::SpecLcg,
_ => UnvoicedNoiseGen::ChipLcg,
}
}
pub const GAMMA_W: f64 = 146.643269;
#[derive(Clone, Debug)]
pub struct UnvoicedSynthState {
lcg_a: u32,
lcg_c: u32,
lcg_m: u32,
lcg: u32,
noise_window: [f64; 209],
prev_idft: [f64; 256],
initialized: bool,
}
impl UnvoicedSynthState {
pub fn new() -> Self {
Self::with_gen(default_lcg_gen())
}
pub fn with_gen(gen: UnvoicedNoiseGen) -> Self {
let (a, c, m, seed) = gen.params();
Self {
lcg_a: a,
lcg_c: c,
lcg_m: m,
lcg: seed,
noise_window: [0.0; 209],
prev_idft: [0.0; 256],
initialized: false,
}
}
#[inline]
fn next_noise(&mut self) -> f64 {
self.lcg = self.lcg_a
.wrapping_mul(self.lcg)
.wrapping_add(self.lcg_c)
% self.lcg_m;
f64::from(self.lcg)
}
fn advance_window(&mut self) {
if !self.initialized {
let burn = match std::env::var("BLIP25_LCG_INIT").ok().as_deref() {
Some("zero") | Some("Zero") => 0,
Some("burn_64") => 64,
Some("burn_96") => 96,
Some("burn_128") => 128,
Some("burn_160") => 160,
Some("burn_49") => 49,
_ => 209,
};
let start = 209usize.saturating_sub(burn);
for i in start..209 {
self.noise_window[i] = self.next_noise();
}
self.initialized = true;
} else {
self.noise_window.copy_within(160..209, 0);
for i in 49..209 {
self.noise_window[i] = self.next_noise();
}
}
}
}
impl Default for UnvoicedSynthState {
fn default() -> Self {
Self::new()
}
}
fn dft_256_windowed(input: &[f64; 209]) -> ([f64; 256], [f64; 256]) {
use num_complex::Complex;
use rustfft::{Fft, FftPlanner};
use std::sync::OnceLock;
static FFT: OnceLock<std::sync::Arc<dyn Fft<f64>>> = OnceLock::new();
let fft = FFT.get_or_init(|| {
let mut planner = FftPlanner::<f64>::new();
planner.plan_fft_forward(256)
});
let mut buf = [Complex::<f64>::new(0.0, 0.0); 256];
for (i, &x) in input.iter().enumerate() {
let n = i as i32 - 104;
let n_nat = n.rem_euclid(256) as usize;
buf[n_nat].re = x;
}
fft.process(&mut buf);
let mut re = [0f64; 256];
let mut im = [0f64; 256];
for m_idx in 0..256 {
let k = (m_idx + 128) & 255;
re[m_idx] = buf[k].re;
im[m_idx] = buf[k].im;
}
(re, im)
}
fn idft_256(re: &[f64; 256], im: &[f64; 256]) -> [f64; 256] {
use num_complex::Complex;
use rustfft::{Fft, FftPlanner};
use std::sync::OnceLock;
static IFFT: OnceLock<std::sync::Arc<dyn Fft<f64>>> = OnceLock::new();
let fft = IFFT.get_or_init(|| {
let mut planner = FftPlanner::<f64>::new();
planner.plan_fft_inverse(256)
});
let mut buf = [Complex::<f64>::new(0.0, 0.0); 256];
for k in 0..256 {
let m_idx = (k + 128) & 255;
buf[k] = Complex::new(re[m_idx], im[m_idx]);
}
fft.process(&mut buf);
let mut out = [0f64; 256];
for n_idx in 0..256 {
let k = (n_idx + 128) & 255;
out[n_idx] = buf[k].re / 256.0;
}
out
}
fn shape_spectrum(
re: &mut [f64; 256],
im: &mut [f64; 256],
omega_0: f64,
m_bar: &[f32],
v_bar: &[bool],
gamma_w: f64,
) {
let l_count = m_bar.len();
let scale = 256.0 / (2.0 * PI64);
let a1 = (scale * 0.5 * omega_0).ceil() as i32;
let b_last = (scale * (l_count as f64 + 0.5) * omega_0).ceil() as i32;
for m_idx in 0..256 {
let m = m_idx as i32 - 128;
if m.unsigned_abs() < a1 as u32 || (m.unsigned_abs() as i32) >= b_last {
re[m_idx] = 0.0;
im[m_idx] = 0.0;
}
}
let mut band_norm: Vec<f64> = Vec::with_capacity(l_count);
let mut band_edges: Vec<(i32, i32)> = Vec::with_capacity(l_count);
for l in 1..=l_count as i32 {
let l_f = f64::from(l);
let a_l = (scale * (l_f - 0.5) * omega_0).ceil() as i32;
let b_l = (scale * (l_f + 0.5) * omega_0).ceil() as i32;
band_edges.push((a_l, b_l));
let mut norm_sum = 0f64;
let count = (b_l - a_l).max(0) as usize;
for eta in a_l..b_l {
for &sign in &[1i32, -1] {
let m_idx = (sign * eta + 128) as usize;
if m_idx < 256 {
norm_sum += re[m_idx] * re[m_idx] + im[m_idx] * im[m_idx];
}
}
}
let norm = if count > 0 && norm_sum > 0.0 {
(norm_sum / (2.0 * count as f64)).sqrt()
} else {
1.0
};
band_norm.push(norm);
}
for (l_idx, &(a_l, b_l)) in band_edges.iter().enumerate() {
let voiced = v_bar[l_idx];
if voiced {
for m_abs in a_l..b_l {
for &sign in &[1i32, -1] {
let m = sign * m_abs;
let m_idx = (m + 128) as usize;
if m_idx < 256 {
re[m_idx] = 0.0;
im[m_idx] = 0.0;
}
}
}
} else {
let m_bar_l = f64::from(m_bar[l_idx]);
let norm = band_norm[l_idx];
let factor = if norm > 0.0 {
gamma_w * m_bar_l / norm
} else {
0.0
};
for m_abs in a_l..b_l {
for &sign in &[1i32, -1] {
let m = sign * m_abs;
let m_idx = (m + 128) as usize;
if m_idx < 256 {
re[m_idx] *= factor;
im[m_idx] *= factor;
}
}
}
}
}
}
pub fn synthesize_unvoiced(
omega_0: f32,
m_bar: &[f32],
v_bar: &[bool],
gamma_w: f64,
state: &mut UnvoicedSynthState,
) -> [f64; FRAME_SAMPLES] {
state.advance_window();
let mut windowed = [0f64; 209];
for i in 0..209 {
let n = i as i32 - 104;
windowed[i] = state.noise_window[i] * f64::from(synth_window(n));
}
let (mut re, mut im) = dft_256_windowed(&windowed);
shape_spectrum(&mut re, &mut im, f64::from(omega_0), m_bar, v_bar, gamma_w);
let u_w = idft_256(&re, &im);
let mut out = [0f64; FRAME_SAMPLES];
for n in 0..FRAME_SAMPLES as i32 {
let ws_n = f64::from(synth_window(n));
let ws_n_minus_n = f64::from(synth_window(n - FRAME_SAMPLES as i32));
let prev_term = if (0..=127).contains(&n) {
ws_n * state.prev_idft[(n + 128) as usize]
} else {
0.0
};
let curr_term = if (32..=159).contains(&n) {
ws_n_minus_n * u_w[(n - 32) as usize]
} else {
0.0
};
let denom = ws_n * ws_n + ws_n_minus_n * ws_n_minus_n;
out[n as usize] = if denom > 1e-30 {
(prev_term + curr_term) / denom
} else {
0.0
};
}
state.prev_idft = u_w;
out
}
pub fn voiced_noise_samples(state: &UnvoicedSynthState) -> [f64; L_MAX as usize] {
let mut out = [0f64; L_MAX as usize];
for l in 1..=L_MAX as usize {
out[l - 1] = state.noise_window[l + 104];
}
out
}
pub const INIT_PREV_OMEGA_0: f64 = 0.02985 * PI64;
#[derive(Clone, Debug)]
pub struct VoicedSynthState {
phi: [f64; L_MAX as usize + 1],
psi: [f64; L_MAX as usize + 1],
prev_m_bar: [f64; L_MAX as usize + 1],
prev_v_bar: [bool; L_MAX as usize + 1],
prev_l: u8,
prev_omega_0: f64,
}
impl VoicedSynthState {
pub fn new() -> Self {
Self {
phi: [0.0; L_MAX as usize + 1],
psi: [0.0; L_MAX as usize + 1],
prev_m_bar: [0.0; L_MAX as usize + 1],
prev_v_bar: [false; L_MAX as usize + 1],
prev_l: 30,
prev_omega_0: INIT_PREV_OMEGA_0,
}
}
}
impl Default for VoicedSynthState {
fn default() -> Self {
Self::new()
}
}
#[inline]
fn wrap_phase(delta_phi: f64) -> f64 {
delta_phi - 2.0 * PI64 * ((delta_phi + PI64) / (2.0 * PI64)).floor()
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PhaseMode {
Baseline,
AmbePlus,
}
pub fn synthesize_voiced(
omega_0: f32,
m_bar: &[f32],
v_bar: &[bool],
noise_samples: &[f64; L_MAX as usize],
phase_mode: PhaseMode,
state: &mut VoicedSynthState,
) -> [f64; FRAME_SAMPLES] {
synthesize_voiced_with_lcg_modulus(omega_0, m_bar, v_bar, noise_samples, phase_mode, 53125, state)
}
pub fn synthesize_voiced_with_lcg_modulus(
omega_0: f32,
m_bar: &[f32],
v_bar: &[bool],
noise_samples: &[f64; L_MAX as usize],
phase_mode: PhaseMode,
state_lcg_m: u32,
state: &mut VoicedSynthState,
) -> [f64; FRAME_SAMPLES] {
let l_curr = m_bar.len() as u8;
let l_prev = state.prev_l;
let max_l = l_curr.max(l_prev) as usize;
let omega_curr = f64::from(omega_0);
let omega_prev = state.prev_omega_0;
let n_f = FRAME_SAMPLES as f64;
let delta_omega = omega_curr - omega_prev;
let mut psi_curr = [0f64; L_MAX as usize + 1];
for l in 1..=L_MAX as usize {
let l_f = l as f64;
psi_curr[l] = state.psi[l] + (omega_prev + omega_curr) * l_f * n_f / 2.0;
}
let l_quarter = (l_curr as usize) / 4;
let mut phi_curr = [0f64; L_MAX as usize + 1];
match phase_mode {
PhaseMode::Baseline => {
let l_uv: u8 = (0..l_curr as usize).filter(|&i| !v_bar[i]).count() as u8;
let lcg_m = f64::from(state_lcg_m);
for l in 1..=L_MAX as usize {
if l <= l_quarter {
phi_curr[l] = psi_curr[l];
} else {
let u_l = noise_samples[l - 1];
let rho_l = (2.0 * PI64 / lcg_m) * u_l - PI64; let l_curr_f = if l_curr > 0 { f64::from(l_curr) } else { 1.0 };
phi_curr[l] = psi_curr[l]
+ f64::from(l_uv) * rho_l / l_curr_f * (l as f64);
}
}
}
PhaseMode::AmbePlus => {
let mut phi_regen = [0f64; L_MAX as usize + 1];
phase_regen::ambe_phase_regen(m_bar, &mut phi_regen);
for l in 1..=L_MAX as usize {
if l <= l_quarter {
phi_curr[l] = psi_curr[l];
} else {
phi_curr[l] = psi_curr[l] + phi_regen[l];
}
}
}
}
let mut ws_n = [0f64; FRAME_SAMPLES];
let mut ws_nm = [0f64; FRAME_SAMPLES];
for n in 0..FRAME_SAMPLES {
ws_n[n] = f64::from(synth_window(n as i32));
ws_nm[n] = f64::from(synth_window(n as i32 - FRAME_SAMPLES as i32));
}
enum Branch {
UvUv,
VUv,
UvV,
VVSum,
VVRamp,
}
let mut s_v = [0f64; FRAME_SAMPLES];
for l in 1..=max_l {
let l_f = l as f64;
let m_curr_l = if l <= l_curr as usize { f64::from(m_bar[l - 1]) } else { 0.0 };
let m_prev_l = if l <= l_prev as usize { state.prev_m_bar[l] } else { 0.0 };
let v_curr = l <= l_curr as usize && v_bar[l - 1];
let v_prev = l <= l_prev as usize && state.prev_v_bar[l];
let branch = match (v_prev, v_curr) {
(false, false) => Branch::UvUv,
(true, false) => Branch::VUv,
(false, true) => Branch::UvV,
(true, true) => {
let pitch_change_ratio = if omega_curr.abs() > 1e-30 {
(delta_omega * l_f / omega_curr).abs()
} else {
0.0
};
if l >= 8 || pitch_change_ratio >= 0.1 {
Branch::VVSum
} else {
Branch::VVRamp
}
}
};
match branch {
Branch::UvUv => {}
Branch::VUv => {
let phi0 = state.phi[l];
let (mut pre, mut pim) = (phi0.cos(), phi0.sin());
let step = omega_prev * l_f;
let (dc, ds) = (step.cos(), step.sin());
let scale = 2.0 * m_prev_l;
for n in 0..FRAME_SAMPLES {
s_v[n] += scale * ws_n[n] * pre;
let npre = pre * dc - pim * ds;
let npim = pre * ds + pim * dc;
pre = npre;
pim = npim;
}
}
Branch::UvV => {
let phi0 = phi_curr[l] - omega_curr * n_f * l_f;
let (mut cre, mut cim) = (phi0.cos(), phi0.sin());
let step = omega_curr * l_f;
let (dc, ds) = (step.cos(), step.sin());
let scale = 2.0 * m_curr_l;
for n in 0..FRAME_SAMPLES {
s_v[n] += scale * ws_nm[n] * cre;
let ncre = cre * dc - cim * ds;
let ncim = cre * ds + cim * dc;
cre = ncre;
cim = ncim;
}
}
Branch::VVSum => {
let (mut pre, mut pim) = (state.phi[l].cos(), state.phi[l].sin());
let pstep = omega_prev * l_f;
let (pdc, pds) = (pstep.cos(), pstep.sin());
let phi_c0 = phi_curr[l] - omega_curr * n_f * l_f;
let (mut cre, mut cim) = (phi_c0.cos(), phi_c0.sin());
let cstep = omega_curr * l_f;
let (cdc, cds) = (cstep.cos(), cstep.sin());
let scale_p = 2.0 * m_prev_l;
let scale_c = 2.0 * m_curr_l;
for n in 0..FRAME_SAMPLES {
s_v[n] += scale_p * ws_n[n] * pre + scale_c * ws_nm[n] * cre;
let npre = pre * pdc - pim * pds;
let npim = pre * pds + pim * pdc;
pre = npre;
pim = npim;
let ncre = cre * cdc - cim * cds;
let ncim = cre * cds + cim * cdc;
cre = ncre;
cim = ncim;
}
}
Branch::VVRamp => {
let delta_phi = phi_curr[l] - state.phi[l]
- (omega_prev + omega_curr) * l_f * n_f / 2.0;
let wrapped = wrap_phase(delta_phi);
let delta_omega_l = wrapped / n_f;
let theta0 = state.phi[l];
let coef_n2 = (omega_curr * l_f - omega_prev * l_f) / (2.0 * n_f);
for n in 0..FRAME_SAMPLES {
let nf = n as f64;
let a_l = m_prev_l + (nf / n_f) * (m_curr_l - m_prev_l);
let theta_l = theta0
+ (omega_prev * l_f + delta_omega_l) * nf
+ coef_n2 * nf * nf;
s_v[n] += 2.0 * a_l * theta_l.cos();
}
}
}
}
for l in 1..=L_MAX as usize {
state.psi[l] = psi_curr[l];
state.phi[l] = phi_curr[l];
}
for l in 1..=l_curr as usize {
state.prev_m_bar[l] = f64::from(m_bar[l - 1]);
state.prev_v_bar[l] = v_bar[l - 1];
}
for l in (l_curr as usize + 1)..=L_MAX as usize {
state.prev_m_bar[l] = 0.0;
state.prev_v_bar[l] = false;
}
state.prev_l = l_curr;
state.prev_omega_0 = omega_curr;
s_v
}
#[derive(Clone, Debug)]
pub struct SynthState {
pub s_e: f64,
pub tau_m: f64,
pub epsilon_r: f64,
pub unvoiced: UnvoicedSynthState,
pub voiced: VoicedSynthState,
pub err: FrameErrorContext,
pub gamma_w: f64,
last_good: Option<LastGoodFrame>,
repeat_count: u32,
repeat_reset_after: Option<u32>,
chip_compat: bool,
chip_compat_spectral_clamp: bool,
prev_l: u8,
last_disposition: Option<FrameDisposition>,
}
pub const SPECTRAL_CLAMP_L_THRESHOLD: i16 = 25;
pub const SPECTRAL_CLAMP_GAMMA: f64 = 0.75;
#[derive(Clone, Debug)]
struct LastGoodFrame {
omega_0: f32,
l: u8,
voiced: [bool; L_MAX as usize],
m_tilde: [f32; L_MAX as usize],
}
impl SynthState {
pub fn new() -> Self {
Self::with_unvoiced_gen(default_lcg_gen())
}
pub fn with_unvoiced_gen(gen: UnvoicedNoiseGen) -> Self {
Self {
s_e: INIT_S_E,
tau_m: INIT_TAU_M,
epsilon_r: 0.0,
unvoiced: UnvoicedSynthState::with_gen(gen),
voiced: VoicedSynthState::new(),
err: FrameErrorContext::default(),
gamma_w: GAMMA_W,
last_good: None,
repeat_count: 0,
repeat_reset_after: None,
chip_compat: false,
chip_compat_spectral_clamp: false,
prev_l: 0,
last_disposition: None,
}
}
#[inline]
pub fn last_disposition(&self) -> Option<FrameDisposition> {
self.last_disposition
}
pub fn set_repeat_reset_after(&mut self, n: Option<u32>) {
self.repeat_reset_after = n;
}
#[inline]
pub fn repeat_reset_after(&self) -> Option<u32> {
self.repeat_reset_after
}
pub fn set_chip_compat(&mut self, on: bool) {
self.chip_compat = on;
}
#[inline]
pub fn chip_compat(&self) -> bool {
self.chip_compat
}
pub fn set_chip_compat_spectral_clamp(&mut self, on: bool) {
self.chip_compat_spectral_clamp = on;
}
#[inline]
pub fn chip_compat_spectral_clamp(&self) -> bool {
self.chip_compat_spectral_clamp
}
}
impl Default for SynthState {
fn default() -> Self {
Self::new()
}
}
#[inline]
pub fn pcm_from_f64(samples: &[f64; FRAME_SAMPLES]) -> [i16; FRAME_SAMPLES] {
let mut out = [0i16; FRAME_SAMPLES];
for (i, &s) in samples.iter().enumerate() {
let clamped = s.clamp(f64::from(i16::MIN), f64::from(i16::MAX));
out[i] = clamped.round() as i16;
}
out
}
pub fn synthesize_frame(
params: &MbeParams,
err: &FrameErrorContext,
gamma_w: f64,
state: &mut SynthState,
) -> [i16; FRAME_SAMPLES] {
synthesize_frame_with_mode(params, err, gamma_w, PhaseMode::Baseline, state)
}
pub fn synthesize_frame_ambe_plus(
params: &MbeParams,
err: &FrameErrorContext,
gamma_w: f64,
state: &mut SynthState,
) -> [i16; FRAME_SAMPLES] {
synthesize_frame_with_mode(params, err, gamma_w, PhaseMode::AmbePlus, state)
}
pub fn synthesize_repeat_with_mode(
phase_mode: PhaseMode,
state: &mut SynthState,
) -> [i16; FRAME_SAMPLES] {
let err = FrameErrorContext { bad_pitch: true, ..state.err };
let params = MbeParams::silence();
synthesize_frame_with_mode(¶ms, &err, state.gamma_w, phase_mode, state)
}
pub fn synthesize_repeat(state: &mut SynthState) -> [i16; FRAME_SAMPLES] {
synthesize_repeat_with_mode(PhaseMode::Baseline, state)
}
pub fn synthesize_repeat_ambe_plus(state: &mut SynthState) -> [i16; FRAME_SAMPLES] {
synthesize_repeat_with_mode(PhaseMode::AmbePlus, state)
}
fn synthesize_frame_with_mode(
params: &MbeParams,
err: &FrameErrorContext,
gamma_w: f64,
phase_mode: PhaseMode,
state: &mut SynthState,
) -> [i16; FRAME_SAMPLES] {
let (disp, epsilon_r) = match phase_mode {
PhaseMode::Baseline => frame_disposition(err, state.epsilon_r),
PhaseMode::AmbePlus => frame_disposition_halfrate(err, state.epsilon_r),
};
state.epsilon_r = if state.chip_compat && disp == FrameDisposition::Repeat {
state.epsilon_r
} else {
epsilon_r
};
state.last_disposition = Some(disp);
let next_repeat_count = match disp {
FrameDisposition::Use => 0,
FrameDisposition::Repeat | FrameDisposition::Mute => state.repeat_count + 1,
};
let force_default = matches!(disp, FrameDisposition::Repeat | FrameDisposition::Mute)
&& state
.repeat_reset_after
.is_some_and(|n| state.repeat_count >= n);
let next_repeat_count = if force_default { 0 } else { next_repeat_count };
let (omega_0, l, voiced_arr, m_tilde_arr) = if force_default {
let l = 30u8;
let voiced = [false; L_MAX as usize];
let mut m_tilde = [0f32; L_MAX as usize];
for i in 0..l as usize {
m_tilde[i] = 1.0;
}
(DEFAULT_OMEGA_0, l, voiced, m_tilde)
} else {
match (disp, &state.last_good) {
(FrameDisposition::Use, _) => {
let l = params.harmonic_count();
let mut voiced = [false; L_MAX as usize];
let mut m_tilde = [0f32; L_MAX as usize];
voiced[..l as usize].copy_from_slice(params.voiced_slice());
m_tilde[..l as usize].copy_from_slice(params.amplitudes_slice());
(params.omega_0(), l, voiced, m_tilde)
}
(FrameDisposition::Repeat | FrameDisposition::Mute, Some(prev)) => {
(prev.omega_0, prev.l, prev.voiced, prev.m_tilde)
}
(FrameDisposition::Repeat | FrameDisposition::Mute, None) => {
let l = params.harmonic_count();
let mut voiced = [false; L_MAX as usize];
let mut m_tilde = [0f32; L_MAX as usize];
voiced[..l as usize].copy_from_slice(params.voiced_slice());
m_tilde[..l as usize].copy_from_slice(params.amplitudes_slice());
(params.omega_0(), l, voiced, m_tilde)
}
}
};
state.repeat_count = next_repeat_count;
let (m_bar_full, s_e_new) = analysis::profile::time(
analysis::profile::Stage::Enhance,
|| enhance_spectral_amplitudes(&m_tilde_arr[..l as usize], omega_0, state.s_e),
);
state.s_e = s_e_new;
let mut smoothed = analysis::profile::time(analysis::profile::Stage::Smoothing, || {
apply_smoothing(
&m_bar_full[..l as usize],
&voiced_arr[..l as usize],
state.s_e,
state.epsilon_r,
err.epsilon_t,
err.epsilon_4,
state.tau_m,
)
});
state.tau_m = smoothed.tau_m;
if matches!(phase_mode, PhaseMode::AmbePlus)
&& (state.chip_compat || state.chip_compat_spectral_clamp)
{
let l_jump = state.prev_l > 0
&& (i16::from(l) - i16::from(state.prev_l)).abs() > SPECTRAL_CLAMP_L_THRESHOLD;
let err_jump = err.epsilon_0 >= 2;
if l_jump || err_jump {
for v in smoothed.m_bar.iter_mut().take(l as usize) {
*v = (f64::from(*v) * SPECTRAL_CLAMP_GAMMA) as f32;
}
}
}
state.prev_l = l;
let s_uv = analysis::profile::time(analysis::profile::Stage::SynthUnvoiced, || {
synthesize_unvoiced(
omega_0,
&smoothed.m_bar[..l as usize],
&smoothed.v_bar[..l as usize],
gamma_w,
&mut state.unvoiced,
)
});
let noise_samples = voiced_noise_samples(&state.unvoiced);
let lcg_m = state.unvoiced.lcg_m;
let s_v = analysis::profile::time(analysis::profile::Stage::SynthVoiced, || {
synthesize_voiced_with_lcg_modulus(
omega_0,
&smoothed.m_bar[..l as usize],
&smoothed.v_bar[..l as usize],
&noise_samples,
phase_mode,
lcg_m,
&mut state.voiced,
)
});
analysis::profile::time(analysis::profile::Stage::SynthMix, || {
let mut s = [0f64; FRAME_SAMPLES];
if disp == FrameDisposition::Mute {
for i in 0..FRAME_SAMPLES {
let raw = state.unvoiced.noise_window[24 + i];
s[i] = (raw - LCG_MEAN) * MUTE_NOISE_GAIN;
}
} else {
for i in 0..FRAME_SAMPLES {
s[i] = s_uv[i] + s_v[i];
}
}
let mut snap_voiced = [false; L_MAX as usize];
let mut snap_m_tilde = [0f32; L_MAX as usize];
snap_voiced[..l as usize].copy_from_slice(&voiced_arr[..l as usize]);
snap_m_tilde[..l as usize].copy_from_slice(&m_tilde_arr[..l as usize]);
state.last_good = Some(LastGoodFrame {
omega_0,
l,
voiced: snap_voiced,
m_tilde: snap_m_tilde,
});
pcm_from_f64(&s)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn synth_window_endpoints_are_zero() {
assert_eq!(synth_window(-105), 0.0);
assert_eq!(synth_window(105), 0.0);
}
#[test]
fn synth_window_symmetric() {
for n in 1..=105 {
assert_eq!(
synth_window(-n),
synth_window(n),
"wS(-{n}) != wS({n})"
);
}
}
#[test]
fn synth_window_peaks_near_zero() {
let centre = synth_window(0);
for n in -105..=105 {
assert!(
synth_window(n) <= centre + 1e-6,
"wS({n}) = {} exceeds wS(0) = {centre}",
synth_window(n)
);
}
}
#[test]
fn synth_window_out_of_range_is_zero() {
assert_eq!(synth_window(-106), 0.0);
assert_eq!(synth_window(106), 0.0);
assert_eq!(synth_window(1000), 0.0);
assert_eq!(synth_window(-1000), 0.0);
}
#[test]
fn synth_window_table_length_matches_constant() {
assert_eq!(IMBE_SYNTH_WINDOW.len(), SYNTH_WINDOW_LEN);
assert_eq!(SYNTH_WINDOW_LEN, 211);
}
fn sum_sq(arr: &[f32]) -> f64 {
arr.iter().map(|&v| f64::from(v) * f64::from(v)).sum()
}
#[test]
fn enhance_silence_passes_through_and_floors_s_e() {
let m_tilde = vec![0.0f32; 30];
let (m_bar, s_e) = enhance_spectral_amplitudes(&m_tilde, 0.1, INIT_S_E);
for i in 0..30 {
assert_eq!(m_bar[i], 0.0, "M̄_{i} = {}", m_bar[i]);
}
assert!((s_e - 71250.0).abs() < 1e-6, "S_E = {s_e}");
}
#[test]
fn enhance_preserves_energy_within_a_few_percent() {
let l = 24usize;
let m_tilde: Vec<f32> = (1..=l).map(|i| (i as f32 * 0.3).sin().abs() + 0.1).collect();
let omega_0 = std::f32::consts::PI / 12.0;
let (m_bar, _) = enhance_spectral_amplitudes(&m_tilde, omega_0, INIT_S_E);
let energy_in = sum_sq(&m_tilde);
let energy_out = sum_sq(&m_bar[..l]);
let rel_err = (energy_out - energy_in).abs() / energy_in;
assert!(rel_err < 1e-4, "energy drift {rel_err} (in={energy_in}, out={energy_out})");
}
#[test]
fn enhance_s_e_floor_engages() {
let m_tilde = vec![0.0f32; 30];
let mut s_e = INIT_S_E;
for _ in 0..200 {
let (_, new_s_e) = enhance_spectral_amplitudes(&m_tilde, 0.1, s_e);
s_e = new_s_e;
}
assert!((s_e - S_E_FLOOR).abs() < 1e-6, "S_E = {s_e}");
}
#[test]
fn enhance_s_e_recurrence_responds_to_input_energy() {
let m_tilde: Vec<f32> = (1..=20).map(|_| 100.0).collect(); let (_, s_e1) = enhance_spectral_amplitudes(&m_tilde, 0.1, INIT_S_E);
assert!((s_e1 - 81250.0).abs() < 1e-2, "S_E = {s_e1}");
}
#[test]
fn enhance_low_harmonic_bypass_holds_per_section_1_10() {
let l = 56usize; let m_tilde: Vec<f32> = (1..=l).map(|i| 1.0 + (i as f32 * 0.05).sin()).collect();
let omega_0 = std::f32::consts::PI / 28.0;
let (m_bar, _) = enhance_spectral_amplitudes(&m_tilde, omega_0, INIT_S_E);
let r0 = m_bar[0] / m_tilde[0];
for i in 1..7 {
let r = m_bar[i] / m_tilde[i];
assert!(
(r - r0).abs() < 1e-4,
"bypassed harmonic l={}: ratio {r} vs {r0}",
i + 1
);
}
}
fn err(epsilon_0: u8, epsilon_t: u8, epsilon_4: u8) -> FrameErrorContext {
FrameErrorContext { epsilon_0, epsilon_4, epsilon_t, bad_pitch: false }
}
#[test]
fn frame_disposition_clean_frame_uses() {
let (d, er) = frame_disposition(&err(0, 0, 0), 0.0);
assert_eq!(d, FrameDisposition::Use);
assert_eq!(er, 0.0);
}
#[test]
fn frame_disposition_bad_pitch_repeats() {
let mut e = err(0, 0, 0);
e.bad_pitch = true;
let (d, _) = frame_disposition(&e, 0.0);
assert_eq!(d, FrameDisposition::Repeat);
}
#[test]
fn frame_disposition_joint_error_threshold_repeats() {
let (d, _) = frame_disposition(&err(2, 12, 0), 0.0);
assert_eq!(d, FrameDisposition::Repeat);
let (d, _) = frame_disposition(&err(1, 12, 0), 0.0);
assert_eq!(d, FrameDisposition::Use);
}
#[test]
fn frame_disposition_high_error_rate_mutes() {
let (d, _) = frame_disposition(&err(0, 50, 0), 0.5);
assert_eq!(d, FrameDisposition::Mute);
}
#[test]
fn synth_baseline_drives_chip_pattern_into_mute() {
let mut state = SynthState::new();
assert!(!state.chip_compat());
let err_ctx = err(3, 13, 1);
let voiced = vec![true; crate::mbe_params::L_MIN as usize];
let amps = vec![1.0f32; crate::mbe_params::L_MIN as usize];
let params = MbeParams::new(
2.0 * core::f32::consts::PI / 50.0,
crate::mbe_params::L_MIN,
&voiced,
&s,
)
.unwrap();
for _ in 0..120 {
let _ = synthesize_frame(¶ms, &err_ctx, GAMMA_W, &mut state);
}
assert_eq!(
state.last_disposition(),
Some(FrameDisposition::Mute),
"spec-faithful path should mute under sustained chip.bit errors"
);
}
#[test]
fn synth_chip_compat_freezes_error_rate_on_repeat() {
let mut state = SynthState::new();
state.set_chip_compat(true);
let err_ctx = err(3, 13, 1);
let voiced = vec![true; crate::mbe_params::L_MIN as usize];
let amps = vec![1.0f32; crate::mbe_params::L_MIN as usize];
let params = MbeParams::new(
2.0 * core::f32::consts::PI / 50.0,
crate::mbe_params::L_MIN,
&voiced,
&s,
)
.unwrap();
for _ in 0..120 {
let _ = synthesize_frame(¶ms, &err_ctx, GAMMA_W, &mut state);
}
assert_eq!(
state.last_disposition(),
Some(FrameDisposition::Repeat),
"chip_compat path should hold Repeat (ε_R frozen below Mute)"
);
assert!(
state.epsilon_r < 0.0875,
"frozen ε_R should stay below Mute threshold (got {})",
state.epsilon_r
);
}
#[test]
fn smoothing_low_error_uses_infinite_v_m() {
let m_bar = vec![1e6f32; 9];
let v_tilde = vec![false; 9];
let s = apply_smoothing(&m_bar, &v_tilde, 75000.0, 0.001, 2, 0, INIT_TAU_M);
for i in 0..9 {
assert!(!s.v_bar[i], "v̄_{i} flipped to voiced under V_M = ∞");
}
}
#[test]
fn smoothing_v_uv_override_when_amplitude_exceeds_threshold() {
let s_e = 100.0; let m_bar = vec![1.0f32, 100.0, 1.0, 100.0, 1.0, 100.0, 1.0, 100.0, 1.0];
let v_tilde = vec![false; 9];
let s = apply_smoothing(&m_bar, &v_tilde, s_e, 0.05, 10, 1, INIT_TAU_M);
for i in 0..9 {
let expected = m_bar[i] > 8.27;
assert_eq!(s.v_bar[i], expected, "v̄_{i}");
}
}
#[test]
fn smoothing_amplitude_low_error_resets_tau_m() {
let m_bar = vec![10.0f32; 9];
let s = apply_smoothing(&m_bar, &vec![true; 9], 1000.0, 0.001, 3, 0, 5000.0);
assert_eq!(s.tau_m, 20480.0);
}
#[test]
fn smoothing_amplitude_recurrence_under_errors() {
let m_bar = vec![10.0f32; 9];
let s = apply_smoothing(&m_bar, &vec![true; 9], 1000.0, 0.05, 8, 1, 10000.0);
assert!((s.tau_m - 13600.0).abs() < 1e-6);
}
#[test]
fn smoothing_gamma_m_is_one_when_tau_m_exceeds_amplitude_total() {
let m_bar = vec![0.01f32; 9];
let s = apply_smoothing(&m_bar, &vec![true; 9], 1000.0, 0.001, 0, 0, INIT_TAU_M);
for i in 0..9 {
assert!((s.m_bar[i] - 0.01).abs() < 1e-6);
}
}
#[test]
fn lcg_first_few_values_match_recurrence() {
let mut chip_state = UnvoicedSynthState::new();
assert_eq!(chip_state.next_noise() as u32, 9121);
let (a, c, m, seed) = UnvoicedNoiseGen::SpecLcg.params();
assert_eq!((a.wrapping_mul(seed).wrapping_add(c)) % m, 18100);
assert_eq!((a.wrapping_mul(18100).wrapping_add(c)) % m, 25063);
}
#[test]
fn dft_idft_roundtrip_recovers_input() {
let mut input = [0f64; 209];
for i in 0..209 {
let n = i as i32 - 104;
input[i] = (f64::from(n) * 0.05).sin() * 100.0;
}
let (re, im) = dft_256_windowed(&input);
let recovered = idft_256(&re, &im);
for n in -104..=104i32 {
let in_val = input[(n + 104) as usize];
let out_val = recovered[(n + 128) as usize];
assert!(
(out_val - in_val).abs() < 1e-6,
"n={n}: in={in_val}, out={out_val}"
);
}
}
#[test]
fn unvoiced_silence_input_produces_silence_output() {
let m_bar = vec![0.0f32; 12];
let v_bar = vec![false; 12];
let mut state = UnvoicedSynthState::new();
let pcm = synthesize_unvoiced(0.2, &m_bar, &v_bar, GAMMA_W, &mut state);
for (i, &s) in pcm.iter().enumerate() {
assert!(s.abs() < 1e-6, "n={i}: {s}");
}
}
#[test]
fn unvoiced_all_voiced_first_frame_produces_silence_output() {
let m_bar = vec![10.0f32; 12];
let v_bar = vec![true; 12];
let mut state = UnvoicedSynthState::new();
let pcm = synthesize_unvoiced(0.2, &m_bar, &v_bar, GAMMA_W, &mut state);
for (i, &s) in pcm.iter().enumerate() {
assert!(s.abs() < 1e-6, "n={i}: {s}");
}
}
#[test]
fn unvoiced_nonzero_input_produces_nonzero_output() {
let m_bar = vec![10.0f32; 12];
let v_bar = vec![false; 12];
let mut state = UnvoicedSynthState::new();
let _ = synthesize_unvoiced(0.2, &m_bar, &v_bar, GAMMA_W, &mut state);
let pcm = synthesize_unvoiced(0.2, &m_bar, &v_bar, GAMMA_W, &mut state);
let energy: f64 = pcm.iter().map(|&s| s * s).sum();
assert!(energy > 1e-3, "energy too low: {energy}");
}
#[test]
fn unvoiced_state_advances_between_frames() {
let m_bar = vec![1.0f32; 9];
let v_bar = vec![false; 9];
let mut state = UnvoicedSynthState::new();
let lcg_init = state.lcg;
let _ = synthesize_unvoiced(0.2, &m_bar, &v_bar, GAMMA_W, &mut state);
let lcg_after_1 = state.lcg;
assert_ne!(lcg_after_1, lcg_init);
let _ = synthesize_unvoiced(0.2, &m_bar, &v_bar, GAMMA_W, &mut state);
let lcg_after_2 = state.lcg;
assert_ne!(lcg_after_2, lcg_after_1);
}
#[test]
fn unvoiced_output_length_is_one_frame() {
let m_bar = vec![1.0f32; 9];
let v_bar = vec![false; 9];
let mut state = UnvoicedSynthState::new();
let pcm = synthesize_unvoiced(0.2, &m_bar, &v_bar, GAMMA_W, &mut state);
assert_eq!(pcm.len(), FRAME_SAMPLES);
assert_eq!(FRAME_SAMPLES, 160);
}
#[test]
fn smoothing_gamma_m_clamps_loud_frames() {
let m_bar = vec![10000.0f32; 9];
let v_tilde = vec![true; 9];
let s = apply_smoothing(&m_bar, &v_tilde, 1000.0, 0.001, 0, 0, INIT_TAU_M);
let expected_gamma = 20480.0 / 90000.0;
for i in 0..9 {
let ratio = s.m_bar[i] / 10000.0;
assert!(
(ratio - expected_gamma as f32).abs() < 1e-3,
"γ_M ratio at l={}: {ratio} vs {expected_gamma}",
i + 1
);
}
}
#[test]
fn voiced_state_init_matches_annex_a() {
let s = VoicedSynthState::new();
assert_eq!(s.prev_l, 30);
assert!((s.prev_omega_0 - 0.02985 * PI64).abs() < 1e-12);
for l in 1..=L_MAX as usize {
assert_eq!(s.phi[l], 0.0);
assert_eq!(s.psi[l], 0.0);
assert_eq!(s.prev_m_bar[l], 0.0);
assert!(!s.prev_v_bar[l]);
}
}
#[test]
fn wrap_phase_into_principal_range() {
let cases = [
(0.0f64, 0.0),
(PI64, -PI64),
(-PI64, -PI64),
(3.0 * PI64, -PI64),
(-3.0 * PI64, -PI64),
];
for (input, expected) in cases {
let w = wrap_phase(input);
assert!(
(w - expected).abs() < 1e-12,
"wrap({input}) = {w}, expected {expected}"
);
}
for k in -10..=10 {
let raw = (k as f64) * 0.7 * PI64;
let w = wrap_phase(raw);
assert!((-PI64..PI64).contains(&w), "wrap({raw}) = {w}");
}
}
#[test]
fn voiced_all_unvoiced_produces_silence() {
let m_bar = vec![10.0f32; 12];
let v_bar = vec![false; 12];
let noise = [0f64; L_MAX as usize];
let mut state = VoicedSynthState::new();
let pcm = synthesize_voiced(0.2, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state);
for &s in pcm.iter() {
assert!(s.abs() < 1e-9);
}
}
#[test]
fn voiced_silence_amplitudes_produces_silence() {
let m_bar = vec![0f32; 12];
let v_bar = vec![true; 12];
let noise = [0f64; L_MAX as usize];
let mut state = VoicedSynthState::new();
let pcm = synthesize_voiced(0.2, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state);
for &s in pcm.iter() {
assert!(s.abs() < 1e-9, "{s}");
}
}
#[test]
fn voiced_state_advances_phase_for_all_56_harmonics() {
let m_bar = vec![1.0f32; 9]; let v_bar = vec![false; 9];
let noise = [0f64; L_MAX as usize];
let mut state = VoicedSynthState::new();
let psi_init = state.psi;
let _ = synthesize_voiced(0.2, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state);
for l in 1..=L_MAX as usize {
assert!(
state.psi[l] > psi_init[l] - 1e-12,
"ψ_{l} did not advance: {} → {}",
psi_init[l],
state.psi[l]
);
assert!(state.psi[l] != psi_init[l], "ψ_{l} unchanged");
}
}
#[test]
fn voiced_steady_state_pure_tone() {
let omega_0 = 0.2f32;
let mut m_bar = [0f32; 12];
m_bar[0] = 100.0; let mut v_bar = [false; 12];
v_bar[0] = true;
let noise = [0f64; L_MAX as usize];
let mut state = VoicedSynthState::new();
let _ = synthesize_voiced(omega_0, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state);
let pcm = synthesize_voiced(omega_0, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state);
let mean: f64 = pcm.iter().sum::<f64>() / pcm.len() as f64;
let var: f64 = pcm.iter().map(|&s| (s - mean).powi(2)).sum::<f64>()
/ pcm.len() as f64;
assert!(var > 100.0, "voiced steady-state variance too low: {var}");
}
#[test]
fn voiced_phase_carries_across_frames_for_continuity() {
let omega_0 = 0.15f32;
let mut m_bar = [0f32; 12];
m_bar[0] = 100.0;
m_bar[1] = 60.0;
let mut v_bar = [false; 12];
v_bar[0] = true;
v_bar[1] = true;
let noise = [0f64; L_MAX as usize];
let mut state = VoicedSynthState::new();
let _ = synthesize_voiced(omega_0, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state);
let _ = synthesize_voiced(omega_0, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state);
let pcm_a = synthesize_voiced(omega_0, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state);
let pcm_b = synthesize_voiced(omega_0, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state);
let last_a = pcm_a[FRAME_SAMPLES - 1];
let first_b = pcm_b[0];
let typical_mag = pcm_a.iter().map(|s| s.abs()).fold(0f64, f64::max);
assert!(
(last_a - first_b).abs() < typical_mag,
"phase discontinuity: last={last_a}, first={first_b}, max={typical_mag}"
);
}
#[test]
fn voiced_output_length_is_one_frame() {
let m_bar = [0f32; 9];
let v_bar = [false; 9];
let noise = [0f64; L_MAX as usize];
let mut state = VoicedSynthState::new();
let pcm = synthesize_voiced(0.2, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state);
assert_eq!(pcm.len(), FRAME_SAMPLES);
}
#[test]
fn voiced_ambe_plus_mode_diverges_from_baseline_on_high_l() {
let omega_0 = 0.15f32;
let mut m_bar = [0f32; 12];
for (l, m) in m_bar.iter_mut().enumerate() {
*m = 100.0 * ((l as f32) + 1.0).sqrt();
}
let v_bar = [true; 12];
let noise = [100f64; L_MAX as usize];
let mut state_base = VoicedSynthState::new();
let _ = synthesize_voiced(
omega_0, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state_base,
);
let pcm_base = synthesize_voiced(
omega_0, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state_base,
);
let mut state_amb = VoicedSynthState::new();
let _ = synthesize_voiced(
omega_0, &m_bar, &v_bar, &noise, PhaseMode::AmbePlus, &mut state_amb,
);
let pcm_amb = synthesize_voiced(
omega_0, &m_bar, &v_bar, &noise, PhaseMode::AmbePlus, &mut state_amb,
);
let diff_rms: f64 = pcm_base
.iter()
.zip(pcm_amb.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
/ FRAME_SAMPLES as f64;
assert!(
diff_rms.sqrt() > 1.0,
"Baseline and AmbePlus produced identical voiced output — phase mode not wired through"
);
}
#[test]
fn voiced_ambe_plus_matches_baseline_on_all_unvoiced() {
let m_bar = vec![100f32; 9];
let v_bar = vec![false; 9];
let noise = [50f64; L_MAX as usize];
let mut state_base = VoicedSynthState::new();
let pcm_base = synthesize_voiced(
0.2, &m_bar, &v_bar, &noise, PhaseMode::Baseline, &mut state_base,
);
let mut state_amb = VoicedSynthState::new();
let pcm_amb = synthesize_voiced(
0.2, &m_bar, &v_bar, &noise, PhaseMode::AmbePlus, &mut state_amb,
);
for (i, (a, b)) in pcm_base.iter().zip(pcm_amb.iter()).enumerate() {
assert!(
(a - b).abs() < 1e-9,
"i={i}: Baseline {a} vs AmbePlus {b} — should match on all-UV frame"
);
}
}
#[test]
fn voiced_noise_samples_extracts_from_window() {
let mut us = UnvoicedSynthState::new();
us.advance_window();
let n = voiced_noise_samples(&us);
for l in 1..=L_MAX as usize {
assert_eq!(n[l - 1], us.noise_window[l + 104]);
}
}
fn build_params(omega_0: f32, voiced: &[bool], amplitudes: &[f32]) -> MbeParams {
let l = voiced.len() as u8;
MbeParams::new(omega_0, l, voiced, amplitudes).expect("valid params")
}
#[test]
fn synthesize_frame_silence_input_produces_silence_output() {
let p = build_params(0.2, &[false; 9], &[0f32; 9]);
let err = FrameErrorContext::default();
let mut state = SynthState::new();
let pcm = synthesize_frame(&p, &err, GAMMA_W, &mut state);
for s in pcm.iter() {
assert_eq!(*s, 0);
}
}
#[test]
fn synthesize_frame_advances_all_substates() {
let voiced = vec![true; 9];
let amps = vec![100f32; 9];
let p = build_params(0.2, &voiced, &s);
let err = FrameErrorContext::default();
let mut state = SynthState::new();
let s_e_init = state.s_e;
let lcg_init = state.unvoiced.lcg;
let psi_init = state.voiced.psi;
let _ = synthesize_frame(&p, &err, GAMMA_W, &mut state);
assert_ne!(state.s_e, s_e_init, "S_E unchanged");
assert_ne!(state.unvoiced.lcg, lcg_init, "LCG unchanged");
let any_changed = (1..=L_MAX as usize).any(|l| state.voiced.psi[l] != psi_init[l]);
assert!(any_changed, "no ψ advanced");
assert!(state.last_good.is_some(), "no snapshot stored");
}
#[test]
fn synthesize_frame_mute_emits_low_amplitude_noise_but_advances_state() {
let voiced = vec![true; 9];
let amps = vec![100f32; 9];
let p = build_params(0.2, &voiced, &s);
let err = FrameErrorContext { epsilon_t: 100, ..Default::default() };
let mut state = SynthState::new();
state.epsilon_r = 0.5; let s_e_init = state.s_e;
let pcm = synthesize_frame(&p, &err, GAMMA_W, &mut state);
let peak = pcm.iter().map(|s| s.unsigned_abs()).max().unwrap_or(0);
let nonzero = pcm.iter().filter(|&&s| s != 0).count();
assert!(peak < 1500, "Mute noise peak too loud: {peak}");
assert!(nonzero > FRAME_SAMPLES / 2, "Mute noise too sparse: {nonzero}");
assert_ne!(state.s_e, s_e_init);
}
#[test]
fn pcm_from_f64_clamps_and_rounds() {
let mut input = [0f64; FRAME_SAMPLES];
input[0] = 0.0;
input[1] = 100.4; input[2] = 100.6; input[3] = -100.6; input[4] = 50000.0; input[5] = -50000.0; let out = pcm_from_f64(&input);
assert_eq!(out[0], 0);
assert_eq!(out[1], 100);
assert_eq!(out[2], 101);
assert_eq!(out[3], -101);
assert_eq!(out[4], 32767);
assert_eq!(out[5], -32768);
}
#[test]
fn synthesize_frame_repeat_reuses_last_good() {
let voiced = vec![true; 9];
let amps = vec![50f32; 9];
let p_good = build_params(0.2, &voiced, &s);
let p_repeat = build_params(0.1, &vec![false; 12], &vec![1.0; 12]);
let err_use = FrameErrorContext::default();
let err_repeat = FrameErrorContext { bad_pitch: true, ..Default::default() };
let mut state_a = SynthState::new();
let _ = synthesize_frame(&p_good, &err_use, GAMMA_W, &mut state_a);
let pcm_repeat = synthesize_frame(&p_repeat, &err_repeat, GAMMA_W, &mut state_a);
let mut state_b = SynthState::new();
let _ = synthesize_frame(&p_good, &err_use, GAMMA_W, &mut state_b);
let pcm_use_again = synthesize_frame(&p_good, &err_use, GAMMA_W, &mut state_b);
for i in 0..FRAME_SAMPLES {
assert_eq!(
pcm_repeat[i], pcm_use_again[i],
"n={i}: repeat={} use={}",
pcm_repeat[i], pcm_use_again[i]
);
}
}
#[test]
fn spectral_clamp_does_not_fire_on_full_rate_imbe() {
let p_low = build_params(0.5, &vec![true; 9], &vec![50f32; 9]);
let p_high = build_params(0.1, &vec![true; 30], &vec![50f32; 30]);
let err = FrameErrorContext::default();
let mut state_off = SynthState::new();
let _ = synthesize_frame(&p_low, &err, GAMMA_W, &mut state_off);
let pcm_off = synthesize_frame(&p_high, &err, GAMMA_W, &mut state_off);
let mut state_on = SynthState::new();
state_on.set_chip_compat(true);
state_on.set_chip_compat_spectral_clamp(true);
let _ = synthesize_frame(&p_low, &err, GAMMA_W, &mut state_on);
let pcm_on = synthesize_frame(&p_high, &err, GAMMA_W, &mut state_on);
assert_eq!(
&pcm_off[..], &pcm_on[..],
"full-rate IMBE (PhaseMode::Baseline) must be unaffected by chip_compat flags"
);
}
#[test]
fn spectral_clamp_auto_enables_under_chip_compat() {
let p_low = build_params(0.5, &vec![true; 9], &vec![50f32; 9]);
let p_high = build_params(0.05, &vec![true; 45], &vec![50f32; 45]);
let err = FrameErrorContext::default();
let rms = |pcm: &[i16]| {
(pcm.iter().map(|&s| f64::from(s).powi(2)).sum::<f64>() / pcm.len() as f64).sqrt()
};
let mut state_off = SynthState::new();
let _ = synthesize_frame_ambe_plus(&p_low, &err, GAMMA_W, &mut state_off);
let pcm_off = synthesize_frame_ambe_plus(&p_high, &err, GAMMA_W, &mut state_off);
let mut state_on = SynthState::new();
state_on.set_chip_compat(true);
assert!(!state_on.chip_compat_spectral_clamp(),
"standalone flag should stay off — clamp is enabled via chip_compat");
let _ = synthesize_frame_ambe_plus(&p_low, &err, GAMMA_W, &mut state_on);
let pcm_on = synthesize_frame_ambe_plus(&p_high, &err, GAMMA_W, &mut state_on);
let rms_off = rms(&pcm_off);
let rms_on = rms(&pcm_on);
assert!(rms_on < rms_off,
"chip_compat should auto-enable the clamp on large L-jump: off={rms_off:.1} on={rms_on:.1}");
}
#[test]
fn spectral_clamp_default_off_leaves_synth_unchanged() {
let p_low = build_params(0.5, &vec![true; 9], &vec![50f32; 9]);
let p_high = build_params(0.1, &vec![true; 30], &vec![50f32; 30]); let err = FrameErrorContext::default();
let mut state_a = SynthState::new();
let _ = synthesize_frame_ambe_plus(&p_low, &err, GAMMA_W, &mut state_a);
let pcm_a = synthesize_frame_ambe_plus(&p_high, &err, GAMMA_W, &mut state_a);
let mut state_b = SynthState::new();
assert!(!state_b.chip_compat_spectral_clamp(), "default must be off");
let _ = synthesize_frame_ambe_plus(&p_low, &err, GAMMA_W, &mut state_b);
let pcm_b = synthesize_frame_ambe_plus(&p_high, &err, GAMMA_W, &mut state_b);
assert_eq!(&pcm_a[..], &pcm_b[..], "default-off must not alter synth output");
}
#[test]
fn spectral_clamp_fires_on_large_l_jump_when_enabled() {
let p_low = build_params(0.5, &vec![true; 9], &vec![50f32; 9]);
let p_high = build_params(0.05, &vec![true; 45], &vec![50f32; 45]);
let err = FrameErrorContext::default();
let rms = |pcm: &[i16]| {
(pcm.iter().map(|&s| f64::from(s).powi(2)).sum::<f64>() / pcm.len() as f64).sqrt()
};
let mut state_off = SynthState::new();
let _ = synthesize_frame_ambe_plus(&p_low, &err, GAMMA_W, &mut state_off);
let pcm_off = synthesize_frame_ambe_plus(&p_high, &err, GAMMA_W, &mut state_off);
let mut state_on = SynthState::new();
state_on.set_chip_compat_spectral_clamp(true);
let _ = synthesize_frame_ambe_plus(&p_low, &err, GAMMA_W, &mut state_on);
let pcm_on = synthesize_frame_ambe_plus(&p_high, &err, GAMMA_W, &mut state_on);
let rms_off = rms(&pcm_off);
let rms_on = rms(&pcm_on);
assert!(rms_on < rms_off, "clamp must reduce RMS on large L-jump: off={rms_off:.1} on={rms_on:.1}");
let ratio = rms_on / rms_off;
assert!(ratio < 0.95, "expected ratio < 0.95, got {ratio:.3}");
assert!(ratio > 0.5, "expected ratio > 0.5 (sanity), got {ratio:.3}");
}
#[test]
fn spectral_clamp_fires_on_large_l_down_jump() {
let p_high = build_params(0.05, &vec![true; 45], &vec![50f32; 45]);
let p_low = build_params(0.5, &vec![true; 9], &vec![50f32; 9]);
let err = FrameErrorContext::default();
let rms = |pcm: &[i16]| {
(pcm.iter().map(|&s| f64::from(s).powi(2)).sum::<f64>() / pcm.len() as f64).sqrt()
};
let mut state_off = SynthState::new();
let _ = synthesize_frame_ambe_plus(&p_high, &err, GAMMA_W, &mut state_off);
let pcm_off = synthesize_frame_ambe_plus(&p_low, &err, GAMMA_W, &mut state_off);
let mut state_on = SynthState::new();
state_on.set_chip_compat_spectral_clamp(true);
let _ = synthesize_frame_ambe_plus(&p_high, &err, GAMMA_W, &mut state_on);
let pcm_on = synthesize_frame_ambe_plus(&p_low, &err, GAMMA_W, &mut state_on);
let rms_off = rms(&pcm_off);
let rms_on = rms(&pcm_on);
assert!(rms_on < rms_off,
"clamp must reduce RMS on large L-down-jump: off={rms_off:.1} on={rms_on:.1}");
}
#[test]
fn spectral_clamp_does_not_fire_on_small_l_change() {
let p_a = build_params(0.2, &vec![true; 18], &vec![50f32; 18]);
let p_b = build_params(0.15, &vec![true; 25], &vec![50f32; 25]);
let err = FrameErrorContext::default();
let mut state_off = SynthState::new();
let _ = synthesize_frame_ambe_plus(&p_a, &err, GAMMA_W, &mut state_off);
let pcm_off = synthesize_frame_ambe_plus(&p_b, &err, GAMMA_W, &mut state_off);
let mut state_on = SynthState::new();
state_on.set_chip_compat_spectral_clamp(true);
let _ = synthesize_frame_ambe_plus(&p_a, &err, GAMMA_W, &mut state_on);
let pcm_on = synthesize_frame_ambe_plus(&p_b, &err, GAMMA_W, &mut state_on);
assert_eq!(&pcm_off[..], &pcm_on[..],
"small L change (|ΔL|=7, below threshold of 25) must not trigger clamp");
}
#[test]
fn spectral_clamp_fires_on_high_epsilon_0() {
let p = build_params(0.3, &vec![true; 20], &vec![50f32; 20]);
let err_clean = FrameErrorContext::default();
let err_e2 = FrameErrorContext { epsilon_0: 2, epsilon_t: 2, ..Default::default() };
let mut state_off = SynthState::new();
let _ = synthesize_frame_ambe_plus(&p, &err_clean, GAMMA_W, &mut state_off);
let pcm_off = synthesize_frame_ambe_plus(&p, &err_e2, GAMMA_W, &mut state_off);
let mut state_on = SynthState::new();
state_on.set_chip_compat_spectral_clamp(true);
let _ = synthesize_frame_ambe_plus(&p, &err_clean, GAMMA_W, &mut state_on);
let pcm_on = synthesize_frame_ambe_plus(&p, &err_e2, GAMMA_W, &mut state_on);
let rms = |pcm: &[i16]| {
(pcm.iter().map(|&s| f64::from(s).powi(2)).sum::<f64>() / pcm.len() as f64).sqrt()
};
let rms_off = rms(&pcm_off);
let rms_on = rms(&pcm_on);
assert!(rms_on < rms_off, "ε₀≥2 must trigger clamp: off={rms_off:.1} on={rms_on:.1}");
}
#[test]
fn enhance_clamps_w_l_within_brackets() {
let l = 12usize; let m_tilde: Vec<f32> = vec![1e-3, 10.0, 0.01, 5.0, 0.05, 3.0, 0.1, 2.0, 0.2, 1.5, 0.3, 1.0];
let omega_0 = std::f32::consts::PI / 6.0;
let (m_bar, _) = enhance_spectral_amplitudes(&m_tilde, omega_0, INIT_S_E);
let mut ratios: Vec<f32> = (1..l).map(|i| m_bar[i] / m_tilde[i]).collect();
ratios.sort_by(|a, b| a.partial_cmp(b).unwrap());
let span = ratios[ratios.len() - 1] / ratios[0];
assert!(span <= 2.4 + 1e-4, "ratio span {span} exceeds 2.4");
}
}