use crate::iq::{Cf64, IqError, IqSource, SampleSpec};
use crate::realdata::ion_sdr::{self, SdrLayout};
use std::io::Read;
pub struct SdrxSource<R> {
inner: R,
layout: SdrLayout,
spec: SampleSpec,
unit: Vec<u8>,
decoded: Vec<Cf64>,
pos_in_decoded: usize,
remaining: u64,
header_left: u64,
}
impl<R: Read> SdrxSource<R> {
pub fn new(inner: R, layout: SdrLayout, data_len: u64) -> Result<Self, IqError> {
if layout.samples_per_chunk() == 0 {
return Err(IqError::Format(
".sdrx layout: a chunk holds no whole lump".into(),
));
}
let remaining = layout.sample_count(data_len as usize) as u64;
let spec = SampleSpec {
fs_hz: layout.sample_rate_hz,
center_hz: layout.center_freq_hz,
if_hz: layout.translated_freq_hz,
};
let header_left = layout.header_bytes as u64;
let mut unit_layout = layout.clone();
unit_layout.header_bytes = 0;
unit_layout.footer_bytes = 0;
Ok(SdrxSource {
inner,
unit: vec![0; layout.chunk_bytes()],
layout: unit_layout,
spec,
decoded: Vec::new(),
pos_in_decoded: 0,
remaining,
header_left,
})
}
pub fn remaining(&self) -> u64 {
self.remaining + (self.decoded.len() - self.pos_in_decoded) as u64
}
pub fn layout(&self) -> &SdrLayout {
&self.layout
}
fn refill(&mut self) -> Result<bool, IqError> {
if self.remaining == 0 {
return Ok(false);
}
if self.header_left > 0 {
let skipped = std::io::copy(
&mut (&mut self.inner).take(self.header_left),
&mut std::io::sink(),
)
.map_err(|e| IqError::Io(e.to_string()))?;
if skipped < self.header_left {
return Err(IqError::Format(".sdrx data ends inside its header".into()));
}
self.header_left = 0;
}
self.inner
.read_exact(&mut self.unit)
.map_err(|e| IqError::Io(format!(".sdrx data: {e}")))?;
let per = self.layout.samples_per_chunk();
let n = (per as u64).min(self.remaining) as usize;
self.decoded = ion_sdr::decode(&self.layout, &self.unit, 0, n).map_err(IqError::Format)?;
self.pos_in_decoded = 0;
self.remaining -= n as u64;
Ok(true)
}
}
impl<R: Read> IqSource for SdrxSource<R> {
fn spec(&self) -> SampleSpec {
self.spec
}
fn read(&mut self, buf: &mut [Cf64]) -> Result<usize, IqError> {
let mut k = 0;
while k < buf.len() {
if self.pos_in_decoded == self.decoded.len() && !self.refill()? {
break;
}
let take = (self.decoded.len() - self.pos_in_decoded).min(buf.len() - k);
buf[k..k + take]
.copy_from_slice(&self.decoded[self.pos_in_decoded..self.pos_in_decoded + take]);
self.pos_in_decoded += take;
k += take;
}
Ok(k)
}
}
pub fn layout_label(l: &SdrLayout) -> String {
use ion_sdr::{Encoding as E, SampleFormat as F};
let enc = match l.encoding {
E::TwosComplement => "tc",
E::OffsetBinary => "ob",
E::SignMagnitude => "sm",
E::Sign => "sign",
};
let fmt = match l.format {
F::Iq => "iq",
F::Qi => "qi",
F::If => "real",
};
format!(
"sdrx {}-bit {enc} {fmt} ({} bits/sample)",
l.quantization, l.packed_bits
)
}
#[cfg(not(target_arch = "wasm32"))]
mod files {
use super::*;
use std::path::{Path, PathBuf};
pub fn read_sdrx(path: &Path) -> Result<(SdrLayout, PathBuf), IqError> {
let text = std::fs::read_to_string(path)
.map_err(|e| IqError::Io(format!("{}: {e}", path.display())))?;
let layout = ion_sdr::parse_sdrx(&text)
.map_err(|e| IqError::Format(format!("{}: {e}", path.display())))?;
let dir = path.parent().unwrap_or(Path::new("."));
let data = dir.join(&layout.url);
Ok((layout, data))
}
pub fn open_sdrx(path: &Path) -> Result<(SdrxSource<std::fs::File>, PathBuf, u64), IqError> {
let (layout, data) = read_sdrx(path)?;
let f = std::fs::File::open(&data)
.map_err(|e| IqError::Io(format!("{}: {e}", data.display())))?;
let len = f
.metadata()
.map_err(|e| IqError::Io(format!("{}: {e}", data.display())))?
.len();
Ok((SdrxSource::new(f, layout, len)?, data, len))
}
}
#[cfg(not(target_arch = "wasm32"))]
pub use files::{open_sdrx, read_sdrx};