use crate::iq::{Cf64, IqError};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TwoBitCode {
TwosComplement,
SignMagnitude,
OffsetBinary,
}
impl TwoBitCode {
const fn table(self) -> [i8; 4] {
match self {
TwoBitCode::TwosComplement => [1, 3, -3, -1],
TwoBitCode::SignMagnitude => [1, 3, -1, -3],
TwoBitCode::OffsetBinary => [-3, -1, 1, 3],
}
}
pub fn level(self, c: u8) -> i8 {
self.table()[(c & 3) as usize]
}
pub fn code(self, v: f64) -> u8 {
let level: i8 = if v >= 2.0 {
3
} else if v >= 0.0 {
1
} else if v >= -2.0 {
-1
} else {
-3
};
self.table()
.iter()
.position(|&l| l == level)
.expect("every level appears in each table") as u8
}
fn tag(self) -> &'static str {
match self {
TwoBitCode::TwosComplement => "tc",
TwoBitCode::SignMagnitude => "sm",
TwoBitCode::OffsetBinary => "ob",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BitOrder {
MsbFirst,
LsbFirst,
}
impl BitOrder {
pub fn shift(self, slot: u8) -> u8 {
self.shift_for(2, slot)
}
pub fn shift_for(self, bits: u8, slot: u8) -> u8 {
match self {
BitOrder::MsbFirst => 8 - bits * (slot + 1),
BitOrder::LsbFirst => bits * slot,
}
}
fn tag(self) -> &'static str {
match self {
BitOrder::MsbFirst => "msb",
BitOrder::LsbFirst => "lsb",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Endian {
Little,
Big,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Justify {
Left,
Right,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Encoding {
I8,
I16Le,
I16Be,
F32Le,
F32Be,
TwoBit {
code: TwoBitCode,
order: BitOrder,
},
TwoBitPerByte {
code: TwoBitCode,
},
I4 {
order: BitOrder,
},
U4 {
order: BitOrder,
},
U8,
I12 {
endian: Endian,
justify: Justify,
},
U16 {
endian: Endian,
},
U12 {
endian: Endian,
justify: Justify,
},
}
impl Encoding {
pub fn bits(self) -> usize {
match self {
Encoding::I8 => 8,
Encoding::I16Le | Encoding::I16Be => 16,
Encoding::F32Le | Encoding::F32Be => 32,
Encoding::TwoBit { .. } => 2,
Encoding::I4 { .. } | Encoding::U4 { .. } => 4,
Encoding::TwoBitPerByte { .. } | Encoding::U8 => 8,
Encoding::I12 { .. } | Encoding::U16 { .. } | Encoding::U12 { .. } => 16,
}
}
pub fn unit_bytes(self) -> usize {
self.bits().div_ceil(8)
}
pub(crate) fn packed_bits(self) -> Option<u8> {
match self {
Encoding::TwoBit { .. } => Some(2),
Encoding::I4 { .. } | Encoding::U4 { .. } => Some(4),
_ => None,
}
}
pub(crate) fn packed_shift(self, slot: u8) -> u8 {
match self {
Encoding::TwoBit { order, .. } => order.shift_for(2, slot),
Encoding::I4 { order } | Encoding::U4 { order } => order.shift_for(4, slot),
_ => unreachable!("only packed encodings have slots"),
}
}
fn packed_level(self, c: u8) -> f64 {
match self {
Encoding::TwoBit { code, .. } => code.level(c) as f64,
Encoding::I4 { .. } => (((c & 0x0f) << 4) as i8 >> 4) as f64,
Encoding::U4 { .. } => 2.0 * (c & 0x0f) as f64 - 15.0,
_ => unreachable!("only packed encodings have codes"),
}
}
pub(crate) fn packed_code(self, v: f64) -> (u8, bool) {
match self {
Encoding::TwoBit { code, .. } => (code.code(v), false),
Encoding::I4 { .. } => {
let (q, c) = quantise(v, -8.0, 7.0);
((q as i8 as u8) & 0x0f, c)
}
Encoding::U4 { .. } => {
let (q, c) = quantise((v + 15.0) / 2.0, 0.0, 15.0);
(q as u8, c)
}
_ => unreachable!("only packed encodings have codes"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Components {
Iq,
Qi,
Real,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SampleFormat {
pub encoding: Encoding,
pub components: Components,
}
impl SampleFormat {
pub const CI8: SampleFormat = SampleFormat::iq(Encoding::I8);
pub const CI16_LE: SampleFormat = SampleFormat::iq(Encoding::I16Le);
pub const CI16_BE: SampleFormat = SampleFormat::iq(Encoding::I16Be);
pub const CF32_LE: SampleFormat = SampleFormat::iq(Encoding::F32Le);
pub const CF32_BE: SampleFormat = SampleFormat::iq(Encoding::F32Be);
pub const fn iq(e: Encoding) -> Self {
SampleFormat {
encoding: e,
components: Components::Iq,
}
}
pub const fn real(e: Encoding) -> Self {
SampleFormat {
encoding: e,
components: Components::Real,
}
}
pub fn is_complex(self) -> bool {
self.components != Components::Real
}
pub fn elements_per_sample(self) -> usize {
if self.is_complex() {
2
} else {
1
}
}
pub fn bits_per_sample(self) -> usize {
self.encoding.bits() * self.elements_per_sample()
}
pub fn samples_in_bytes(self, bytes: u64) -> u64 {
bytes * 8 / self.bits_per_sample() as u64
}
pub fn name(self) -> String {
let p = if self.is_complex() { "c" } else { "r" };
let e = match self.encoding {
Encoding::I8 => "i8".to_string(),
Encoding::I16Le => "i16_le".to_string(),
Encoding::I16Be => "i16_be".to_string(),
Encoding::F32Le => "f32_le".to_string(),
Encoding::F32Be => "f32_be".to_string(),
Encoding::TwoBit { code, order } => format!("2{}_{}", code.tag(), order.tag()),
Encoding::TwoBitPerByte { code } => format!("2{}_byte", code.tag()),
Encoding::I4 { order } => format!("i4_{}", order.tag()),
Encoding::U4 { order } => format!("u4_{}", order.tag()),
Encoding::U8 => "u8".to_string(),
Encoding::I12 { endian, justify } => format!(
"i12{}_{}",
if justify == Justify::Left { "l" } else { "r" },
if endian == Endian::Little { "le" } else { "be" }
),
Encoding::U16 { endian } => {
format!("u16_{}", if endian == Endian::Little { "le" } else { "be" })
}
Encoding::U12 { endian, justify } => format!(
"u12{}_{}",
if justify == Justify::Left { "l" } else { "r" },
if endian == Endian::Little { "le" } else { "be" }
),
};
let s = if self.components == Components::Qi {
"_qi"
} else {
""
};
format!("{p}{e}{s}")
}
pub fn parse(name: &str) -> Result<Self, IqError> {
let bad = || {
IqError::Format(format!(
"unknown sample format {name:?}: expected e.g. ci8, cu8, ci16_le, ci16_be, \
cf32_le, cf32_be, ri16_le, ci12r_le, ci12l_be, cu16_le, cu12r_be, ci4_msb, cu4_lsb, c2tc_msb, \
r2sm_lsb, c2sm_byte, optionally ending _qi for complex"
))
};
let (body, qi) = match name.strip_suffix("_qi") {
Some(b) => (b, true),
None => (name, false),
};
let (components, rest) = match body.split_at_checked(1) {
Some(("c", r)) => (if qi { Components::Qi } else { Components::Iq }, r),
Some(("r", r)) if !qi => (Components::Real, r),
_ => return Err(bad()),
};
let encoding = match rest {
"i8" => Encoding::I8,
"i16_le" => Encoding::I16Le,
"i16_be" => Encoding::I16Be,
"f32_le" => Encoding::F32Le,
"f32_be" => Encoding::F32Be,
"u8" => Encoding::U8,
"i12l_le" | "i12l_be" | "i12r_le" | "i12r_be" => Encoding::I12 {
justify: if &rest[3..4] == "l" {
Justify::Left
} else {
Justify::Right
},
endian: if rest.ends_with("le") {
Endian::Little
} else {
Endian::Big
},
},
"u16_le" => Encoding::U16 {
endian: Endian::Little,
},
"u16_be" => Encoding::U16 {
endian: Endian::Big,
},
"u12l_le" | "u12l_be" | "u12r_le" | "u12r_be" => Encoding::U12 {
justify: if &rest[3..4] == "l" {
Justify::Left
} else {
Justify::Right
},
endian: if rest.ends_with("le") {
Endian::Little
} else {
Endian::Big
},
},
other => {
let order_of = |o: &str| match o {
"msb" => Some(BitOrder::MsbFirst),
"lsb" => Some(BitOrder::LsbFirst),
_ => None,
};
if let Some(o) = other.strip_prefix("i4_") {
Encoding::I4 {
order: order_of(o).ok_or_else(bad)?,
}
} else if let Some(o) = other.strip_prefix("u4_") {
Encoding::U4 {
order: order_of(o).ok_or_else(bad)?,
}
} else {
let t = other.strip_prefix('2').ok_or_else(bad)?;
let (c, o) = t.split_once('_').ok_or_else(bad)?;
let code = match c {
"tc" => TwoBitCode::TwosComplement,
"sm" => TwoBitCode::SignMagnitude,
"ob" => TwoBitCode::OffsetBinary,
_ => return Err(bad()),
};
if o == "byte" {
Encoding::TwoBitPerByte { code }
} else {
Encoding::TwoBit {
code,
order: order_of(o).ok_or_else(bad)?,
}
}
}
}
};
Ok(SampleFormat {
encoding,
components,
})
}
pub fn all() -> Vec<SampleFormat> {
let mut encs = vec![
Encoding::I8,
Encoding::U8,
Encoding::I16Le,
Encoding::I16Be,
Encoding::F32Le,
Encoding::F32Be,
];
for endian in [Endian::Little, Endian::Big] {
for justify in [Justify::Left, Justify::Right] {
encs.push(Encoding::I12 { endian, justify });
encs.push(Encoding::U12 { endian, justify });
}
encs.push(Encoding::U16 { endian });
}
for code in [
TwoBitCode::TwosComplement,
TwoBitCode::SignMagnitude,
TwoBitCode::OffsetBinary,
] {
for order in [BitOrder::MsbFirst, BitOrder::LsbFirst] {
encs.push(Encoding::TwoBit { code, order });
}
encs.push(Encoding::TwoBitPerByte { code });
}
for order in [BitOrder::MsbFirst, BitOrder::LsbFirst] {
encs.push(Encoding::I4 { order });
encs.push(Encoding::U4 { order });
}
let mut out = Vec::new();
for e in encs {
for components in [Components::Iq, Components::Qi, Components::Real] {
out.push(SampleFormat {
encoding: e,
components,
});
}
}
out
}
}
impl std::fmt::Display for SampleFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.name())
}
}
fn word(endian: Endian, c: &[u8]) -> u16 {
match endian {
Endian::Little => u16::from_le_bytes([c[0], c[1]]),
Endian::Big => u16::from_be_bytes([c[0], c[1]]),
}
}
pub(crate) fn for_each_element(enc: Encoding, bytes: &[u8], mut f: impl FnMut(f64)) {
match enc {
Encoding::I8 => bytes.iter().for_each(|&b| f(b as i8 as f64)),
Encoding::I16Le => bytes
.chunks_exact(2)
.for_each(|c| f(i16::from_le_bytes([c[0], c[1]]) as f64)),
Encoding::I16Be => bytes
.chunks_exact(2)
.for_each(|c| f(i16::from_be_bytes([c[0], c[1]]) as f64)),
Encoding::F32Le => bytes
.chunks_exact(4)
.for_each(|c| f(f32::from_le_bytes([c[0], c[1], c[2], c[3]]) as f64)),
Encoding::F32Be => bytes
.chunks_exact(4)
.for_each(|c| f(f32::from_be_bytes([c[0], c[1], c[2], c[3]]) as f64)),
Encoding::TwoBitPerByte { code } => bytes.iter().for_each(|&b| f(code.level(b) as f64)),
Encoding::U8 => bytes.iter().for_each(|&b| f(2.0 * b as f64 - 255.0)),
Encoding::U16 { endian } => bytes
.chunks_exact(2)
.for_each(|c| f(2.0 * word(endian, c) as f64 - 65_535.0)),
Encoding::U12 { endian, justify } => bytes.chunks_exact(2).for_each(|c| {
let w = word(endian, c);
let code = match justify {
Justify::Left => w >> 4,
Justify::Right => w & 0x0fff,
};
f(2.0 * code as f64 - 4_095.0)
}),
Encoding::I12 { endian, justify } => bytes.chunks_exact(2).for_each(|c| {
let w = word(endian, c);
let v = match justify {
Justify::Left => (w as i16) >> 4,
Justify::Right => ((w << 4) as i16) >> 4,
};
f(v as f64)
}),
Encoding::TwoBit { .. } | Encoding::I4 { .. } | Encoding::U4 { .. } => {
let bits = enc.packed_bits().expect("packed");
for &b in bytes {
for slot in 0..8 / bits {
f(enc.packed_level(b >> enc.packed_shift(slot)));
}
}
}
}
}
fn quantise(v: f64, lo: f64, hi: f64) -> (f64, bool) {
if !v.is_finite() {
return (0.0, true);
}
let q = v.round();
(q.clamp(lo, hi), q < lo || q > hi)
}
pub(crate) fn push_element(enc: Encoding, v: f64, out: &mut Vec<u8>) -> bool {
match enc {
Encoding::I8 => {
let (q, c) = quantise(v, -128.0, 127.0);
out.push(q as i8 as u8);
c
}
Encoding::I16Le | Encoding::I16Be => {
let (q, c) = quantise(v, -32_768.0, 32_767.0);
let q = q as i16;
out.extend_from_slice(&if enc == Encoding::I16Le {
q.to_le_bytes()
} else {
q.to_be_bytes()
});
c
}
Encoding::F32Le => {
out.extend_from_slice(&(v as f32).to_le_bytes());
false
}
Encoding::F32Be => {
out.extend_from_slice(&(v as f32).to_be_bytes());
false
}
Encoding::U8 => {
let (q, c) = quantise((v + 255.0) / 2.0, 0.0, 255.0);
out.push(q as u8);
c
}
Encoding::TwoBitPerByte { code } => {
out.push(code.code(v));
false
}
Encoding::I12 { endian, justify } => {
let (q, c) = quantise(v, -2048.0, 2047.0);
let w = match justify {
Justify::Left => ((q as i16) << 4) as u16,
Justify::Right => q as i16 as u16,
};
out.extend_from_slice(&match endian {
Endian::Little => w.to_le_bytes(),
Endian::Big => w.to_be_bytes(),
});
c
}
Encoding::U16 { endian } => {
let (q, c) = quantise((v + 65_535.0) / 2.0, 0.0, 65_535.0);
let w = q as u16;
out.extend_from_slice(&match endian {
Endian::Little => w.to_le_bytes(),
Endian::Big => w.to_be_bytes(),
});
c
}
Encoding::U12 { endian, justify } => {
let (q, c) = quantise((v + 4_095.0) / 2.0, 0.0, 4_095.0);
let w = match justify {
Justify::Left => (q as u16) << 4,
Justify::Right => q as u16,
};
out.extend_from_slice(&match endian {
Endian::Little => w.to_le_bytes(),
Endian::Big => w.to_be_bytes(),
});
c
}
Encoding::TwoBit { .. } | Encoding::I4 { .. } | Encoding::U4 { .. } => {
unreachable!("packed sub-byte elements are packed by the caller")
}
}
}
pub(crate) fn sample_elements(c: Components, s: Cf64) -> ([f64; 2], usize) {
match c {
Components::Iq => ([s.re, s.im], 2),
Components::Qi => ([s.im, s.re], 2),
Components::Real => ([s.re, 0.0], 1),
}
}
pub fn decode_samples(format: SampleFormat, bytes: &[u8], scale: f64) -> Vec<Cf64> {
use crate::realdata::iqif::{load_iq, IqFormat};
let legacy = match format {
SampleFormat::CI8 => Some(IqFormat::Int8),
SampleFormat::CI16_LE => Some(IqFormat::Int16Le),
_ => None,
};
if let Some(f) = legacy {
return load_iq(bytes, f).into_iter().map(|s| s * scale).collect();
}
let mut elems = Vec::with_capacity(bytes.len() * 8 / format.encoding.bits());
for_each_element(format.encoding, bytes, |v| elems.push(v * scale));
match format.components {
Components::Iq => elems
.chunks_exact(2)
.map(|c| Cf64::new(c[0], c[1]))
.collect(),
Components::Qi => elems
.chunks_exact(2)
.map(|c| Cf64::new(c[1], c[0]))
.collect(),
Components::Real => elems.into_iter().map(|v| Cf64::new(v, 0.0)).collect(),
}
}
pub fn encode_samples(format: SampleFormat, samples: &[Cf64], scale: f64) -> (Vec<u8>, u64) {
let mut w = super::stream::IqWriter::new(Vec::new(), format).with_scale(scale);
crate::iq::IqSink::write(&mut w, samples).expect("writing to a Vec cannot fail");
crate::iq::IqSink::finish(&mut w).expect("writing to a Vec cannot fail");
let clipped = w.clipped();
(w.into_inner(), clipped)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn names_round_trip_for_every_format() {
for f in SampleFormat::all() {
assert_eq!(SampleFormat::parse(&f.name()).unwrap(), f, "{}", f.name());
}
assert_eq!(SampleFormat::CI16_LE.name(), "ci16_le");
assert!(SampleFormat::parse("ri8_qi").is_err());
assert!(SampleFormat::parse("cu16_x").is_err());
assert!(SampleFormat::parse("cu12x_le").is_err());
assert!(SampleFormat::parse("ci4_mid").is_err());
assert!(SampleFormat::parse("ci12x_le").is_err());
}
#[test]
fn two_bit_quantiser_inverts_every_level() {
for code in [
TwoBitCode::TwosComplement,
TwoBitCode::SignMagnitude,
TwoBitCode::OffsetBinary,
] {
for c in 0..4u8 {
assert_eq!(code.code(code.level(c) as f64), c);
}
assert_eq!(code.level(code.code(0.4)), 1);
assert_eq!(code.level(code.code(-7.0)), -3);
}
}
}