1extern crate csv;
19#[cfg(feature = "png")]
20extern crate png;
21
22mod builder;
23mod colour_gradient;
24mod errors;
25mod freq_scales;
26mod spec_core;
27mod window_fn;
28
29pub use builder::SpecOptionsBuilder;
30pub use colour_gradient::{ColourGradient, ColourTheme, RGBAColour};
31pub use errors::SonogramError;
32pub use freq_scales::{FreqScaler, FrequencyScale};
33use num_traits::NumCast;
34pub use spec_core::SpecCompute;
35pub use window_fn::*;
36
37#[cfg(feature = "png")]
38use std::fs::File;
39#[cfg(feature = "png")]
40use std::io::BufWriter;
41use std::path::Path;
42
43use resize::Pixel::GrayF32;
44use resize::Type::Lanczos3;
45use rgb::FromSlice;
46
47#[cfg(feature = "png")]
48use png::HasParameters; #[cfg(feature = "rayon")]
51use rayon::prelude::*;
52
53pub struct Spectrogram {
54 spec: Vec<f32>,
55 width: usize,
56 height: usize,
57}
58
59impl Spectrogram {
60 pub fn from_raw<T: NumCast + Copy>(
72 buf: &[T],
73 width: usize,
74 height: usize,
75 ) -> Result<Spectrogram, SonogramError> {
76 if buf.len() != width * height {
77 return Err(SonogramError::InvalidRawDataSize);
78 }
79
80 let spec = Spectrogram {
81 spec: buf.iter().map(|&x| NumCast::from(x).unwrap()).collect(),
82 width,
83 height,
84 };
85
86 Ok(spec)
87 }
88
89 #[cfg(feature = "png")]
101 pub fn to_png(
102 &mut self,
103 fname: &Path,
104 freq_scale: FrequencyScale,
105 gradient: &mut ColourGradient,
106 w_img: usize,
107 h_img: usize,
108 ) -> Result<(), std::io::Error> {
109 let buf = self.to_buffer(freq_scale, w_img, h_img);
110
111 let mut img: Vec<u8> = vec![0u8; w_img * h_img * 4];
112 self.buf_to_img(&buf, &mut img, gradient);
113
114 let file = File::create(fname)?;
115 let w = &mut BufWriter::new(file);
116 let mut encoder = png::Encoder::new(w, w_img as u32, h_img as u32);
117 encoder.set(png::ColorType::RGBA).set(png::BitDepth::Eight);
118 let mut writer = encoder.write_header()?;
119 writer.write_image_data(&img)?; Ok(())
122 }
123
124 #[cfg(feature = "png")]
135 pub fn to_png_in_memory(
136 &mut self,
137 freq_scale: FrequencyScale,
138 gradient: &mut ColourGradient,
139 w_img: usize,
140 h_img: usize,
141 ) -> Result<Vec<u8>, std::io::Error> {
142 let buf = self.to_buffer(freq_scale, w_img, h_img);
143
144 let mut img: Vec<u8> = vec![0u8; w_img * h_img * 4];
145 self.buf_to_img(&buf, &mut img, gradient);
146
147 let mut pngbuf: Vec<u8> = Vec::new();
148 let mut encoder = png::Encoder::new(&mut pngbuf, w_img as u32, h_img as u32);
149 encoder.set(png::ColorType::RGBA).set(png::BitDepth::Eight);
150 let mut writer = encoder.write_header()?;
151 writer.write_image_data(&img)?;
152
153 drop(writer);
155 Ok(pngbuf)
156 }
157
158 pub fn to_rgba_in_memory(
169 &mut self,
170 freq_scale: FrequencyScale,
171 gradient: &mut ColourGradient,
172 w_img: usize,
173 h_img: usize,
174 ) -> Vec<u8> {
175 let buf = self.to_buffer(freq_scale, w_img, h_img);
176
177 let mut img: Vec<u8> = vec![0u8; w_img * h_img * 4];
178 self.buf_to_img(&buf, &mut img, gradient);
179
180 img
181 }
182
183 fn buf_to_img(&self, buf: &[f32], img: &mut [u8], gradient: &mut ColourGradient) {
185 let (min, max) = get_min_max(buf);
186 gradient.set_min(min);
187 gradient.set_max(max);
188
189 buf.iter()
191 .map(|val| gradient.get_colour(*val))
192 .flat_map(|c| [c.r, c.g, c.b, c.a].into_iter())
193 .zip(img.iter_mut())
194 .for_each(|(val_rgba, img_rgba)| *img_rgba = val_rgba);
195 }
196
197 pub fn to_csv(
208 &mut self,
209 fname: &Path,
210 freq_scale: FrequencyScale,
211 cols: usize,
212 rows: usize,
213 ) -> Result<(), std::io::Error> {
214 let result = self.to_buffer(freq_scale, cols, rows);
215
216 let mut writer = csv::Writer::from_path(fname)?;
217
218 let mut csv_record: Vec<String> = (0..cols).map(|x| x.to_string()).collect();
220 writer.write_record(&csv_record)?;
221
222 let mut i = 0;
223 for _ in 0..rows {
224 for c_rec in csv_record.iter_mut().take(cols) {
225 let val = result[i];
226 i += 1;
227 *c_rec = val.to_string();
228 }
229 writer.write_record(&csv_record)?;
230 }
231
232 writer.flush()?; Ok(())
235 }
236
237 pub fn to_buffer(
250 &self,
251 freq_scale: FrequencyScale,
252 img_width: usize,
253 img_height: usize,
254 ) -> Vec<f32> {
255 let mut buf = Vec::with_capacity(self.height * self.width);
256
257 match freq_scale {
259 FrequencyScale::Log => {
260 let scaler = FreqScaler::create(freq_scale, self.height, self.height);
261 let mut vert_slice = vec![0.0; self.height];
262 for h in 0..self.height {
263 let (f1, f2) = scaler.scale(h);
264 let (h1, mut h2) = (f1.floor() as usize, f2.ceil() as usize);
265 if h2 >= self.height {
266 h2 = self.height - 1;
267 }
268 for w in 0..self.width {
269 for (hh, val) in vert_slice.iter_mut().enumerate().take(h2).skip(h1) {
270 *val = self.spec[(hh * self.width) + w];
271 }
272 let value = integrate(f1, f2, &vert_slice);
273 buf.push(value);
274 }
275 }
276 }
277 FrequencyScale::Linear => {
278 buf.clone_from(&self.spec);
279 }
280 }
281
282 to_db(&mut buf);
284
285 resize(&buf, self.width, self.height, img_width, img_height)
286 }
287
288 pub fn get_min_max(&self) -> (f32, f32) {
292 get_min_max(&self.spec)
293 }
294
295 pub fn shape(&self) -> (usize, usize) {
299 (self.width, self.height)
300 }
301
302 pub fn row_iter<'a>(&'a self, row_idx: usize) -> impl Iterator<Item = &'a f32> + 'a {
306 self.spec
307 .chunks_exact(self.width)
308 .skip(row_idx)
309 .flatten()
310 .take(self.width)
311 }
312}
313
314pub fn get_min_max(data: &[f32]) -> (f32, f32) {
315 let mut min = f32::MAX;
316 let mut max = f32::MIN;
317 for val in data {
318 min = f32::min(*val, min);
319 max = f32::max(*val, max);
320 }
321 (min, max)
322}
323
324#[cfg(feature = "rayon")]
325fn to_db(buf: &mut [f32]) {
326 let ref_db = buf
327 .par_chunks(1_000)
328 .fold(
329 || f32::MIN,
330 |acc, chunk| {
331 let v = chunk.iter().fold(f32::MIN, |acc, &v| f32::max(acc, v));
332 if acc > v {
333 acc
334 } else {
335 v
336 }
337 },
338 )
339 .reduce(|| f32::MIN, f32::max);
340
341 let amp_ref = ref_db * ref_db;
342 let offset = 10.0 * (f32::max(1e-10, amp_ref)).log10();
343 let log_spec_max = buf
344 .par_iter_mut()
345 .map(|val| {
346 *val = 10.0 * (f32::max(1e-10, *val * *val)).log10() - offset;
347 *val
348 })
349 .fold(|| f32::MIN, f32::max)
350 .reduce(|| f32::MIN, f32::max);
351 let log_spec_max = log_spec_max - 80.0; buf.par_chunks_mut(1_000).for_each(|chunk| {
354 for val in chunk.iter_mut() {
355 *val = f32::max(*val, log_spec_max);
356 }
357 });
358}
359
360#[cfg(not(feature = "rayon"))]
361fn to_db(buf: &mut [f32]) {
362 let mut ref_db = f32::MIN;
363 buf.iter().for_each(|v| ref_db = f32::max(ref_db, *v));
364
365 let amp_ref = ref_db * ref_db;
366 let offset = 10.0 * (f32::max(1e-10, amp_ref)).log10();
367 let mut log_spec_max = f32::MIN;
368
369 for val in buf.iter_mut() {
370 *val = 10.0 * (f32::max(1e-10, *val * *val)).log10() - offset;
371 log_spec_max = f32::max(log_spec_max, *val);
372 }
373
374 for val in buf.iter_mut() {
375 *val = f32::max(*val, log_spec_max - 80.0);
376 }
377}
378
379fn resize(buf: &[f32], w_in: usize, h_in: usize, w_out: usize, h_out: usize) -> Vec<f32> {
383 if let Ok(mut resizer) = resize::new(w_in, h_in, w_out, h_out, GrayF32, Lanczos3) {
385 let mut resized_buf = vec![0.0; w_out * h_out];
386 let result = resizer.resize(buf.as_gray(), resized_buf.as_gray_mut());
387 if result.is_ok() {
388 return resized_buf;
389 }
390 }
391
392 vec![]
394}
395
396fn integrate(x1: f32, x2: f32, spec: &[f32]) -> f32 {
414 let mut i_x1 = x1.floor() as usize;
415 let i_x2 = (x2 - 0.000001).floor() as usize;
416
417 let area = |y, frac| y * frac;
419
420 if i_x1 >= i_x2 {
421 area(spec[i_x1], x2 - x1)
423 } else {
424 let mut result = area(spec[i_x1], (i_x1 + 1) as f32 - x1);
426 i_x1 += 1;
427 while i_x1 < i_x2 {
428 result += spec[i_x1];
429 i_x1 += 1;
430 }
431 if i_x1 >= spec.len() {
432 i_x1 = spec.len() - 1;
433 }
434 result += area(spec[i_x1], x2 - i_x1 as f32);
435 result
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442
443 #[test]
444 fn test_integrate() {
445 let v = vec![1.0, 2.0, 4.0, 1.123];
446
447 let c = integrate(0.0, 0.0, &v);
449 assert!((c - 0.0).abs() < 0.0001);
450
451 let c = integrate(0.25, 1.0, &v);
453 assert!((c - 0.75).abs() < 0.0001);
454
455 let c = integrate(0.0, 1.0, &v);
456 assert!((c - 1.0).abs() < 0.0001);
457
458 let c = integrate(3.75, 4.0, &v);
459 assert!((c - 1.123 / 4.0).abs() < 0.0001);
460
461 let c = integrate(0.5, 1.0, &v);
462 assert!((c - 0.5).abs() < 0.0001);
463
464 let c = integrate(0.75, 1.25, &v);
466 assert!((c - 0.75).abs() < 0.0001);
467
468 let c = integrate(1.8, 2.6, &v);
469 assert!((c - 2.8).abs() < 0.0001);
470
471 let c = integrate(0.0, 4.0, &v);
473 assert!((c - 8.123).abs() < 0.0001);
474 }
475
476 #[test]
477 fn test_from_raw() {
478 let raw = vec![-1i16, -2, -3, 4, 5, 6];
479 let spec = Spectrogram::from_raw(&raw, 2, 3).unwrap();
480 assert_eq!(spec.width, 2);
481 assert_eq!(spec.height, 3);
482 assert_eq!(spec.spec, vec![-1.0f32, -2.0, -3.0, 4.0, 5.0, 6.0]);
483 }
484}