1#[cfg(feature = "resample")]
7use crate::error::PiperError;
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct AudioFormat {
12 pub sample_rate: u32,
13 pub channels: u16,
14 pub bits_per_sample: u16,
15}
16
17impl AudioFormat {
18 pub fn mono_16bit(sample_rate: u32) -> Self {
19 Self {
20 sample_rate,
21 channels: 1,
22 bits_per_sample: 16,
23 }
24 }
25
26 pub fn piper_default() -> Self {
28 Self::mono_16bit(22050)
29 }
30}
31
32pub fn resample_linear(samples: &[i16], from_rate: u32, to_rate: u32) -> Vec<i16> {
35 if samples.is_empty() || from_rate == 0 || to_rate == 0 {
36 return Vec::new();
37 }
38
39 if from_rate == to_rate {
40 return samples.to_vec();
41 }
42
43 let ratio = to_rate as f64 / from_rate as f64;
44 let out_len = (samples.len() as f64 * ratio).ceil() as usize;
45 let mut output = Vec::with_capacity(out_len);
46
47 let step = from_rate as f64 / to_rate as f64; let mut src_pos = 0.0_f64;
49 for _i in 0..out_len {
50 let src_idx = src_pos as usize;
51 let frac = src_pos - src_idx as f64;
52
53 let sample = if src_idx + 1 < samples.len() {
54 let a = samples[src_idx] as f64;
55 let b = samples[src_idx + 1] as f64;
56 (a + (b - a) * frac) as i16
57 } else {
58 samples[samples.len() - 1]
60 };
61
62 output.push(sample);
63 src_pos += step; }
65
66 output
67}
68
69#[cfg(feature = "resample")]
72pub fn resample_sinc(
73 samples: &[i16],
74 from_rate: u32,
75 to_rate: u32,
76) -> Result<Vec<i16>, PiperError> {
77 use rubato::{
78 Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction,
79 };
80
81 if samples.is_empty() || from_rate == 0 || to_rate == 0 {
82 return Ok(Vec::new());
83 }
84
85 if from_rate == to_rate {
86 return Ok(samples.to_vec());
87 }
88
89 let params = SincInterpolationParameters {
90 sinc_len: 256,
91 f_cutoff: 0.95,
92 interpolation: SincInterpolationType::Linear,
93 oversampling_factor: 256,
94 window: WindowFunction::BlackmanHarris2,
95 };
96
97 let ratio = to_rate as f64 / from_rate as f64;
98 let chunk_size = 1024;
99
100 let mut resampler = SincFixedIn::<f64>::new(
101 ratio, 2.0, params, chunk_size, 1, )
103 .map_err(|e| PiperError::Inference(format!("resample init failed: {e}")))?;
104
105 let input_f64: Vec<f64> = samples.iter().map(|&s| s as f64 / 32768.0).collect();
107
108 let mut output_f64 = Vec::new();
109
110 let mut pos = 0;
112 while pos + chunk_size <= input_f64.len() {
113 let chunk = &input_f64[pos..pos + chunk_size];
114 let result = resampler
115 .process(&[chunk], None)
116 .map_err(|e| PiperError::Inference(format!("resample failed: {e}")))?;
117 output_f64.extend_from_slice(&result[0]);
118 pos += chunk_size;
119 }
120
121 if pos < input_f64.len() {
123 let remaining = &input_f64[pos..];
124 let mut padded = remaining.to_vec();
125 padded.resize(chunk_size, 0.0);
126 let result = resampler
127 .process(&[&padded], None)
128 .map_err(|e| PiperError::Inference(format!("resample failed: {e}")))?;
129 let expected = ((input_f64.len() - pos) as f64 * ratio).ceil() as usize;
131 let take = expected.min(result[0].len());
132 output_f64.extend_from_slice(&result[0][..take]);
133 }
134
135 let output: Vec<i16> = output_f64
137 .iter()
138 .map(|&s| (s * 32767.0).clamp(-32768.0, 32767.0) as i16)
139 .collect();
140
141 Ok(output)
142}
143
144pub fn mono_to_stereo(samples: &[i16]) -> Vec<i16> {
146 let mut output = Vec::with_capacity(samples.len() * 2);
147 for &s in samples {
148 output.push(s);
149 output.push(s);
150 }
151 output
152}
153
154pub fn stereo_to_mono(samples: &[i16]) -> Vec<i16> {
156 samples
157 .chunks_exact(2)
158 .map(|pair| {
159 ((pair[0] as i32 + pair[1] as i32) / 2) as i16
161 })
162 .collect()
163}
164
165pub fn i16_to_f32(samples: &[i16]) -> Vec<f32> {
167 samples.iter().map(|&s| s as f32 / 32768.0).collect()
168}
169
170pub fn f32_to_i16(samples: &[f32]) -> Vec<i16> {
172 samples
173 .iter()
174 .map(|&s| (s * 32768.0).clamp(-32768.0, 32767.0) as i16)
175 .collect()
176}
177
178pub fn normalize_peak(samples: &mut [i16], target_db: f32) {
183 if samples.is_empty() {
184 return;
185 }
186
187 let current_peak = samples
189 .iter()
190 .map(|&s| (s as i32).unsigned_abs())
191 .max()
192 .unwrap_or(0);
193
194 if current_peak == 0 {
195 return; }
197
198 let target_linear = 32767.0_f64 * 10.0_f64.powf(target_db as f64 / 20.0);
200 let scale = target_linear / current_peak as f64;
201
202 for s in samples.iter_mut() {
203 *s = (*s as f64 * scale).clamp(-32768.0, 32767.0) as i16;
204 }
205}
206
207pub fn rms_db(samples: &[i16]) -> f32 {
212 if samples.is_empty() {
213 return f32::NEG_INFINITY;
214 }
215
216 let sum_sq: f64 = samples.iter().map(|&s| (s as f64) * (s as f64)).sum();
217 let rms = (sum_sq / samples.len() as f64).sqrt();
218
219 if rms == 0.0 {
220 return f32::NEG_INFINITY;
221 }
222
223 (20.0 * (rms / 32768.0).log10()) as f32
224}
225
226pub fn trim_silence(samples: &[i16], threshold_db: f32) -> &[i16] {
231 if samples.is_empty() {
232 return samples;
233 }
234
235 let threshold_linear = (32768.0_f64 * 10.0_f64.powf(threshold_db as f64 / 20.0)) as i32;
237
238 let start = samples
240 .iter()
241 .position(|&s| (s as i32).abs() >= threshold_linear)
242 .unwrap_or(0);
243
244 let end = samples
246 .iter()
247 .rposition(|&s| (s as i32).abs() >= threshold_linear)
248 .map(|p| p + 1)
249 .unwrap_or(0);
250
251 if start >= end {
252 return &samples[0..0];
253 }
254
255 &samples[start..end]
256}
257
258pub fn fade_in(samples: &mut [i16], fade_samples: usize) {
260 let fade_len = fade_samples.min(samples.len());
261 if fade_len == 0 {
262 return;
263 }
264 let inv_len = 1.0_f64 / fade_len as f64; let mut gain = 0.0_f64;
266 for s in samples[..fade_len].iter_mut() {
267 *s = (*s as f64 * gain) as i16;
268 gain += inv_len; }
270}
271
272pub fn fade_out(samples: &mut [i16], fade_samples: usize) {
274 let len = samples.len();
275 let fade_len = fade_samples.min(len);
276 if fade_len == 0 {
277 return;
278 }
279 let fade_start = len - fade_len;
280 let inv_len = 1.0_f64 / fade_len as f64; let mut gain = 1.0_f64;
282 for s in samples[fade_start..].iter_mut() {
283 *s = (*s as f64 * gain) as i16;
284 gain -= inv_len; }
286}
287
288pub fn concat_audio(chunks: &[&[i16]], crossfade_samples: usize) -> Vec<i16> {
293 if chunks.is_empty() {
294 return Vec::new();
295 }
296
297 if chunks.len() == 1 {
298 return chunks[0].to_vec();
299 }
300
301 if crossfade_samples == 0 {
302 let total_len: usize = chunks.iter().map(|c| c.len()).sum();
304 let mut output = Vec::with_capacity(total_len);
305 for chunk in chunks {
306 output.extend_from_slice(chunk);
307 }
308 return output;
309 }
310
311 let mut output = chunks[0].to_vec();
313
314 for chunk in &chunks[1..] {
315 let xfade = crossfade_samples.min(output.len()).min(chunk.len());
316
317 if xfade == 0 {
318 output.extend_from_slice(chunk);
319 continue;
320 }
321
322 let out_start = output.len() - xfade;
324 let slope = 1.0_f64 / (xfade + 1) as f64; let mut t = slope;
326 for i in 0..xfade {
327 let a = output[out_start + i] as f64 * (1.0 - t);
328 let b = chunk[i] as f64 * t;
329 output[out_start + i] = (a + b).clamp(-32768.0, 32767.0) as i16;
330 t += slope; }
332
333 if xfade < chunk.len() {
335 output.extend_from_slice(&chunk[xfade..]);
336 }
337 }
338
339 output
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
349 fn test_audio_format_mono_16bit() {
350 let fmt = AudioFormat::mono_16bit(44100);
351 assert_eq!(fmt.sample_rate, 44100);
352 assert_eq!(fmt.channels, 1);
353 assert_eq!(fmt.bits_per_sample, 16);
354 }
355
356 #[test]
357 fn test_audio_format_piper_default() {
358 let fmt = AudioFormat::piper_default();
359 assert_eq!(fmt.sample_rate, 22050);
360 assert_eq!(fmt.channels, 1);
361 assert_eq!(fmt.bits_per_sample, 16);
362 }
363
364 #[test]
365 fn test_audio_format_equality() {
366 let a = AudioFormat::mono_16bit(16000);
367 let b = AudioFormat::mono_16bit(16000);
368 let c = AudioFormat::mono_16bit(22050);
369 assert_eq!(a, b);
370 assert_ne!(a, c);
371 }
372
373 #[test]
376 fn test_resample_linear_identity() {
377 let input: Vec<i16> = (0..100).map(|i| (i * 100) as i16).collect();
378 let output = resample_linear(&input, 22050, 22050);
379 assert_eq!(input, output);
380 }
381
382 #[test]
383 fn test_resample_linear_empty() {
384 let output = resample_linear(&[], 22050, 44100);
385 assert!(output.is_empty());
386 }
387
388 #[test]
389 fn test_resample_linear_upsample_length() {
390 let input: Vec<i16> = vec![0; 100];
391 let output = resample_linear(&input, 22050, 44100);
392 assert!((output.len() as f64 - 200.0).abs() < 2.0);
394 }
395
396 #[test]
397 fn test_resample_linear_downsample_length() {
398 let input: Vec<i16> = vec![0; 200];
399 let output = resample_linear(&input, 44100, 22050);
400 assert!((output.len() as f64 - 100.0).abs() < 2.0);
402 }
403
404 #[test]
405 fn test_resample_linear_preserves_dc() {
406 let input: Vec<i16> = vec![1000; 100];
408 let output = resample_linear(&input, 22050, 44100);
409 for &s in &output {
410 assert_eq!(s, 1000);
411 }
412 }
413
414 #[test]
415 fn test_resample_linear_zero_rate() {
416 let input: Vec<i16> = vec![100; 10];
417 assert!(resample_linear(&input, 0, 44100).is_empty());
418 assert!(resample_linear(&input, 44100, 0).is_empty());
419 }
420
421 #[test]
424 fn test_mono_to_stereo() {
425 let mono = vec![100i16, 200, 300];
426 let stereo = mono_to_stereo(&mono);
427 assert_eq!(stereo, vec![100, 100, 200, 200, 300, 300]);
428 }
429
430 #[test]
431 fn test_stereo_to_mono() {
432 let stereo = vec![100i16, 200, 300, 400];
433 let mono = stereo_to_mono(&stereo);
434 assert_eq!(mono, vec![150, 350]);
435 }
436
437 #[test]
438 fn test_mono_stereo_roundtrip() {
439 let original = vec![100i16, -200, 300, -400, 500];
440 let stereo = mono_to_stereo(&original);
441 let back = stereo_to_mono(&stereo);
442 assert_eq!(original, back);
443 }
444
445 #[test]
446 fn test_stereo_to_mono_empty() {
447 let result = stereo_to_mono(&[]);
448 assert!(result.is_empty());
449 }
450
451 #[test]
454 fn test_i16_to_f32_range() {
455 let samples = vec![0i16, 32767, -32768];
456 let floats = i16_to_f32(&samples);
457 assert!((floats[0]).abs() < 1e-6);
458 assert!((floats[1] - 32767.0 / 32768.0).abs() < 1e-5);
459 assert!((floats[2] - (-1.0)).abs() < 1e-5);
460 }
461
462 #[test]
463 fn test_f32_to_i16_clamping() {
464 let samples = vec![2.0f32, -2.0, 0.5];
465 let ints = f32_to_i16(&samples);
466 assert_eq!(ints[0], 32767); assert_eq!(ints[1], -32768); assert_eq!(ints[2], 16384);
470 }
471
472 #[test]
473 fn test_i16_f32_roundtrip() {
474 let original = vec![0i16, 1000, -1000, 16384, -16384];
475 let floats = i16_to_f32(&original);
476 let back = f32_to_i16(&floats);
477 for (a, b) in original.iter().zip(back.iter()) {
479 assert!(
480 (*a as i32 - *b as i32).abs() <= 1,
481 "roundtrip mismatch: {a} vs {b}"
482 );
483 }
484 }
485
486 #[test]
487 fn test_i16_to_f32_empty() {
488 assert!(i16_to_f32(&[]).is_empty());
489 }
490
491 #[test]
494 fn test_normalize_peak_to_full_scale() {
495 let mut samples = vec![1000i16, -1000, 500, -500];
496 normalize_peak(&mut samples, 0.0);
497 let peak = samples.iter().map(|&s| (s as i32).abs()).max().unwrap();
499 assert!((peak - 32767).abs() <= 1);
500 }
501
502 #[test]
503 fn test_normalize_peak_minus_6db() {
504 let mut samples = vec![32767i16, -32767];
505 normalize_peak(&mut samples, -6.0);
506 let peak = samples.iter().map(|&s| (s as i32).abs()).max().unwrap();
508 let expected = (32767.0 * 10.0_f64.powf(-6.0 / 20.0)) as i32;
509 assert!(
510 (peak - expected).abs() <= 1,
511 "peak={peak}, expected={expected}"
512 );
513 }
514
515 #[test]
516 fn test_normalize_peak_silence() {
517 let mut samples = vec![0i16; 100];
518 normalize_peak(&mut samples, -1.0);
519 assert!(samples.iter().all(|&s| s == 0));
521 }
522
523 #[test]
524 fn test_normalize_peak_empty() {
525 let mut samples: Vec<i16> = Vec::new();
526 normalize_peak(&mut samples, -1.0);
527 assert!(samples.is_empty());
528 }
529
530 #[test]
533 fn test_rms_db_silence() {
534 let samples = vec![0i16; 100];
535 assert_eq!(rms_db(&samples), f32::NEG_INFINITY);
536 }
537
538 #[test]
539 fn test_rms_db_full_scale_square() {
540 let samples = vec![32767i16; 1000];
542 let db = rms_db(&samples);
543 assert!(
544 (db - 0.0).abs() < 0.01,
545 "expected ~0 dB for full-scale, got {db}"
546 );
547 }
548
549 #[test]
550 fn test_rms_db_known_signal() {
551 let half = (32768.0 / 2.0) as i16; let samples = vec![half; 1000];
554 let db = rms_db(&samples);
555 assert!((db - (-6.02)).abs() < 0.1, "expected ~-6 dB, got {db}");
556 }
557
558 #[test]
559 fn test_rms_db_empty() {
560 assert_eq!(rms_db(&[]), f32::NEG_INFINITY);
561 }
562
563 #[test]
566 fn test_trim_silence_basic() {
567 let samples = vec![0i16, 0, 1000, 2000, 3000, 0, 0];
569 let trimmed = trim_silence(&samples, -30.0);
571 assert_eq!(trimmed, &[2000, 3000]);
573 }
574
575 #[test]
576 fn test_trim_silence_all_silence() {
577 let samples = vec![0i16; 100];
578 let trimmed = trim_silence(&samples, -60.0);
579 assert!(trimmed.is_empty());
580 }
581
582 #[test]
583 fn test_trim_silence_no_silence() {
584 let samples = vec![10000i16, 20000, 30000];
585 let trimmed = trim_silence(&samples, -96.0);
587 assert_eq!(trimmed, &[10000, 20000, 30000]);
588 }
589
590 #[test]
591 fn test_trim_silence_empty() {
592 let trimmed = trim_silence(&[], -30.0);
593 assert!(trimmed.is_empty());
594 }
595
596 #[test]
599 fn test_fade_in() {
600 let mut samples = vec![10000i16; 10];
601 fade_in(&mut samples, 5);
602 assert_eq!(samples[0], 0);
604 assert_eq!(samples[5], 10000);
606 assert_eq!(samples[9], 10000);
607 for i in 0..4 {
609 assert!(samples[i] <= samples[i + 1]);
610 }
611 }
612
613 #[test]
614 fn test_fade_out() {
615 let mut samples = vec![10000i16; 10];
616 fade_out(&mut samples, 5);
617 assert_eq!(samples[0], 10000);
619 assert_eq!(samples[4], 10000);
620 assert!(
622 (samples[9] - 2000).abs() <= 1,
623 "expected ~2000, got {}",
624 samples[9]
625 );
626 for i in 5..9 {
628 assert!(samples[i] >= samples[i + 1]);
629 }
630 }
631
632 #[test]
633 fn test_fade_in_larger_than_length() {
634 let mut samples = vec![10000i16; 3];
635 fade_in(&mut samples, 100); assert_eq!(samples[0], 0);
637 }
639
640 #[test]
641 fn test_fade_out_larger_than_length() {
642 let mut samples = vec![10000i16; 3];
643 fade_out(&mut samples, 100); assert_eq!(samples[0], 10000);
647 assert!(samples[2] < samples[0], "last should be smaller than first");
649 }
650
651 #[test]
654 fn test_concat_audio_simple() {
655 let a: Vec<i16> = vec![1, 2, 3];
656 let b: Vec<i16> = vec![4, 5, 6];
657 let result = concat_audio(&[&a, &b], 0);
658 assert_eq!(result, vec![1, 2, 3, 4, 5, 6]);
659 }
660
661 #[test]
662 fn test_concat_audio_single_chunk() {
663 let a: Vec<i16> = vec![1, 2, 3];
664 let result = concat_audio(&[&a], 0);
665 assert_eq!(result, vec![1, 2, 3]);
666 }
667
668 #[test]
669 fn test_concat_audio_empty() {
670 let result = concat_audio(&[], 0);
671 assert!(result.is_empty());
672 }
673
674 #[test]
675 fn test_concat_audio_with_crossfade_length() {
676 let a: Vec<i16> = vec![10000; 10];
677 let b: Vec<i16> = vec![10000; 10];
678 let result = concat_audio(&[&a, &b], 3);
680 assert_eq!(result.len(), 17);
681 }
682
683 #[test]
684 fn test_concat_audio_crossfade_values() {
685 let a: Vec<i16> = vec![5000; 10];
687 let b: Vec<i16> = vec![5000; 10];
688 let result = concat_audio(&[&a, &b], 4);
689 for &s in &result {
691 assert!((s - 5000).abs() <= 1, "expected ~5000, got {s}");
692 }
693 }
694
695 #[test]
696 fn test_concat_audio_three_chunks() {
697 let a: Vec<i16> = vec![1, 2, 3];
698 let b: Vec<i16> = vec![4, 5, 6];
699 let c: Vec<i16> = vec![7, 8, 9];
700 let result = concat_audio(&[&a, &b, &c], 0);
701 assert_eq!(result, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
702 }
703
704 #[test]
707 fn test_f32_to_i16_nan() {
708 let result = f32_to_i16(&[f32::NAN]);
711 assert_eq!(result.len(), 1);
712 assert_eq!(result[0], 0);
714 }
715
716 #[test]
717 fn test_f32_to_i16_positive_infinity() {
718 let result = f32_to_i16(&[f32::INFINITY]);
719 assert_eq!(result.len(), 1);
720 assert_eq!(result[0], i16::MAX);
721 }
722
723 #[test]
724 fn test_f32_to_i16_negative_infinity() {
725 let result = f32_to_i16(&[f32::NEG_INFINITY]);
726 assert_eq!(result.len(), 1);
727 assert_eq!(result[0], i16::MIN);
728 }
729
730 #[test]
731 fn test_resample_linear_single_sample() {
732 let input = vec![12345i16];
734 let output = resample_linear(&input, 22050, 44100);
735 assert!(!output.is_empty());
736 assert_eq!(output[0], 12345);
738 }
739
740 #[test]
741 fn test_resample_linear_zero_from_rate() {
742 let input = vec![100i16; 10];
743 let output = resample_linear(&input, 0, 44100);
744 assert!(output.is_empty());
745 }
746
747 #[test]
748 fn test_resample_linear_zero_to_rate() {
749 let input = vec![100i16; 10];
750 let output = resample_linear(&input, 44100, 0);
751 assert!(output.is_empty());
752 }
753
754 #[test]
755 fn test_fade_in_zero_fade_samples() {
756 let mut samples = vec![1000i16, 2000, 3000];
758 let original = samples.clone();
759 fade_in(&mut samples, 0);
760 assert_eq!(samples, original);
761 }
762
763 #[test]
764 fn test_fade_out_zero_fade_samples() {
765 let mut samples = vec![1000i16, 2000, 3000];
767 let original = samples.clone();
768 fade_out(&mut samples, 0);
769 assert_eq!(samples, original);
770 }
771
772 #[test]
773 fn test_stereo_to_mono_odd_length() {
774 let stereo = vec![100i16, 200, 300, 400, 500];
776 let mono = stereo_to_mono(&stereo);
777 assert_eq!(mono.len(), 2);
779 assert_eq!(mono[0], 150);
780 assert_eq!(mono[1], 350);
781 }
782
783 #[test]
784 fn test_normalize_peak_single_sample() {
785 let mut samples = vec![1000i16];
786 normalize_peak(&mut samples, 0.0);
787 assert!((samples[0] as i32 - 32767).abs() <= 1);
789 }
790
791 #[test]
792 fn test_trim_silence_very_low_threshold() {
793 let samples = vec![0i16, 1, 2, 3, 0];
796 let trimmed = trim_silence(&samples, -90.0);
797 assert_eq!(trimmed, &[1, 2, 3]);
798 }
799
800 #[test]
801 fn test_concat_audio_crossfade_exceeds_chunk_length() {
802 let a: Vec<i16> = vec![5000; 3];
804 let b: Vec<i16> = vec![5000; 2];
805 let result = concat_audio(&[&a, &b], 100);
806 assert_eq!(result.len(), 3);
809 for &s in &result {
812 assert!((s - 5000).abs() <= 1, "expected ~5000, got {s}");
813 }
814 }
815}