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 {
match self {
BitOrder::MsbFirst => 6 - 2 * slot,
BitOrder::LsbFirst => 2 * slot,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Encoding {
I8,
I16Le,
I16Be,
F32Le,
F32Be,
TwoBit {
code: TwoBitCode,
order: BitOrder,
},
}
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,
}
}
pub fn unit_bytes(self) -> usize {
self.bits().div_ceil(8)
}
}
#[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(),
if order == BitOrder::MsbFirst {
"msb"
} else {
"lsb"
}
),
};
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, ci16_le, ci16_be, cf32_le, \
cf32_be, ri16_le, c2tc_msb, r2sm_lsb, 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,
two => {
let t = two.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()),
};
let order = match o {
"msb" => BitOrder::MsbFirst,
"lsb" => BitOrder::LsbFirst,
_ => return Err(bad()),
};
Encoding::TwoBit { code, order }
}
};
Ok(SampleFormat {
encoding,
components,
})
}
pub fn all() -> Vec<SampleFormat> {
let mut encs = vec![
Encoding::I8,
Encoding::I16Le,
Encoding::I16Be,
Encoding::F32Le,
Encoding::F32Be,
];
for code in [
TwoBitCode::TwosComplement,
TwoBitCode::SignMagnitude,
TwoBitCode::OffsetBinary,
] {
for order in [BitOrder::MsbFirst, BitOrder::LsbFirst] {
encs.push(Encoding::TwoBit { code, 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())
}
}
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::TwoBit { code, order } => {
for &b in bytes {
for slot in 0..4 {
f(code.level(b >> order.shift(slot)) as f64);
}
}
}
}
}
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::TwoBit { .. } => unreachable!("2-bit 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("cu8").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);
}
}
}