pub mod quantize;
use core::f32::consts::PI;
pub const L_MIN: u8 = 9;
pub const L_MAX: u8 = 56;
pub const SAMPLE_RATE_HZ: u32 = 8_000;
pub const SAMPLES_PER_FRAME: u16 = 160;
pub const FRAME_DURATION_MS: u16 = 20;
const L_CAP: usize = L_MAX as usize;
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MbeParams {
omega_0: f32,
l: u8,
#[cfg_attr(feature = "serde", serde(with = "serde_l_cap_bool"))]
voiced: [bool; L_CAP],
#[cfg_attr(feature = "serde", serde(with = "serde_l_cap_f32"))]
amplitudes: [f32; L_CAP],
}
#[cfg(feature = "serde")]
mod serde_l_cap_bool {
use super::L_CAP;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(v: &[bool; L_CAP], s: S) -> Result<S::Ok, S::Error> {
v.as_slice().serialize(s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[bool; L_CAP], D::Error> {
let v = Vec::<bool>::deserialize(d)?;
if v.len() != L_CAP {
return Err(serde::de::Error::invalid_length(v.len(), &format!("{L_CAP}").as_str()));
}
let mut out = [false; L_CAP];
out.copy_from_slice(&v);
Ok(out)
}
}
#[cfg(feature = "serde")]
mod serde_l_cap_f32 {
use super::L_CAP;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(v: &[f32; L_CAP], s: S) -> Result<S::Ok, S::Error> {
v.as_slice().serialize(s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[f32; L_CAP], D::Error> {
let v = Vec::<f32>::deserialize(d)?;
if v.len() != L_CAP {
return Err(serde::de::Error::invalid_length(v.len(), &format!("{L_CAP}").as_str()));
}
let mut out = [0.0f32; L_CAP];
out.copy_from_slice(&v);
Ok(out)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MbeParamsError {
OmegaOutOfRange,
HarmonicCountOutOfRange,
InvalidAmplitude,
}
impl MbeParams {
pub fn new(
omega_0: f32,
l: u8,
voiced: &[bool],
amplitudes: &[f32],
) -> Result<Self, MbeParamsError> {
if !(omega_0 > 0.0 && omega_0 < PI) {
return Err(MbeParamsError::OmegaOutOfRange);
}
if l < L_MIN || l > L_MAX {
return Err(MbeParamsError::HarmonicCountOutOfRange);
}
let n = l as usize;
if voiced.len() != n || amplitudes.len() != n {
return Err(MbeParamsError::HarmonicCountOutOfRange);
}
for &m in amplitudes {
if !m.is_finite() || m < 0.0 {
return Err(MbeParamsError::InvalidAmplitude);
}
}
let mut v = [false; L_CAP];
let mut a = [0.0f32; L_CAP];
v[..n].copy_from_slice(voiced);
a[..n].copy_from_slice(amplitudes);
Ok(Self { omega_0, l, voiced: v, amplitudes: a })
}
pub fn silence() -> Self {
Self {
omega_0: 4.0 * PI / 39.5,
l: L_MIN,
voiced: [false; L_CAP],
amplitudes: [0.0; L_CAP],
}
}
pub fn silence_ambe_plus2() -> Self {
const HALFRATE_B0_ZERO_OMEGA: f32 = 0.313_977;
Self {
omega_0: HALFRATE_B0_ZERO_OMEGA,
l: L_MIN,
voiced: [false; L_CAP],
amplitudes: [0.0; L_CAP],
}
}
#[inline]
pub fn omega_0(&self) -> f32 { self.omega_0 }
#[inline]
pub fn fundamental_hz(&self) -> f32 {
self.omega_0 * (SAMPLE_RATE_HZ as f32) / (2.0 * PI)
}
#[inline]
pub fn harmonic_count(&self) -> u8 { self.l }
#[inline]
pub fn voiced(&self, l: u8) -> bool {
debug_assert!(l >= 1 && l <= self.l, "harmonic index out of range");
*self.voiced.get(l as usize - 1).unwrap_or(&false)
}
#[inline]
pub fn amplitude(&self, l: u8) -> f32 {
debug_assert!(l >= 1 && l <= self.l, "harmonic index out of range");
*self.amplitudes.get(l as usize - 1).unwrap_or(&0.0)
}
#[inline]
pub fn voiced_slice(&self) -> &[bool] {
&self.voiced[..self.l as usize]
}
#[inline]
pub fn amplitudes_slice(&self) -> &[f32] {
&self.amplitudes[..self.l as usize]
}
pub fn harmonic_count_for(omega_0: f32) -> u8 {
if !(omega_0 > 0.0) {
return L_MIN;
}
let inner = (PI / omega_0 + 0.25).floor();
let raw = (0.9254 * inner).floor() as i32;
raw.clamp(L_MIN as i32, L_MAX as i32) as u8
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn silence_is_all_zero_and_unvoiced() {
let p = MbeParams::silence();
assert_eq!(p.harmonic_count(), L_MIN);
for l in 1..=p.harmonic_count() {
assert_eq!(p.amplitude(l), 0.0);
assert!(!p.voiced(l));
}
}
#[test]
fn silence_ambe_plus2_uses_half_rate_table_max_omega() {
let p = MbeParams::silence_ambe_plus2();
assert_eq!(p.harmonic_count(), L_MIN);
for l in 1..=p.harmonic_count() {
assert_eq!(p.amplitude(l), 0.0);
assert!(!p.voiced(l));
}
let b0 = crate::ambe_plus2_wire::dequantize::encode_pitch(p.omega_0());
assert_eq!(b0, Some(0));
let p_full = MbeParams::silence();
assert_eq!(crate::ambe_plus2_wire::dequantize::encode_pitch(p_full.omega_0()), None);
}
#[test]
fn rejects_omega_out_of_range() {
let v = vec![false; L_MIN as usize];
let a = vec![1.0; L_MIN as usize];
assert_eq!(
MbeParams::new(0.0, L_MIN, &v, &a),
Err(MbeParamsError::OmegaOutOfRange)
);
assert_eq!(
MbeParams::new(PI, L_MIN, &v, &a),
Err(MbeParamsError::OmegaOutOfRange)
);
}
#[test]
fn rejects_harmonic_count_out_of_range() {
let v = vec![false; 8];
let a = vec![1.0; 8];
assert_eq!(
MbeParams::new(0.1, 8, &v, &a),
Err(MbeParamsError::HarmonicCountOutOfRange)
);
}
#[test]
fn rejects_negative_amplitude() {
let v = vec![false; L_MIN as usize];
let mut a = vec![1.0f32; L_MIN as usize];
a[3] = -0.1;
assert_eq!(
MbeParams::new(0.1, L_MIN, &v, &a),
Err(MbeParamsError::InvalidAmplitude)
);
}
#[test]
fn harmonic_count_for_matches_spec_rule() {
assert_eq!(MbeParams::harmonic_count_for(PI / 20.0), 18);
assert_eq!(MbeParams::harmonic_count_for(0.001), L_MAX);
assert_eq!(MbeParams::harmonic_count_for(PI / 2.0), L_MIN);
}
#[test]
fn harmonic_count_matches_full_rate_b0_endpoints() {
let omega_at_b0_zero = 4.0 * PI / 39.5;
assert_eq!(MbeParams::harmonic_count_for(omega_at_b0_zero), 9);
let omega_at_b0_max = 4.0 * PI / 246.5;
assert_eq!(MbeParams::harmonic_count_for(omega_at_b0_max), 56);
}
#[test]
fn accessors_round_trip() {
let l = 12u8;
let voiced: Vec<bool> = (0..l).map(|i| i % 2 == 0).collect();
let amps: Vec<f32> = (0..l).map(|i| (i as f32) + 1.0).collect();
let p = MbeParams::new(0.2, l, &voiced, &s).unwrap();
assert_eq!(p.harmonic_count(), l);
assert_eq!(p.voiced_slice(), voiced.as_slice());
assert_eq!(p.amplitudes_slice(), amps.as_slice());
for i in 1..=l {
assert_eq!(p.voiced(i), voiced[i as usize - 1]);
assert_eq!(p.amplitude(i), amps[i as usize - 1]);
}
}
}