1use std::collections::VecDeque;
10use std::ops::Range;
11use std::sync::{Arc, Mutex};
12
13use dspk_core::recording::check_read;
14use dspk_core::{DspResult, MemoryOrder, RecordingInfo, RecordingSource};
15
16pub struct CachedRecording {
17 inner: Box<dyn RecordingSource>,
18 chunk: u64,
20 capacity: usize,
22 chunks: Mutex<VecDeque<(u64, Arc<Vec<f32>>)>>,
24}
25
26impl CachedRecording {
27 pub fn wrap(inner: Box<dyn RecordingSource>, budget_bytes: usize) -> Box<dyn RecordingSource> {
30 match inner.chunk_samples().filter(|&c| c > 0) {
31 Some(chunk) => {
32 let bytes = chunk as usize * inner.info().channel_count().max(1) * 4;
33 let capacity = (budget_bytes / bytes).max(1);
34 Box::new(Self { inner, chunk, capacity, chunks: Mutex::new(VecDeque::new()) })
35 }
36 None => inner,
37 }
38 }
39
40 fn chunk(&self, i: u64) -> DspResult<Arc<Vec<f32>>> {
42 {
43 let mut chunks = self.chunks.lock().unwrap();
44 if let Some(at) = chunks.iter().position(|(c, _)| *c == i) {
45 let hit = chunks.remove(at).expect("position is in range");
46 chunks.push_front(hit.clone());
47 return Ok(hit.1);
48 }
49 }
50 let info = self.inner.info();
52 let range = i * self.chunk..((i + 1) * self.chunk).min(info.samples);
53 let all: Vec<usize> = (0..info.channel_count()).collect();
54 let mut data = vec![0.0f32; all.len() * (range.end - range.start) as usize];
55 self.inner.read(&all, range, &mut data)?;
56 let data = Arc::new(data);
57 let mut chunks = self.chunks.lock().unwrap();
58 chunks.retain(|(c, _)| *c != i);
59 chunks.push_front((i, data.clone()));
60 chunks.truncate(self.capacity);
61 Ok(data)
62 }
63}
64
65impl RecordingSource for CachedRecording {
66 fn info(&self) -> &RecordingInfo {
67 self.inner.info()
68 }
69
70 fn chunk_samples(&self) -> Option<u64> {
71 Some(self.chunk)
72 }
73
74 fn read(&self, channels: &[usize], samples: Range<u64>, out: &mut [f32]) -> DspResult<()> {
75 let n = check_read(self.info(), channels, &samples, out.len())?;
76 if n == 0 || channels.is_empty() {
77 return Ok(());
78 }
79 let total = self.info().samples;
80 for i in samples.start / self.chunk..samples.end.div_ceil(self.chunk) {
81 let data = self.chunk(i)?;
82 let (c0, c1) = (i * self.chunk, ((i + 1) * self.chunk).min(total));
83 let len = (c1 - c0) as usize;
84 let (s0, s1) = (samples.start.max(c0), samples.end.min(c1));
85 let (src, dst, count) = ((s0 - c0) as usize, (s0 - samples.start) as usize, (s1 - s0) as usize);
86 for (row, &ch) in out.chunks_exact_mut(n).zip(channels) {
87 row[dst..dst + count].copy_from_slice(&data[ch * len + src..ch * len + src + count]);
88 }
89 }
90 Ok(())
91 }
92
93 fn read_native(&self, samples: Range<u64>, out: &mut [f32]) -> DspResult<MemoryOrder> {
94 self.inner.read_native(samples, out)
95 }
96
97 fn read_stored(&self, channels: &[usize], samples: Range<u64>, out: &mut [u8]) -> DspResult<()> {
98 self.inner.read_stored(channels, samples, out)
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use dspk_core::MemoryRecording;
106 use std::sync::atomic::{AtomicUsize, Ordering};
107
108 struct Chunked(MemoryRecording, Arc<AtomicUsize>);
110
111 impl RecordingSource for Chunked {
112 fn info(&self) -> &RecordingInfo {
113 self.0.info()
114 }
115 fn chunk_samples(&self) -> Option<u64> {
116 Some(100)
117 }
118 fn read(&self, channels: &[usize], samples: Range<u64>, out: &mut [f32]) -> DspResult<()> {
119 self.1.fetch_add(1, Ordering::Relaxed);
120 self.0.read(channels, samples, out)
121 }
122 }
123
124 #[test]
125 fn test_reads_match_the_source_and_reuse_chunks() {
126 let data: Vec<f32> = (0..3 * 1050).map(|v| v as f32).collect();
127 let plain = MemoryRecording::new("m", data.clone(), 3, 1000.0).unwrap();
128 let reads = Arc::new(AtomicUsize::new(0));
129 let cached = CachedRecording::wrap(Box::new(Chunked(MemoryRecording::new("m", data, 3, 1000.0).unwrap(), reads.clone())), 4 * 100 * 3 * 4);
131
132 for (channels, range) in [(vec![2, 0], 50u64..330), (vec![1], 1000..1050), (vec![0, 1, 2], 120..180)] {
133 let (mut a, mut b) = (vec![0.0; channels.len() * (range.end - range.start) as usize], vec![]);
134 b.resize(a.len(), 0.0);
135 cached.read(&channels, range.clone(), &mut a).unwrap();
136 plain.read(&channels, range.clone(), &mut b).unwrap();
137 assert_eq!(a, b, "{channels:?} {range:?}");
138 }
139 assert_eq!(reads.load(Ordering::Relaxed), 5);
141 let mut one = [0.0];
142 cached.read(&[0], 0..1, &mut one).unwrap();
143 assert_eq!(reads.load(Ordering::Relaxed), 6, "chunk 0 was evicted by the 4-chunk budget");
144 }
145}