1use std::ops::Range;
9
10use dspk_core::recording::{check_read, check_read_stored};
11use dspk_core::{DspError, DspResult, MemoryOrder, RecordingInfo, RecordingSource, SampleFormat};
12
13pub type MergeInput = (Box<dyn RecordingSource>, Option<Vec<usize>>);
15
16pub struct MergedRecording {
18 parts: Vec<Box<dyn RecordingSource>>,
19 map: Vec<(usize, usize)>,
21 info: RecordingInfo,
22 stored_passthrough: bool,
24}
25
26impl MergedRecording {
27 pub fn new(parts: Vec<MergeInput>) -> DspResult<Self> {
33 let Some((first, _)) = parts.first() else {
34 return Err(DspError::InvalidConfig("merge: no recordings".into()));
35 };
36 let (rate, samples) = (first.info().sample_rate, first.info().samples);
37 let format = first.info().format;
38 let mut map = Vec::new();
39 let mut channels = Vec::new();
40 let mut names = Vec::new();
41 let mut stored_passthrough = true;
42 for (p, (source, picked)) in parts.iter().enumerate() {
43 let info = source.info();
44 if info.sample_rate != rate || info.samples != samples {
45 return Err(DspError::InvalidConfig(format!(
46 "merge: {} has {} samples at {} Hz, {} has {} at {} Hz (parts must match)",
47 info.name,
48 info.samples,
49 info.sample_rate_hz(),
50 first.info().name,
51 samples,
52 rate.rate_hz()
53 )));
54 }
55 stored_passthrough &= info.format == format;
56 let picked = picked.clone().unwrap_or_else(|| (0..info.channel_count()).collect());
57 for c in picked {
58 let ch = info.channels.get(c).ok_or_else(|| {
59 DspError::InvalidConfig(format!("merge: {} has no channel {c} ({} channels)", info.name, info.channel_count()))
60 })?;
61 map.push((p, c));
62 channels.push(ch.clone());
63 }
64 names.push(info.name.clone());
65 }
66 let stored_format = if stored_passthrough { format } else { SampleFormat::F32 };
67 let mut info = RecordingInfo::new(names.join("+"), channels.len(), samples, rate, stored_format, MemoryOrder::ChannelMajor);
68 if !stored_passthrough {
69 for ch in &mut channels {
71 ch.gain = 1.0;
72 ch.offset = 0.0;
73 }
74 }
75 info.channels = channels;
76 let parts = parts.into_iter().map(|(s, _)| s).collect();
77 Ok(Self { parts, map, info, stored_passthrough })
78 }
79
80 fn by_part(&self, channels: &[usize]) -> Vec<(Vec<usize>, Vec<usize>)> {
82 let mut groups = vec![(Vec::new(), Vec::new()); self.parts.len()];
83 for (i, &c) in channels.iter().enumerate() {
84 let (p, local) = self.map[c];
85 groups[p].0.push(i);
86 groups[p].1.push(local);
87 }
88 groups
89 }
90}
91
92impl RecordingSource for MergedRecording {
93 fn info(&self) -> &RecordingInfo {
94 &self.info
95 }
96
97 fn read(&self, channels: &[usize], samples: Range<u64>, out: &mut [f32]) -> DspResult<()> {
98 let n = check_read(&self.info, channels, &samples, out.len())?;
99 let mut buf = Vec::new();
100 for (p, (positions, local)) in self.by_part(channels).into_iter().enumerate() {
101 if positions.is_empty() {
102 continue;
103 }
104 buf.resize(local.len() * n, 0.0f32);
105 self.parts[p].read(&local, samples.clone(), &mut buf)?;
106 for (k, &i) in positions.iter().enumerate() {
107 out[i * n..(i + 1) * n].copy_from_slice(&buf[k * n..(k + 1) * n]);
108 }
109 }
110 Ok(())
111 }
112
113 fn read_stored(&self, channels: &[usize], samples: Range<u64>, out: &mut [u8]) -> DspResult<()> {
114 if !self.stored_passthrough {
115 let n = check_read_stored(&self.info, channels, &samples, out.len())?;
117 let mut values = vec![0.0f32; channels.len() * n];
118 self.read(channels, samples, &mut values)?;
119 for (dst, v) in out.as_chunks_mut::<4>().0.iter_mut().zip(values) {
120 *dst = v.to_le_bytes();
121 }
122 return Ok(());
123 }
124 let n = check_read_stored(&self.info, channels, &samples, out.len())?;
125 let row = n * self.info.format.bytes();
126 let mut buf = Vec::new();
127 for (p, (positions, local)) in self.by_part(channels).into_iter().enumerate() {
128 if positions.is_empty() {
129 continue;
130 }
131 buf.resize(local.len() * row, 0u8);
132 self.parts[p].read_stored(&local, samples.clone(), &mut buf)?;
133 for (k, &i) in positions.iter().enumerate() {
134 out[i * row..(i + 1) * row].copy_from_slice(&buf[k * row..(k + 1) * row]);
135 }
136 }
137 Ok(())
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use dspk_core::MemoryRecording;
145
146 fn rec(name: &str, channels: usize, samples: usize, offset: f32) -> Box<dyn RecordingSource> {
147 let data = (0..channels * samples).map(|i| i as f32 + offset).collect();
148 Box::new(MemoryRecording::new(name, data, channels, 1_000.0).unwrap())
149 }
150
151 #[test]
152 fn channels_from_each_part_side_by_side() {
153 let merged = MergedRecording::new(vec![(rec("a", 3, 50, 0.0), Some(vec![2, 0])), (rec("b", 2, 50, 1_000.0), None)]).unwrap();
154 assert_eq!(merged.info().channel_count(), 4);
155 assert_eq!(merged.info().name, "a+b");
156 let mut out = vec![0.0f32; 3 * 10];
157 merged.read(&[3, 0, 2], 5..15, &mut out).unwrap();
159 let expect = |base: f32, c: usize| (5..15).map(move |t| base + (c * 50 + t) as f32);
160 let want: Vec<f32> = expect(1_000.0, 1).chain(expect(0.0, 2)).chain(expect(1_000.0, 0)).collect();
161 assert_eq!(out, want);
162 }
163
164 #[test]
165 fn stored_reads_match_scaled_reads() {
166 let merged = MergedRecording::new(vec![(rec("a", 2, 40, 0.0), None), (rec("b", 1, 40, 7.0), None)]).unwrap();
167 crate::test_support::assert_stored_matches(&merged, &[2, 0, 1], 3..30);
168 }
169
170 #[test]
171 fn mismatched_parts_are_refused() {
172 let err = MergedRecording::new(vec![(rec("a", 1, 40, 0.0), None), (rec("b", 1, 41, 0.0), None)]).err().unwrap();
173 assert!(err.to_string().contains("parts must match"), "{err}");
174 let err = MergedRecording::new(vec![(rec("a", 1, 40, 0.0), Some(vec![3]))]).err().unwrap();
175 assert!(err.to_string().contains("no channel 3"), "{err}");
176 }
177}