use super::JxsError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NltType {
None,
Quadratic,
Extended,
}
#[derive(Debug, Clone, Copy)]
pub struct NltParams {
pub nlt_type: NltType,
pub t1: u16,
pub t2: u16,
}
impl NltParams {
pub fn none() -> Self {
Self {
nlt_type: NltType::None,
t1: 0,
t2: 0,
}
}
pub fn quadratic(t1: u16, t2: u16) -> Self {
Self {
nlt_type: NltType::Quadratic,
t1,
t2,
}
}
}
fn isqrt64_floor(n: u64) -> u64 {
if n == 0 {
return 0;
}
let mut x = (n as f64).sqrt() as u64;
while x > 0 && x.saturating_mul(x) > n {
x = (x + n / x) / 2;
}
while let Some(xp1) = x.checked_add(1) {
if xp1.saturating_mul(xp1) <= n {
x = xp1;
} else {
break;
}
}
x
}
fn isqrt64_ceil(n: u64) -> u64 {
if n == 0 {
return 0;
}
isqrt64_floor(n - 1) + 1
}
fn nlt_quadratic_inverse(s_prime: i32, t1: i32, t2: i32, max_val: i32) -> i32 {
if s_prime <= t1 {
s_prime
} else if s_prime <= t2 {
let delta = (s_prime - t1) as u64;
let scale = (t2 - t1) as u64;
t1 + isqrt64_ceil(delta * scale) as i32
} else {
let delta = (max_val - s_prime) as u64;
let denom = (max_val + 1 - t2) as u64;
max_val - isqrt64_ceil(delta * denom) as i32
}
}
pub fn apply_nlt_reverse(
samples: &mut [i32],
params: &NltParams,
bit_depth: u8,
) -> Result<(), JxsError> {
match params.nlt_type {
NltType::None => Ok(()),
NltType::Quadratic => {
let max_val = ((1u32 << bit_depth) as i32).saturating_sub(1);
let t1 = i32::from(params.t1);
let t2 = i32::from(params.t2);
if t1 >= t2 || t2 > max_val {
return Err(JxsError::InvalidHeader(format!(
"NLT quadratic: T1={t1} T2={t2} MaxVal={max_val} — must have T1 < T2 ≤ MaxVal"
)));
}
for s in samples.iter_mut() {
*s = nlt_quadratic_inverse(*s, t1, t2, max_val);
}
Ok(())
}
NltType::Extended => {
let _ = (samples, bit_depth);
Err(JxsError::Unsupported(
"NLT extended reverse transform not yet implemented".to_string(),
))
}
}
}
pub fn parse_nlt_payload(payload: &[u8]) -> Result<NltParams, JxsError> {
if payload.len() < 5 {
return Err(JxsError::InvalidHeader(format!(
"NLT payload too short: {} < 5 bytes",
payload.len()
)));
}
let tnlt = payload[0];
let t1 = u16::from_be_bytes([payload[1], payload[2]]);
let t2 = u16::from_be_bytes([payload[3], payload[4]]);
let nlt_type = match tnlt {
0 => NltType::Quadratic,
1 => NltType::Extended,
_ => {
return Err(JxsError::InvalidHeader(format!(
"NLT Tnlt={tnlt}: unknown NLT type"
)));
}
};
Ok(NltParams { nlt_type, t1, t2 })
}
#[cfg(test)]
mod tests {
use super::*;
fn nlt_quadratic_forward(s: i32, t1: i32, t2: i32, max_val: i32) -> i32 {
if s <= t1 {
s
} else if s <= t2 {
let num = (s - t1) as i64 * (s - t1) as i64;
let den = (t2 - t1) as i64;
t1 + (num / den) as i32
} else {
let num = (max_val - s) as i64 * (max_val - s) as i64;
let den = (max_val + 1 - t2) as i64;
max_val - (num / den) as i32
}
}
#[test]
fn isqrt64_floor_zero() {
assert_eq!(isqrt64_floor(0), 0);
}
#[test]
fn isqrt64_floor_perfect_squares() {
for k in 0u64..=1024 {
let n = k * k;
assert_eq!(isqrt64_floor(n), k, "isqrt64_floor({n}) should be {k}");
}
}
#[test]
fn isqrt64_floor_non_perfect() {
assert_eq!(isqrt64_floor(2), 1);
assert_eq!(isqrt64_floor(3), 1);
assert_eq!(isqrt64_floor(8), 2);
assert_eq!(isqrt64_floor(10), 3);
assert_eq!(isqrt64_floor(65535 * 65535), 65535);
}
#[test]
fn isqrt64_ceil_zero() {
assert_eq!(isqrt64_ceil(0), 0);
}
#[test]
fn isqrt64_ceil_perfect_squares() {
for k in 0u64..=1024 {
let n = k * k;
assert_eq!(isqrt64_ceil(n), k, "isqrt64_ceil({n}) should be {k}");
}
}
#[test]
fn isqrt64_ceil_non_perfect() {
assert_eq!(isqrt64_ceil(2), 2);
assert_eq!(isqrt64_ceil(3), 2);
assert_eq!(isqrt64_ceil(5), 3);
assert_eq!(isqrt64_ceil(8), 3);
assert_eq!(isqrt64_ceil(10), 4);
assert_eq!(isqrt64_ceil(128), 12);
}
#[test]
fn nlt_none_is_passthrough() {
let mut samples = vec![100i32, 200, 300];
let params = NltParams::none();
apply_nlt_reverse(&mut samples, ¶ms, 8).unwrap();
assert_eq!(samples, vec![100, 200, 300]);
}
#[test]
fn nlt_quadratic_identity_below_t1() {
let params = NltParams::quadratic(64, 192);
let mut samples = vec![0i32, 32, 64];
apply_nlt_reverse(&mut samples, ¶ms, 8).unwrap();
assert_eq!(samples, vec![0, 32, 64]);
}
#[test]
fn nlt_quadratic_identity_at_t1_boundary() {
let t1: u16 = 64;
let t2: u16 = 192;
let params = NltParams::quadratic(t1, t2);
let mut samples = vec![i32::from(t1)];
apply_nlt_reverse(&mut samples, ¶ms, 8).unwrap();
assert_eq!(samples[0], i32::from(t1));
}
#[test]
fn nlt_quadratic_middle_region_roundtrip_8bit() {
let bit_depth = 8u8;
let max_val = 255i32;
let t1 = 64i32;
let t2 = 192i32;
let params = NltParams::quadratic(t1 as u16, t2 as u16);
for s in (t1 + 1)..=t2 {
let s_prime = nlt_quadratic_forward(s, t1, t2, max_val);
let mut buf = vec![s_prime];
apply_nlt_reverse(&mut buf, ¶ms, bit_depth).unwrap();
let r = buf[0];
assert!(
r >= 0 && r <= max_val,
"s={s}, s'={s_prime}: r={r} out of range"
);
let reencoded = nlt_quadratic_forward(r, t1, t2, max_val);
assert_eq!(
reencoded, s_prime,
"s={s}, s'={s_prime}: inverse gave r={r}, forward(r)={reencoded} ≠ s'"
);
}
}
#[test]
fn nlt_quadratic_upper_region_roundtrip_8bit() {
let bit_depth = 8u8;
let max_val = 255i32;
let t1 = 64i32;
let t2 = 192i32;
let params = NltParams::quadratic(t1 as u16, t2 as u16);
for s in (t2 + 1)..=max_val {
let s_prime = nlt_quadratic_forward(s, t1, t2, max_val);
let mut buf = vec![s_prime];
apply_nlt_reverse(&mut buf, ¶ms, bit_depth).unwrap();
let r = buf[0];
assert!(
r >= 0 && r <= max_val,
"s={s}, s'={s_prime}: r={r} out of range"
);
let reencoded = nlt_quadratic_forward(r, t1, t2, max_val);
assert_eq!(
reencoded, s_prime,
"s={s}, s'={s_prime}: inverse gave r={r}, forward(r)={reencoded} ≠ s'"
);
}
}
#[test]
fn nlt_quadratic_middle_region_roundtrip_10bit() {
let bit_depth = 10u8;
let max_val = 1023i32;
let t1 = 128i32;
let t2 = 768i32;
let params = NltParams::quadratic(t1 as u16, t2 as u16);
for s in ((t1 + 1)..=t2).step_by(7) {
let s_prime = nlt_quadratic_forward(s, t1, t2, max_val);
let mut buf = vec![s_prime];
apply_nlt_reverse(&mut buf, ¶ms, bit_depth).unwrap();
let r = buf[0];
assert!(
r >= 0 && r <= max_val,
"10-bit s={s}, s'={s_prime}: r={r} out of range"
);
let reencoded = nlt_quadratic_forward(r, t1, t2, max_val);
assert_eq!(
reencoded, s_prime,
"10-bit s={s}, s'={s_prime}: inverse gave r={r}, forward(r)={reencoded} ≠ s'"
);
}
}
#[test]
fn nlt_quadratic_upper_region_roundtrip_10bit() {
let bit_depth = 10u8;
let max_val = 1023i32;
let t1 = 128i32;
let t2 = 768i32;
let params = NltParams::quadratic(t1 as u16, t2 as u16);
for s in ((t2 + 1)..=max_val).step_by(7) {
let s_prime = nlt_quadratic_forward(s, t1, t2, max_val);
let mut buf = vec![s_prime];
apply_nlt_reverse(&mut buf, ¶ms, bit_depth).unwrap();
let r = buf[0];
assert!(
r >= 0 && r <= max_val,
"10-bit s={s}, s'={s_prime}: r={r} out of range"
);
let reencoded = nlt_quadratic_forward(r, t1, t2, max_val);
assert_eq!(
reencoded, s_prime,
"10-bit s={s}, s'={s_prime}: inverse gave r={r}, forward(r)={reencoded} ≠ s'"
);
}
}
#[test]
fn nlt_quadratic_t1_zero_low_region_identity() {
let bit_depth = 8u8;
let params = NltParams::quadratic(0, 128);
let mut buf = vec![0i32];
apply_nlt_reverse(&mut buf, ¶ms, bit_depth).unwrap();
assert_eq!(buf[0], 0);
}
#[test]
fn nlt_quadratic_t1_zero_mid_region_roundtrip() {
let bit_depth = 8u8;
let max_val = 255i32;
let t1 = 0i32;
let t2 = 128i32;
let params = NltParams::quadratic(t1 as u16, t2 as u16);
for s in 1..=t2 {
let s_prime = nlt_quadratic_forward(s, t1, t2, max_val);
let mut buf = vec![s_prime];
apply_nlt_reverse(&mut buf, ¶ms, bit_depth).unwrap();
let r = buf[0];
let reencoded = nlt_quadratic_forward(r, t1, t2, max_val);
assert_eq!(
reencoded, s_prime,
"t1=0 mid: s={s}, s'={s_prime}, r={r}, forward(r)={reencoded}"
);
}
}
#[test]
fn nlt_quadratic_t1_zero_upper_region_roundtrip() {
let bit_depth = 8u8;
let max_val = 255i32;
let t1 = 0i32;
let t2 = 128i32;
let params = NltParams::quadratic(t1 as u16, t2 as u16);
for s in (t2 + 1)..=max_val {
let s_prime = nlt_quadratic_forward(s, t1, t2, max_val);
let mut buf = vec![s_prime];
apply_nlt_reverse(&mut buf, ¶ms, bit_depth).unwrap();
let r = buf[0];
let reencoded = nlt_quadratic_forward(r, t1, t2, max_val);
assert_eq!(
reencoded, s_prime,
"t1=0 upper: s={s}, s'={s_prime}, r={r}, forward(r)={reencoded}"
);
}
}
#[test]
fn nlt_invalid_params_t1_equals_t2_returns_error() {
let params = NltParams::quadratic(128, 128); let mut samples = vec![50i32];
let result = apply_nlt_reverse(&mut samples, ¶ms, 8);
assert!(
matches!(result, Err(JxsError::InvalidHeader(_))),
"expected InvalidHeader, got {result:?}"
);
}
#[test]
fn nlt_invalid_params_t1_greater_than_t2_returns_error() {
let params = NltParams::quadratic(200, 100); let mut samples = vec![50i32];
let result = apply_nlt_reverse(&mut samples, ¶ms, 8);
assert!(
matches!(result, Err(JxsError::InvalidHeader(_))),
"expected InvalidHeader, got {result:?}"
);
}
#[test]
fn nlt_invalid_params_t2_exceeds_max_val_returns_error() {
let params = NltParams::quadratic(64, 256);
let mut samples = vec![50i32];
let result = apply_nlt_reverse(&mut samples, ¶ms, 8);
assert!(
matches!(result, Err(JxsError::InvalidHeader(_))),
"expected InvalidHeader, got {result:?}"
);
}
#[test]
fn nlt_extended_returns_unsupported() {
let mut samples = vec![10i32];
let params = NltParams {
nlt_type: NltType::Extended,
t1: 0,
t2: 0,
};
let result = apply_nlt_reverse(&mut samples, ¶ms, 8);
assert!(result.is_err());
assert!(matches!(result, Err(JxsError::Unsupported(_))));
}
#[test]
fn parse_nlt_payload_quadratic() {
let payload = [0x00u8, 0x00, 0x64, 0x03, 0x84];
let params = parse_nlt_payload(&payload).unwrap();
assert_eq!(params.nlt_type, NltType::Quadratic);
assert_eq!(params.t1, 100);
assert_eq!(params.t2, 900);
}
#[test]
fn parse_nlt_payload_extended() {
let payload = [0x01u8, 0x00, 0x00, 0x00, 0x00];
let params = parse_nlt_payload(&payload).unwrap();
assert_eq!(params.nlt_type, NltType::Extended);
}
#[test]
fn parse_nlt_payload_unknown_tnlt_returns_error() {
let payload = [0x05u8, 0x00, 0x00, 0x00, 0x00];
assert!(parse_nlt_payload(&payload).is_err());
}
#[test]
fn parse_nlt_payload_too_short_returns_error() {
let payload = [0x00u8, 0x01]; assert!(parse_nlt_payload(&payload).is_err());
}
}