#![cfg_attr(not(feature = "std"), no_std)]
#![deny(clippy::all)]
#![deny(unsafe_op_in_unsafe_fn)]
#[cfg(all(not(feature = "std"), feature = "alloc"))]
extern crate alloc;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::vec;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::vec::Vec;
pub mod error;
pub use error::DecodeError;
#[cfg(feature = "alloc")]
pub(crate) mod coder;
#[cfg(feature = "alloc")]
pub mod delta;
#[cfg(feature = "alloc")]
pub mod zigzag;
#[cfg(feature = "alloc")]
macro_rules! impl_dispatch_encode {
($name:ident, $T:ty, $avx2_fn:path, $sse2_fn:path, $neon_fn:path, $scalar_fn:path) => {
fn $name(values: &[$T], out: &mut Vec<u8>) {
#[cfg(all(feature = "simd-avx2", target_arch = "x86_64"))]
{
return unsafe { $avx2_fn(values, out) };
}
#[cfg(all(
feature = "simd-ssse3",
not(feature = "simd-avx2"),
target_arch = "x86_64"
))]
{
return unsafe { $sse2_fn(values, out) };
}
#[cfg(all(feature = "simd-neon", target_arch = "aarch64"))]
{
return unsafe { $neon_fn(values, out) };
}
#[cfg(all(
feature = "simd-auto",
not(any(feature = "simd-avx2", feature = "simd-ssse3", feature = "simd-neon"))
))]
{
#[cfg(all(feature = "std", target_arch = "x86_64"))]
{
if is_x86_feature_detected!("avx2") {
return unsafe { $avx2_fn(values, out) };
}
if is_x86_feature_detected!("ssse3") {
return unsafe { $sse2_fn(values, out) };
}
}
#[cfg(target_arch = "aarch64")]
{
return unsafe { $neon_fn(values, out) };
}
}
$scalar_fn(values, out)
}
};
}
#[cfg(feature = "alloc")]
macro_rules! impl_dispatch_decode {
($name:ident, $T:ty, $avx2_fn:path, $sse2_fn:path, $neon_fn:path, $scalar_fn:path) => {
fn $name(
data: &[u8],
n: usize,
out: &mut Vec<$T>,
) -> Result<(), crate::error::DecodeError> {
#[cfg(all(feature = "simd-avx2", target_arch = "x86_64"))]
{
return unsafe { $avx2_fn(data, n, out) };
}
#[cfg(all(
feature = "simd-ssse3",
not(feature = "simd-avx2"),
target_arch = "x86_64"
))]
{
return unsafe { $sse2_fn(data, n, out) };
}
#[cfg(all(feature = "simd-neon", target_arch = "aarch64"))]
{
return unsafe { $neon_fn(data, n, out) };
}
#[cfg(all(
feature = "simd-auto",
not(any(feature = "simd-avx2", feature = "simd-ssse3", feature = "simd-neon"))
))]
{
#[cfg(all(feature = "std", target_arch = "x86_64"))]
{
if is_x86_feature_detected!("avx2") {
return unsafe { $avx2_fn(data, n, out) };
}
if is_x86_feature_detected!("ssse3") {
return unsafe { $sse2_fn(data, n, out) };
}
}
#[cfg(target_arch = "aarch64")]
{
return unsafe { $neon_fn(data, n, out) };
}
}
$scalar_fn(data, n, out)
}
};
}
#[cfg(feature = "alloc")]
pub mod u16;
#[cfg(feature = "alloc")]
pub mod u32;
#[cfg(feature = "alloc")]
pub mod u64;
#[cfg(feature = "alloc")]
pub use vbz::{decode_vbz, decode_vbz_into, encode_vbz, encode_vbz_into};
#[cfg(feature = "alloc")]
mod vbz_fused;
#[cfg(feature = "alloc")]
mod svbzd_fused;
#[cfg(feature = "alloc")]
mod vbz {
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
use crate::error::DecodeError;
use crate::{delta, u16::Svb16, zigzag};
pub fn encode_vbz(samples: &[i16]) -> Vec<u8> {
let mut out = Vec::new();
encode_vbz_into(samples, &mut out);
out
}
pub fn encode_vbz_into(samples: &[i16], out: &mut Vec<u8>) {
let deltas = delta::encode(samples);
let codes = zigzag::encode(&deltas);
Svb16.encode_into(&codes, out);
}
pub fn decode_vbz(data: &[u8], n: usize) -> Result<Vec<i16>, DecodeError> {
let mut out = Vec::with_capacity(n);
decode_vbz_into(data, n, &mut out)?;
Ok(out)
}
pub fn decode_vbz_into(data: &[u8], n: usize, out: &mut Vec<i16>) -> Result<(), DecodeError> {
let codes = Svb16.decode(data, n)?;
let deltas = zigzag::decode(&codes);
delta::decode_into(&deltas, out);
Ok(())
}
}
#[cfg(feature = "alloc")]
pub fn decode_vbz_fused(data: &[u8], n: usize) -> Result<Vec<i16>, DecodeError> {
let mut out = Vec::with_capacity(n);
decode_vbz_fused_into(data, n, &mut out)?;
Ok(out)
}
#[cfg(feature = "alloc")]
pub fn decode_vbz_fused_into(data: &[u8], n: usize, out: &mut Vec<i16>) -> Result<(), DecodeError> {
vbz_fused::decode_into(data, n, out)
}
#[cfg(feature = "alloc")]
pub fn decode_vbz_fused_from(
data: &[u8],
n: usize,
initial_carry: i16,
) -> Result<Vec<i16>, DecodeError> {
let mut out = Vec::with_capacity(n);
decode_vbz_fused_from_into(data, n, initial_carry, &mut out)?;
Ok(out)
}
#[cfg(feature = "alloc")]
pub fn decode_vbz_fused_from_into(
data: &[u8],
n: usize,
initial_carry: i16,
out: &mut Vec<i16>,
) -> Result<(), DecodeError> {
vbz_fused::decode_from_into(data, n, initial_carry, out)
}
#[cfg(feature = "alloc")]
#[deprecated(since = "0.1.0", note = "use encode_vbzk(samples, 2) instead")]
pub fn encode_vbz2(samples: &[i16]) -> Vec<u8> {
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
let n = samples.len();
if n == 0 {
return vec![0u8; 6];
}
let n_half = (n / 2) & !7;
let mid_carry: i16 = if n_half > 0 { samples[n_half - 1] } else { 0 };
let svb = encode_vbz(samples);
let ctrl_len = n.div_ceil(8);
let ctrl_half = n_half / 8;
let ctrl = &svb[..ctrl_len];
let mut mid_data_offset: u32 = 0;
for &cb in &ctrl[..ctrl_half] {
mid_data_offset += 8 + cb.count_ones();
}
let mut out = Vec::with_capacity(6 + svb.len());
out.extend_from_slice(&mid_carry.to_le_bytes());
out.extend_from_slice(&mid_data_offset.to_le_bytes());
out.extend_from_slice(&svb);
out
}
#[cfg(feature = "alloc")]
#[deprecated(
since = "0.1.0",
note = "use decode_vbzk / decode_vbzk_parallel_into instead"
)]
#[allow(deprecated)]
pub fn decode_vbz2(data: &[u8], n: usize) -> Result<Vec<i16>, DecodeError> {
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
let mut out = Vec::new();
decode_vbz2_into(data, n, &mut out)?;
Ok(out)
}
#[cfg(feature = "alloc")]
#[deprecated(
since = "0.1.0",
note = "use decode_vbzk_into / decode_vbzk_parallel_into instead"
)]
pub fn decode_vbz2_into(data: &[u8], n: usize, out: &mut Vec<i16>) -> Result<(), DecodeError> {
if n == 0 {
return Ok(());
}
if data.len() < 6 {
return Err(DecodeError::ControlStreamTooShort {
need: 6,
have: data.len(),
});
}
let mid_carry = i16::from_le_bytes([data[0], data[1]]);
let mid_data_offset = u32::from_le_bytes([data[2], data[3], data[4], data[5]]) as usize;
vbz_fused::decode_2chain_into(&data[6..], n, mid_carry, mid_data_offset, out)
}
#[cfg(all(test, feature = "alloc"))]
mod vbz_tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[test]
fn roundtrip_empty() {
assert_eq!(decode_vbz(&encode_vbz(&[]), 0).unwrap(), &[] as &[i16]);
}
#[test]
fn roundtrip_single() {
for v in [0i16, 1, -1, i16::MIN, i16::MAX] {
assert_eq!(decode_vbz(&encode_vbz(&[v]), 1).unwrap(), [v]);
}
}
#[test]
fn roundtrip_flat_signal() {
let samples = vec![1000i16; 256];
assert_eq!(decode_vbz(&encode_vbz(&samples), 256).unwrap(), samples);
}
#[test]
fn roundtrip_ramp() {
let samples: Vec<i16> = (0..128).collect();
assert_eq!(decode_vbz(&encode_vbz(&samples), 128).unwrap(), samples);
}
#[test]
fn roundtrip_extremes() {
let samples = vec![i16::MIN, i16::MAX, i16::MIN, i16::MAX];
assert_eq!(decode_vbz(&encode_vbz(&samples), 4).unwrap(), samples);
}
#[test]
fn encode_vbz_into_appends() {
let mut out = encode_vbz(&[1i16, 2, 3]);
let first_len = out.len();
encode_vbz_into(&[4i16, 5, 6], &mut out);
let first = decode_vbz(&out[..first_len], 3).unwrap();
let second = decode_vbz(&out[first_len..], 3).unwrap();
assert_eq!(first, [1, 2, 3]);
assert_eq!(second, [4, 5, 6]);
}
#[test]
fn decode_vbz_into_appends() {
let enc = encode_vbz(&[10i16, 20, 30]);
let mut out = vec![99i16];
decode_vbz_into(&enc, 3, &mut out).unwrap();
assert_eq!(out, [99, 10, 20, 30]);
}
}
#[cfg(all(test, feature = "alloc"))]
#[allow(deprecated)]
mod vbz2_tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
fn split_vbz2_streams(encoded: &[u8], n: usize) -> (i16, Vec<u8>, Vec<u8>, usize, usize) {
let mid_carry = i16::from_le_bytes([encoded[0], encoded[1]]);
let mid_data_offset =
u32::from_le_bytes([encoded[2], encoded[3], encoded[4], encoded[5]]) as usize;
let svb = &encoded[6..];
let n_half = (n / 2) & !7;
let ctrl_len = n.div_ceil(8);
let ctrl_half = n_half / 8;
let mut stream_a = svb[..ctrl_half].to_vec();
stream_a.extend_from_slice(&svb[ctrl_len..ctrl_len + mid_data_offset]);
let mut stream_b = svb[ctrl_half..ctrl_len].to_vec();
stream_b.extend_from_slice(&svb[ctrl_len + mid_data_offset..]);
(mid_carry, stream_a, stream_b, n_half, n - n_half)
}
#[test]
fn roundtrip_basic() {
let samples: Vec<i16> = vec![100, 101, 103, 102, 98, 95, 97, 100];
let encoded = encode_vbz2(&samples);
let decoded = decode_vbz2(&encoded, samples.len()).unwrap();
assert_eq!(decoded, samples);
}
#[test]
fn roundtrip_large() {
let samples: Vec<i16> = (0..8192)
.map(|i| {
((i as i32 % 500 - 250) as i16).wrapping_add((i as i16).wrapping_mul(37) % 7 - 3)
})
.collect();
let encoded = encode_vbz2(&samples);
let decoded = decode_vbz2(&encoded, samples.len()).unwrap();
assert_eq!(decoded, samples);
}
#[test]
fn matches_vbz_output() {
let samples: Vec<i16> = (0..1024)
.map(|i| (i as i16 * 13).wrapping_sub(500))
.collect();
let decoded_vbz = decode_vbz(&encode_vbz(&samples), samples.len()).unwrap();
let decoded_vbz2 = decode_vbz2(&encode_vbz2(&samples), samples.len()).unwrap();
assert_eq!(decoded_vbz, decoded_vbz2);
}
#[test]
fn roundtrip_empty() {
let encoded = encode_vbz2(&[]);
let decoded = decode_vbz2(&encoded, 0).unwrap();
assert!(decoded.is_empty());
}
#[test]
fn roundtrip_small_n() {
let samples: Vec<i16> = vec![10, 20, 15, 5, -10, -20, -5, 0, 10, 20, 15, 5, -10, -20, -5];
let encoded = encode_vbz2(&samples);
let decoded = decode_vbz2(&encoded, samples.len()).unwrap();
assert_eq!(decoded, samples);
}
#[test]
fn roundtrip_extremes() {
let samples = vec![
i16::MIN,
i16::MAX,
0,
-1,
1,
i16::MIN,
i16::MAX,
0,
i16::MIN,
i16::MAX,
0,
-1,
1,
i16::MIN,
i16::MAX,
0,
];
let encoded = encode_vbz2(&samples);
let decoded = decode_vbz2(&encoded, samples.len()).unwrap();
assert_eq!(decoded, samples);
}
#[test]
fn parallel_decode_correctness() {
let n = 8192usize;
#[cfg(feature = "std")]
use std::vec::Vec;
let samples: Vec<i16> = (0..n)
.map(|i| {
((i as i32 % 500 - 250) as i16).wrapping_add((i as i16).wrapping_mul(37) % 7 - 3)
})
.collect();
let encoded = encode_vbz2(&samples);
let (mid_carry, stream_a, stream_b, n_a, n_b) = split_vbz2_streams(&encoded, n);
let out_a = decode_vbz_fused_from(&stream_a, n_a, 0).unwrap();
let out_b = decode_vbz_fused_from(&stream_b, n_b, mid_carry).unwrap();
let mut combined = out_a;
combined.extend_from_slice(&out_b);
assert_eq!(combined, samples);
#[cfg(feature = "std")]
{
let (out_a, out_b) = std::thread::scope(|s| {
let ha = s.spawn(|| decode_vbz_fused_from(&stream_a, n_a, 0).unwrap());
let hb = s.spawn(|| decode_vbz_fused_from(&stream_b, n_b, mid_carry).unwrap());
(ha.join().unwrap(), hb.join().unwrap())
});
let mut combined = out_a;
combined.extend_from_slice(&out_b);
assert_eq!(combined, samples);
}
}
}
#[cfg(feature = "alloc")]
pub fn encode_vbzk(samples: &[i16], k: usize) -> Vec<u8> {
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
assert!(k != 0, "k must be >= 1");
let n = samples.len();
let n_sub = if k > 1 { (n / k) & !7 } else { 0 };
let effective_k = if n_sub == 0 { 1 } else { k };
let svb = encode_vbz(samples);
if effective_k == 1 {
let mut out = Vec::with_capacity(1 + svb.len());
out.push(1u8);
out.extend_from_slice(&svb);
return out;
}
let ctrl_len = n.div_ceil(8);
let ctrl = &svb[..ctrl_len];
let header_size = 1 + (effective_k - 1) * 6;
let mut out = Vec::with_capacity(header_size + svb.len());
out.push(effective_k as u8);
let mut cumulative_data_offset: u32 = 0;
let mut ctrl_byte_idx = 0usize;
for i in 1..effective_k {
let split_pos = n_sub * i;
let ctrl_boundary = split_pos / 8;
while ctrl_byte_idx < ctrl_boundary {
cumulative_data_offset += 8 + ctrl[ctrl_byte_idx].count_ones();
ctrl_byte_idx += 1;
}
let carry: i16 = samples[split_pos - 1];
out.extend_from_slice(&carry.to_le_bytes());
out.extend_from_slice(&cumulative_data_offset.to_le_bytes());
}
out.extend_from_slice(&svb);
out
}
#[cfg(feature = "alloc")]
pub fn decode_vbzk(data: &[u8], n: usize) -> Result<Vec<i16>, DecodeError> {
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
let mut out = Vec::new();
decode_vbzk_into(data, n, &mut out)?;
Ok(out)
}
#[cfg(feature = "alloc")]
pub fn decode_vbzk_into(data: &[u8], n: usize, out: &mut Vec<i16>) -> Result<(), DecodeError> {
if n == 0 {
return Ok(());
}
if data.is_empty() {
return Err(DecodeError::ControlStreamTooShort { need: 1, have: 0 });
}
let k = data[0] as usize;
if k == 0 {
return Err(DecodeError::ControlStreamTooShort { need: 1, have: 0 });
}
let header_len = 1 + (k - 1) * 6;
if data.len() < header_len {
return Err(DecodeError::ControlStreamTooShort {
need: header_len,
have: data.len(),
});
}
#[cfg(not(feature = "std"))]
use alloc::vec;
let mut sub_carry = vec![0i16; k];
let mut data_start = vec![0usize; k + 1];
for i in 1..k {
let off = 1 + (i - 1) * 6;
let carry = i16::from_le_bytes([data[off], data[off + 1]]);
let d_off = u32::from_le_bytes([data[off + 2], data[off + 3], data[off + 4], data[off + 5]])
as usize;
sub_carry[i] = carry;
data_start[i] = d_off;
}
let svb = &data[header_len..];
let ctrl_len = n.div_ceil(8);
if svb.len() < ctrl_len {
return Err(DecodeError::ControlStreamTooShort {
need: ctrl_len,
have: svb.len(),
});
}
let ctrl = &svb[..ctrl_len];
let data_bytes = &svb[ctrl_len..];
data_start[k] = data_bytes.len();
let n_sub = (n / k) & !7;
out.reserve(n);
for i in 0..k {
let sub_n = if i < k - 1 {
n_sub
} else {
n - (k - 1) * n_sub
};
let ctrl_start = i * (n_sub / 8);
let ctrl_end = ctrl_start + sub_n.div_ceil(8);
let sub_ctrl = &ctrl[ctrl_start..ctrl_end];
let sub_data = &data_bytes[data_start[i]..data_start[i + 1]];
let initial = sub_carry[i];
vbz_fused::decode_parts_into(sub_ctrl, sub_data, sub_n, initial, out)?;
}
Ok(())
}
#[cfg(all(feature = "alloc", feature = "std"))]
pub fn decode_vbzk_parallel(data: &[u8], n: usize) -> Result<Vec<i16>, DecodeError> {
let mut out = Vec::new();
decode_vbzk_parallel_into(data, n, &mut out)?;
Ok(out)
}
#[cfg(all(feature = "alloc", feature = "std"))]
pub fn decode_vbzk_parallel_into(
data: &[u8],
n: usize,
out: &mut Vec<i16>,
) -> Result<(), DecodeError> {
if n == 0 {
return Ok(());
}
if data.is_empty() {
return Err(DecodeError::ControlStreamTooShort { need: 1, have: 0 });
}
let k = data[0] as usize;
if k == 0 {
return Err(DecodeError::ControlStreamTooShort { need: 1, have: 0 });
}
let header_len = 1 + (k - 1) * 6;
if data.len() < header_len {
return Err(DecodeError::ControlStreamTooShort {
need: header_len,
have: data.len(),
});
}
let mut sub_carry = vec![0i16; k];
let mut data_start = vec![0usize; k + 1];
for i in 1..k {
let off = 1 + (i - 1) * 6;
let carry = i16::from_le_bytes([data[off], data[off + 1]]);
let d_off = u32::from_le_bytes([data[off + 2], data[off + 3], data[off + 4], data[off + 5]])
as usize;
sub_carry[i] = carry;
data_start[i] = d_off;
}
let svb = &data[header_len..];
let ctrl_len = n.div_ceil(8);
if svb.len() < ctrl_len {
return Err(DecodeError::ControlStreamTooShort {
need: ctrl_len,
have: svb.len(),
});
}
let ctrl = &svb[..ctrl_len];
let data_bytes = &svb[ctrl_len..];
data_start[k] = data_bytes.len();
let n_sub = (n / k) & !7;
struct SubStream<'a> {
ctrl: &'a [u8],
data: &'a [u8],
sub_n: usize,
initial: i16,
}
let streams: Vec<SubStream<'_>> = (0..k)
.map(|i| {
let sub_n = if i < k - 1 {
n_sub
} else {
n - (k - 1) * n_sub
};
let ctrl_start = i * (n_sub / 8);
let ctrl_end = ctrl_start + sub_n.div_ceil(8);
SubStream {
ctrl: &ctrl[ctrl_start..ctrl_end],
data: &data_bytes[data_start[i]..data_start[i + 1]],
sub_n,
initial: sub_carry[i],
}
})
.collect();
let results: Vec<Result<Vec<i16>, DecodeError>> = std::thread::scope(|scope| {
let handles: Vec<_> = streams
.iter()
.map(|s| {
scope.spawn(move || {
let mut sub_out = Vec::with_capacity(s.sub_n);
vbz_fused::decode_parts_into(s.ctrl, s.data, s.sub_n, s.initial, &mut sub_out)?;
Ok(sub_out)
})
})
.collect();
handles
.into_iter()
.map(|h| {
h.join()
.unwrap_or(Err(DecodeError::DataTruncated { index: 0 }))
})
.collect()
});
out.reserve(n);
for result in results {
out.extend_from_slice(&result?);
}
Ok(())
}
#[cfg(all(test, feature = "alloc"))]
mod vbzk_tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
fn make_samples(n: usize) -> Vec<i16> {
(0..n)
.map(|i| {
let base = (i as i32 % 500 - 250) as i16;
let noise = (i as i16).wrapping_mul(37) % 7 - 3;
base.wrapping_add(noise)
})
.collect()
}
#[test]
fn roundtrip_k1_to_k8_n8192() {
let samples = make_samples(8192);
for k in [1usize, 2, 4, 8] {
let encoded = encode_vbzk(&samples, k);
let decoded = decode_vbzk(&encoded, samples.len()).unwrap();
assert_eq!(decoded, samples, "k={k} roundtrip failed");
}
}
#[test]
fn sequential_matches_vbz() {
let samples = make_samples(8192);
let expected = decode_vbz(&encode_vbz(&samples), samples.len()).unwrap();
for k in [1usize, 2, 4, 8] {
let encoded = encode_vbzk(&samples, k);
let decoded = decode_vbzk(&encoded, samples.len()).unwrap();
assert_eq!(decoded, expected, "k={k} does not match decode_vbz output");
}
}
#[cfg(feature = "std")]
#[test]
fn parallel_matches_vbz() {
let samples = make_samples(8192);
let expected = decode_vbz(&encode_vbz(&samples), samples.len()).unwrap();
for k in [1usize, 2, 4, 8] {
let encoded = encode_vbzk(&samples, k);
let decoded = decode_vbzk_parallel(&encoded, samples.len()).unwrap();
assert_eq!(
decoded, expected,
"k={k} parallel does not match decode_vbz"
);
}
}
#[test]
fn k1_small_n() {
let encoded = encode_vbzk(&[], 1);
assert_eq!(decode_vbzk(&encoded, 0).unwrap(), Vec::<i16>::new());
let samples = vec![1i16, 2, 3, 4];
let encoded = encode_vbzk(&samples, 1);
assert_eq!(decode_vbzk(&encoded, 4).unwrap(), samples);
let samples: Vec<i16> = (0..8).collect();
let encoded = encode_vbzk(&samples, 1);
assert_eq!(decode_vbzk(&encoded, 8).unwrap(), samples);
}
#[test]
fn k_larger_than_useful_falls_back_to_k1() {
let samples: Vec<i16> = (0..16).collect();
let encoded = encode_vbzk(&samples, 1000);
assert_eq!(encoded[0], 1u8, "expected header k=1");
let decoded = decode_vbzk(&encoded, 16).unwrap();
assert_eq!(decoded, samples);
}
#[test]
fn sequential_and_parallel_agree() {
#[cfg(feature = "std")]
{
let samples = make_samples(8192);
for k in [2usize, 4, 8] {
let encoded = encode_vbzk(&samples, k);
let seq = decode_vbzk(&encoded, samples.len()).unwrap();
let par = decode_vbzk_parallel(&encoded, samples.len()).unwrap();
assert_eq!(seq, par, "k={k} sequential != parallel");
}
}
}
}
#[cfg(all(test, feature = "alloc"))]
mod vbz_fused_tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[test]
fn fused_matches_reference_empty() {
assert_eq!(
decode_vbz_fused(&encode_vbz(&[]), 0).unwrap(),
&[] as &[i16]
);
}
#[test]
fn fused_matches_reference_single() {
for v in [0i16, 1, -1, i16::MIN, i16::MAX] {
let enc = encode_vbz(&[v]);
assert_eq!(
decode_vbz_fused(&enc, 1).unwrap(),
decode_vbz(&enc, 1).unwrap(),
);
}
}
#[test]
fn fused_matches_reference_ramp() {
let samples: Vec<i16> = (0..128).collect();
let enc = encode_vbz(&samples);
assert_eq!(
decode_vbz_fused(&enc, 128).unwrap(),
decode_vbz(&enc, 128).unwrap(),
);
}
#[test]
fn fused_matches_reference_large() {
let samples: Vec<i16> = (0..1024)
.map(|i| {
((i as i32 % 500 - 250) as i16).wrapping_add((i as i16).wrapping_mul(37) % 7 - 3)
})
.collect();
let enc = encode_vbz(&samples);
assert_eq!(
decode_vbz_fused(&enc, 1024).unwrap(),
decode_vbz(&enc, 1024).unwrap(),
);
}
#[test]
fn fused_matches_reference_extremes() {
let samples = vec![i16::MIN, i16::MAX, i16::MIN, i16::MAX];
let enc = encode_vbz(&samples);
assert_eq!(
decode_vbz_fused(&enc, 4).unwrap(),
decode_vbz(&enc, 4).unwrap(),
);
}
}
#[cfg(feature = "alloc")]
pub fn encode_svbzd(samples: &[i16]) -> Vec<u8> {
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
let mut out = Vec::new();
encode_svbzd_into(samples, &mut out);
out
}
#[cfg(feature = "alloc")]
pub fn encode_svbzd_into(samples: &[i16], out: &mut Vec<u8>) {
svbzd_fused::encode_into(samples, out);
}
#[cfg(feature = "alloc")]
pub fn decode_svbzd(data: &[u8], n: usize) -> Result<Vec<i16>, DecodeError> {
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
let mut out = Vec::with_capacity(n);
decode_svbzd_into(data, n, &mut out)?;
Ok(out)
}
#[cfg(feature = "alloc")]
pub fn decode_svbzd_into(data: &[u8], n: usize, out: &mut Vec<i16>) -> Result<(), DecodeError> {
let codes = crate::u32::U32Classic.decode(data, n)?;
let mut acc: i32 = 0;
for zz in codes {
let delta = ((zz >> 1) as i32) ^ -((zz & 1) as i32);
acc = acc.wrapping_add(delta);
out.push(acc as i16);
}
Ok(())
}
#[cfg(feature = "alloc")]
pub fn decode_svbzd_fused(data: &[u8], n: usize) -> Result<Vec<i16>, DecodeError> {
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
let mut out = Vec::with_capacity(n);
decode_svbzd_fused_into(data, n, &mut out)?;
Ok(out)
}
#[cfg(feature = "alloc")]
pub fn decode_svbzd_fused_into(
data: &[u8],
n: usize,
out: &mut Vec<i16>,
) -> Result<(), DecodeError> {
svbzd_fused::decode_into(data, n, out)
}
#[cfg(feature = "alloc")]
pub fn decode_svbzd_fused_from(
data: &[u8],
n: usize,
initial_carry: i32,
) -> Result<Vec<i16>, DecodeError> {
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
let mut out = Vec::with_capacity(n);
decode_svbzd_fused_from_into(data, n, initial_carry, &mut out)?;
Ok(out)
}
#[cfg(feature = "alloc")]
pub fn decode_svbzd_fused_from_into(
data: &[u8],
n: usize,
initial_carry: i32,
out: &mut Vec<i16>,
) -> Result<(), DecodeError> {
svbzd_fused::decode_from_into(data, n, initial_carry, out)
}
#[cfg(all(test, feature = "alloc"))]
mod svbzd_tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[test]
fn roundtrip_empty() {
assert_eq!(decode_svbzd(&encode_svbzd(&[]), 0).unwrap(), &[] as &[i16]);
}
#[test]
fn roundtrip_single() {
for v in [0i16, 1, -1, i16::MIN, i16::MAX] {
assert_eq!(decode_svbzd(&encode_svbzd(&[v]), 1).unwrap(), [v]);
}
}
#[test]
fn roundtrip_ramp() {
let samples: Vec<i16> = (0..128).collect();
assert_eq!(decode_svbzd(&encode_svbzd(&samples), 128).unwrap(), samples);
}
#[test]
fn roundtrip_extremes() {
let samples = vec![i16::MIN, i16::MAX, i16::MIN, i16::MAX];
assert_eq!(decode_svbzd(&encode_svbzd(&samples), 4).unwrap(), samples);
}
#[test]
fn fused_matches_3pass() {
let samples: Vec<i16> = (0..1024)
.map(|i| {
((i as i32 % 500 - 250) as i16).wrapping_add((i as i16).wrapping_mul(37) % 7 - 3)
})
.collect();
let enc = encode_svbzd(&samples);
assert_eq!(
decode_svbzd_fused(&enc, samples.len()).unwrap(),
decode_svbzd(&enc, samples.len()).unwrap(),
);
}
#[test]
fn fused_matches_3pass_extremes() {
let samples = vec![i16::MIN, i16::MAX, 0, -1, 1, i16::MIN, i16::MAX, 0];
let enc = encode_svbzd(&samples);
assert_eq!(
decode_svbzd_fused(&enc, samples.len()).unwrap(),
decode_svbzd(&enc, samples.len()).unwrap(),
);
}
#[test]
fn fused_from_parallel_split() {
let n = 128usize;
let samples: Vec<i16> = (0..n).map(|i| (i as i16 * 17).wrapping_sub(500)).collect();
let enc = encode_svbzd(&samples);
let n_half = (n / 2) & !3;
let ctrl_len = n.div_ceil(4);
let ctrl_half = n_half / 4;
let ctrl = &enc[..ctrl_len];
let data_bytes = &enc[ctrl_len..];
let mid_data_off: usize = ctrl[..ctrl_half]
.iter()
.map(|&cb| crate::u32::shuffle::DATA_LEN[cb as usize] as usize)
.sum();
let mid_carry = samples[n_half - 1] as i32;
let mut stream_a = ctrl[..ctrl_half].to_vec();
stream_a.extend_from_slice(&data_bytes[..mid_data_off]);
let mut stream_b = ctrl[ctrl_half..ctrl_len].to_vec();
stream_b.extend_from_slice(&data_bytes[mid_data_off..]);
let out_a = decode_svbzd_fused_from(&stream_a, n_half, 0).unwrap();
let out_b = decode_svbzd_fused_from(&stream_b, n - n_half, mid_carry).unwrap();
let mut combined = out_a;
combined.extend_from_slice(&out_b);
assert_eq!(combined, samples);
}
}