1use std::ops::Range;
5
6use dspk_core::{DspError, DspResult, RecordingSource};
7
8use crate::envelope::fold::{finish, fold_block, Columns, EMPTY};
9use crate::pyramid::Pyramid;
10use crate::read::read_channel_blocks;
11
12pub const RAW_BLOCK_VALUES: usize = 1 << 22;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct View {
19 pub channels: Vec<usize>,
20 pub start: u64,
22 pub end: u64,
24 pub width: usize,
26}
27
28#[derive(Debug, Clone, PartialEq)]
30pub enum Envelope {
31 Samples(Vec<f32>),
33 Columns { values: Vec<[f32; 2]>, complete: bool },
37}
38
39impl View {
40 pub fn samples(&self) -> u64 {
42 self.end.saturating_sub(self.start)
43 }
44
45 pub fn columns(&self) -> Columns {
47 Columns::Even { start: self.start, len: self.samples(), width: self.width }
48 }
49
50 pub fn validate(&self, channels: usize, samples: u64) -> DspResult<()> {
52 if let Some(&channel) = self.channels.iter().find(|&&c| c >= channels) {
53 return Err(DspError::InvalidChannel { channel, total: channels });
54 }
55 if self.start >= self.end || self.end > samples || self.width == 0 {
56 return Err(DspError::SampleRange { start: self.start, end: self.end, total: samples });
57 }
58 Ok(())
59 }
60
61 pub fn read(&self, source: &dyn RecordingSource, pyramid: Option<&Pyramid>) -> DspResult<Envelope> {
63 let mut out = Envelope::Samples(Vec::new());
64 self.read_into(source, pyramid, &mut out)?;
65 Ok(out)
66 }
67
68 pub fn read_into(&self, source: &dyn RecordingSource, pyramid: Option<&Pyramid>, out: &mut Envelope) -> DspResult<()> {
76 let info = source.info();
77 self.validate(info.channel_count(), info.samples)?;
78 let (rows, n) = (self.channels.len(), self.samples());
79 let (mut samples, mut values) = match std::mem::replace(out, Envelope::Samples(Vec::new())) {
80 Envelope::Samples(s) => (s, Vec::new()),
81 Envelope::Columns { values, .. } => (Vec::new(), values),
82 };
83 if n <= self.width as u64 {
84 samples.clear();
85 samples.resize(rows * n as usize, 0.0);
86 source.read(&self.channels, self.start..self.end, &mut samples)?;
87 *out = Envelope::Samples(samples);
88 return Ok(());
89 }
90 values.clear();
91 values.resize(rows * self.width, EMPTY);
92 if let Some(pyramid) = pyramid {
93 if pyramid.envelope(&self.channels, self.start, self.end, self.width, &mut values)? {
94 *out = Envelope::Columns { values, complete: pyramid.covers(self.start, self.end) };
95 return Ok(());
96 }
97 }
98 let columns = self.columns();
99 let ranges = chunk_aligned(self.start, self.end, source.chunk_samples(), RAW_BLOCK_VALUES / rows.max(1));
100 read_channel_blocks(source, &self.channels, &ranges, |block| fold_block(block, 0..block.samples, columns, &mut values))?;
101 finish(&mut values);
102 *out = Envelope::Columns { values, complete: true };
103 Ok(())
104 }
105}
106
107fn chunk_aligned(start: u64, end: u64, chunk: Option<u64>, max_samples: usize) -> Vec<Range<u64>> {
110 let chunk = chunk.filter(|&c| c > 0).unwrap_or(1);
111 let step = ((max_samples as u64) / chunk).max(1) * chunk;
112 let mut ranges = Vec::new();
113 let mut b0 = start;
114 while b0 < end {
115 let b1 = (b0 / chunk * chunk + step).min(end);
117 ranges.push(b0..b1);
118 b0 = b1;
119 }
120 ranges
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn blocks_cover_the_window_on_chunk_edges() {
129 let ranges = chunk_aligned(10, 1000, Some(64), 200);
130 assert_eq!(ranges.first().map(|r| r.start), Some(10));
131 assert_eq!(ranges.last().map(|r| r.end), Some(1000));
132 assert!(ranges.windows(2).all(|w| w[0].end == w[1].start && w[1].start % 64 == 0));
133 assert!(ranges.iter().all(|r| r.end - r.start <= 192));
134 assert!(chunk_aligned(0, 1000, Some(512), 10).iter().all(|r| r.end - r.start <= 512));
136 }
137
138 #[test]
139 fn streamed_blocks_fold_like_one_read() {
140 let rec = recording();
141 let (channels, start, end, width) = (vec![0usize, 2], 37u64, 19_000u64, 300usize);
142 let columns = Columns::Even { start, len: end - start, width };
143 let mut whole = vec![EMPTY; channels.len() * width];
144 let mut data = vec![0.0; channels.len() * (end - start) as usize];
145 rec.read(&channels, start..end, &mut data).unwrap();
146 for (row, acc) in data.chunks_exact((end - start) as usize).zip(whole.chunks_exact_mut(width)) {
147 crate::envelope::fold::fold_row(row, start, columns, acc);
148 }
149 let mut streamed = vec![EMPTY; channels.len() * width];
150 let ranges = chunk_aligned(start, end, Some(1000), 2500);
151 assert!(ranges.len() > 2);
152 read_channel_blocks(&rec, &channels, &ranges, |b| fold_block(b, 0..b.samples, columns, &mut streamed)).unwrap();
153 assert_eq!(streamed, whole);
154 }
155 use dspk_core::MemoryRecording;
156
157 fn recording() -> MemoryRecording {
158 let (channels, samples) = (3usize, 20_000usize);
159 let data: Vec<f32> = (0..channels * samples).map(|i| ((i * 7919) % 1013) as f32 - 500.0).collect();
160 MemoryRecording::new("v", data, channels, 1000.0).unwrap()
161 }
162
163 #[test]
164 fn short_windows_return_samples() {
165 let rec = recording();
166 let view = View { channels: vec![2, 0], start: 100, end: 150, width: 64 };
167 let Envelope::Samples(s) = view.read(&rec, None).unwrap() else { panic!("samples expected") };
168 let mut expected = vec![0.0; 2 * 50];
169 rec.read(&[2, 0], 100..150, &mut expected).unwrap();
170 assert_eq!(s, expected);
171 }
172
173 #[test]
174 fn pyramid_and_raw_reads_agree_on_every_peak() {
175 let rec = recording();
176 let pyramid = Pyramid::in_memory(&rec, 16).unwrap();
177 let view = View { channels: vec![1], start: 0, end: 20_000, width: 50 };
178
179 let Envelope::Columns { complete, .. } = view.read(&rec, Some(&pyramid)).unwrap() else { panic!() };
180 assert!(!complete, "nothing built yet");
181
182 assert!(pyramid.fill(&rec, 0, 20_000, 1 << 20, || false).unwrap());
183 let Envelope::Columns { values: from_pyramid, complete } = view.read(&rec, Some(&pyramid)).unwrap() else { panic!() };
184 let Envelope::Columns { values: raw, .. } = view.read(&rec, None).unwrap() else { panic!() };
185 assert!(complete);
186 let span = |v: &[[f32; 2]]| v.iter().fold(EMPTY, |a, c| [a[0].min(c[0]), a[1].max(c[1])]);
187 assert_eq!(span(&from_pyramid), span(&raw), "the same extremes, column edges aside");
188 }
189
190 #[test]
191 fn invalid_views_are_rejected() {
192 let rec = recording();
193 assert!(View { channels: vec![3], start: 0, end: 10, width: 4 }.read(&rec, None).is_err());
194 assert!(View { channels: vec![0], start: 10, end: 10, width: 4 }.read(&rec, None).is_err());
195 assert!(View { channels: vec![0], start: 0, end: 20_001, width: 4 }.read(&rec, None).is_err());
196 }
197}