#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::super::error::Result;
use super::super::ics::WindowSequence;
use super::super::swb::LONG_WINDOW_LEN;
use super::LcEncoder;
use super::enc_block_tests::{add_click, decode_all, seqs_of, sine_frame};
fn encode_lookahead(frames: &[Vec<f32>]) -> Result<Vec<Vec<u8>>> {
let mut enc = LcEncoder::new(48_000, 1, 128_000)?.with_lookahead(true);
let mut payloads = Vec::new();
for pcm in frames {
if let Some(p) = enc.push_frame(&[pcm])? {
payloads.push(p);
}
}
if let Some(p) = enc.flush()? {
payloads.push(p);
}
Ok(payloads)
}
#[test]
fn early_attack_shifts_sequence_one_frame_earlier() -> Result<()> {
let frames: Vec<Vec<f32>> = (0..12)
.map(|f| {
let mut pcm = sine_frame(f, 0.2);
if f == 5 {
add_click(&mut pcm, 100, 0.8);
}
pcm
})
.collect();
let causal: Vec<WindowSequence> = {
let mut enc = LcEncoder::new(48_000, 1, 128_000)?;
let mut payloads = Vec::new();
for pcm in &frames {
payloads.push(enc.encode_frame(&[pcm])?);
}
seqs_of(&payloads)
};
assert_eq!(causal[5], WindowSequence::LongStart, "causal: attack frame");
assert_eq!(causal[6], WindowSequence::EightShort);
assert_eq!(causal[7], WindowSequence::LongStop);
let lookahead = seqs_of(&encode_lookahead(&frames)?);
assert_eq!(lookahead.len(), frames.len(), "one payload per frame");
for (f, &s) in lookahead.iter().enumerate() {
let want = match f {
4 => WindowSequence::LongStart, 5 => WindowSequence::EightShort, 6 => WindowSequence::LongStop,
_ => WindowSequence::OnlyLong,
};
assert_eq!(s, want, "lookahead frame {f}");
}
Ok(())
}
#[test]
fn stream_start_attack_still_switches_on_first_frame() -> Result<()> {
let frames: Vec<Vec<f32>> = (0..8)
.map(|f| {
let mut pcm = vec![0.0f32; LONG_WINDOW_LEN];
if f == 0 {
add_click(&mut pcm, 600, 0.8);
}
pcm
})
.collect();
let seqs = seqs_of(&encode_lookahead(&frames)?);
assert_eq!(seqs[0], WindowSequence::LongStart);
assert_eq!(seqs[1], WindowSequence::EightShort);
assert_eq!(seqs[2], WindowSequence::LongStop);
assert_eq!(seqs[3], WindowSequence::OnlyLong);
Ok(())
}
#[test]
fn single_frame_input_matches_causal_bytes() -> Result<()> {
let mut pcm = sine_frame(0, 0.2);
add_click(&mut pcm, 600, 0.8);
let mut causal = LcEncoder::new(48_000, 1, 128_000)?;
let want = causal.encode_frame(&[&pcm])?;
let mut enc = LcEncoder::new(48_000, 1, 128_000)?.with_lookahead(true);
assert!(enc.push_frame(&[&pcm])?.is_none(), "one frame of latency");
let got = enc.flush()?.expect("flush emits the held frame");
assert_eq!(got, want);
assert!(enc.flush()?.is_none(), "nothing left to flush");
Ok(())
}
#[test]
fn lookahead_reduces_early_attack_pre_echo() -> Result<()> {
let mut pcm = [vec![0.0f32; 14 * LONG_WINDOW_LEN]];
add_click(&mut pcm[0], 5 * LONG_WINDOW_LEN + 100, 0.9);
let frames: Vec<Vec<f32>> = (0..14)
.map(|f| pcm[0][f * LONG_WINDOW_LEN..(f + 1) * LONG_WINDOW_LEN].to_vec())
.collect();
let causal: Vec<Vec<u8>> = {
let mut enc = LcEncoder::new(48_000, 1, 128_000)?;
let mut payloads = Vec::new();
for f in &frames {
payloads.push(enc.encode_frame(&[f])?);
}
payloads
};
let lookahead = encode_lookahead(&frames)?;
let enc = LcEncoder::new(48_000, 1, 128_000)?;
let dec_causal = decode_all(&enc, &causal)?;
let dec_lookahead = decode_all(&enc, &lookahead)?;
let click_dec = 5 * LONG_WINDOW_LEN + 100 + LONG_WINDOW_LEN;
let pre = click_dec - 480..click_dec;
let e_causal: f64 = dec_causal[pre.clone()]
.iter()
.map(|&x| f64::from(x) * f64::from(x))
.sum();
let e_lookahead: f64 = dec_lookahead[pre]
.iter()
.map(|&x| f64::from(x) * f64::from(x))
.sum();
eprintln!(
"early-attack pre-echo energy: causal {e_causal:.6}, lookahead {e_lookahead:.6}, \
improvement {:.1} dB",
10.0 * (e_causal / e_lookahead.max(1e-12)).log10()
);
assert!(
e_lookahead * 100.0 < e_causal,
"lookahead should cut early-attack pre-echo by ≥20 dB \
(causal {e_causal:.6} vs lookahead {e_lookahead:.6})"
);
Ok(())
}