xdf/parsers/
samples.rs

1use std::{cell::RefCell, collections::HashMap, rc::Rc};
2
3use nom::{
4    combinator,
5    error::context,
6    multi,
7    number::complete::{le_f64, u8},
8    IResult,
9};
10
11use crate::{
12    chunk_structs::{SamplesChunk, StreamHeaderChunkInfo},
13    Format, Sample,
14};
15
16use super::{chunk_content, chunk_length::length, chunk_tags::samples_tag, stream_id, values};
17
18fn optional_timestamp(input: &[u8]) -> IResult<&[u8], Option<f64>> {
19    let (input, timestamp_bytes) = u8(input)?;
20    match timestamp_bytes {
21        0 => Ok((input, None)),
22        8 => {
23            let (input, timestamp) = le_f64(input)?;
24            Ok((input, Some(timestamp)))
25        }
26        _ => Err(nom::Err::Failure(nom::error::Error::new(
27            input,
28            nom::error::ErrorKind::Char, // not how these errors should be used but nom is a bit of a pain here
29        ))),
30    }
31}
32
33// structure of a sample:
34// [TimeStampBytes] [OptionalTimeStamp] [Value 1] [Value 2] ... [Value N]
35// [0 or 8] [Double, in seconds] [Value as defined by format] ...
36// [1][8 if TimeStampBytes==8, 0 if TimeStampBytes==0] [[Variable]] ...
37
38fn sample(input: &[u8], num_channels: usize, format: Format) -> IResult<&[u8], Sample> {
39    let (input, timestamp) = context("sample optional_timestamp", optional_timestamp)(input)?;
40    let (input, values) = context("sample values", |i| values(i, format, num_channels))(input)?;
41
42    Ok((input, Sample { timestamp, values }))
43}
44
45#[allow(clippy::needless_pass_by_value)]
46pub(super) fn samples(
47    input: &[u8],
48    // stream_info: &HashMap<u32, StreamHeaderChunkInfo>,
49    stream_info: Rc<RefCell<HashMap<u32, StreamHeaderChunkInfo>>>,
50) -> IResult<&[u8], SamplesChunk> {
51    let stream_info = stream_info.borrow();
52    let (input, chunk_content) = context("samples chunk_content", chunk_content)(input)?;
53    let (chunk_content, _tag) = context("samples tag", samples_tag)(chunk_content)?; // 2 bytes
54    let (chunk_content, stream_id) = context("samples stream_id", stream_id)(chunk_content)?; // 4 bytes
55    let (chunk_content, num_samples) = context("samples num_samples", length)(chunk_content)?;
56
57    let Some(stream_info) = stream_info.get(&stream_id) else {
58        return context("samples get(&stream_id), missing a stream header", combinator::fail)(&[0]);
59    };
60    let num_channels = stream_info.channel_count as usize;
61    let format = stream_info.channel_format;
62
63    let (_chunk_content, samples) = multi::count(|i| sample(i, num_channels, format), num_samples)(chunk_content)?;
64
65    Ok((input, SamplesChunk { stream_id, samples }))
66}