use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use crate::signal::FormatDecoder;
use crate::{Error, Result, Sample, SignalInfo};
struct SignalGroup {
decoder: Box<dyn FormatDecoder>,
reader: BufReader<File>,
signal_indices: Vec<usize>,
signal_infos: Vec<SignalInfo>,
}
pub struct MultiSignalReader {
groups: Vec<SignalGroup>,
num_signals: usize,
signal_to_group: Vec<(usize, usize)>,
current_frame: u64,
}
impl MultiSignalReader {
pub(crate) fn new(base_path: &Path, signals: &[SignalInfo]) -> Result<Self> {
if signals.is_empty() {
return Err(Error::InvalidHeader("No signals to read".to_string()));
}
let mut file_groups: HashMap<String, Vec<usize>> = HashMap::new();
for (idx, signal) in signals.iter().enumerate() {
file_groups
.entry(signal.file_name.clone())
.or_default()
.push(idx);
}
let mut groups = Vec::new();
let mut signal_to_group = vec![(0, 0); signals.len()];
for (file_name, signal_indices) in file_groups {
let group_index = groups.len();
let first_signal = &signals[signal_indices[0]];
let signal_path = base_path.join(&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);
if let Some(offset) = first_signal.byte_offset {
use std::io::Seek;
reader.seek(std::io::SeekFrom::Start(offset))?;
}
let initial_value = first_signal.initial_value.unwrap_or(0);
let decoder = crate::signal::get_decoder(first_signal.format, initial_value)?;
let signal_infos: Vec<SignalInfo> = signal_indices
.iter()
.map(|&idx| signals[idx].clone())
.collect();
for (within_group_idx, &signal_idx) in signal_indices.iter().enumerate() {
signal_to_group[signal_idx] = (group_index, within_group_idx);
}
groups.push(SignalGroup {
decoder,
reader,
signal_indices: signal_indices.clone(),
signal_infos,
});
}
Ok(Self {
groups,
num_signals: signals.len(),
signal_to_group,
current_frame: 0,
})
}
pub fn read_frame(&mut self) -> Result<Vec<Sample>> {
let mut frame = vec![0; self.num_signals];
for group in &mut self.groups {
let mut group_samples = vec![0; group.signal_indices.len()];
let n = group
.decoder
.decode_buf(&mut group.reader, &mut group_samples)?;
if n == 0 {
return Ok(vec![]); }
if n != group.signal_indices.len() {
return Err(Error::InvalidHeader(
"Incomplete frame read from signal group".to_string(),
));
}
for (within_group_idx, &signal_idx) in group.signal_indices.iter().enumerate() {
frame[signal_idx] = group_samples[within_group_idx];
}
}
self.current_frame += 1;
Ok(frame)
}
pub fn read_frames(&mut self, count: usize) -> Result<Vec<Vec<Sample>>> {
let mut frames = Vec::with_capacity(count);
for _ in 0..count {
let frame = self.read_frame()?;
if frame.is_empty() {
break;
}
frames.push(frame);
}
Ok(frames)
}
pub fn read_frames_physical(&mut self, count: usize) -> Result<Vec<Vec<f64>>> {
let frames = self.read_frames(count)?;
Ok(frames
.into_iter()
.map(|frame| self.frame_to_physical(&frame))
.collect())
}
fn frame_to_physical(&self, adc_frame: &[Sample]) -> Vec<f64> {
adc_frame
.iter()
.enumerate()
.map(|(signal_idx, &adc_value)| {
let (group_idx, within_group_idx) = self.signal_to_group[signal_idx];
let signal_info = &self.groups[group_idx].signal_infos[within_group_idx];
let baseline = f64::from(signal_info.baseline());
let gain = signal_info.adc_gain();
(f64::from(adc_value) - baseline) / gain
})
.collect()
}
#[must_use]
pub const fn num_signals(&self) -> usize {
self.num_signals
}
pub fn seek_to_frame(&mut self, frame: u64) -> Result<u64> {
use std::io::Seek;
for group in &mut self.groups {
let num_signals = group.signal_indices.len();
let first_signal = &group.signal_infos[0];
let initial_offset = first_signal.byte_offset.unwrap_or(0);
if let Some(bytes_per_frame) = group.decoder.bytes_per_frame(num_signals) {
let byte_offset = initial_offset + frame * bytes_per_frame as u64;
group.reader.seek(std::io::SeekFrom::Start(byte_offset))?;
} else {
return Err(Error::InvalidHeader(
"Seeking not supported for this signal format".to_string(),
));
}
group.decoder.reset();
}
self.current_frame = frame;
Ok(frame)
}
#[must_use]
pub const fn position(&self) -> u64 {
self.current_frame
}
}