Reading .acq Files

API surface

The read feature exposes three entry points:

FunctionDescription
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.