use crate::analysis;
use crate::bitstream::BitstreamWriter;
use crate::decoder::{compute_bit_alloc_for_frame, increment_allocation_bins};
use crate::fixedpoint::*;
use crate::tables::*;
const MAX_SUBBANDS: usize = 14;
const SAMPLES_PER_SUBBAND: usize = 20;
pub const FRAME_SIZE: usize = 320;
pub struct EncoderState {
pub bitrate: u16,
pub bits_per_frame: i16,
pub encoded_frame_size: i16,
pub num_subbands: i16,
pub analysis_memory: [i16; 320],
}
impl EncoderState {
pub fn new(bitrate: u16) -> Result<Self, u32> {
if bitrate < 4800 || bitrate > 32000 {
return Err(8);
}
let snapped = ((bitrate as u32 + 400) / 800 * 800) as u16;
if snapped != bitrate {
return Err(8);
}
let bits_per_frame = (bitrate as i32 / 50) as i16;
let encoded_frame_size = (bitrate as i32 / 800) as i16;
let num_subbands = if bitrate >= 16000 {
14
} else if bitrate >= 12000 {
12
} else if bitrate >= 9600 {
10
} else {
8
};
Ok(EncoderState {
bitrate,
bits_per_frame,
encoded_frame_size,
num_subbands,
analysis_memory: [0; 320],
})
}
pub fn encode_frame_to_bitstream(
&mut self,
pcm_input: &[i16],
output: &mut [i16],
) {
let mut subbands = [0i16; FRAME_SIZE];
let scale_param = analysis::analysis_filter(
pcm_input,
&mut self.analysis_memory,
&mut subbands,
FRAME_SIZE as i16,
);
self.encode_frame(&mut subbands, output, scale_param);
}
fn encode_frame(
&self,
subbands: &mut [i16],
output: &mut [i16],
scale_param: i16,
) {
let ns = self.num_subbands as usize;
let mut gain_indices = [0i16; MAX_SUBBANDS];
let mut gain_codes = [0i16; MAX_SUBBANDS];
let mut gain_widths = [0i16; MAX_SUBBANDS];
let mut bits_used: i16 = 0;
encode_gains(
subbands,
scale_param,
ns,
&mut gain_indices,
&mut gain_codes,
&mut gain_widths,
&mut bits_used,
);
let decoder_sp = compute_scale_param_from_gains(&gain_indices, ns);
let remaining_bits = sub(sub(self.bits_per_frame, bits_used), 4);
let mut alloc = [0i16; MAX_SUBBANDS];
let mut scratch = [0i16; 32];
compute_bit_alloc_for_frame(
remaining_bits,
self.num_subbands,
&gain_indices[..ns],
&mut alloc[..ns],
&mut scratch,
);
let frame_param = select_frame_param(&alloc, &scratch, ns, remaining_bits);
increment_allocation_bins(frame_param, &mut alloc[..ns], &scratch);
let offset = add(shl(decoder_sp, 1), 0x18);
for i in 0..ns {
gain_indices[i] = add(gain_indices[i], offset);
}
prescale_subbands(&mut gain_indices, subbands, ns);
let mut encoded_data = [0i16; 560];
let mut subband_bits = [0i16; MAX_SUBBANDS];
encode_subframes(
subbands,
&alloc,
&gain_indices,
ns,
&mut encoded_data,
&mut subband_bits,
);
write_bitstream(
output,
&gain_codes,
&gain_widths,
ns,
frame_param,
&encoded_data,
&subband_bits,
&alloc,
);
}
}
fn compute_scale_param_from_gains(gain_indices: &[i16; MAX_SUBBANDS], num_subbands: usize) -> i16 {
let mut total_cost: i16 = 0;
let mut max_eff_gain: i16 = 0;
for i in 0..num_subbands {
let eff = extract_l(l_add(gain_indices[i] as i32, 0x18));
let diff = sub(eff, max_eff_gain);
if diff > 0 {
max_eff_gain = eff;
}
total_cost = add(total_cost, SCALE_FACTOR_BITS[eff as usize]);
}
let mut sp: i16 = 9;
let mut cost_check = sub(total_cost, 8);
let mut gain_check = sub(max_eff_gain, 0x1c);
loop {
if cost_check < 0 && gain_check < 1 {
break;
}
sp = sub(sp, 1);
total_cost = shr(total_cost, 1);
max_eff_gain = sub(max_eff_gain, 2);
cost_check = sub(total_cost, 8);
gain_check = sub(max_eff_gain, 0x1c);
if sp < 0 {
break;
}
}
sp
}
fn encode_gains(
subbands: &[i16],
scale_param: i16,
num_subbands: usize,
gain_indices: &mut [i16; MAX_SUBBANDS],
gain_codes: &mut [i16; MAX_SUBBANDS],
gain_widths: &mut [i16; MAX_SUBBANDS],
bits_used: &mut i16,
) {
for sb in 0..num_subbands {
let base = sb * SAMPLES_PER_SUBBAND;
let mut energy: i32 = l_deposit_l(0);
for j in 0..SAMPLES_PER_SUBBAND {
energy = l_mac0(energy, subbands[base + j], subbands[base + j]);
}
let mut shift_count: i16 = 0;
while (energy as u32 & 0x7fff0000) != 0 {
energy = l_shr(energy, 1);
shift_count = add(shift_count, 1);
}
let mut check = l_sub(energy, 0x7fff);
let mut check2 = add(shift_count, 0xf);
while check < 1 && check2 >= 0 {
energy = l_shl(energy, 1);
check = l_sub(energy, 0x7fff);
shift_count -= 1;
check2 = add(shift_count, 0xf);
}
energy = l_shr(energy, 1);
let rounded = l_sub(energy, 0x7123);
if rounded >= 0 {
shift_count = add(shift_count, 1);
}
let sp_times2 = extract_l(l_shl(l_deposit_l(scale_param), 1));
let adjusted = l_sub(shift_count as i32, sp_times2 as i32);
let with_offset = l_add(0x23, adjusted);
let final_val = l_sub(with_offset, 0x18);
gain_indices[sb] = extract_l(final_val);
}
if num_subbands >= 2 {
for i in (0..num_subbands - 1).rev() {
let floor = sub(gain_indices[i + 1], 0xb);
if sub(gain_indices[i], floor) < 0 {
gain_indices[i] = floor;
}
}
}
let lo = sub(1, 7); let hi = sub(0x1f, 7); if sub(gain_indices[0], lo) < 0 {
gain_indices[0] = lo;
}
if sub(gain_indices[0], hi) > 0 {
gain_indices[0] = hi;
}
gain_widths[0] = 5;
gain_codes[0] = add(gain_indices[0], 7);
*bits_used = 5;
if num_subbands > 1 {
for sb in 1..num_subbands {
if sub(gain_indices[sb], sub(-8, 7)) < 0 {
gain_indices[sb] = sub(-8, 7);
}
if sub(gain_indices[sb], sub(0x1f, 7)) > 0 {
gain_indices[sb] = sub(0x1f, 7);
}
}
if num_subbands > 1 {
let mut section_base: usize = 24; for sb in 0..num_subbands - 1 {
let diff = sub(gain_indices[sb + 1], gain_indices[sb]);
let biased_diff = sub(diff, -12); let clamped = if biased_diff < 0 { 0 } else { biased_diff };
gain_indices[sb + 1] = add(add(gain_indices[sb], clamped), -12);
let table_idx = clamped as usize + section_base;
let width = GAIN_HUFFMAN_BIT_WIDTHS[table_idx];
let code = GAIN_HUFFMAN_CODES[table_idx];
gain_codes[sb + 1] = code;
gain_widths[sb + 1] = width;
*bits_used = add(*bits_used, width);
section_base += 24;
}
}
}
}
fn prescale_subbands(
gain_indices: &mut [i16; MAX_SUBBANDS],
subbands: &mut [i16],
num_subbands: usize,
) {
for sb in 0..num_subbands {
let base = sb * SAMPLES_PER_SUBBAND;
let shift = shr(sub(gain_indices[sb], 0x27), 1);
if shift > 0 {
for j in 0..SAMPLES_PER_SUBBAND {
let extended = l_shl(subbands[base + j] as i32, 0x10);
let rounded = l_add(extended, 0x8000);
let shifted = l_shr(rounded, shift);
let result = l_shr(shifted, 0x10);
subbands[base + j] = extract_l(result);
}
gain_indices[sb] = sub(gain_indices[sb], shl(shift, 1));
}
}
}
fn select_frame_param(alloc: &[i16; MAX_SUBBANDS], scratch: &[i16], num_subbands: usize, budget: i16) -> i16 {
let mut cost: i16 = 0;
let mut working = [0i16; MAX_SUBBANDS];
for i in 0..num_subbands {
working[i] = alloc[i];
cost = add(cost, BIT_ALLOC_COST[alloc[i] as usize]);
}
if sub(cost, budget) <= 0 {
return 0;
}
for k in 0..15i16 {
let sb = scratch[k as usize] as usize;
if sb >= num_subbands {
break;
}
let old_step = working[sb];
if sub(old_step, 7) < 0 {
cost = sub(cost, BIT_ALLOC_COST[old_step as usize]);
working[sb] = add(old_step, 1);
cost = add(cost, BIT_ALLOC_COST[working[sb] as usize]);
}
if sub(cost, budget) <= 0 {
return add(k, 1);
}
}
15
}
fn encode_subframes(
subbands: &[i16],
alloc: &[i16; MAX_SUBBANDS],
gain_indices: &[i16; MAX_SUBBANDS],
num_subbands: usize,
encoded_data: &mut [i16],
subband_bits: &mut [i16; MAX_SUBBANDS],
) {
let mut enc_pos = 0usize;
for sb in 0..num_subbands {
let step = alloc[sb];
let base = sb * SAMPLES_PER_SUBBAND;
subband_bits[sb] = 0;
if sub(step, 7) >= 0 {
continue; }
let gain = gain_indices[sb];
let num_subframes = QUANT_NUM_COEFF[step as usize];
let num_levels = QUANT_LEVELS_M1[step as usize];
if num_subframes < 1 {
continue;
}
let mut in_pos = base;
for _sf in 0..num_subframes {
let (symbol, sign_bits, num_signs) =
forward_quantize(&subbands[in_pos..], num_levels as usize, step, gain);
in_pos += num_levels as usize;
let code_table = fwd_codebook_codes(step);
let width_table = fwd_codebook_widths(step);
let code = code_table[symbol as usize];
let width = width_table[symbol as usize];
encoded_data[enc_pos] = width;
encoded_data[enc_pos + 1] = code;
encoded_data[enc_pos + 2] = num_signs;
encoded_data[enc_pos + 3] = sign_bits;
enc_pos += 4;
let total_width = add(width, num_signs);
subband_bits[sb] = add(subband_bits[sb], total_width);
}
}
}
fn forward_quantize(
samples: &[i16],
num_levels: usize,
step: i16,
gain: i16,
) -> (i16, i16, i16) {
let si = step as usize;
let max_level = QUANT_INV_STEP[si];
let divisor = add(max_level, 1);
let rounding = QUANT_ROUNDING[si] as i32;
let gain_idx = gain as usize;
let scale_by_gain = if gain_idx < QUANT_SCALE_BY_GAIN.len() {
QUANT_SCALE_BY_GAIN[gain_idx]
} else {
0
};
let prod = l_mult(QUANT_SCALE_FACTOR[si], scale_by_gain);
let prod = l_shr(prod, 1);
let prod = l_add(prod, 0x1000);
let prod = l_shr(prod, 0xd);
let prod = l_shr(prod, 2);
let quant_scale = extract_l(prod);
let mut symbol: i16 = 0;
let mut sign_bits: i16 = 0;
let mut num_signs: i16 = 0;
for j in 0..num_levels {
let sample = samples[j];
let magnitude = abs_s(sample);
let scaled = l_mult(magnitude, quant_scale);
let scaled = l_shr(scaled, 1);
let rounded = l_add(scaled, rounding);
let shifted = l_shr(rounded, 0xd);
let mut level = extract_l(shifted);
if sub(level, max_level) > 0 {
level = max_level;
}
if level != 0 {
num_signs = add(num_signs, 1);
sign_bits = shl(sign_bits, 1);
if sample > 0 {
sign_bits = add(sign_bits, 1); }
}
let acc = l_mult(symbol, divisor);
let acc = l_shr(acc, 1);
symbol = extract_l(acc);
symbol = add(symbol, level);
}
(symbol, sign_bits, num_signs)
}
fn write_bitstream(
output: &mut [i16],
gain_codes: &[i16; MAX_SUBBANDS],
gain_widths: &[i16; MAX_SUBBANDS],
num_subbands: usize,
frame_param: i16,
encoded_data: &[i16],
_subband_bits: &[i16; MAX_SUBBANDS],
alloc: &[i16; MAX_SUBBANDS],
) {
let mut bw = BitstreamWriter::new(output);
for sb in 0..num_subbands {
bw.write_bits(gain_codes[sb], gain_widths[sb]);
}
bw.write_bits(frame_param, 4);
let mut enc_pos = 0usize;
for sb in 0..num_subbands {
let step = alloc[sb];
if sub(step, 7) >= 0 {
continue;
}
let num_subframes = QUANT_NUM_COEFF[step as usize];
if num_subframes < 1 {
continue;
}
for _sf in 0..num_subframes {
let width = encoded_data[enc_pos];
let code = encoded_data[enc_pos + 1];
let num_signs = encoded_data[enc_pos + 2];
let sign_bits = encoded_data[enc_pos + 3];
enc_pos += 4;
bw.write_bits(code, width);
if num_signs > 0 {
bw.write_bits(sign_bits, num_signs);
}
}
}
bw.flush();
}
fn fwd_codebook_codes(step: i16) -> &'static [i16] {
match step {
0 => &FWD_CODEBOOK_CODES_0,
1 => &FWD_CODEBOOK_CODES_1,
2 => &FWD_CODEBOOK_CODES_2,
3 => &FWD_CODEBOOK_CODES_3,
4 => &FWD_CODEBOOK_CODES_4,
5 => &FWD_CODEBOOK_CODES_5,
6 => &FWD_CODEBOOK_CODES_6,
_ => &FWD_CODEBOOK_CODES_0,
}
}
fn fwd_codebook_widths(step: i16) -> &'static [i16] {
match step {
0 => &FWD_CODEBOOK_WIDTHS_0,
1 => &FWD_CODEBOOK_WIDTHS_1,
2 => &FWD_CODEBOOK_WIDTHS_2,
3 => &FWD_CODEBOOK_WIDTHS_3,
4 => &FWD_CODEBOOK_WIDTHS_4,
5 => &FWD_CODEBOOK_WIDTHS_5,
6 => &FWD_CODEBOOK_WIDTHS_6,
_ => &FWD_CODEBOOK_WIDTHS_0,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encoder_init() {
let enc = EncoderState::new(16000).unwrap();
assert_eq!(enc.bits_per_frame, 320);
assert_eq!(enc.encoded_frame_size, 20);
assert_eq!(enc.num_subbands, 14);
}
#[test]
fn test_encoder_init_invalid() {
assert!(EncoderState::new(3000).is_err());
assert!(EncoderState::new(16100).is_err());
assert!(EncoderState::new(33000).is_err());
}
#[test]
fn test_encoder_init_subbands() {
assert_eq!(EncoderState::new(8000).unwrap().num_subbands, 8);
assert_eq!(EncoderState::new(9600).unwrap().num_subbands, 10);
assert_eq!(EncoderState::new(12000).unwrap().num_subbands, 12);
assert_eq!(EncoderState::new(16000).unwrap().num_subbands, 14);
}
#[test]
fn test_forward_quantize_zeros() {
let samples = [0i16; 6];
let (symbol, _sign_bits, num_signs) = forward_quantize(&samples, 2, 0, 0);
assert_eq!(symbol, 0);
assert_eq!(num_signs, 0);
}
#[test]
fn test_encode_does_not_panic() {
use crate::A1800Encoder;
let mut encoder = A1800Encoder::new(16000).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let pcm = [0i16; 320];
encoder.encode_frame(&pcm, &mut encoded).unwrap();
let pcm = [1000i16; 320];
encoder.encode_frame(&pcm, &mut encoded).unwrap();
let mut pcm = [0i16; 320];
for i in 0..320 {
let t = i as f64 / 16000.0;
pcm[i] = (10000.0 * (2.0 * std::f64::consts::PI * 1000.0 * t).sin()) as i16;
}
encoder.encode_frame(&pcm, &mut encoded).unwrap();
}
#[test]
fn test_encode_produces_nonzero_output() {
use crate::A1800Encoder;
let mut encoder = A1800Encoder::new(16000).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let pcm = [5000i16; 320];
encoder.encode_frame(&pcm, &mut encoded).unwrap();
let any_nonzero = encoded.iter().any(|&w| w != 0);
assert!(any_nonzero, "encoded output should not be all-zero for non-silent input");
}
#[test]
fn test_encode_multi_frame_no_panic() {
use crate::A1800Encoder;
let mut encoder = A1800Encoder::new(16000).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
for frame in 0..10 {
let mut pcm = [0i16; 320];
for i in 0..320 {
let t = (frame * 320 + i) as f64 / 16000.0;
pcm[i] = (5000.0 * (2.0 * std::f64::consts::PI * 440.0 * t).sin()) as i16;
}
for w in encoded.iter_mut() { *w = 0; }
encoder.encode_frame(&pcm, &mut encoded).unwrap();
}
}
#[test]
fn test_multi_frame_signal_quality() {
use crate::{A1800Encoder, A1800Decoder};
let mut encoder = A1800Encoder::new(16000).unwrap();
let mut decoder = A1800Decoder::new(16000).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let mut decoded = vec![0i16; 320];
let mut frame_energies = Vec::new();
for frame in 0..20 {
let mut pcm = [0i16; 320];
for i in 0..320 {
let t = (frame * 320 + i) as f64 / 16000.0;
pcm[i] = (8000.0 * (2.0 * std::f64::consts::PI * 440.0 * t).sin()) as i16;
}
encoder.encode_frame(&pcm, &mut encoded).unwrap();
decoder.decode_frame(&encoded, &mut decoded).unwrap();
let energy: f64 = decoded.iter().map(|&s| (s as f64).powi(2)).sum();
frame_energies.push(energy);
}
for (i, &e) in frame_energies.iter().enumerate().skip(1) {
assert!(e > 1000.0, "frame {} energy {:.0} too low", i, e);
}
let steady: Vec<f64> = frame_energies[5..].to_vec();
let avg: f64 = steady.iter().sum::<f64>() / steady.len() as f64;
for (i, &e) in steady.iter().enumerate() {
let ratio = e / avg;
assert!(ratio > 0.3 && ratio < 3.0,
"frame {} energy {:.0} deviates too much from average {:.0}", i + 5, e, avg);
}
}
#[test]
fn test_multi_frame_boundary_continuity() {
use crate::{A1800Encoder, A1800Decoder};
let mut encoder = A1800Encoder::new(16000).unwrap();
let mut decoder = A1800Decoder::new(16000).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let mut all_decoded: Vec<i16> = Vec::new();
for frame in 0..10 {
let mut pcm = [0i16; 320];
for i in 0..320 {
let t = (frame * 320 + i) as f64 / 16000.0;
pcm[i] = (6000.0 * (2.0 * std::f64::consts::PI * 200.0 * t).sin()) as i16;
}
let mut decoded = [0i16; 320];
encoder.encode_frame(&pcm, &mut encoded).unwrap();
decoder.decode_frame(&encoded, &mut decoded).unwrap();
all_decoded.extend_from_slice(&decoded);
}
for boundary in 2..10 {
let idx = boundary * 320;
let diff = ((all_decoded[idx] as i32) - (all_decoded[idx - 1] as i32)).abs();
assert!(diff < 10000,
"boundary {}-{}: jump of {} between samples {} and {}",
boundary - 1, boundary, diff, idx - 1, idx);
}
}
#[test]
fn test_multi_frame_long_stability() {
use crate::{A1800Encoder, A1800Decoder};
let mut encoder = A1800Encoder::new(16000).unwrap();
let mut decoder = A1800Decoder::new(16000).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let mut decoded = vec![0i16; 320];
for frame in 0..100 {
let mut pcm = [0i16; 320];
for i in 0..320 {
let t = (frame * 320 + i) as f64 / 16000.0;
pcm[i] = (5000.0 * (2.0 * std::f64::consts::PI * 1000.0 * t).sin()) as i16;
}
for w in encoded.iter_mut() { *w = 0; }
encoder.encode_frame(&pcm, &mut encoded).unwrap();
decoder.decode_frame(&encoded, &mut decoded).unwrap();
}
let energy: f64 = decoded.iter().map(|&s| (s as f64).powi(2)).sum();
assert!(energy > 0.0, "frame 99 energy {:.0} too low after 100 frames", energy);
let max_abs = decoded.iter().map(|&s| (s as i32).abs()).max().unwrap();
assert!(max_abs < 32768, "sample overflow after 100 frames");
}
#[test]
fn test_multi_frame_silence_to_signal() {
use crate::{A1800Encoder, A1800Decoder};
let mut encoder = A1800Encoder::new(16000).unwrap();
let mut decoder = A1800Decoder::new(16000).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let mut decoded = vec![0i16; 320];
for _ in 0..5 {
let pcm = [0i16; 320];
encoder.encode_frame(&pcm, &mut encoded).unwrap();
decoder.decode_frame(&encoded, &mut decoded).unwrap();
}
let silence_energy: f64 = decoded.iter().map(|&s| (s as f64).powi(2)).sum();
for frame in 0..5 {
let mut pcm = [0i16; 320];
for i in 0..320 {
let t = (frame * 320 + i) as f64 / 16000.0;
pcm[i] = (10000.0 * (2.0 * std::f64::consts::PI * 500.0 * t).sin()) as i16;
}
encoder.encode_frame(&pcm, &mut encoded).unwrap();
decoder.decode_frame(&encoded, &mut decoded).unwrap();
}
let signal_energy: f64 = decoded.iter().map(|&s| (s as f64).powi(2)).sum();
assert!(signal_energy > silence_energy + 10000.0,
"signal energy {:.0} should be much larger than silence energy {:.0}",
signal_energy, silence_energy);
}
#[test]
fn test_multi_frame_multiple_bitrates() {
use crate::{A1800Encoder, A1800Decoder};
for &bitrate in &[4800u16, 8000, 9600, 12000, 16000, 24000] {
let mut encoder = A1800Encoder::new(bitrate).unwrap();
let mut decoder = A1800Decoder::new(bitrate).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let mut decoded = vec![0i16; 320];
for frame in 0..10 {
let mut pcm = [0i16; 320];
for i in 0..320 {
let t = (frame * 320 + i) as f64 / 16000.0;
pcm[i] = (6000.0 * (2.0 * std::f64::consts::PI * 440.0 * t).sin()) as i16;
}
encoder.encode_frame(&pcm, &mut encoded).unwrap();
decoder.decode_frame(&encoded, &mut decoded).unwrap();
}
let energy: f64 = decoded.iter().map(|&s| (s as f64).powi(2)).sum();
assert!(energy > 0.0,
"bitrate {} multi-frame: last frame energy {:.0}", bitrate, energy);
}
}
#[test]
fn test_roundtrip_silence() {
use crate::{A1800Encoder, A1800Decoder};
let mut encoder = A1800Encoder::new(16000).unwrap();
let mut decoder = A1800Decoder::new(16000).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let mut decoded = vec![0i16; 320];
let pcm = [0i16; 320];
encoder.encode_frame(&pcm, &mut encoded).unwrap();
decoder.decode_frame(&encoded, &mut decoded).unwrap();
let max_abs = decoded.iter().map(|&s| (s as i32).abs()).max().unwrap_or(0);
assert!(max_abs <= 1, "silence roundtrip max deviation = {}, expected <= 1", max_abs);
}
#[test]
fn test_roundtrip_dc() {
use crate::{A1800Encoder, A1800Decoder};
let mut encoder = A1800Encoder::new(16000).unwrap();
let mut decoder = A1800Decoder::new(16000).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let mut decoded = vec![0i16; 320];
let pcm = [5000i16; 320];
encoder.encode_frame(&pcm, &mut encoded).unwrap();
decoder.decode_frame(&encoded, &mut decoded).unwrap();
let any_nonzero = decoded.iter().any(|&s| s != 0);
assert!(any_nonzero, "DC signal roundtrip should produce non-zero output");
}
#[test]
fn test_roundtrip_sine() {
use crate::{A1800Encoder, A1800Decoder};
let mut encoder = A1800Encoder::new(16000).unwrap();
let mut decoder = A1800Decoder::new(16000).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let mut decoded = vec![0i16; 320];
let mut pcm = [0i16; 320];
for i in 0..320 {
let t = i as f64 / 16000.0;
pcm[i] = (10000.0 * (2.0 * std::f64::consts::PI * 1000.0 * t).sin()) as i16;
}
encoder.encode_frame(&pcm, &mut encoded).unwrap();
decoder.decode_frame(&encoded, &mut decoded).unwrap();
let energy: f64 = decoded.iter().map(|&s| (s as f64) * (s as f64)).sum();
let rms = (energy / 320.0).sqrt();
assert!(rms > 10.0, "sine roundtrip RMS = {:.1}, expected > 10", rms);
}
#[test]
fn test_roundtrip_multi_frame() {
use crate::{A1800Encoder, A1800Decoder};
let mut encoder = A1800Encoder::new(16000).unwrap();
let mut decoder = A1800Decoder::new(16000).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let mut decoded = vec![0i16; 320];
for frame in 0..10 {
let mut pcm = [0i16; 320];
for i in 0..320 {
let t = (frame * 320 + i) as f64 / 16000.0;
pcm[i] = (8000.0 * (2.0 * std::f64::consts::PI * 440.0 * t).sin()) as i16;
}
encoder.encode_frame(&pcm, &mut encoded).unwrap();
decoder.decode_frame(&encoded, &mut decoded).unwrap();
}
let max_abs = decoded.iter().map(|&s| (s as i32).abs()).max().unwrap_or(0);
assert!(max_abs < 32768, "decoded samples should be in valid i16 range");
}
#[test]
fn test_roundtrip_idempotent() {
use crate::{A1800Encoder, A1800Decoder};
let bitrate = 16000u16;
let mut enc1 = A1800Encoder::new(bitrate).unwrap();
let mut dec1 = A1800Decoder::new(bitrate).unwrap();
let mut enc2 = A1800Encoder::new(bitrate).unwrap();
let mut dec2 = A1800Decoder::new(bitrate).unwrap();
let enc_size = enc1.encoded_frame_size();
let mut encoded1 = vec![0i16; enc_size];
let mut decoded1 = vec![0i16; 320];
let mut encoded2 = vec![0i16; enc_size];
let mut decoded2 = vec![0i16; 320];
let mut pcm = [0i16; 320];
for i in 0..320 {
let t = i as f64 / 16000.0;
pcm[i] = (6000.0 * (2.0 * std::f64::consts::PI * 800.0 * t).sin()) as i16;
}
enc1.encode_frame(&pcm, &mut encoded1).unwrap();
dec1.decode_frame(&encoded1, &mut decoded1).unwrap();
enc2.encode_frame(&decoded1, &mut encoded2).unwrap();
dec2.decode_frame(&encoded2, &mut decoded2).unwrap();
let max_diff: i32 = decoded1.iter().zip(decoded2.iter())
.map(|(&a, &b)| ((a as i32) - (b as i32)).abs())
.max().unwrap_or(0);
assert!(max_diff < 5000,
"idempotent roundtrip max diff = {}, expected < 5000", max_diff);
}
#[test]
fn test_select_frame_param() {
let mut alloc = [0i16; MAX_SUBBANDS];
let scratch = [0i16; 32];
alloc[0] = 2; alloc[1] = 3; let fp = select_frame_param(&alloc, &scratch, 2, 100);
assert_eq!(fp, 0, "should be 0 when base alloc fits");
let mut alloc2 = [0i16; MAX_SUBBANDS];
alloc2[0] = 0;
alloc2[1] = 0;
let scratch2: [i16; 32] = {
let mut s = [0i16; 32];
s[0] = 0; s[1] = 1; s
};
let fp2 = select_frame_param(&alloc2, &scratch2, 2, 100);
assert_eq!(fp2, 1, "one increment should bring cost from 104 to 99 (≤100)");
let fp3 = select_frame_param(&alloc2, &scratch2, 2, 95);
assert_eq!(fp3, 2, "two increments bring cost from 104 to 94 (≤95)");
}
#[test]
fn test_roundtrip_wav_file() {
use crate::{A1800Encoder, A1800Decoder};
use crate::wav::read_wav_samples;
use std::fs::File;
let wav_path = concat!(env!("CARGO_MANIFEST_DIR"), "/test_data/test_input.wav");
let mut f = File::open(wav_path)
.expect("test_input.wav not found — run the generation script from Testing.md");
let (input_samples, sample_rate) = read_wav_samples(&mut f).unwrap();
assert_eq!(sample_rate, 16000, "expected 16kHz WAV");
assert_eq!(input_samples.len(), 80000, "expected 5 seconds (80000 samples)");
let bitrate = 16000u16;
let mut encoder = A1800Encoder::new(bitrate).unwrap();
let mut decoder = A1800Decoder::new(bitrate).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let mut decoded_buf = [0i16; 320];
let num_frames = input_samples.len() / 320;
assert_eq!(num_frames, 250);
let mut output_samples: Vec<i16> = Vec::with_capacity(input_samples.len());
for frame in 0..num_frames {
let start = frame * 320;
let pcm = &input_samples[start..start + 320];
encoder.encode_frame(pcm, &mut encoded).unwrap();
decoder.decode_frame(&encoded, &mut decoded_buf).unwrap();
output_samples.extend_from_slice(&decoded_buf);
}
assert_eq!(output_samples.len(), input_samples.len());
let segment_names = ["440Hz sine", "1kHz sine", "multi-tone", "chirp", "decay"];
let samples_per_seg = 16000;
for (seg, name) in segment_names.iter().enumerate() {
let start = seg * samples_per_seg;
let end = start + samples_per_seg;
let inp = &input_samples[start..end];
let out = &output_samples[start..end];
let skip = if seg == 0 { 640 } else { 0 };
let mut max_err: i32 = 0;
for i in skip..samples_per_seg {
max_err = max_err.max((inp[i] as i32 - out[i] as i32).abs());
}
assert!(max_err < 32768,
"segment '{}': max error {} exceeds i16 range", name, max_err);
}
let skip = 640;
let n = input_samples.len() - skip;
let mean_in: f64 = input_samples[skip..].iter().map(|&s| s as f64).sum::<f64>() / n as f64;
let mean_out: f64 = output_samples[skip..].iter().map(|&s| s as f64).sum::<f64>() / n as f64;
let mut cov = 0.0f64;
let mut var_in = 0.0f64;
let mut var_out = 0.0f64;
for i in skip..input_samples.len() {
let a = input_samples[i] as f64 - mean_in;
let b = output_samples[i] as f64 - mean_out;
cov += a * b;
var_in += a * a;
var_out += b * b;
}
let correlation = cov / (var_in.sqrt() * var_out.sqrt());
let in_rms: f64 = (input_samples[skip..].iter()
.map(|&s| (s as f64).powi(2)).sum::<f64>() / n as f64).sqrt();
let out_rms: f64 = (output_samples[skip..].iter()
.map(|&s| (s as f64).powi(2)).sum::<f64>() / n as f64).sqrt();
let rms_ratio = out_rms / in_rms;
assert!(correlation > 0.3,
"overall correlation {:.4} too low (expected > 0.3)", correlation);
assert!(rms_ratio > 0.5 && rms_ratio < 2.0,
"RMS ratio {:.3} out of range (expected 0.5-2.0)", rms_ratio);
}
#[test]
fn test_roundtrip_multiple_bitrates() {
use crate::{A1800Encoder, A1800Decoder};
for &bitrate in &[4800u16, 8000, 9600, 12000, 16000, 24000, 32000] {
let mut encoder = A1800Encoder::new(bitrate).unwrap();
let mut decoder = A1800Decoder::new(bitrate).unwrap();
let enc_size = encoder.encoded_frame_size();
let mut encoded = vec![0i16; enc_size];
let mut decoded = vec![0i16; 320];
let mut pcm = [0i16; 320];
for i in 0..320 {
let t = i as f64 / 16000.0;
pcm[i] = (4000.0 * (2.0 * std::f64::consts::PI * 500.0 * t).sin()) as i16;
}
encoder.encode_frame(&pcm, &mut encoded).unwrap();
decoder.decode_frame(&encoded, &mut decoded).unwrap();
if bitrate <= 24000 {
let energy: f64 = decoded.iter().map(|&s| (s as f64) * (s as f64)).sum();
assert!(energy > 0.0, "bitrate {} roundtrip produced all zeros", bitrate);
}
}
}
}