use anyhow::{Context, Result};
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
use super::constants::*;
use super::info::{ChannelInfo, MeasInfo};
const FIFFT_ID_STRUCT: u32 = 31;
const FIFFT_CH_INFO_STRUCT_LOCAL: u32 = FIFFT_CH_INFO_STRUCT;
pub fn write_raw<P: AsRef<Path>>(
info: &MeasInfo,
data: &ndarray::Array2<f64>,
path: P,
) -> Result<()> {
let path = path.as_ref();
let f = File::create(path).with_context(|| format!("create {}", path.display()))?;
let mut w = BufWriter::new(f);
let (n_chan, n_times) = (data.nrows(), data.ncols());
anyhow::ensure!(
n_chan == info.n_chan,
"write_raw: data has {n_chan} channels, info says {}",
info.n_chan,
);
anyhow::ensure!(
info.chs.len() == n_chan,
"info.chs has {} entries, n_chan = {n_chan}",
info.chs.len()
);
let mut id = [0u8; 20];
id[0..4].copy_from_slice(&0x10001_i32.to_be_bytes()); write_tag(&mut w, FIFF_FILE_ID, FIFFT_ID_STRUCT, &id, FIFFV_NEXT_SEQ)?;
write_tag(
&mut w,
FIFF_DIR_POINTER,
FIFFT_INT,
&(-1_i32).to_be_bytes(),
FIFFV_NEXT_SEQ,
)?;
write_block_start(&mut w, FIFFB_MEAS)?;
write_block_start(&mut w, FIFFB_MEAS_INFO)?;
write_tag(
&mut w,
FIFF_NCHAN,
FIFFT_INT,
&(n_chan as i32).to_be_bytes(),
FIFFV_NEXT_SEQ,
)?;
write_tag(
&mut w,
FIFF_SFREQ,
FIFFT_FLOAT,
&(info.sfreq as f32).to_be_bytes(),
FIFFV_NEXT_SEQ,
)?;
if let Some(hp) = info.highpass {
write_tag(
&mut w,
FIFF_HIGHPASS,
FIFFT_FLOAT,
&(hp as f32).to_be_bytes(),
FIFFV_NEXT_SEQ,
)?;
}
if let Some(lp) = info.lowpass {
write_tag(
&mut w,
FIFF_LOWPASS,
FIFFT_FLOAT,
&(lp as f32).to_be_bytes(),
FIFFV_NEXT_SEQ,
)?;
}
if let Some(lf) = info.line_freq {
write_tag(
&mut w,
FIFF_LINE_FREQ,
FIFFT_FLOAT,
&(lf as f32).to_be_bytes(),
FIFFV_NEXT_SEQ,
)?;
}
for ch in &info.chs {
let payload = ch_info_to_bytes(ch);
write_tag(
&mut w,
FIFF_CH_INFO,
FIFFT_CH_INFO_STRUCT_LOCAL,
&payload,
FIFFV_NEXT_SEQ,
)?;
}
write_block_end(&mut w, FIFFB_MEAS_INFO)?;
write_block_start(&mut w, FIFFB_RAW_DATA)?;
write_tag(
&mut w,
FIFF_FIRST_SAMPLE,
FIFFT_INT,
&0_i32.to_be_bytes(),
FIFFV_NEXT_SEQ,
)?;
let total = n_chan * n_times;
let mut buf: Vec<u8> = Vec::with_capacity(total * 4);
for t in 0..n_times {
for c in 0..n_chan {
buf.extend_from_slice(&(data[[c, t]] as f32).to_be_bytes());
}
}
write_tag(&mut w, FIFF_DATA_BUFFER, FIFFT_FLOAT, &buf, FIFFV_NEXT_SEQ)?;
write_block_end(&mut w, FIFFB_RAW_DATA)?;
write_block_end_last(&mut w, FIFFB_MEAS)?;
w.flush()?;
Ok(())
}
fn write_tag<W: Write>(
w: &mut W,
kind: i32,
ftype: u32,
payload: &[u8],
next: i32,
) -> std::io::Result<()> {
w.write_all(&kind.to_be_bytes())?;
w.write_all(&ftype.to_be_bytes())?;
w.write_all(&(payload.len() as i32).to_be_bytes())?;
w.write_all(&next.to_be_bytes())?;
if !payload.is_empty() {
w.write_all(payload)?;
}
Ok(())
}
fn write_block_start<W: Write>(w: &mut W, block: i32) -> std::io::Result<()> {
write_tag(
w,
FIFF_BLOCK_START,
FIFFT_INT,
&block.to_be_bytes(),
FIFFV_NEXT_SEQ,
)
}
fn write_block_end<W: Write>(w: &mut W, block: i32) -> std::io::Result<()> {
write_tag(
w,
FIFF_BLOCK_END,
FIFFT_INT,
&block.to_be_bytes(),
FIFFV_NEXT_SEQ,
)
}
fn write_block_end_last<W: Write>(w: &mut W, block: i32) -> std::io::Result<()> {
write_tag(
w,
FIFF_BLOCK_END,
FIFFT_INT,
&block.to_be_bytes(),
FIFFV_NEXT_NONE,
)
}
fn ch_info_to_bytes(ch: &ChannelInfo) -> [u8; 96] {
let mut out = [0u8; 96];
out[0..4].copy_from_slice(&ch.scan_no.to_be_bytes());
out[4..8].copy_from_slice(&ch.log_no.to_be_bytes());
out[8..12].copy_from_slice(&ch.kind.to_be_bytes());
out[12..16].copy_from_slice(&ch.range.to_be_bytes());
out[16..20].copy_from_slice(&ch.cal.to_be_bytes());
out[20..24].copy_from_slice(&ch.coil_type.to_be_bytes());
for (i, &v) in ch.loc.iter().enumerate() {
out[24 + i * 4..28 + i * 4].copy_from_slice(&v.to_be_bytes());
}
out[72..76].copy_from_slice(&ch.unit.to_be_bytes());
out[76..80].copy_from_slice(&ch.unit_mul.to_be_bytes());
let name_bytes = ch.name.as_bytes();
let n = name_bytes.len().min(16);
out[80..80 + n].copy_from_slice(&name_bytes[..n]);
out
}