Reading .acq Files
API surface
The read feature exposes three entry points:
| Function | Description |
|---|---|
biodream::read_file(path) | Read a .acq file from disk. |
biodream::read_stream(reader) | Read from any Read + Seek. |
biodream::LazyDatafile::open(path, opts) | Deferred streaming reader. |
All return ParseResult<Datafile> (or LazyDatafile for the lazy variant).
ParseResult and warnings
Biodream distinguishes between fatal errors (returned as Err(BiopacError))
and non-fatal warnings (accumulated in ParseResult::warnings). Warnings
cover things like unknown section types, unexpected padding bytes, or fields
that are out of expected range but still parse cleanly.
#![allow(unused)] fn main() { let result = biodream::read_file("recording.acq")?; if !result.is_clean() { for w in &result.warnings { eprintln!("warning: {w}"); } } // Consumes result, giving you the Datafile let df = result.into_value(); }
Always check result.warnings — a clean parse on a healthy file returns zero
warnings.
Datafile structure
Datafile
├── metadata: GraphMetadata — title, date/time, byte order, revision
├── channels: Vec<Channel> — ordered by channel index
├── markers: Vec<Marker> — event markers (may be empty)
└── journal: Option<Journal> — free-text journal section
GraphMetadata
#![allow(unused)] fn main() { let meta = &df.metadata; println!("recorded: {}", meta.recorded_at); // chrono::NaiveDateTime println!("revision: {}", meta.revision); // FileRevision (v30–v84+) println!("byte order: {:?}", meta.byte_order); // ByteOrder::LittleEndian / BigEndian }
Channel
#![allow(unused)] fn main() { for ch in &df.channels { // Scaled f64 samples (raw i16 converted via scale + offset) let samples: Vec<f64> = ch.scaled_samples(); // Raw i16 samples without scaling let raw: &[i16] = ch.raw_samples(); println!( "{} [{}]: {} samples @ {} Hz", ch.name, ch.units, samples.len(), ch.samples_per_second, ); } }
Channels with a sampling rate lower than the base rate are upsampled to the
base rate via linear interpolation in scaled_samples().
Mixed sampling rates
AcqKnowledge supports channels at different fractions of the base rate. Each
channel carries a frequency_divider; biodream computes samples_per_second
correctly for all channels regardless of their divider.
#![allow(unused)] fn main() { let base_hz = df.metadata.samples_per_second; for ch in &df.channels { println!("{}: {} Hz (divider {})", ch.name, ch.samples_per_second, ch.frequency_divider); } }
Lazy reader
For recordings with many channels or large sample buffers, LazyDatafile
reads only the channel headers on open and loads individual channel data
on demand:
#![allow(unused)] fn main() { use biodream::{LazyDatafile, ReadOptions}; let lazy = LazyDatafile::open("large.acq", &ReadOptions::default())?; // Load channels selectively — no unnecessary I/O let ecg = lazy.load_channel(0)?; let resp = lazy.load_channel(2)?; }
This is the recommended approach for batch-processing pipelines that only need a subset of channels.
Byte order
AcqKnowledge pre-4 files were often written on big-endian Mac hardware.
Biodream detects the byte order from the lVersion field sign bit and
handles both transparently. The detected order is exposed in
GraphMetadata::byte_order.