use super::error::*;
use hdf5_metno::{Dataset, Extents, File, H5Type, SimpleExtents};
use ndarray::{ArrayView, ShapeBuilder};
use reinterpret::reinterpret_slice;
use snafu::prelude::*;
use std::path::{Path, PathBuf};
type Result<T> = std::result::Result<T, MeasurementError>;
use crate::{
MeasurementMetadata,
daq::{DataType, RawStreamData},
file_attr_names::{
INPUT_SIGNAL_CLIPPED_ATTR, MEASURED_INPUT_DATASET_NAME, MEASURED_PRECAPTURE_DATASET_NAME,
MEASUREMENT_STILL_RUNNING,
},
measurement::{Measurement, MeasurementError, SharedMeasurement},
tools::h5::{write_h5_attr_scalar, write_h5_attr_scalar_overwrite},
};
pub struct EmptyMeasurement;
pub struct MeasurementWithMeta {
dset: Dataset,
precapture_dset: Option<Dataset>,
recorded_frames_ctr: usize,
precapture_frames_ctr: usize,
meta: MeasurementMetadata,
}
pub struct MeasurementWriter<S> {
file: File,
state: S,
}
impl MeasurementWriter<EmptyMeasurement> {
pub fn new(path: &Path) -> Result<Self> {
let file = hdf5_metno::File::create_excl(path)
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: format!("opening file {}", path.to_string_lossy()),
})?;
write_h5_attr_scalar(&file, MEASUREMENT_STILL_RUNNING, true)?;
Ok(Self {
file,
state: EmptyMeasurement,
})
}
pub fn write_meta(
self,
meta: MeasurementMetadata,
) -> Result<MeasurementWriter<MeasurementWithMeta>> {
meta.writeToH5(&self.file)?;
let dset = create_dataset_type(&self.file, &meta, MEASURED_INPUT_DATASET_NAME)?;
Ok(MeasurementWriter {
file: self.file,
state: MeasurementWithMeta {
dset,
precapture_dset: None,
recorded_frames_ctr: 0,
precapture_frames_ctr: 0,
meta,
},
})
}
}
impl MeasurementWriter<MeasurementWithMeta> {
fn write_to_dataset(
dset: &Dataset,
frames_ctr: &mut usize,
meta: &MeasurementMetadata,
data: &RawStreamData,
) -> Result<()> {
let nchannels = meta.nchannels();
let new_nframes = data.nsamples() / nchannels;
dset.resize((*frames_ctr + new_nframes, nchannels))
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: "resizing dataset",
})?;
append_to_dset(dset, *frames_ctr, meta, data)?;
*frames_ctr += new_nframes;
Ok(())
}
pub fn write(&mut self, data: &RawStreamData) -> Result<()> {
let Self {
state:
MeasurementWithMeta {
dset,
recorded_frames_ctr,
meta,
..
},
..
} = self;
Self::write_to_dataset(dset, recorded_frames_ctr, meta, data)
}
pub fn write_precapture(&mut self, data: &RawStreamData) -> Result<()> {
if self.state.precapture_dset.is_none() {
let dset = create_dataset_type(
&self.file,
&self.state.meta,
MEASURED_PRECAPTURE_DATASET_NAME,
)?;
self.state.precapture_dset = Some(dset);
}
Self::write_to_dataset(
self.state.precapture_dset.as_ref().unwrap(),
&mut self.state.precapture_frames_ctr,
&self.state.meta,
data,
)
}
pub fn is_empty(&self) -> bool {
self.frames_recorded() == 0
}
#[inline]
pub fn frames_recorded(&self) -> usize {
self.state.recorded_frames_ctr
}
pub fn finish(self, clipped: bool) -> Result<SharedMeasurement> {
let Self {
file,
state:
MeasurementWithMeta {
dset,
precapture_dset,
..
},
} = self;
let path = PathBuf::from(file.filename());
if clipped {
write_h5_attr_scalar_overwrite(&file, INPUT_SIGNAL_CLIPPED_ATTR, clipped)?;
}
write_h5_attr_scalar_overwrite(&file, MEASUREMENT_STILL_RUNNING, false)?;
drop(dset);
drop(precapture_dset);
drop(file);
Measurement::from_file(&path)
}
}
fn create_dataset_type(file: &File, meta: &MeasurementMetadata, name: &str) -> Result<Dataset> {
match meta.dataType() {
DataType::I8 => create_dataset_type_with_name::<i8>(file, meta, name),
DataType::I16 => create_dataset_type_with_name::<i16>(file, meta, name),
DataType::I32 => create_dataset_type_with_name::<i32>(file, meta, name),
DataType::I24 => create_dataset_type_with_name::<i32>(file, meta, name),
DataType::F32 => create_dataset_type_with_name::<f32>(file, meta, name),
DataType::F64 => create_dataset_type_with_name::<f64>(file, meta, name),
}
}
fn create_dataset_type_with_name<T>(
file: &File,
meta: &MeasurementMetadata,
name: &str,
) -> Result<Dataset>
where
T: H5Type,
{
let nchannels = meta.nchannels();
let extents = Extents::new(vec![0, nchannels]).resizable();
match file
.new_dataset::<T>()
.chunk((10.max(*meta.samplerate() as usize / 10), nchannels))
.shape(extents)
.create(name)
{
Ok(dset) => {
assert!(dset.is_resizable());
Ok(dset)
}
err @ Err(_) => err.map_err(H5Error::from).context(H5FileProblemSnafu {
operation: format!("creating dataset '{name}'"),
}),
}
}
#[inline]
fn append_to_dset(
ds: &Dataset,
start_frame: usize,
meta: &MeasurementMetadata,
data: &RawStreamData,
) -> Result<()> {
macro_rules! write_data_slice {
($dat:expr, $ds:expr, $ctr:expr, $nchannels:expr) => {{
let view = ArrayView::from_shape(
($dat.len() / $nchannels, $nchannels).strides(($nchannels, 1)),
&$dat,
)
.unwrap();
$ds.write_slice(&view, (start_frame.., ..))
.map_err(H5Error::from)
.context(H5FileProblemSnafu {
operation: "writing data to measurement file",
})?;
}};
}
let nchannels = meta.nchannels();
match data {
RawStreamData::Datai8(dat) => write_data_slice!(dat, ds, ctr, nchannels),
RawStreamData::Datai16(dat) => write_data_slice!(dat, ds, ctr, nchannels),
RawStreamData::Datai24(dat) => {
assert!(std::mem::size_of::<dasp_sample::I24>() == std::mem::size_of::<i32>());
let dat = unsafe { reinterpret_slice::<_, i32>(dat) };
write_data_slice!(dat, ds, ctr, nchannels);
}
RawStreamData::Datai32(dat) => write_data_slice!(dat, ds, ctr, nchannels),
RawStreamData::Dataf32(dat) => write_data_slice!(dat, ds, ctr, nchannels),
RawStreamData::Dataf64(dat) => write_data_slice!(dat, ds, ctr, nchannels),
};
Ok(())
}