use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use crate::signal::FormatDecoder;
use crate::{Error, Result, Sample, SignalInfo};
pub struct SignalReader {
decoder: Box<dyn FormatDecoder>,
reader: BufReader<File>,
signal_info: SignalInfo,
signal_index_in_file: usize,
signals_in_file: usize,
frame_buffer: Vec<Sample>,
current_sample: u64,
bytes_per_sample: usize,
initial_offset: u64,
sampling_frequency: Option<f64>,
}
impl SignalReader {
pub(crate) fn new(
base_path: &Path,
signal_info: &SignalInfo,
all_signals: &[SignalInfo],
signal_index: usize,
sampling_frequency: Option<f64>,
) -> Result<Self> {
let signal_path = base_path.join(&signal_info.file_name);
let file = File::open(&signal_path).map_err(|e| {
Error::InvalidPath(format!(
"Failed to open signal file '{}': {}",
signal_path.display(),
e
))
})?;
let mut reader = BufReader::new(file);
let initial_value = signal_info.initial_value.unwrap_or(0);
let decoder = crate::signal::get_decoder(signal_info.format, initial_value)?;
let bytes_per_sample = decoder.bytes_per_sample().unwrap_or(0);
let initial_offset = signal_info.byte_offset.unwrap_or(0);
if initial_offset > 0 {
use std::io::Seek;
reader.seek(std::io::SeekFrom::Start(initial_offset))?;
}
let mut signal_index_in_file = 0;
let mut signals_in_file = 0;
for (idx, sig) in all_signals.iter().enumerate() {
if sig.file_name == signal_info.file_name && sig.format == signal_info.format {
if idx == signal_index {
signal_index_in_file = signals_in_file;
}
signals_in_file += 1;
}
}
let frame_buffer = if signals_in_file > 1 {
vec![0; signals_in_file]
} else {
Vec::new()
};
Ok(Self {
decoder,
reader,
signal_info: signal_info.clone(),
signal_index_in_file,
signals_in_file,
frame_buffer,
current_sample: 0,
bytes_per_sample,
initial_offset,
sampling_frequency,
})
}
pub fn read_samples_buf(&mut self, buffer: &mut [Sample]) -> Result<usize> {
if self.signals_in_file <= 1 {
let count = self.decoder.decode_buf(&mut self.reader, buffer)?;
self.current_sample += count as u64;
Ok(count)
} else if self.bytes_per_sample == 0 {
use std::io::Seek;
let mut count = 0;
for sample in buffer.iter_mut() {
self.decoder.reset();
let frame_number = self.current_sample;
let bytes_per_frame = self.decoder.bytes_per_frame(self.signals_in_file)
.ok_or_else(|| Error::InvalidHeader(
"Format does not support frame size calculation for interleaved reading".to_string()
))?;
let byte_offset = self.initial_offset + frame_number * bytes_per_frame as u64;
self.reader.seek(std::io::SeekFrom::Start(byte_offset))?;
let n = self
.decoder
.decode_buf(&mut self.reader, &mut self.frame_buffer)?;
if n == 0 {
break; }
if n < self.signals_in_file {
return Err(Error::InvalidHeader(
"Incomplete frame in interleaved signal file".to_string(),
));
}
*sample = self.frame_buffer[self.signal_index_in_file];
self.current_sample += 1;
count += 1;
}
Ok(count)
} else {
use std::io::Seek;
let mut count = 0;
for sample in buffer.iter_mut() {
let frame_number = self.current_sample;
let byte_offset = self.initial_offset
+ frame_number * (self.signals_in_file * self.bytes_per_sample) as u64;
self.reader.seek(std::io::SeekFrom::Start(byte_offset))?;
let n = self
.decoder
.decode_buf(&mut self.reader, &mut self.frame_buffer)?;
if n == 0 {
break; }
if n < self.signals_in_file {
return Err(Error::InvalidHeader(
"Incomplete frame in interleaved signal file".to_string(),
));
}
*sample = self.frame_buffer[self.signal_index_in_file];
self.current_sample += 1;
count += 1;
}
Ok(count)
}
}
pub fn read_samples(&mut self, count: usize) -> Result<Vec<Sample>> {
let mut buffer = vec![0; count];
let n = self.read_samples_buf(&mut buffer)?;
buffer.truncate(n);
Ok(buffer)
}
pub fn read_physical_buf(&mut self, buffer: &mut [f64]) -> Result<usize> {
let mut adc_buffer = vec![0i32; buffer.len()];
let n = self.read_samples_buf(&mut adc_buffer)?;
for i in 0..n {
buffer[i] = self.to_physical(adc_buffer[i]);
}
Ok(n)
}
pub fn read_physical(&mut self, count: usize) -> Result<Vec<f64>> {
let adc_values = self.read_samples(count)?;
Ok(adc_values.iter().map(|&v| self.to_physical(v)).collect())
}
#[must_use]
pub fn to_physical(&self, adc_value: Sample) -> f64 {
let baseline = f64::from(self.signal_info.baseline());
let gain = self.signal_info.adc_gain();
(f64::from(adc_value) - baseline) / gain
}
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn to_adc(&self, physical_value: f64) -> Sample {
let baseline = f64::from(self.signal_info.baseline());
let gain = self.signal_info.adc_gain();
physical_value.mul_add(gain, baseline).round() as Sample
}
pub const fn samples(&mut self) -> SampleIterator<'_> {
SampleIterator {
reader: self,
buffer: [0; 1],
done: false,
}
}
#[must_use]
pub const fn signal_info(&self) -> &SignalInfo {
&self.signal_info
}
#[must_use]
pub fn description(&self) -> Option<&str> {
self.signal_info.description.as_deref()
}
#[must_use]
pub fn units(&self) -> &str {
self.signal_info.units()
}
#[must_use]
pub fn gain(&self) -> f64 {
self.signal_info.adc_gain()
}
#[must_use]
pub fn baseline(&self) -> i32 {
self.signal_info.baseline()
}
pub fn seek_to_sample(&mut self, sample: u64) -> Result<u64> {
use std::io::Seek;
if self.signals_in_file <= 1 {
if self.bytes_per_sample > 0 {
let byte_offset = self.initial_offset + sample * self.bytes_per_sample as u64;
self.reader.seek(std::io::SeekFrom::Start(byte_offset))?;
self.decoder.reset();
self.current_sample = sample;
Ok(sample)
} else {
Err(Error::InvalidHeader(
"Seeking not supported for this signal format".to_string(),
))
}
} else {
if self.bytes_per_sample == 0 {
let bytes_per_frame = self
.decoder
.bytes_per_frame(self.signals_in_file)
.ok_or_else(|| {
Error::InvalidHeader(
"Seeking not supported for this signal format".to_string(),
)
})?;
let byte_offset = self.initial_offset + sample * bytes_per_frame as u64;
self.reader.seek(std::io::SeekFrom::Start(byte_offset))?;
} else {
let byte_offset = self.initial_offset
+ sample * (self.signals_in_file * self.bytes_per_sample) as u64;
self.reader.seek(std::io::SeekFrom::Start(byte_offset))?;
}
self.decoder.reset();
self.current_sample = sample;
Ok(sample)
}
}
#[must_use]
pub const fn position(&self) -> u64 {
self.current_sample
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn seek_to_time(&mut self, seconds: f64) -> Result<u64> {
let freq = self.sampling_frequency.ok_or_else(|| {
Error::InvalidHeader(
"Sampling frequency not available for time-based seeking".to_string(),
)
})?;
let sample = (seconds * freq).round() as u64;
self.seek_to_sample(sample)
}
}
pub struct SampleIterator<'a> {
reader: &'a mut SignalReader,
buffer: [Sample; 1],
done: bool,
}
impl Iterator for SampleIterator<'_> {
type Item = Result<Sample>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
match self.reader.read_samples_buf(&mut self.buffer) {
Ok(0) => {
self.done = true;
None
}
Ok(_) => Some(Ok(self.buffer[0])),
Err(e) => {
self.done = true;
Some(Err(e))
}
}
}
}