mod multi_signal_reader;
pub(crate) mod segment;
mod segment_reader;
mod signal_reader;
pub use multi_signal_reader::MultiSignalReader;
pub use segment_reader::SegmentReader;
pub use signal_reader::SignalReader;
use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use crate::{Error, Header, Metadata, Result, SegmentInfo, SignalInfo};
#[derive(Debug, Clone)]
pub struct Record {
header: Header,
base_path: PathBuf,
}
impl Record {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
let header_path = if path.extension().is_some_and(|ext| ext == "hea") {
path.to_path_buf()
} else {
path.with_extension("hea")
};
if !header_path.exists() {
return Err(Error::InvalidPath(format!(
"Header file not found: {}",
header_path.display()
)));
}
let file = File::open(&header_path)?;
let mut reader = BufReader::new(file);
let header = Header::from_reader(&mut reader)?;
let base_path = header_path
.parent()
.unwrap_or_else(|| Path::new("."))
.to_path_buf();
Ok(Self { header, base_path })
}
#[must_use]
pub const fn from_header(header: Header, base_path: PathBuf) -> Self {
Self { header, base_path }
}
#[must_use]
pub const fn metadata(&self) -> &Metadata {
&self.header.metadata
}
#[must_use]
pub const fn specifications(&self) -> &crate::header::Specifications {
&self.header.specifications
}
#[must_use]
pub fn signal_info(&self) -> Option<&[SignalInfo]> {
self.header.specifications.signals()
}
#[must_use]
pub fn segment_info(&self) -> Option<&[SegmentInfo]> {
self.header.specifications.segments()
}
#[must_use]
pub fn info_strings(&self) -> &[String] {
&self.header.info_strings
}
#[must_use]
pub const fn is_multi_segment(&self) -> bool {
self.header.specifications.is_multi_segment()
}
#[must_use]
pub fn signal_count(&self) -> usize {
self.signal_info().map_or(0, <[SignalInfo]>::len)
}
#[must_use]
pub fn segment_count(&self) -> usize {
self.segment_info().map_or(0, <[SegmentInfo]>::len)
}
#[must_use]
pub fn base_path(&self) -> &Path {
&self.base_path
}
pub fn signal_reader(&self, signal_index: usize) -> Result<SignalReader> {
if self.is_multi_segment() {
return Err(Error::InvalidHeader(
"Single signal readers not yet supported for multi-segment records".to_string(),
));
}
let signals = self.signal_info().ok_or_else(|| {
Error::InvalidHeader("No signal specifications in header".to_string())
})?;
if signal_index >= signals.len() {
return Err(Error::InvalidHeader(format!(
"Signal index {} out of bounds (record has {} signals)",
signal_index,
signals.len()
)));
}
let sampling_frequency = Some(self.metadata().sampling_frequency());
SignalReader::new(
&self.base_path,
&signals[signal_index],
signals,
signal_index,
sampling_frequency,
)
}
pub fn multi_signal_reader(&self) -> Result<MultiSignalReader> {
if self.is_multi_segment() {
return Err(Error::InvalidHeader(
"Multi-signal readers not yet supported for multi-segment records".to_string(),
));
}
let signals = self.signal_info().ok_or_else(|| {
Error::InvalidHeader("No signal specifications in header".to_string())
})?;
MultiSignalReader::new(&self.base_path, signals)
}
pub fn segment_reader(&self) -> Result<SegmentReader> {
if !self.is_multi_segment() {
return Err(Error::InvalidHeader(
"Segment readers only supported for multi-segment records".to_string(),
));
}
let segments = self.segment_info().ok_or_else(|| {
Error::InvalidHeader("No segment specifications in header".to_string())
})?;
Ok(SegmentReader::new(
self.base_path.clone(),
segments.to_vec(),
))
}
}