Skip to main content

dspk_core/recording/
source.rs

1use std::ops::Range;
2
3use super::format::SampleFormat;
4use super::info::RecordingInfo;
5use crate::buffer::{MemoryOrder, SignalChunk};
6use crate::error::{DspError, DspResult};
7use crate::time::RationalTime;
8
9/// Read-only, thread-shareable continuous recording, read in bounded chunks.
10///
11/// Implementors never need to hold the whole recording in memory: callers ask for the
12/// channels and sample range they draw or process.
13pub trait RecordingSource: Send + Sync {
14    fn info(&self) -> &RecordingInfo;
15
16    /// Reads `samples` of every channel in `channels` into `out`, channel-major and scaled to each
17    /// channel's unit ([`ChannelInfo`](super::ChannelInfo)):
18    /// `out[i * n..(i + 1) * n]` holds `channels[i]`, where `n = samples.end - samples.start`.
19    fn read(&self, channels: &[usize], samples: Range<u64>, out: &mut [f32]) -> DspResult<()>;
20
21    /// Reads the stored values (`info().format`, little-endian, before each channel's gain and
22    /// offset) of `samples` of every channel in `channels` into `out`, channel-major:
23    /// `out` holds `channels.len() · n · format.bytes()` bytes. Lets pipelines move compact integer
24    /// samples and scale them on the device.
25    ///
26    /// The default serves `f32` sources whose channels have unit gain and zero offset through
27    /// [`Self::read`]; other sources without a native implementation return
28    /// [`DspError::UnsupportedFormat`].
29    fn read_stored(&self, channels: &[usize], samples: Range<u64>, out: &mut [u8]) -> DspResult<()> {
30        let info = self.info();
31        let identity = channels.iter().all(|&c| info.channels.get(c).is_some_and(|ch| ch.gain == 1.0 && ch.offset == 0.0));
32        if info.format != SampleFormat::F32 || !identity {
33            return Err(DspError::UnsupportedFormat(format!("{}: stored {} reads", info.name, info.format.name())));
34        }
35        let n = check_read_stored(info, channels, &samples, out.len())?;
36        let mut values = vec![0.0f32; channels.len() * n];
37        self.read(channels, samples, &mut values)?;
38        for (dst, v) in out.chunks_exact_mut(4).zip(values) {
39            dst.copy_from_slice(&v.to_le_bytes());
40        }
41        Ok(())
42    }
43
44    /// Reads `samples` of every channel, scaled, in the order the source stores them, and returns
45    /// that order: [`MemoryOrder::ChannelMajor`] fills `out` as [`Self::read`] of all channels;
46    /// [`MemoryOrder::TimeMajor`] fills `out[t * channels + c]`. Whole-recording passes (min/max
47    /// summaries) use it to skip the transposition `read` does for time-major sources.
48    ///
49    /// The default reads every channel through [`Self::read`] (channel-major).
50    fn read_native(&self, samples: Range<u64>, out: &mut [f32]) -> DspResult<MemoryOrder> {
51        let all: Vec<usize> = (0..self.info().channel_count()).collect();
52        self.read(&all, samples, out)?;
53        Ok(MemoryOrder::ChannelMajor)
54    }
55
56    /// Samples per storage chunk along time, for formats stored in independently decoded chunks
57    /// (Zarr, NWB, mtscomp). Reads covering whole multiples of it, aligned to it, decode each
58    /// chunk once; `None` when every range costs the same (memory-mapped, in-memory, procedural).
59    fn chunk_samples(&self) -> Option<u64> {
60        None
61    }
62
63    fn read_channel(&self, channel: usize, samples: Range<u64>, out: &mut [f32]) -> DspResult<()> {
64        self.read(&[channel], samples, out)
65    }
66
67    /// Reads into a newly allocated channel-major [`SignalChunk`].
68    fn read_chunk(&self, channels: &[usize], samples: Range<u64>) -> DspResult<SignalChunk> {
69        let n = (samples.end.saturating_sub(samples.start)) as usize;
70        let mut data = vec![0.0f32; channels.len() * n];
71        self.read(channels, samples.clone(), &mut data)?;
72        let info = self.info();
73        let start = RationalTime::from_samples(samples.start, info.sample_rate)?;
74        SignalChunk::new(channels.len(), n, info.sample_rate, start, MemoryOrder::ChannelMajor, data)
75    }
76}
77
78/// Validates a [`RecordingSource::read_stored`] request (`out_len` in bytes) and returns the
79/// samples per channel.
80pub fn check_read_stored(info: &RecordingInfo, channels: &[usize], samples: &Range<u64>, out_len: usize) -> DspResult<usize> {
81    let bytes = info.format.bytes();
82    if out_len % bytes != 0 {
83        return Err(DspError::ShapeMismatch { expected: vec![channels.len(), bytes], actual: vec![out_len] });
84    }
85    check_read(info, channels, samples, out_len / bytes)
86}
87
88/// Validates a [`RecordingSource::read`] request and returns the samples per channel.
89pub fn check_read(info: &RecordingInfo, channels: &[usize], samples: &Range<u64>, out_len: usize) -> DspResult<usize> {
90    if samples.start > samples.end || samples.end > info.samples {
91        return Err(DspError::SampleRange { start: samples.start, end: samples.end, total: info.samples });
92    }
93    let total = info.channels.len();
94    if let Some(&channel) = channels.iter().find(|&&c| c >= total) {
95        return Err(DspError::InvalidChannel { channel, total });
96    }
97    let n = (samples.end - samples.start) as usize;
98    if out_len != channels.len() * n {
99        return Err(DspError::ShapeMismatch { expected: vec![channels.len(), n], actual: vec![out_len] });
100    }
101    Ok(n)
102}