1use bon::bon;
7use num_complex::Complex;
8use std::fs::File;
9use std::io::{BufReader, ErrorKind, Read};
10use std::path::Path;
11
12pub struct SdrFileReader {
27 reader: BufReader<File>,
28 samples_per_chunk: usize,
29 sample_type: SampleType,
30}
31
32pub enum SampleType {
35 U8,
37 I8,
39 U16,
41 I16,
43 F32,
45 F64,
47}
48
49impl SampleType {
50 #[must_use]
54 pub fn sample_len(&self) -> usize {
55 match self {
56 SampleType::U8 | SampleType::I8 => 2,
57 SampleType::I16 | SampleType::U16 => 4,
58 SampleType::F32 => 8,
59 SampleType::F64 => 16,
60 }
61 }
62}
63
64#[bon]
65impl SdrFileReader {
66 #[allow(missing_docs)]
67 #[builder]
68 pub fn new(
69 file_path: impl AsRef<Path>,
70 samples_per_chunk: usize,
71 sample_type: SampleType,
72 ) -> Result<Self, std::io::Error> {
73 let file = File::open(file_path)?;
74 let reader = BufReader::new(file);
75 Ok(SdrFileReader {
76 reader,
77 samples_per_chunk,
78 sample_type,
79 })
80 }
81
82 pub fn read_next_chunk_complexf32(
94 &mut self,
95 ) -> Result<Option<Vec<Complex<f32>>>, std::io::Error> {
96 let mut buffer = vec![0u8; self.samples_per_chunk * self.sample_type.sample_len()]; match self.reader.read_exact(&mut buffer) {
98 Ok(()) => {
99 let mut samples = Vec::with_capacity(self.samples_per_chunk);
100 match self.sample_type {
101 SampleType::U8 => buffer
102 .chunks_exact(self.sample_type.sample_len())
103 .for_each(|s| samples.push(Complex::new(f32::from(s[0]), f32::from(s[1])))),
104 SampleType::I8 => {
105 buffer
106 .chunks_exact(self.sample_type.sample_len())
107 .for_each(|s| {
108 samples.push(Complex::new(
109 f32::from(i8::from_ne_bytes([s[0]])),
110 f32::from(i8::from_ne_bytes([s[1]])),
111 ));
112 });
113 }
114 SampleType::U16 => {
115 buffer
116 .chunks_exact(self.sample_type.sample_len())
117 .for_each(|s| {
118 samples.push(Complex::new(
119 f32::from(u16::from_ne_bytes([s[0], s[1]])),
120 f32::from(u16::from_ne_bytes([s[2], s[3]])),
121 ));
122 });
123 }
124 SampleType::I16 => {
125 buffer
126 .chunks_exact(self.sample_type.sample_len())
127 .for_each(|s| {
128 samples.push(Complex::new(
129 f32::from(i16::from_ne_bytes([s[0], s[1]])),
130 f32::from(i16::from_ne_bytes([s[2], s[3]])),
131 ));
132 });
133 }
134 SampleType::F32 => {
135 buffer
136 .chunks_exact(self.sample_type.sample_len())
137 .for_each(|s| {
138 samples.push(Complex::new(
139 f32::from_ne_bytes([s[0], s[1], s[2], s[3]]),
140 f32::from_ne_bytes([s[4], s[5], s[6], s[7]]),
141 ));
142 });
143 }
144 #[allow(clippy::cast_possible_truncation)]
145 SampleType::F64 => {
146 buffer
147 .chunks_exact(self.sample_type.sample_len())
148 .for_each(|s| {
149 samples.push(Complex::new(
150 f64::from_ne_bytes([
151 s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7],
152 ]) as f32,
153 f64::from_ne_bytes([
154 s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15],
155 ]) as f32,
156 ));
157 });
158 }
159 }
160 Ok(Some(samples))
161 }
162 Err(why) => match why.kind() {
163 ErrorKind::UnexpectedEof => Ok(None),
164 _ => Err(why),
165 },
166 }
167 }
168
169 pub fn read_next_chunk_complexf64(
178 &mut self,
179 ) -> Result<Option<Vec<Complex<f64>>>, std::io::Error> {
180 let mut buffer = vec![0u8; self.samples_per_chunk * self.sample_type.sample_len()]; match self.reader.read_exact(&mut buffer) {
182 Ok(()) => {
183 let mut samples = Vec::with_capacity(self.samples_per_chunk);
184 match self.sample_type {
185 SampleType::U8 => buffer
186 .chunks_exact(self.sample_type.sample_len())
187 .for_each(|s| samples.push(Complex::new(f64::from(s[0]), f64::from(s[1])))),
188 SampleType::I8 => {
189 buffer
190 .chunks_exact(self.sample_type.sample_len())
191 .for_each(|s| {
192 samples.push(Complex::new(
193 f64::from(i8::from_ne_bytes([s[0]])),
194 f64::from(i8::from_ne_bytes([s[1]])),
195 ));
196 });
197 }
198 SampleType::U16 => {
199 buffer
200 .chunks_exact(self.sample_type.sample_len())
201 .for_each(|s| {
202 samples.push(Complex::new(
203 f64::from(u16::from_ne_bytes([s[0], s[1]])),
204 f64::from(u16::from_ne_bytes([s[2], s[3]])),
205 ));
206 });
207 }
208 SampleType::I16 => {
209 buffer
210 .chunks_exact(self.sample_type.sample_len())
211 .for_each(|s| {
212 samples.push(Complex::new(
213 f64::from(i16::from_ne_bytes([s[0], s[1]])),
214 f64::from(i16::from_ne_bytes([s[2], s[3]])),
215 ));
216 });
217 }
218 SampleType::F32 => {
219 buffer
220 .chunks_exact(self.sample_type.sample_len())
221 .for_each(|s| {
222 samples.push(Complex::new(
223 f64::from(f32::from_ne_bytes([s[0], s[1], s[2], s[3]])),
224 f64::from(f32::from_ne_bytes([s[4], s[5], s[6], s[7]])),
225 ));
226 });
227 }
228 SampleType::F64 => {
229 buffer
230 .chunks_exact(self.sample_type.sample_len())
231 .for_each(|s| {
232 samples.push(Complex::new(
233 f64::from_ne_bytes([
234 s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7],
235 ]),
236 f64::from_ne_bytes([
237 s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15],
238 ]),
239 ));
240 });
241 }
242 }
243 Ok(Some(samples))
244 }
245 Err(why) => match why.kind() {
246 ErrorKind::UnexpectedEof => Ok(None),
247 _ => Err(why),
248 },
249 }
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn test_sdr_file_reader_f32() {
259 let file_path = "gqrx_20240929_015218_580206500_2400000_fc.raw";
260 let mut reader = SdrFileReader::builder()
261 .file_path(file_path)
262 .samples_per_chunk(1024)
263 .sample_type(SampleType::F32)
264 .build()
265 .expect("Failed to create SdrFileReader");
266 while let Some(samples) = reader.read_next_chunk_complexf32().unwrap() {
267 assert_eq!(samples.len(), 1024);
268 }
270 }
271
272 #[test]
273 fn test_sdr_file_reader_f64() {
274 let file_path = "gqrx_20240929_015218_580206500_2400000_fc.raw";
275 let mut reader = SdrFileReader::builder()
276 .file_path(file_path)
277 .samples_per_chunk(1024)
278 .sample_type(SampleType::F32)
279 .build()
280 .expect("Failed to create SdrFileReader");
281 while let Some(samples) = reader.read_next_chunk_complexf64().unwrap() {
282 assert_eq!(samples.len(), 1024);
283 }
285 }
286
287 #[test]
288 fn test_compare_f32_f64_readings() {
289 let file_path = "gqrx_20240929_015218_580206500_2400000_fc.raw";
290 let mut reader_f32 = SdrFileReader::builder()
291 .file_path(file_path)
292 .samples_per_chunk(1024)
293 .sample_type(SampleType::F32)
294 .build()
295 .expect("Failed to create SdrFileReader for f32");
296
297 let mut reader_f64 = SdrFileReader::builder()
298 .file_path(file_path)
299 .samples_per_chunk(1024)
300 .sample_type(SampleType::F32)
301 .build()
302 .expect("Failed to create SdrFileReader for f64");
303
304 while let (Some(samples_f32), Some(samples_f64)) = (
305 reader_f32.read_next_chunk_complexf32().unwrap(),
306 reader_f64.read_next_chunk_complexf64().unwrap(),
307 ) {
308 assert_eq!(samples_f32.len(), samples_f64.len());
309 for (s32, s64) in samples_f32.iter().zip(samples_f64.iter()) {
310 assert!((f64::from(s32.re) - s64.re).abs() < f64::from(f32::EPSILON));
312 assert!((f64::from(s32.im) - s64.im).abs() < f64::from(f32::EPSILON));
313 }
314 }
315 }
316}