use crate::FORMAT_MAJOR;
pub const BILX_MAGIC: [u8; 4] = *b"BILX";
pub const BILX_MAJOR: u8 = 1;
pub const BILX_MINOR: u8 = 0;
pub const BILX_VERSION: u16 = u16::from_ne_bytes([BILX_MAJOR, BILX_MINOR]);
pub(crate) const CHANNEL_META_SIZE: usize = 24;
#[derive(Debug)]
pub(crate) enum MultiError {
NoChannels,
LengthMismatch {
channel: usize,
expected: usize,
got: usize,
},
Truncated,
BadMagic([u8; 4]),
BadVersion(u16),
UnsupportedTransform(u8),
UnsupportedFlag(u8),
Core(String),
}
impl std::fmt::Display for MultiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MultiError::NoChannels => f.write_str("at least one channel is required"),
MultiError::LengthMismatch {
channel,
expected,
got,
} => write!(
f,
"channel {channel} length {got} != shared length {expected}"
),
MultiError::Truncated => f.write_str("container truncated"),
MultiError::BadMagic(m) => write!(f, "bad magic {m:?}"),
MultiError::BadVersion(v) => write!(f, "unsupported container version {v}"),
MultiError::UnsupportedTransform(t) => {
write!(f, "unsupported inter-channel transform {t}")
}
MultiError::UnsupportedFlag(x) => write!(f, "unsupported flags {x:#x}"),
MultiError::Core(s) => write!(f, "channel codec error: {s}"),
}
}
}
impl std::error::Error for MultiError {}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub(crate) struct ChannelMeta {
pub min: f32,
pub max: f32,
pub mean: f32,
pub quant_multiplier: f32,
pub scale: u8,
pub entropy_coder: u8,
pub compressed_size: u32,
}
impl ChannelMeta {
fn to_bytes(self) -> [u8; CHANNEL_META_SIZE] {
let mut b = [0u8; CHANNEL_META_SIZE];
b[0..4].copy_from_slice(&self.min.to_bits().to_le_bytes());
b[4..8].copy_from_slice(&self.max.to_bits().to_le_bytes());
b[8..12].copy_from_slice(&self.mean.to_bits().to_le_bytes());
b[12..16].copy_from_slice(&self.quant_multiplier.to_bits().to_le_bytes());
b[16] = self.scale;
b[17] = self.entropy_coder;
b[20..24].copy_from_slice(&self.compressed_size.to_le_bytes());
b
}
fn from_bytes(b: &[u8]) -> Self {
let rd = |s: &[u8]| f32::from_bits(u32::from_le_bytes(s.try_into().unwrap()));
ChannelMeta {
min: rd(&b[0..4]),
max: rd(&b[4..8]),
mean: rd(&b[8..12]),
quant_multiplier: rd(&b[12..16]),
scale: b[16],
entropy_coder: b[17],
compressed_size: u32::from_le_bytes(b[20..24].try_into().unwrap()),
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct ContainerHeader {
pub n_channels: u16,
pub signal_length: u32,
pub compression_method: [u8; 4],
pub levels: u8,
pub inter_channel_transform: u8,
pub flags: u8,
pub data_type: u8,
pub transform_data_len: u16,
pub reserved: [u8; 8],
}
pub(crate) const CONTAINER_HEADER_SIZE: usize = size_of::<ContainerHeader>();
impl ContainerHeader {
fn to_bytes(self) -> [u8; CONTAINER_HEADER_SIZE] {
let mut b = [0u8; CONTAINER_HEADER_SIZE];
b[0..4].copy_from_slice(&BILX_MAGIC);
b[4..6].copy_from_slice(&BILX_VERSION.to_le_bytes());
b[6..8].copy_from_slice(&self.n_channels.to_le_bytes());
b[8..12].copy_from_slice(&self.signal_length.to_le_bytes());
b[12..16].copy_from_slice(&self.compression_method);
b[16] = self.levels;
b[17] = self.inter_channel_transform;
b[18] = self.flags;
b[19] = self.data_type;
b[20..22].copy_from_slice(&self.transform_data_len.to_le_bytes());
b
}
fn from_bytes(b: &[u8]) -> Result<Self, MultiError> {
if b.len() < CONTAINER_HEADER_SIZE {
return Err(MultiError::Truncated);
}
let magic: [u8; 4] = b[0..4].try_into().unwrap();
if magic != BILX_MAGIC {
return Err(MultiError::BadMagic(magic));
}
let (major, minor) = (b[4], b[5]);
if major != FORMAT_MAJOR {
return Err(MultiError::BadVersion(u16::from_le_bytes([major, minor])));
}
Ok(ContainerHeader {
n_channels: u16::from_le_bytes(b[6..8].try_into().unwrap()),
signal_length: u32::from_le_bytes(b[8..12].try_into().unwrap()),
compression_method: b[12..16].try_into().unwrap(),
levels: b[16],
inter_channel_transform: b[17],
flags: b[18],
data_type: b[19],
transform_data_len: u16::from_le_bytes(b[20..22].try_into().unwrap()),
reserved: [0; 8],
})
}
}
pub(crate) trait ChannelCore {
type Err: std::fmt::Display;
fn encode(
&self,
data: &[f32],
method: [u8; 4],
levels: u8,
) -> Result<(ChannelMeta, Vec<u8>), Self::Err>;
fn decode(
&self,
meta: &ChannelMeta,
payload: &[u8],
method: [u8; 4],
levels: u8,
signal_length: u32,
) -> Result<Vec<f32>, Self::Err>;
}
fn assemble(header: &ContainerHeader, metas: &[ChannelMeta], payloads: &[Vec<u8>]) -> Vec<u8> {
let body: usize = payloads.iter().map(|p| p.len()).sum();
let mut out =
Vec::with_capacity(CONTAINER_HEADER_SIZE + metas.len() * CHANNEL_META_SIZE + body);
out.extend_from_slice(&header.to_bytes());
for m in metas {
out.extend_from_slice(&m.to_bytes());
}
for p in payloads {
out.extend_from_slice(p);
}
out
}
#[allow(clippy::type_complexity)]
fn parse(bytes: &[u8]) -> Result<(ContainerHeader, Vec<ChannelMeta>, Vec<&[u8]>), MultiError> {
let header = ContainerHeader::from_bytes(bytes)?;
if header.inter_channel_transform != 0 {
return Err(MultiError::UnsupportedTransform(
header.inter_channel_transform,
));
}
if header.flags != 0 {
return Err(MultiError::UnsupportedFlag(header.flags));
}
let n = header.n_channels as usize;
if n == 0 {
return Err(MultiError::NoChannels);
}
let mut pos = CONTAINER_HEADER_SIZE
.checked_add(header.transform_data_len as usize)
.ok_or(MultiError::Truncated)?;
let metas_end = pos
.checked_add(
n.checked_mul(CHANNEL_META_SIZE)
.ok_or(MultiError::Truncated)?,
)
.ok_or(MultiError::Truncated)?;
if bytes.len() < metas_end {
return Err(MultiError::Truncated);
}
let mut metas = Vec::with_capacity(n);
for _ in 0..n {
metas.push(ChannelMeta::from_bytes(
&bytes[pos..pos + CHANNEL_META_SIZE],
));
pos += CHANNEL_META_SIZE;
}
let mut payloads = Vec::with_capacity(n);
for m in &metas {
let cs = m.compressed_size as usize;
let end = pos.checked_add(cs).ok_or(MultiError::Truncated)?;
if end > bytes.len() {
return Err(MultiError::Truncated);
}
payloads.push(&bytes[pos..end]);
pos = end;
}
Ok((header, metas, payloads))
}
pub(crate) fn compress_multi_with<C: ChannelCore>(
core: &C,
channels: &[&[f32]],
method: [u8; 4],
levels: u8,
data_type: u8,
) -> Result<Vec<u8>, MultiError> {
if channels.is_empty() {
return Err(MultiError::NoChannels);
}
let len = channels[0].len();
for (i, c) in channels.iter().enumerate() {
if c.len() != len {
return Err(MultiError::LengthMismatch {
channel: i,
expected: len,
got: c.len(),
});
}
}
let mut metas = Vec::with_capacity(channels.len());
let mut payloads = Vec::with_capacity(channels.len());
for c in channels {
let (mut meta, payload) = core
.encode(c, method, levels)
.map_err(|e| MultiError::Core(e.to_string()))?;
meta.compressed_size = payload.len() as u32; metas.push(meta);
payloads.push(payload);
}
let header = ContainerHeader {
n_channels: channels.len() as u16,
signal_length: len as u32,
compression_method: method,
levels,
inter_channel_transform: 0,
flags: 0,
data_type,
transform_data_len: 0,
reserved: [0; 8],
};
Ok(assemble(&header, &metas, &payloads))
}
pub(crate) fn decompress_multi_with<C: ChannelCore>(
core: &C,
bytes: &[u8],
) -> Result<Vec<Vec<f32>>, MultiError> {
let (header, metas, payloads) = parse(bytes)?;
let mut out = Vec::with_capacity(metas.len());
for (meta, payload) in metas.iter().zip(payloads.iter()) {
out.push(
core.decode(
meta,
payload,
header.compression_method,
header.levels,
header.signal_length,
)
.map_err(|e| MultiError::Core(e.to_string()))?,
);
}
Ok(out)
}