use crate::codecs::ambe_plus2;
use crate::codecs::mbe_baseline::analysis::{
AnalysisError, AnalysisOutput, AnalysisState, ToneDetection,
detect_tone, encode as analysis_encode,
encode_ambe_plus2 as analysis_encode_ambe_plus2,
profile as analysis_profile,
};
use crate::codecs::mbe_baseline::{
FrameDisposition, FrameErrorContext, GAMMA_W, SynthState, UnvoicedNoiseGen, synthesize_frame,
};
use crate::enhancement::{self, EnhancementMode, EnhancementState};
use crate::mbe_params::MbeParams;
use crate::imbe_wire;
use crate::ambe_plus2_wire;
const SAMPLE_RATE_HZ: f32 = 8_000.0;
pub const FRAME_SAMPLES: usize = 160;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum Rate {
Imbe7200x4400,
Imbe4400x4400,
AmbePlus2_3600x2450,
AmbePlus2_2450x2450,
}
impl Rate {
#[inline]
pub const fn fec_frame_bytes(self) -> usize {
match self {
Rate::Imbe7200x4400 => 18,
Rate::Imbe4400x4400 => 11,
Rate::AmbePlus2_3600x2450 => 9,
Rate::AmbePlus2_2450x2450 => 7,
}
}
#[inline]
pub const fn frame_samples(self) -> usize {
FRAME_SAMPLES
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum AmbePlus2Synth {
AmbePlus,
Baseline,
}
impl Default for AmbePlus2Synth {
fn default() -> Self {
Self::AmbePlus
}
}
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FrameStats {
pub analysis: Option<AnalysisStats>,
pub decode: Option<DecodeStats>,
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AnalysisStats {
pub output: AnalysisOutputKind,
pub params: MbeParams,
pub tone_detect: Option<ToneDetection>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AnalysisOutputKind {
Voice,
Silence,
Tone {
id: u8,
amplitude: u8,
},
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DecodeStats {
pub epsilon_0: u8,
pub epsilon_t: u8,
pub disposition: Option<FrameDisposition>,
}
#[derive(Debug)]
pub enum VocoderError {
WrongPcmLength {
expected: usize,
got: usize,
},
WrongBitsLength {
expected: usize,
got: usize,
},
Analysis(AnalysisError),
Quantize(String),
UnsupportedTranscode {
from: Rate,
to: Rate,
},
}
impl core::fmt::Display for VocoderError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
VocoderError::WrongPcmLength { expected, got } => {
write!(f, "expected {expected} PCM samples per frame, got {got}")
}
VocoderError::WrongBitsLength { expected, got } => {
write!(f, "expected {expected} FEC bytes per frame, got {got}")
}
VocoderError::Analysis(e) => write!(f, "analysis encoder error: {e:?}"),
VocoderError::Quantize(msg) => write!(f, "quantize error: {msg}"),
VocoderError::UnsupportedTranscode { from, to } => {
write!(f, "unsupported transcode direction: {from:?} -> {to:?}")
}
}
}
}
impl std::error::Error for VocoderError {}
pub struct Vocoder {
rate: Rate,
analysis: AnalysisState,
imbe_dec: imbe_wire::dequantize::DecoderState,
ambe_plus2_dec: ambe_plus2_wire::dequantize::DecoderState,
synth: SynthState,
last_stats: FrameStats,
tone_detection: bool,
ambe_plus2_synth: AmbePlus2Synth,
enhancement: EnhancementMode,
enhancement_state: EnhancementState,
prev_disposition: Option<FrameDisposition>,
}
impl Vocoder {
pub fn new(rate: Rate) -> Self {
let noise_gen = match rate {
Rate::Imbe7200x4400 | Rate::Imbe4400x4400 => UnvoicedNoiseGen::SpecLcg,
Rate::AmbePlus2_3600x2450 | Rate::AmbePlus2_2450x2450 => UnvoicedNoiseGen::ChipLcg,
};
Self {
rate,
analysis: AnalysisState::new(),
imbe_dec: imbe_wire::dequantize::DecoderState::new(),
ambe_plus2_dec: ambe_plus2_wire::dequantize::DecoderState::new(),
synth: SynthState::with_unvoiced_gen(noise_gen),
last_stats: FrameStats::default(),
tone_detection: false,
ambe_plus2_synth: AmbePlus2Synth::AmbePlus,
enhancement: EnhancementMode::Classical(crate::enhancement::ClassicalConfig::default()),
enhancement_state: EnhancementState::default(),
prev_disposition: None,
}
}
pub fn set_ambe_plus2_synth(&mut self, gen: AmbePlus2Synth) {
self.ambe_plus2_synth = gen;
}
#[inline]
pub fn ambe_plus2_synth(&self) -> AmbePlus2Synth {
self.ambe_plus2_synth
}
pub fn set_tone_detection(&mut self, enabled: bool) {
self.tone_detection = enabled;
}
#[inline]
pub fn tone_detection(&self) -> bool {
self.tone_detection
}
pub fn set_repeat_reset_after(&mut self, n: Option<u32>) {
self.synth.set_repeat_reset_after(n);
}
#[inline]
pub fn repeat_reset_after(&self) -> Option<u32> {
self.synth.repeat_reset_after()
}
pub fn set_chip_compat(&mut self, on: bool) {
self.synth.set_chip_compat(on);
}
#[inline]
pub fn chip_compat(&self) -> bool {
self.synth.chip_compat()
}
pub fn set_chip_compat_spectral_clamp(&mut self, on: bool) {
self.synth.set_chip_compat_spectral_clamp(on);
}
#[inline]
pub fn chip_compat_spectral_clamp(&self) -> bool {
self.synth.chip_compat_spectral_clamp()
}
pub fn set_silence_dispatch(&mut self, on: bool) {
self.analysis.set_silence_detection(on);
}
#[inline]
pub fn silence_dispatch(&self) -> bool {
self.analysis.silence_detection_enabled()
}
pub fn set_pitch_silence_override(&mut self, on: bool) {
self.analysis.set_pitch_silence_override(on);
}
#[inline]
pub fn pitch_silence_override(&self) -> bool {
self.analysis.pitch_silence_override_enabled()
}
pub fn set_default_pitch_on_silence(&mut self, on: bool) {
self.analysis.set_default_pitch_on_silence(on);
}
#[inline]
pub fn default_pitch_on_silence(&self) -> bool {
self.analysis.default_pitch_on_silence_enabled()
}
pub fn set_pyin_pitch(&mut self, on: bool) {
self.analysis.set_pyin_pitch(on);
}
#[inline]
pub fn pyin_pitch(&self) -> bool {
self.analysis.pyin_pitch_enabled()
}
pub fn set_spectral_subtraction(&mut self, on: bool) {
self.analysis.set_spectral_subtraction(on);
}
#[inline]
pub fn spectral_subtraction(&self) -> bool {
self.analysis.spectral_subtraction_enabled()
}
pub fn set_amp_ema_alpha(&mut self, alpha: f64) {
self.analysis.set_amp_ema_alpha(alpha);
}
#[inline]
pub fn amp_ema_alpha(&self) -> f64 {
self.analysis.amp_ema_alpha()
}
pub fn set_enhancement(&mut self, mode: EnhancementMode) {
self.enhancement = mode;
self.enhancement_state = EnhancementState::default();
}
#[inline]
pub fn enhancement(&self) -> &EnhancementMode {
&self.enhancement
}
#[inline]
pub fn builder(rate: Rate) -> VocoderBuilder {
VocoderBuilder::new(rate)
}
#[inline]
pub fn rate(&self) -> Rate {
self.rate
}
#[inline]
pub fn frame_samples(&self) -> usize {
self.rate.frame_samples()
}
#[inline]
pub fn fec_frame_bytes(&self) -> usize {
self.rate.fec_frame_bytes()
}
#[inline]
pub fn last_stats(&self) -> &FrameStats {
&self.last_stats
}
#[inline]
pub fn last_disposition(&self) -> Option<FrameDisposition> {
self.synth.last_disposition()
}
pub fn reset(&mut self) {
self.analysis = AnalysisState::new();
self.imbe_dec = imbe_wire::dequantize::DecoderState::new();
self.ambe_plus2_dec = ambe_plus2_wire::dequantize::DecoderState::new();
self.synth = SynthState::new();
self.last_stats = FrameStats::default();
self.enhancement_state = EnhancementState::default();
self.prev_disposition = None;
}
pub fn encode_pcm(&mut self, pcm: &[i16]) -> Result<Vec<u8>, VocoderError> {
if pcm.len() != self.frame_samples() {
return Err(VocoderError::WrongPcmLength {
expected: self.frame_samples(),
got: pcm.len(),
});
}
let (bytes, stats) = match self.rate {
Rate::Imbe7200x4400 => imbe_pipeline::encode(pcm, self, true)?,
Rate::Imbe4400x4400 => imbe_pipeline::encode(pcm, self, false)?,
Rate::AmbePlus2_3600x2450 => ambe_plus2_pipeline::encode(pcm, self, true)?,
Rate::AmbePlus2_2450x2450 => ambe_plus2_pipeline::encode(pcm, self, false)?,
};
self.last_stats.analysis = Some(stats);
Ok(bytes)
}
pub fn decode_bits(&mut self, bits: &[u8]) -> Result<Vec<i16>, VocoderError> {
if bits.len() != self.fec_frame_bytes() {
return Err(VocoderError::WrongBitsLength {
expected: self.fec_frame_bytes(),
got: bits.len(),
});
}
let (mut pcm, stats) = match self.rate {
Rate::Imbe7200x4400 => imbe_pipeline::decode(bits, self, true),
Rate::Imbe4400x4400 => imbe_pipeline::decode(bits, self, false),
Rate::AmbePlus2_3600x2450 => ambe_plus2_pipeline::decode(bits, self, true),
Rate::AmbePlus2_2450x2450 => ambe_plus2_pipeline::decode(bits, self, false),
};
let prev_was_use = matches!(self.prev_disposition, Some(FrameDisposition::Use));
enhancement::apply(
&self.enhancement,
&mut self.enhancement_state,
&mut pcm,
SAMPLE_RATE_HZ,
prev_was_use,
);
self.prev_disposition = stats.disposition;
self.last_stats.decode = Some(stats);
Ok(pcm)
}
pub fn encode_stream<'a>(&'a mut self, pcm: &'a [i16]) -> EncodeStream<'a> {
EncodeStream { vocoder: self, pcm, pos: 0 }
}
pub fn extract_params(&mut self, pcm: &[i16]) -> Result<MbeParams, VocoderError> {
if pcm.len() != self.frame_samples() {
return Err(VocoderError::WrongPcmLength {
expected: self.frame_samples(),
got: pcm.len(),
});
}
let frame = pcm.try_into().expect("length already validated");
let analysis_out = match self.rate {
Rate::Imbe7200x4400 | Rate::Imbe4400x4400 => {
analysis_encode(frame, &mut self.analysis)
}
Rate::AmbePlus2_3600x2450 | Rate::AmbePlus2_2450x2450 => {
analysis_encode_ambe_plus2(frame, &mut self.analysis)
}
}
.map_err(VocoderError::Analysis)?;
Ok(match analysis_out {
AnalysisOutput::Voice(p) => p,
AnalysisOutput::Silence => match self.rate {
Rate::Imbe7200x4400 | Rate::Imbe4400x4400 => MbeParams::silence(),
Rate::AmbePlus2_3600x2450 | Rate::AmbePlus2_2450x2450 => {
MbeParams::silence_ambe_plus2()
}
},
})
}
pub fn synthesize_params(&mut self, params: &MbeParams) -> Vec<i16> {
let prev_err = self.synth.err;
self.synth.err = FrameErrorContext::default();
let err = self.synth.err;
let gamma_w = self.synth.gamma_w;
let pcm: [i16; FRAME_SAMPLES] = match self.rate {
Rate::Imbe7200x4400 | Rate::Imbe4400x4400 => {
synthesize_frame(params, &err, gamma_w, &mut self.synth)
}
Rate::AmbePlus2_3600x2450 | Rate::AmbePlus2_2450x2450 => match self.ambe_plus2_synth {
AmbePlus2Synth::AmbePlus => ambe_plus2::synthesize_frame(params, &mut self.synth),
AmbePlus2Synth::Baseline => synthesize_frame(params, &err, gamma_w, &mut self.synth),
},
};
self.synth.err = prev_err;
let mut pcm = pcm.to_vec();
let prev_was_use = matches!(self.prev_disposition, Some(FrameDisposition::Use));
enhancement::apply(
&self.enhancement,
&mut self.enhancement_state,
&mut pcm,
SAMPLE_RATE_HZ,
prev_was_use,
);
self.prev_disposition = self.synth.last_disposition();
pcm
}
pub fn decode_stream<'a>(&'a mut self, bits: &'a [u8]) -> DecodeStream<'a> {
DecodeStream { vocoder: self, bits, pos: 0 }
}
}
pub struct EncodeStream<'a> {
vocoder: &'a mut Vocoder,
pcm: &'a [i16],
pos: usize,
}
impl Iterator for EncodeStream<'_> {
type Item = Result<Vec<u8>, VocoderError>;
fn next(&mut self) -> Option<Self::Item> {
let n = self.vocoder.frame_samples();
if self.pos + n > self.pcm.len() {
return None;
}
let frame = &self.pcm[self.pos..self.pos + n];
self.pos += n;
Some(self.vocoder.encode_pcm(frame))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = (self.pcm.len() - self.pos) / self.vocoder.frame_samples();
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for EncodeStream<'_> {}
pub struct DecodeStream<'a> {
vocoder: &'a mut Vocoder,
bits: &'a [u8],
pos: usize,
}
impl Iterator for DecodeStream<'_> {
type Item = Result<Vec<i16>, VocoderError>;
fn next(&mut self) -> Option<Self::Item> {
let n = self.vocoder.fec_frame_bytes();
if self.pos + n > self.bits.len() {
return None;
}
let frame = &self.bits[self.pos..self.pos + n];
self.pos += n;
Some(self.vocoder.decode_bits(frame))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = (self.bits.len() - self.pos) / self.vocoder.fec_frame_bytes();
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for DecodeStream<'_> {}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TranscodeDirection {
pub from: Rate,
pub to: Rate,
}
impl TranscodeDirection {
#[inline]
pub const fn new(from: Rate, to: Rate) -> Self {
Self { from, to }
}
#[inline]
pub const fn input_frame_bytes(self) -> usize {
self.from.fec_frame_bytes()
}
#[inline]
pub const fn output_frame_bytes(self) -> usize {
self.to.fec_frame_bytes()
}
}
pub struct Transcoder {
direction: TranscodeDirection,
full_to_half: Option<crate::rate_conversion::FullToHalfConverter>,
half_to_full: Option<crate::rate_conversion::HalfToFullConverter>,
}
impl Transcoder {
pub fn new(from: Rate, to: Rate) -> Result<Self, VocoderError> {
let direction = TranscodeDirection { from, to };
match (from, to) {
(Rate::Imbe7200x4400, Rate::AmbePlus2_3600x2450) => Ok(Self {
direction,
full_to_half: Some(crate::rate_conversion::FullToHalfConverter::new()),
half_to_full: None,
}),
(Rate::AmbePlus2_3600x2450, Rate::Imbe7200x4400) => Ok(Self {
direction,
full_to_half: None,
half_to_full: Some(crate::rate_conversion::HalfToFullConverter::new()),
}),
(Rate::Imbe7200x4400, Rate::Imbe4400x4400)
| (Rate::Imbe4400x4400, Rate::Imbe7200x4400)
| (Rate::AmbePlus2_3600x2450, Rate::AmbePlus2_2450x2450)
| (Rate::AmbePlus2_2450x2450, Rate::AmbePlus2_3600x2450) => Ok(Self {
direction,
full_to_half: None,
half_to_full: None,
}),
_ => Err(VocoderError::UnsupportedTranscode { from, to }),
}
}
#[inline]
pub fn direction(&self) -> TranscodeDirection {
self.direction
}
pub fn transcode(&mut self, bits: &[u8]) -> Result<Vec<u8>, VocoderError> {
let in_n = self.direction.input_frame_bytes();
if bits.len() != in_n {
return Err(VocoderError::WrongBitsLength {
expected: in_n,
got: bits.len(),
});
}
match (self.direction.from, self.direction.to) {
(Rate::Imbe7200x4400, Rate::AmbePlus2_3600x2450) => {
let dibits_in = unpack_dibits_n::<72>(bits);
let dibits_out = self
.full_to_half
.as_mut()
.expect("constructed with this direction")
.convert(&dibits_in)
.map_err(|e| VocoderError::Quantize(format!("{e:?}")))?;
Ok(pack_dibits_n::<36, 9>(&dibits_out).to_vec())
}
(Rate::AmbePlus2_3600x2450, Rate::Imbe7200x4400) => {
let dibits_in = unpack_dibits_n::<36>(bits);
let dibits_out = self
.half_to_full
.as_mut()
.expect("constructed with this direction")
.convert(&dibits_in)
.map_err(|e| VocoderError::Quantize(format!("{e:?}")))?;
Ok(pack_dibits_n::<72, 18>(&dibits_out).to_vec())
}
(Rate::Imbe7200x4400, Rate::Imbe4400x4400) => {
Ok(imbe_pipeline::fec_to_info_bytes(bits).to_vec())
}
(Rate::Imbe4400x4400, Rate::Imbe7200x4400) => {
Ok(imbe_pipeline::info_to_fec_bytes(bits).to_vec())
}
(Rate::AmbePlus2_3600x2450, Rate::AmbePlus2_2450x2450) => {
Ok(ambe_plus2_pipeline::fec_to_info_bytes(bits).to_vec())
}
(Rate::AmbePlus2_2450x2450, Rate::AmbePlus2_3600x2450) => {
Ok(ambe_plus2_pipeline::info_to_fec_bytes(bits).to_vec())
}
(from, to) => Err(VocoderError::UnsupportedTranscode { from, to }),
}
}
pub fn reset(&mut self) {
*self = Self::new(self.direction.from, self.direction.to)
.expect("direction was validated at construction");
}
}
fn unpack_dibits_n<const N: usize>(bytes: &[u8]) -> [u8; N] {
let mut out = [0u8; N];
let mut bit = 0usize;
for slot in &mut out {
let mut d = 0u8;
for _ in 0..2 {
let b = (bytes[bit / 8] >> (7 - (bit % 8))) & 1;
d = (d << 1) | b;
bit += 1;
}
*slot = d;
}
out
}
fn pack_dibits_n<const N: usize, const B: usize>(dibits: &[u8; N]) -> [u8; B] {
let mut out = [0u8; B];
let mut bit = 0usize;
for &d in dibits {
for pos in (0..2).rev() {
let b = (d >> pos) & 1;
out[bit / 8] |= b << (7 - (bit % 8));
bit += 1;
}
}
out
}
pub struct LiveEncoder {
vocoder: Vocoder,
pcm_buf: Vec<i16>,
}
impl LiveEncoder {
pub fn new(rate: Rate) -> Self {
Self {
vocoder: Vocoder::new(rate),
pcm_buf: Vec::new(),
}
}
#[inline]
pub fn vocoder(&self) -> &Vocoder {
&self.vocoder
}
pub fn push(&mut self, pcm: &[i16]) -> Vec<Result<Vec<u8>, VocoderError>> {
self.pcm_buf.extend_from_slice(pcm);
let n = self.vocoder.frame_samples();
let mut out = Vec::with_capacity(self.pcm_buf.len() / n);
while self.pcm_buf.len() >= n {
let result = self.vocoder.encode_pcm(&self.pcm_buf[..n]);
self.pcm_buf.drain(..n);
out.push(result);
}
out
}
#[inline]
pub fn pending_samples(&self) -> usize {
self.pcm_buf.len()
}
#[inline]
pub fn discard_pending(&mut self) {
self.pcm_buf.clear();
}
pub fn flush(&mut self) -> Result<Option<Vec<u8>>, VocoderError> {
if self.pcm_buf.is_empty() {
return Ok(None);
}
let n = self.vocoder.frame_samples();
self.pcm_buf.resize(n, 0);
let bits = self.vocoder.encode_pcm(&self.pcm_buf)?;
self.pcm_buf.clear();
Ok(Some(bits))
}
pub fn reset(&mut self) {
self.vocoder.reset();
self.pcm_buf.clear();
}
#[inline]
pub fn rate(&self) -> Rate {
self.vocoder.rate()
}
}
pub struct LiveDecoder {
vocoder: Vocoder,
bits_buf: Vec<u8>,
}
impl LiveDecoder {
pub fn new(rate: Rate) -> Self {
Self {
vocoder: Vocoder::new(rate),
bits_buf: Vec::new(),
}
}
#[inline]
pub fn vocoder(&self) -> &Vocoder {
&self.vocoder
}
pub fn push(&mut self, bits: &[u8]) -> Vec<Result<Vec<i16>, VocoderError>> {
self.bits_buf.extend_from_slice(bits);
let n = self.vocoder.fec_frame_bytes();
let mut out = Vec::with_capacity(self.bits_buf.len() / n);
while self.bits_buf.len() >= n {
let result = self.vocoder.decode_bits(&self.bits_buf[..n]);
self.bits_buf.drain(..n);
out.push(result);
}
out
}
#[inline]
pub fn pending_bytes(&self) -> usize {
self.bits_buf.len()
}
#[inline]
pub fn discard_pending(&mut self) {
self.bits_buf.clear();
}
pub fn reset(&mut self) {
self.vocoder.reset();
self.bits_buf.clear();
}
#[inline]
pub fn rate(&self) -> Rate {
self.vocoder.rate()
}
}
#[derive(Clone, Debug)]
pub struct VocoderBuilder {
rate: Rate,
tone_detection: bool,
repeat_reset_after: Option<u32>,
chip_compat: bool,
silence_dispatch: bool,
pitch_silence_override: bool,
default_pitch_on_silence: bool,
pyin_pitch: bool,
spectral_subtraction: bool,
amp_ema_alpha: f64,
ambe_plus2_synth: AmbePlus2Synth,
enhancement: EnhancementMode,
}
impl VocoderBuilder {
#[inline]
pub fn new(rate: Rate) -> Self {
Self {
rate,
tone_detection: false,
repeat_reset_after: None,
chip_compat: false,
silence_dispatch: false,
pitch_silence_override: false,
default_pitch_on_silence: false,
pyin_pitch: false,
spectral_subtraction: true,
amp_ema_alpha: 0.0,
ambe_plus2_synth: AmbePlus2Synth::AmbePlus,
enhancement: EnhancementMode::Classical(
crate::enhancement::ClassicalConfig::default(),
),
}
}
#[inline]
pub fn tone_detection(mut self, on: bool) -> Self {
self.tone_detection = on;
self
}
#[inline]
pub fn repeat_reset_after(mut self, n: Option<u32>) -> Self {
self.repeat_reset_after = n;
self
}
#[inline]
pub fn chip_compat(mut self, on: bool) -> Self {
self.chip_compat = on;
self
}
#[inline]
pub fn silence_dispatch(mut self, on: bool) -> Self {
self.silence_dispatch = on;
self
}
#[inline]
pub fn pitch_silence_override(mut self, on: bool) -> Self {
self.pitch_silence_override = on;
self
}
#[inline]
pub fn default_pitch_on_silence(mut self, on: bool) -> Self {
self.default_pitch_on_silence = on;
self
}
#[inline]
pub fn pyin_pitch(mut self, on: bool) -> Self {
self.pyin_pitch = on;
self
}
#[inline]
pub fn spectral_subtraction(mut self, on: bool) -> Self {
self.spectral_subtraction = on;
self
}
#[inline]
pub fn amp_ema_alpha(mut self, alpha: f64) -> Self {
self.amp_ema_alpha = alpha;
self
}
#[inline]
pub fn ambe_plus2_synth(mut self, gen: AmbePlus2Synth) -> Self {
self.ambe_plus2_synth = gen;
self
}
#[inline]
pub fn enhancement(mut self, mode: EnhancementMode) -> Self {
self.enhancement = mode;
self
}
pub fn build(self) -> Vocoder {
let mut v = Vocoder::new(self.rate);
v.set_tone_detection(self.tone_detection);
v.set_repeat_reset_after(self.repeat_reset_after);
v.set_chip_compat(self.chip_compat);
v.set_silence_dispatch(self.silence_dispatch);
v.set_pitch_silence_override(self.pitch_silence_override);
v.set_default_pitch_on_silence(self.default_pitch_on_silence);
v.set_pyin_pitch(self.pyin_pitch);
v.set_spectral_subtraction(self.spectral_subtraction);
v.set_amp_ema_alpha(self.amp_ema_alpha);
v.set_ambe_plus2_synth(self.ambe_plus2_synth);
v.set_enhancement(self.enhancement);
v
}
}
impl core::fmt::Debug for Vocoder {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Vocoder")
.field("rate", &self.rate)
.field("frame_samples", &self.frame_samples())
.field("fec_frame_bytes", &self.fec_frame_bytes())
.finish_non_exhaustive()
}
}
mod imbe_pipeline {
use super::*;
use crate::imbe_wire::dequantize::{dequantize, quantize};
use crate::imbe_wire::frame::{INFO_WIDTHS, decode_frame, encode_frame};
use crate::imbe_wire::priority::{deprioritize, prioritize};
pub(super) fn encode(
pcm: &[i16],
vocoder: &mut Vocoder,
apply_fec: bool,
) -> Result<(Vec<u8>, AnalysisStats), VocoderError> {
let tone_detect = if vocoder.tone_detection {
detect_tone(pcm)
} else {
None
};
let frame = pcm.try_into().expect("length already validated");
let (kind, params) = match analysis_encode(frame, &mut vocoder.analysis)
.map_err(VocoderError::Analysis)?
{
AnalysisOutput::Voice(p) => (AnalysisOutputKind::Voice, p),
AnalysisOutput::Silence => (AnalysisOutputKind::Silence, MbeParams::silence()),
};
let mut snapshot = vocoder.imbe_dec.clone();
let b = quantize(¶ms, &mut vocoder.imbe_dec)
.map_err(|e| VocoderError::Quantize(format!("{e:?}")))?;
let l = params.harmonic_count();
let info = prioritize(&b, l);
let _ = deprioritize; let _ = dequantize(&info, &mut snapshot);
vocoder.imbe_dec = snapshot;
let bytes: Vec<u8> = if apply_fec {
let dibits = encode_frame(&info);
pack_dibits_full(&dibits).to_vec()
} else {
pack_info_full(&info).to_vec()
};
Ok((bytes, AnalysisStats { output: kind, params, tone_detect }))
}
pub(super) fn decode(
bits: &[u8],
vocoder: &mut Vocoder,
apply_fec: bool,
) -> (Vec<i16>, DecodeStats) {
let (info, stats_eps0, stats_epst, eps4) = if apply_fec {
let dibits = analysis_profile::time(analysis_profile::Stage::DibitUnpack, || {
unpack_dibits_full(bits)
});
let imbe = analysis_profile::time(analysis_profile::Stage::DecodeFrame, || {
decode_frame(&dibits)
});
let s0: u8 = imbe.errors[0];
let st: u8 = imbe.error_total().min(255) as u8;
let e4: u8 = imbe.errors[4];
(imbe.info, s0, st, e4)
} else {
let info = unpack_info_full(bits);
(info, 0u8, 0u8, 0u8)
};
let dq = analysis_profile::time(analysis_profile::Stage::Dequantize, || {
dequantize(&info, &mut vocoder.imbe_dec)
});
let pcm: [i16; FRAME_SAMPLES] = match dq {
Ok(params) => {
let err = FrameErrorContext {
epsilon_0: stats_eps0,
epsilon_4: eps4,
epsilon_t: stats_epst,
bad_pitch: false,
};
synthesize_frame(¶ms, &err, GAMMA_W, &mut vocoder.synth)
}
Err(_) => [0i16; FRAME_SAMPLES],
};
let disposition = vocoder.synth.last_disposition();
(
pcm.to_vec(),
DecodeStats {
epsilon_0: stats_eps0,
epsilon_t: stats_epst,
disposition,
},
)
}
fn pack_info_full(info: &[u16; 8]) -> [u8; 11] {
let mut out = [0u8; 11];
super::pack_info_bits(info, &INFO_WIDTHS, &mut out);
out
}
fn unpack_info_full(bytes: &[u8]) -> [u16; 8] {
let mut out = [0u16; 8];
super::unpack_info_bits(bytes, &INFO_WIDTHS, &mut out);
out
}
pub(super) fn fec_to_info_bytes(fec_bytes: &[u8]) -> [u8; 11] {
let dibits = unpack_dibits_full(fec_bytes);
let frame = decode_frame(&dibits);
pack_info_full(&frame.info)
}
pub(super) fn info_to_fec_bytes(info_bytes: &[u8]) -> [u8; 18] {
let info = unpack_info_full(info_bytes);
let dibits = encode_frame(&info);
pack_dibits_full(&dibits)
}
fn pack_dibits_full(dibits: &[u8; 72]) -> [u8; 18] {
let mut out = [0u8; 18];
let mut bit = 0usize;
for &d in dibits {
for pos in (0..2).rev() {
let b = (d >> pos) & 1;
out[bit / 8] |= b << (7 - (bit % 8));
bit += 1;
}
}
out
}
fn unpack_dibits_full(bytes: &[u8]) -> [u8; 72] {
let mut out = [0u8; 72];
let mut bit = 0usize;
for slot in &mut out {
let mut d = 0u8;
for _ in 0..2 {
let b = (bytes[bit / 8] >> (7 - (bit % 8))) & 1;
d = (d << 1) | b;
bit += 1;
}
*slot = d;
}
out
}
}
mod ambe_plus2_pipeline {
use super::*;
use crate::ambe_plus2_wire::dequantize::{
Decoded, decode_to_params, encode_tone_frame_info, quantize, tone_to_mbe_params,
};
use crate::ambe_plus2_wire::frame::{INFO_WIDTHS, decode_frame, encode_frame};
pub(super) fn encode(
pcm: &[i16],
vocoder: &mut Vocoder,
apply_fec: bool,
) -> Result<(Vec<u8>, AnalysisStats), VocoderError> {
let tone_detect = if vocoder.tone_detection {
detect_tone(pcm)
} else {
None
};
if let Some(ToneDetection { id, amplitude }) = tone_detect {
let info = encode_tone_frame_info(id, amplitude);
let bytes = pack_info_or_fec(&info, apply_fec);
let params = tone_to_mbe_params(id, amplitude)
.unwrap_or_else(MbeParams::silence_ambe_plus2);
return Ok((
bytes,
AnalysisStats {
output: AnalysisOutputKind::Tone { id, amplitude },
params,
tone_detect,
},
));
}
let frame = pcm.try_into().expect("length already validated");
let (kind, params) = match analysis_encode_ambe_plus2(frame, &mut vocoder.analysis)
.map_err(VocoderError::Analysis)?
{
AnalysisOutput::Voice(p) => (AnalysisOutputKind::Voice, p),
AnalysisOutput::Silence => (AnalysisOutputKind::Silence, MbeParams::silence_ambe_plus2()),
};
let info = quantize(¶ms, &mut vocoder.ambe_plus2_dec)
.map_err(|e| VocoderError::Quantize(format!("{e:?}")))?;
let bytes = pack_info_or_fec(&info, apply_fec);
Ok((bytes, AnalysisStats { output: kind, params, tone_detect }))
}
fn pack_info_or_fec(info: &[u16; 4], apply_fec: bool) -> Vec<u8> {
if apply_fec {
let dibits = encode_frame(info);
pack_dibits_half(&dibits).to_vec()
} else {
pack_info_half(info).to_vec()
}
}
pub(super) fn decode(
bits: &[u8],
vocoder: &mut Vocoder,
apply_fec: bool,
) -> (Vec<i16>, DecodeStats) {
let (info, stats_eps0, stats_epst, eps3) = if apply_fec {
let dibits = analysis_profile::time(analysis_profile::Stage::DibitUnpack, || {
unpack_dibits_half(bits)
});
let frame = analysis_profile::time(analysis_profile::Stage::DecodeFrame, || {
decode_frame(&dibits)
});
const E0_UNCORRECTABLE_CAP: u8 = 4;
let raw_e0 = frame.errors[0];
let s0: u8 = if raw_e0 == u8::MAX { E0_UNCORRECTABLE_CAP } else { raw_e0 };
let s1: u8 = frame.errors[1];
let st: u8 = u16::from(s0)
.saturating_add(u16::from(s1))
.min(255) as u8;
let e3: u8 = frame.errors[3];
(frame.info, s0, st, e3)
} else {
let info = unpack_info_half(bits);
(info, 0u8, 0u8, 0u8)
};
let err = FrameErrorContext {
epsilon_0: stats_eps0,
epsilon_4: eps3, epsilon_t: stats_epst,
bad_pitch: false,
};
vocoder.synth.err = err;
let dq = analysis_profile::time(analysis_profile::Stage::Dequantize, || {
decode_to_params(&info, &mut vocoder.ambe_plus2_dec)
});
let pcm: [i16; FRAME_SAMPLES] = match dq {
Ok(Decoded::Voice(p)) => match vocoder.ambe_plus2_synth {
AmbePlus2Synth::AmbePlus => ambe_plus2::synthesize_frame(&p, &mut vocoder.synth),
AmbePlus2Synth::Baseline => {
let gamma_w = vocoder.synth.gamma_w;
synthesize_frame(&p, &err, gamma_w, &mut vocoder.synth)
}
},
Ok(Decoded::Tone { params, .. }) => match vocoder.ambe_plus2_synth {
AmbePlus2Synth::AmbePlus => ambe_plus2::synthesize_tone(¶ms, &mut vocoder.synth),
AmbePlus2Synth::Baseline => {
let gamma_w = vocoder.synth.gamma_w;
synthesize_frame(¶ms, &err, gamma_w, &mut vocoder.synth)
}
},
Ok(Decoded::Erasure) | Err(_) => [0i16; FRAME_SAMPLES],
};
let disposition = vocoder.synth.last_disposition();
(
pcm.to_vec(),
DecodeStats {
epsilon_0: stats_eps0,
epsilon_t: stats_epst,
disposition,
},
)
}
fn pack_dibits_half(dibits: &[u8; 36]) -> [u8; 9] {
let mut out = [0u8; 9];
let mut bit = 0usize;
for &d in dibits {
for pos in (0..2).rev() {
let b = (d >> pos) & 1;
out[bit / 8] |= b << (7 - (bit % 8));
bit += 1;
}
}
out
}
fn unpack_dibits_half(bytes: &[u8]) -> [u8; 36] {
let mut out = [0u8; 36];
let mut bit = 0usize;
for slot in &mut out {
let mut d = 0u8;
for _ in 0..2 {
let b = (bytes[bit / 8] >> (7 - (bit % 8))) & 1;
d = (d << 1) | b;
bit += 1;
}
*slot = d;
}
out
}
fn pack_info_half(info: &[u16; 4]) -> [u8; 7] {
let mut out = [0u8; 7];
super::pack_info_bits(info, &INFO_WIDTHS, &mut out);
out
}
fn unpack_info_half(bytes: &[u8]) -> [u16; 4] {
let mut out = [0u16; 4];
super::unpack_info_bits(bytes, &INFO_WIDTHS, &mut out);
out
}
pub(super) fn fec_to_info_bytes(fec_bytes: &[u8]) -> [u8; 7] {
let dibits = unpack_dibits_half(fec_bytes);
let frame = decode_frame(&dibits);
pack_info_half(&frame.info)
}
pub(super) fn info_to_fec_bytes(info_bytes: &[u8]) -> [u8; 9] {
let info = unpack_info_half(info_bytes);
let dibits = encode_frame(&info);
pack_dibits_half(&dibits)
}
}
fn pack_info_bits<const N: usize>(info: &[u16; N], widths: &[u8; N], out: &mut [u8]) {
let mut bit_idx = 0usize;
for i in 0..N {
let w = widths[i] as usize;
let v = info[i];
for k in (0..w).rev() {
let b = ((v >> k) & 1) as u8;
out[bit_idx / 8] |= b << (7 - (bit_idx % 8));
bit_idx += 1;
}
}
}
fn unpack_info_bits<const N: usize>(bytes: &[u8], widths: &[u8; N], out: &mut [u16; N]) {
let mut bit_idx = 0usize;
for i in 0..N {
let w = widths[i] as usize;
let mut v = 0u16;
for _ in 0..w {
let b = (bytes[bit_idx / 8] >> (7 - (bit_idx % 8))) & 1;
v = (v << 1) | u16::from(b);
bit_idx += 1;
}
out[i] = v;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn periodic_pcm(period: usize, amplitude: i16) -> [i16; FRAME_SAMPLES] {
let mut out = [0i16; FRAME_SAMPLES];
for (n, slot) in out.iter_mut().enumerate() {
let phase = (n % period) as f32 / period as f32;
*slot = (amplitude as f32 * (2.0 * core::f32::consts::PI * phase).sin()) as i16;
}
out
}
#[test]
fn rate_byte_sizes_match_wire_layouts() {
assert_eq!(Rate::Imbe7200x4400.fec_frame_bytes(), 18);
assert_eq!(Rate::Imbe4400x4400.fec_frame_bytes(), 11);
assert_eq!(Rate::AmbePlus2_3600x2450.fec_frame_bytes(), 9);
assert_eq!(Rate::AmbePlus2_2450x2450.fec_frame_bytes(), 7);
assert_eq!(Rate::Imbe7200x4400.frame_samples(), 160);
assert_eq!(Rate::AmbePlus2_3600x2450.frame_samples(), 160);
}
#[test]
fn no_fec_imbe_roundtrip_smoke() {
let mut tx = Vocoder::new(Rate::Imbe4400x4400);
let mut rx = Vocoder::new(Rate::Imbe4400x4400);
for _ in 0..5 {
let pcm = periodic_pcm(40, 8000);
let bits = tx.encode_pcm(&pcm).expect("encode");
assert_eq!(bits.len(), 11, "no-FEC IMBE wire frame is 11 bytes (88 info bits)");
let out = rx.decode_bits(&bits).expect("decode");
assert_eq!(out.len(), FRAME_SAMPLES);
}
}
#[test]
fn no_fec_ambeplus2_roundtrip_smoke() {
let mut tx = Vocoder::new(Rate::AmbePlus2_2450x2450);
let mut rx = Vocoder::new(Rate::AmbePlus2_2450x2450);
for _ in 0..5 {
let pcm = periodic_pcm(40, 6000);
let bits = tx.encode_pcm(&pcm).expect("encode");
assert_eq!(
bits.len(),
7,
"no-FEC AMBE+2 wire frame is 7 bytes (49 info bits + 7 pad)"
);
let out = rx.decode_bits(&bits).expect("decode");
assert_eq!(out.len(), FRAME_SAMPLES);
}
}
#[test]
fn no_fec_full_matches_fec_full_on_clean_channel() {
let mut tx_fec = Vocoder::new(Rate::Imbe7200x4400);
let mut rx_fec = Vocoder::new(Rate::Imbe7200x4400);
let mut tx_raw = Vocoder::new(Rate::Imbe4400x4400);
let mut rx_raw = Vocoder::new(Rate::Imbe4400x4400);
for k in 0..6 {
let pcm = periodic_pcm(40 + k, 8000);
let pcm_fec = rx_fec
.decode_bits(&tx_fec.encode_pcm(&pcm).unwrap())
.unwrap();
let pcm_raw = rx_raw
.decode_bits(&tx_raw.encode_pcm(&pcm).unwrap())
.unwrap();
assert_eq!(
pcm_fec, pcm_raw,
"no-FEC and FEC paths must match on a clean channel (frame {k})"
);
}
}
#[test]
fn no_fec_half_matches_fec_half_on_clean_channel() {
let mut tx_fec = Vocoder::new(Rate::AmbePlus2_3600x2450);
let mut rx_fec = Vocoder::new(Rate::AmbePlus2_3600x2450);
let mut tx_raw = Vocoder::new(Rate::AmbePlus2_2450x2450);
let mut rx_raw = Vocoder::new(Rate::AmbePlus2_2450x2450);
for k in 0..6 {
let pcm = periodic_pcm(40 + k, 6000);
let pcm_fec = rx_fec
.decode_bits(&tx_fec.encode_pcm(&pcm).unwrap())
.unwrap();
let pcm_raw = rx_raw
.decode_bits(&tx_raw.encode_pcm(&pcm).unwrap())
.unwrap();
assert_eq!(
pcm_fec, pcm_raw,
"no-FEC and FEC paths must match on a clean channel (frame {k})"
);
}
}
#[test]
fn imbe_roundtrip_smoke() {
let mut tx = Vocoder::new(Rate::Imbe7200x4400);
let mut rx = Vocoder::new(Rate::Imbe7200x4400);
for _ in 0..5 {
let pcm = periodic_pcm(40, 8000);
let bits = tx.encode_pcm(&pcm).expect("encode");
assert_eq!(bits.len(), 18);
let out = rx.decode_bits(&bits).expect("decode");
assert_eq!(out.len(), FRAME_SAMPLES);
}
let stats = tx.last_stats();
assert!(stats.analysis.is_some(), "encoder didn't fill stats");
}
#[test]
fn ambe_plus2_roundtrip_smoke() {
let mut tx = Vocoder::new(Rate::AmbePlus2_3600x2450);
let mut rx = Vocoder::new(Rate::AmbePlus2_3600x2450);
for _ in 0..5 {
let pcm = periodic_pcm(40, 8000);
let bits = tx.encode_pcm(&pcm).expect("encode");
assert_eq!(bits.len(), 9);
let out = rx.decode_bits(&bits).expect("decode");
assert_eq!(out.len(), FRAME_SAMPLES);
}
let stats = rx.last_stats();
assert!(stats.decode.is_some(), "decoder didn't fill stats");
}
#[test]
fn wrong_pcm_length_errors() {
let mut v = Vocoder::new(Rate::Imbe7200x4400);
let r = v.encode_pcm(&[0i16; 159]);
assert!(matches!(r, Err(VocoderError::WrongPcmLength { expected: 160, got: 159 })));
}
#[test]
fn wrong_bits_length_errors_per_rate() {
let mut a = Vocoder::new(Rate::Imbe7200x4400);
assert!(matches!(
a.decode_bits(&[0u8; 9]),
Err(VocoderError::WrongBitsLength { expected: 18, got: 9 })
));
let mut b = Vocoder::new(Rate::AmbePlus2_3600x2450);
assert!(matches!(
b.decode_bits(&[0u8; 18]),
Err(VocoderError::WrongBitsLength { expected: 9, got: 18 })
));
}
#[test]
fn reset_clears_state_and_keeps_rate() {
let mut v = Vocoder::new(Rate::AmbePlus2_3600x2450);
let pcm = periodic_pcm(40, 8000);
let _ = v.encode_pcm(&pcm).unwrap();
assert!(v.last_stats().analysis.is_some());
v.reset();
assert!(v.last_stats().analysis.is_none());
assert_eq!(v.rate(), Rate::AmbePlus2_3600x2450);
}
#[cfg(feature = "serde")]
#[test]
fn frame_stats_round_trip_through_json() {
let mut v = Vocoder::new(Rate::Imbe7200x4400);
let pcm = periodic_pcm(40, 8000);
for _ in 0..3 {
let bits = v.encode_pcm(&pcm).unwrap();
let _ = v.decode_bits(&bits).unwrap();
}
let stats = v.last_stats().clone();
let s = serde_json::to_string(&stats).expect("serialize FrameStats");
let back: FrameStats = serde_json::from_str(&s).expect("deserialize FrameStats");
assert_eq!(stats.analysis.is_some(), back.analysis.is_some());
assert_eq!(stats.decode.is_some(), back.decode.is_some());
if let (Some(a), Some(b)) = (&stats.analysis, &back.analysis) {
assert_eq!(a.output, b.output);
assert_eq!(a.params, b.params);
}
if let (Some(a), Some(b)) = (&stats.decode, &back.decode) {
assert_eq!(a.epsilon_0, b.epsilon_0);
assert_eq!(a.epsilon_t, b.epsilon_t);
assert_eq!(a.disposition, b.disposition);
}
}
#[cfg(feature = "serde")]
#[test]
fn rate_serializes_as_named_variant() {
let s = serde_json::to_string(&Rate::Imbe7200x4400).unwrap();
assert_eq!(s, "\"Imbe7200x4400\"");
let back: Rate = serde_json::from_str(&s).unwrap();
assert_eq!(back, Rate::Imbe7200x4400);
}
#[test]
fn encode_stream_yields_one_per_frame_drops_partial() {
let mut v = Vocoder::new(Rate::Imbe7200x4400);
let mut pcm: Vec<i16> = Vec::with_capacity(5 * FRAME_SAMPLES + 50);
for f in 0..5 {
pcm.extend_from_slice(&periodic_pcm(40, (1000 + f * 100) as i16));
}
pcm.extend(std::iter::repeat(0i16).take(50));
let stream = v.encode_stream(&pcm);
assert_eq!(stream.len(), 5);
let bits: Vec<Vec<u8>> = stream.collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(bits.len(), 5);
for b in &bits {
assert_eq!(b.len(), 18); }
}
#[test]
fn decode_stream_yields_one_per_frame_drops_partial() {
let mut tx = Vocoder::new(Rate::AmbePlus2_3600x2450);
let mut pcm: Vec<i16> = Vec::with_capacity(7 * FRAME_SAMPLES);
for _ in 0..7 {
pcm.extend_from_slice(&periodic_pcm(40, 5000));
}
let bits: Vec<u8> = tx
.encode_stream(&pcm)
.collect::<Result<Vec<_>, _>>()
.unwrap()
.into_iter()
.flatten()
.collect();
let mut padded = bits.clone();
padded.extend_from_slice(&[0; 4]);
let mut rx = Vocoder::new(Rate::AmbePlus2_3600x2450);
let frames: Vec<Vec<i16>> = rx
.decode_stream(&padded)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(frames.len(), 7);
for f in &frames {
assert_eq!(f.len(), FRAME_SAMPLES);
}
}
#[test]
fn decode_stats_carry_disposition() {
let mut tx = Vocoder::new(Rate::Imbe7200x4400);
let mut rx = Vocoder::new(Rate::Imbe7200x4400);
for _ in 0..5 {
let pcm = periodic_pcm(40, 8000);
let bits = tx.encode_pcm(&pcm).unwrap();
let _ = rx.decode_bits(&bits).unwrap();
}
let disp = rx
.last_stats()
.decode
.as_ref()
.expect("decode stats populated after decode call")
.disposition
.expect("disposition surfaced");
assert_eq!(disp, FrameDisposition::Use);
assert_eq!(rx.last_disposition(), Some(FrameDisposition::Use));
}
#[test]
fn tone_detection_emits_tone_frame_and_decoder_recognises_it() {
let mut tx = Vocoder::new(Rate::AmbePlus2_3600x2450);
tx.set_tone_detection(true);
let mut pcm = [0i16; FRAME_SAMPLES];
let two_pi = 2.0 * core::f64::consts::PI;
for (n, slot) in pcm.iter_mut().enumerate() {
let s = 8000.0_f64 * (two_pi * 312.5 * n as f64 / 8000.0).sin();
*slot = s.round() as i16;
}
let bits = tx.encode_pcm(&pcm).unwrap();
assert_eq!(bits.len(), 9);
match tx.last_stats().analysis.as_ref().unwrap().output {
AnalysisOutputKind::Tone { id, amplitude: _ } => assert_eq!(id, 10),
other => panic!("expected Tone, got {other:?}"),
}
use crate::ambe_plus2_wire::dequantize::{FrameKind, classify_ambe_plus2_frame};
use crate::ambe_plus2_wire::frame::decode_frame;
let mut dibits = [0u8; 36];
let mut bit = 0;
for slot in &mut dibits {
let mut d = 0u8;
for _ in 0..2 {
let b = (bits[bit / 8] >> (7 - (bit % 8))) & 1;
d = (d << 1) | b;
bit += 1;
}
*slot = d;
}
let frame = decode_frame(&dibits);
assert_eq!(classify_ambe_plus2_frame(&frame.info), FrameKind::Tone);
let mut rx = Vocoder::new(Rate::AmbePlus2_3600x2450);
let out = rx.decode_bits(&bits).unwrap();
assert_eq!(out.len(), FRAME_SAMPLES);
}
#[test]
fn tone_detection_off_means_voice_path_even_for_pure_sine() {
let mut tx = Vocoder::new(Rate::AmbePlus2_3600x2450);
let mut pcm = [0i16; FRAME_SAMPLES];
let two_pi = 2.0 * core::f64::consts::PI;
for (n, slot) in pcm.iter_mut().enumerate() {
let s = 8000.0_f64 * (two_pi * 312.5 * n as f64 / 8000.0).sin();
*slot = s.round() as i16;
}
let _ = tx.encode_pcm(&pcm).unwrap();
let stats = tx.last_stats().analysis.as_ref().unwrap();
assert!(matches!(
stats.output,
AnalysisOutputKind::Voice | AnalysisOutputKind::Silence
));
assert!(!matches!(stats.output, AnalysisOutputKind::Tone { .. }));
assert!(stats.tone_detect.is_none());
}
#[test]
fn tone_detection_on_phase1_surfaces_metadata_without_changing_wire() {
let mut tx = Vocoder::new(Rate::Imbe7200x4400);
tx.set_tone_detection(true);
let mut pcm = [0i16; FRAME_SAMPLES];
let two_pi = 2.0 * core::f64::consts::PI;
for (n, slot) in pcm.iter_mut().enumerate() {
let s = 8000.0_f64 * (two_pi * 312.5 * n as f64 / 8000.0).sin();
*slot = s.round() as i16;
}
let bits_with_detect = tx.encode_pcm(&pcm).unwrap();
let stats = tx.last_stats().analysis.as_ref().unwrap();
assert!(matches!(
stats.output,
AnalysisOutputKind::Voice | AnalysisOutputKind::Silence
));
let det = stats.tone_detect.expect("Phase 1 detection metadata");
assert_eq!(det.id, 10);
let mut tx_off = Vocoder::new(Rate::Imbe7200x4400);
let bits_no_detect = tx_off.encode_pcm(&pcm).unwrap();
assert_eq!(bits_with_detect, bits_no_detect);
}
#[test]
fn live_encoder_handles_arbitrary_chunk_sizes() {
let mut total_pcm: Vec<i16> = Vec::with_capacity(7 * FRAME_SAMPLES);
for _ in 0..7 {
total_pcm.extend_from_slice(&periodic_pcm(40, 6000));
}
let mut ref_v = Vocoder::new(Rate::Imbe7200x4400);
let mut ref_bits: Vec<u8> = Vec::new();
for chunk in total_pcm.chunks_exact(FRAME_SAMPLES) {
ref_bits.extend(ref_v.encode_pcm(chunk).unwrap());
}
let mut live = LiveEncoder::new(Rate::Imbe7200x4400);
let mut live_bits: Vec<u8> = Vec::new();
let splits = [250usize, 50, 333];
let mut pos = 0;
for &n in &splits {
let end = (pos + n).min(total_pcm.len());
for r in live.push(&total_pcm[pos..end]) {
live_bits.extend(r.unwrap());
}
pos = end;
}
for r in live.push(&total_pcm[pos..]) {
live_bits.extend(r.unwrap());
}
assert_eq!(live_bits, ref_bits);
assert_eq!(live.pending_samples(), 0);
}
#[test]
fn live_encoder_residue_held_across_calls() {
let mut live = LiveEncoder::new(Rate::Imbe7200x4400);
let pcm: Vec<i16> = periodic_pcm(40, 6000)
.iter()
.copied()
.chain(periodic_pcm(40, 6000)[..80].iter().copied())
.collect();
assert_eq!(pcm.len(), 240);
let frames = live.push(&pcm[..120]);
assert!(frames.is_empty());
assert_eq!(live.pending_samples(), 120);
let frames = live.push(&pcm[120..]);
assert_eq!(frames.len(), 1);
assert!(frames[0].is_ok());
assert_eq!(live.pending_samples(), 80);
}
#[test]
fn live_decoder_handles_arbitrary_chunk_sizes() {
let mut tx = Vocoder::new(Rate::AmbePlus2_3600x2450);
let mut all_pcm: Vec<i16> = Vec::with_capacity(5 * FRAME_SAMPLES);
for _ in 0..5 {
all_pcm.extend_from_slice(&periodic_pcm(40, 5000));
}
let bits: Vec<u8> = tx
.encode_stream(&all_pcm)
.collect::<Result<Vec<_>, _>>()
.unwrap()
.into_iter()
.flatten()
.collect();
let mut ref_v = Vocoder::new(Rate::AmbePlus2_3600x2450);
let mut ref_pcm: Vec<i16> = Vec::new();
for chunk in bits.chunks_exact(9) {
ref_pcm.extend(ref_v.decode_bits(chunk).unwrap());
}
let mut live = LiveDecoder::new(Rate::AmbePlus2_3600x2450);
let mut live_pcm: Vec<i16> = Vec::new();
for chunk in bits.chunks(7) {
for r in live.push(chunk) {
live_pcm.extend(r.unwrap());
}
}
assert_eq!(live_pcm, ref_pcm);
assert_eq!(live.pending_bytes(), 0); }
#[test]
fn live_encoder_discard_pending_clears_residue() {
let mut live = LiveEncoder::new(Rate::Imbe7200x4400);
let pcm = periodic_pcm(40, 6000);
let frames = live.push(&pcm[..80]);
assert!(frames.is_empty());
assert_eq!(live.pending_samples(), 80);
live.discard_pending();
assert_eq!(live.pending_samples(), 0);
}
#[test]
fn transcoder_phase1_to_phase2_changes_frame_size() {
let mut tx = Transcoder::new(Rate::Imbe7200x4400, Rate::AmbePlus2_3600x2450).unwrap();
assert_eq!(
tx.direction(),
TranscodeDirection::new(Rate::Imbe7200x4400, Rate::AmbePlus2_3600x2450)
);
let mut enc = Vocoder::new(Rate::Imbe7200x4400);
let pcm = periodic_pcm(40, 6000);
for _ in 0..3 {
let phase1 = enc.encode_pcm(&pcm).unwrap();
assert_eq!(phase1.len(), 18);
let phase2 = tx.transcode(&phase1).unwrap();
assert_eq!(phase2.len(), 9, "P1→P2 transcode produces 9-byte frames");
}
}
#[test]
fn transcoder_phase2_to_phase1_changes_frame_size() {
let mut tx = Transcoder::new(Rate::AmbePlus2_3600x2450, Rate::Imbe7200x4400).unwrap();
let mut enc = Vocoder::new(Rate::AmbePlus2_3600x2450);
let pcm = periodic_pcm(40, 6000);
for _ in 0..3 {
let phase2 = enc.encode_pcm(&pcm).unwrap();
assert_eq!(phase2.len(), 9);
let phase1 = tx.transcode(&phase2).unwrap();
assert_eq!(phase1.len(), 18);
}
}
#[test]
fn transcoder_rejects_wrong_input_length() {
let mut tx = Transcoder::new(Rate::Imbe7200x4400, Rate::AmbePlus2_3600x2450).unwrap();
assert!(matches!(
tx.transcode(&[0u8; 9]),
Err(VocoderError::WrongBitsLength { expected: 18, got: 9 })
));
}
#[test]
fn transcoder_rejects_unsupported_pair() {
let res = Transcoder::new(Rate::Imbe7200x4400, Rate::Imbe7200x4400);
assert!(matches!(
res.err(),
Some(VocoderError::UnsupportedTranscode {
from: Rate::Imbe7200x4400,
to: Rate::Imbe7200x4400,
})
));
}
#[test]
fn transcoder_full_fec_to_info_roundtrip_is_lossless() {
let mut enc = Vocoder::new(Rate::Imbe7200x4400);
let mut strip =
Transcoder::new(Rate::Imbe7200x4400, Rate::Imbe4400x4400).unwrap();
let mut add =
Transcoder::new(Rate::Imbe4400x4400, Rate::Imbe7200x4400).unwrap();
for k in 0..4 {
let pcm = periodic_pcm(40 + k, 7000);
let fec = enc.encode_pcm(&pcm).unwrap();
assert_eq!(fec.len(), 18);
let info = strip.transcode(&fec).unwrap();
assert_eq!(info.len(), 11);
let fec2 = add.transcode(&info).unwrap();
assert_eq!(fec2, fec, "FEC strip + add round-trips byte-for-byte");
}
}
#[test]
fn transcoder_half_fec_to_info_roundtrip_is_lossless() {
let mut enc = Vocoder::new(Rate::AmbePlus2_3600x2450);
let mut strip =
Transcoder::new(Rate::AmbePlus2_3600x2450, Rate::AmbePlus2_2450x2450).unwrap();
let mut add =
Transcoder::new(Rate::AmbePlus2_2450x2450, Rate::AmbePlus2_3600x2450).unwrap();
for k in 0..4 {
let pcm = periodic_pcm(40 + k, 6000);
let fec = enc.encode_pcm(&pcm).unwrap();
assert_eq!(fec.len(), 9);
let info = strip.transcode(&fec).unwrap();
assert_eq!(info.len(), 7);
let fec2 = add.transcode(&info).unwrap();
assert_eq!(fec2, fec, "FEC strip + add round-trips byte-for-byte");
}
}
#[test]
fn extract_params_returns_params_per_frame() {
let mut v = Vocoder::new(Rate::Imbe7200x4400);
let pcm = periodic_pcm(40, 6000);
let p1 = v.extract_params(&pcm).unwrap();
let p2 = v.extract_params(&pcm).unwrap();
let p3 = v.extract_params(&pcm).unwrap();
let any_voice = [&p1, &p2, &p3].iter().any(|p| {
let amps = p.amplitudes_slice();
amps.iter().any(|&a| a > 0.0)
});
assert!(any_voice, "no voice params after 3 frames of periodic PCM");
}
#[test]
fn extract_then_synthesize_roundtrips_through_params() {
let mut a = Vocoder::new(Rate::Imbe7200x4400);
let mut b = Vocoder::new(Rate::Imbe7200x4400);
let pcm = periodic_pcm(40, 6000);
for _ in 0..5 {
let params = a.extract_params(&pcm).unwrap();
let resynth = b.synthesize_params(¶ms);
assert_eq!(resynth.len(), FRAME_SAMPLES);
}
}
#[test]
fn synthesize_params_emits_one_frame_per_call() {
let mut tx = Vocoder::new(Rate::Imbe7200x4400);
let pcm = tx.synthesize_params(&MbeParams::silence());
assert_eq!(pcm.len(), FRAME_SAMPLES);
let peak = pcm.iter().map(|&s| s.unsigned_abs()).max().unwrap_or(0);
assert!(peak < 5000, "silence params produced peak={peak}");
let mut rx = Vocoder::new(Rate::AmbePlus2_3600x2450);
let pcm = rx.synthesize_params(&MbeParams::silence_ambe_plus2());
assert_eq!(pcm.len(), FRAME_SAMPLES);
let peak = pcm.iter().map(|&s| s.unsigned_abs()).max().unwrap_or(0);
assert!(peak < 5000);
}
#[test]
fn synthesize_params_advances_synth_disposition() {
let mut v = Vocoder::new(Rate::Imbe7200x4400);
assert_eq!(v.last_disposition(), None);
let _ = v.synthesize_params(&MbeParams::silence());
assert_eq!(v.last_disposition(), Some(FrameDisposition::Use));
}
#[test]
fn builder_configures_all_knobs() {
let v = Vocoder::builder(Rate::AmbePlus2_3600x2450)
.tone_detection(true)
.repeat_reset_after(Some(3))
.silence_dispatch(true)
.pitch_silence_override(true)
.ambe_plus2_synth(AmbePlus2Synth::Baseline)
.build();
assert_eq!(v.rate(), Rate::AmbePlus2_3600x2450);
assert!(v.tone_detection());
assert_eq!(v.repeat_reset_after(), Some(3));
assert!(v.silence_dispatch());
assert!(v.pitch_silence_override());
assert_eq!(v.ambe_plus2_synth(), AmbePlus2Synth::Baseline);
}
#[test]
fn ambe_plus2_synth_modes_both_decode_cleanly() {
let mut tx = Vocoder::new(Rate::AmbePlus2_3600x2450);
let pcm = periodic_pcm(40, 6000);
let mut bits_buf: Vec<u8> = Vec::new();
for _ in 0..5 {
bits_buf.extend(tx.encode_pcm(&pcm).unwrap());
}
for gen in [AmbePlus2Synth::AmbePlus, AmbePlus2Synth::Baseline] {
let mut rx = Vocoder::builder(Rate::AmbePlus2_3600x2450)
.ambe_plus2_synth(gen)
.build();
assert_eq!(rx.ambe_plus2_synth(), gen);
let mut out_pcm: Vec<i16> = Vec::new();
for chunk in bits_buf.chunks_exact(9) {
out_pcm.extend(rx.decode_bits(chunk).unwrap());
}
let rms = (out_pcm.iter().map(|&s| (s as f64) * (s as f64)).sum::<f64>()
/ out_pcm.len() as f64).sqrt();
assert!(rms > 50.0, "{gen:?} output too quiet: rms={rms}");
}
}
#[test]
fn builder_defaults_match_vocoder_new() {
let a = Vocoder::builder(Rate::Imbe7200x4400).build();
let b = Vocoder::new(Rate::Imbe7200x4400);
assert_eq!(a.rate(), b.rate());
assert_eq!(a.tone_detection(), b.tone_detection());
assert_eq!(a.repeat_reset_after(), b.repeat_reset_after());
assert_eq!(a.silence_dispatch(), b.silence_dispatch());
assert_eq!(a.pitch_silence_override(), b.pitch_silence_override());
assert_eq!(a.ambe_plus2_synth(), b.ambe_plus2_synth());
assert!(!a.tone_detection());
assert!(a.repeat_reset_after().is_none());
assert!(!a.silence_dispatch());
assert!(!a.pitch_silence_override());
assert_eq!(a.ambe_plus2_synth(), AmbePlus2Synth::AmbePlus);
}
#[test]
fn live_encoder_flush_emits_padded_residue() {
let mut live = LiveEncoder::new(Rate::Imbe7200x4400);
assert!(matches!(live.flush(), Ok(None)));
let pcm = periodic_pcm(40, 6000);
let _ = live.push(&pcm[..80]);
assert_eq!(live.pending_samples(), 80);
let tail = live.flush().unwrap().expect("residue → frame");
assert_eq!(tail.len(), 18);
assert_eq!(live.pending_samples(), 0);
assert!(matches!(live.flush(), Ok(None)));
}
#[test]
fn live_encoder_reset_clears_everything() {
let mut live = LiveEncoder::new(Rate::AmbePlus2_3600x2450);
let pcm = periodic_pcm(40, 5000);
let _ = live.push(&pcm[..120]);
assert_eq!(live.pending_samples(), 120);
let _ = live.vocoder().last_stats();
live.reset();
assert_eq!(live.pending_samples(), 0);
assert!(live.vocoder().last_stats().analysis.is_none());
assert_eq!(live.rate(), Rate::AmbePlus2_3600x2450);
}
#[test]
fn encode_stream_matches_per_frame_calls_byte_for_byte() {
let mut a = Vocoder::new(Rate::Imbe7200x4400);
let mut b = Vocoder::new(Rate::Imbe7200x4400);
let mut pcm: Vec<i16> = Vec::with_capacity(4 * FRAME_SAMPLES);
for _ in 0..4 {
pcm.extend_from_slice(&periodic_pcm(40, 6000));
}
let by_stream: Vec<u8> = a
.encode_stream(&pcm)
.collect::<Result<Vec<_>, _>>()
.unwrap()
.into_iter()
.flatten()
.collect();
let mut by_call: Vec<u8> = Vec::new();
for chunk in pcm.chunks_exact(FRAME_SAMPLES) {
by_call.extend(b.encode_pcm(chunk).unwrap());
}
assert_eq!(by_stream, by_call);
}
}