1use std::fs::File;
26use std::path::Path;
27
28use num_traits::Float;
29use thiserror::Error;
30
31#[cfg(feature = "resample")]
32use crate::resample::{ResampleError, resample};
33
34#[cfg(feature = "symphonia")]
35mod general;
36
37#[cfg(feature = "resample")]
41pub use rubato::Sample as ResampleSample;
42
43#[cfg(not(feature = "resample"))]
48pub trait ResampleSample {}
49
50#[cfg(not(feature = "resample"))]
51impl<F> ResampleSample for F {}
52
53#[derive(Debug, Clone)]
55pub struct Audio<F> {
56 pub samples_interleaved: Vec<F>,
58 pub sample_rate: u32,
60 pub num_channels: u16,
62}
63
64#[derive(Debug, Error)]
65#[non_exhaustive]
66pub enum ReadError {
67 #[error("could not read file: {0}")]
68 Io(#[from] std::io::Error),
69
70 #[cfg(feature = "symphonia")]
74 #[error("could not decode audio: {0}")]
75 Decode(#[from] symphonia::core::errors::Error),
76
77 #[error("no decoder in this build can read this file")]
81 UnsupportedFormat,
82
83 #[error("no track found")]
84 NoTrack,
85
86 #[error("no sample rate found")]
87 NoSampleRate,
88
89 #[error("could not determine the number of channels")]
90 NoChannels,
91
92 #[error("channel count ({0}) exceeds the supported maximum of 65535")]
93 TooManyChannels(usize),
94
95 #[error("start frame ({start}) must not exceed end frame ({end})")]
96 InvalidFrameRange { start: usize, end: usize },
97
98 #[error("start channel {start} out of bounds (file has {total} channels)")]
99 InvalidStartChannel { start: usize, total: usize },
100
101 #[error("channel count must not be zero")]
102 ZeroChannels,
103
104 #[error(
105 "channel range out of bounds: {count} channels starting at channel {start} (file has {total} channels)"
106 )]
107 InvalidChannelRange {
108 start: usize,
109 count: usize,
110 total: usize,
111 },
112
113 #[error("channel count changed mid-stream (was {expected}, now {found})")]
114 ChannelCountChanged { expected: usize, found: usize },
115
116 #[error("sample rate changed mid-stream (was {expected}, now {found})")]
117 SampleRateChanged { expected: u32, found: u32 },
118
119 #[cfg(feature = "symphonia")]
122 #[error("frames {start}..{end} are missing, the file is damaged or incomplete")]
123 MissingFrames { start: u64, end: u64 },
124
125 #[cfg(feature = "resample")]
126 #[error("resample failed")]
127 Resample(#[from] ResampleError),
128}
129
130#[derive(Default, Debug, Clone, Copy)]
132pub enum Position {
133 #[default]
135 Default,
136 Time(std::time::Duration),
138 Frame(usize),
140}
141
142#[derive(Default)]
143pub struct ReadConfig {
144 pub start: Position,
146 pub stop: Position,
148 pub start_channel: Option<usize>,
150 pub num_channels: Option<usize>,
152 #[cfg(feature = "resample")]
157 pub sample_rate: Option<u32>,
158}
159
160pub(crate) const MAX_PREALLOC_SAMPLES: usize = 16 * 1024 * 1024;
167
168pub fn read<F: Float + ResampleSample>(
181 path: impl AsRef<Path>,
182 config: ReadConfig,
183) -> Result<Audio<F>, ReadError> {
184 let decoded = decode::<F>(path.as_ref(), &config)?;
185 let num_channels = checked_num_channels(decoded.num_channels)?;
186 let (samples, sample_rate) = resolve_output_rate(decoded, &config)?;
187
188 Ok(Audio {
189 samples_interleaved: samples,
190 sample_rate,
191 num_channels,
192 })
193}
194
195#[cfg(feature = "resample")]
199fn resolve_output_rate<F: Float + ResampleSample>(
200 decoded: Decoded<F>,
201 config: &ReadConfig,
202) -> Result<(Vec<F>, u32), ReadError> {
203 Ok(match config.sample_rate {
204 Some(sr_out) if sr_out != decoded.sample_rate => (
205 resample(
206 &decoded.samples,
207 decoded.num_channels,
208 decoded.sample_rate,
209 sr_out,
210 )?,
211 sr_out,
212 ),
213 _ => (decoded.samples, decoded.sample_rate),
214 })
215}
216
217#[cfg(not(feature = "resample"))]
218fn resolve_output_rate<F>(
219 decoded: Decoded<F>,
220 _config: &ReadConfig,
221) -> Result<(Vec<F>, u32), ReadError> {
222 Ok((decoded.samples, decoded.sample_rate))
223}
224
225fn checked_num_channels(count: usize) -> Result<u16, ReadError> {
228 u16::try_from(count).map_err(|_| ReadError::TooManyChannels(count))
229}
230
231struct Decoded<F> {
233 samples: Vec<F>,
234 num_channels: usize,
235 sample_rate: u32,
236}
237
238#[derive(Clone, Copy)]
240struct Layout {
241 #[cfg_attr(not(feature = "symphonia"), allow(dead_code))]
246 total: usize,
247 start: usize,
249 count: usize,
251}
252
253#[derive(Clone, Copy)]
265struct Plan {
266 sample_rate: u32,
267 layout: Layout,
268 start_frame: usize,
270 end_frame: Option<usize>,
272}
273
274impl Plan {
275 fn resolve(sample_rate: u32, channels: usize, config: &ReadConfig) -> Result<Self, ReadError> {
277 let start_frame = position_to_frame(config.start, sample_rate).unwrap_or(0);
278 let end_frame = position_to_frame(config.stop, sample_rate);
279
280 if let Some(end_frame) = end_frame
281 && start_frame > end_frame
282 {
283 return Err(ReadError::InvalidFrameRange {
284 start: start_frame,
285 end: end_frame,
286 });
287 }
288
289 let (start, count) = channel_range(config, channels)?;
290 checked_num_channels(count)?;
293
294 Ok(Self {
295 sample_rate,
296 layout: Layout {
297 total: channels,
298 start,
299 count,
300 },
301 start_frame,
302 end_frame,
303 })
304 }
305}
306
307fn try_native_wav<F: Float>(
317 path: &Path,
318 config: &ReadConfig,
319) -> Result<Option<Decoded<F>>, ReadError> {
320 let file = File::open(path)?;
321 let Some(wav) = crate::wav::open_wav(file)? else {
322 return Ok(None);
323 };
324
325 let plan = Plan::resolve(wav.sample_rate, wav.num_channels, config)?;
326 let samples = crate::wav::read_frames::<F>(
327 wav,
328 plan.start_frame,
329 plan.end_frame,
330 plan.layout.start,
331 plan.layout.count,
332 )?;
333
334 Ok(Some(Decoded {
335 samples,
336 num_channels: plan.layout.count,
337 sample_rate: plan.sample_rate,
338 }))
339}
340
341fn decode<F: Float>(path: &Path, config: &ReadConfig) -> Result<Decoded<F>, ReadError> {
342 if let Some(decoded) = try_native_wav(path, config)? {
343 return Ok(decoded);
344 }
345
346 #[cfg(not(feature = "symphonia"))]
349 {
350 Err(ReadError::UnsupportedFormat)
351 }
352 #[cfg(feature = "symphonia")]
353 {
354 general::decode_with_symphonia(path, config)
355 }
356}
357
358fn channel_range(config: &ReadConfig, total: usize) -> Result<(usize, usize), ReadError> {
360 let start = config.start_channel.unwrap_or(0);
361 if start >= total {
362 return Err(ReadError::InvalidStartChannel { start, total });
363 }
364
365 let count = config.num_channels.unwrap_or(total - start);
366 if count == 0 {
367 return Err(ReadError::ZeroChannels);
368 }
369 if start.checked_add(count).is_none_or(|end| end > total) {
372 return Err(ReadError::InvalidChannelRange {
373 start,
374 count,
375 total,
376 });
377 }
378
379 Ok((start, count))
380}
381
382fn position_to_frame(position: Position, sample_rate: u32) -> Option<usize> {
383 match position {
384 Position::Default => None,
385 Position::Time(duration) => {
386 Some((duration.as_secs_f64() * sample_rate as f64).round() as usize)
387 }
388 Position::Frame(frame) => Some(frame),
389 }
390}
391
392#[cfg(feature = "audio-blocks")]
393pub fn read_block<F: num_traits::Float + 'static + ResampleSample>(
394 path: impl AsRef<Path>,
395 config: ReadConfig,
396) -> Result<(audio_blocks::Interleaved<F>, u32), ReadError> {
397 let audio = read(path, config)?;
398 Ok((
399 audio_blocks::Interleaved::from_slice(&audio.samples_interleaved, audio.num_channels),
400 audio.sample_rate,
401 ))
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 use audio_blocks::{AudioBlock, InterleavedView};
413 use std::time::Duration;
414
415 fn to_block<F: num_traits::Float + 'static>(audio: &Audio<F>) -> InterleavedView<'_, F> {
416 InterleavedView::from_slice(&audio.samples_interleaved, audio.num_channels)
417 }
418
419 #[test]
422 fn test_missing_file_is_reported() {
423 match read::<f32>(crate::tmp_path("does-not-exist.wav"), ReadConfig::default()) {
424 Err(ReadError::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::NotFound),
425 other => panic!("{:?}", other.map(|audio| audio.num_channels)),
426 }
427 }
428
429 #[cfg(not(feature = "symphonia"))]
433 #[test]
434 fn test_unsupported_format_without_the_general_decoder() {
435 match read::<f32>("test_data/test_mp3.mp3", ReadConfig::default()) {
436 Err(ReadError::UnsupportedFormat) => (),
437 other => panic!("{:?}", other.map(|audio| audio.num_channels)),
438 }
439 }
440
441 #[test]
445 fn test_wav_round_trips_without_the_general_decoder() {
446 use crate::writer::{SampleFormat, WriteConfig, write};
447
448 let path = crate::tmp_path("no-symphonia-round-trip.wav");
449 let samples: Vec<f32> = (0..96).map(|i| (i as f32 / 48.0) - 1.0).collect();
450
451 for sample_format in [
452 SampleFormat::Int16,
453 SampleFormat::Int32,
454 SampleFormat::Float32,
455 ] {
456 write(&path, &samples, 3, 48_000, WriteConfig { sample_format }).unwrap();
457
458 let audio = read::<f32>(&path, ReadConfig::default()).unwrap();
459 assert_eq!(audio.num_channels, 3, "{sample_format:?}");
460 assert_eq!(audio.sample_rate, 48_000, "{sample_format:?}");
461 approx::assert_abs_diff_eq!(
462 samples.as_slice(),
463 audio.samples_interleaved.as_slice(),
464 epsilon = 1e-4
465 );
466
467 let audio = read::<f32>(
469 &path,
470 ReadConfig {
471 start: Position::Frame(4),
472 stop: Position::Frame(9),
473 start_channel: Some(1),
474 num_channels: Some(2),
475 #[cfg(feature = "resample")]
476 sample_rate: None,
477 },
478 )
479 .unwrap();
480 assert_eq!(audio.num_channels, 2, "{sample_format:?}");
481 let src = samples.as_slice();
482 let expected: Vec<f32> = (4..9)
483 .flat_map(|frame| (1..3).map(move |ch| src[frame * 3 + ch]))
484 .collect();
485 approx::assert_abs_diff_eq!(
486 expected.as_slice(),
487 audio.samples_interleaved.as_slice(),
488 epsilon = 1e-4
489 );
490 }
491
492 std::fs::remove_file(&path).unwrap();
493 }
494
495 #[test]
501 fn test_sine_wave_data_integrity() {
502 const SAMPLE_RATE: f64 = 48000.0;
503 const N_SAMPLES: usize = 48000;
504 const FREQUENCIES: [f64; 4] = [440.0, 554.37, 659.25, 880.0];
505
506 let audio = read::<f32>("test_data/test_4ch.wav", ReadConfig::default()).unwrap();
507 let block = to_block(&audio);
508
509 assert_eq!(audio.sample_rate, 48000);
510 assert_eq!(block.num_frames(), N_SAMPLES);
511 assert_eq!(block.num_channels(), 4);
512
513 for (ch, &freq) in FREQUENCIES.iter().enumerate() {
515 for frame in 0..N_SAMPLES {
516 let expected =
517 (2.0 * std::f64::consts::PI * freq * frame as f64 / SAMPLE_RATE).sin() as f32;
518 let actual = block.sample(ch as u16, frame);
519 assert!(
520 (actual - expected).abs() < 1e-4,
521 "Mismatch at channel {ch}, frame {frame}: expected {expected}, got {actual}"
522 );
523 }
524 }
525
526 let audio = read::<f32>(
528 "test_data/test_4ch.wav",
529 ReadConfig {
530 start: Position::Frame(24000),
531 stop: Position::Frame(24100),
532 ..Default::default()
533 },
534 )
535 .unwrap();
536 let block = to_block(&audio);
537
538 for (ch, &freq) in FREQUENCIES.iter().enumerate() {
539 for frame in 0..100 {
540 let actual_frame = 24000 + frame;
541 let expected = (2.0 * std::f64::consts::PI * freq * actual_frame as f64
542 / SAMPLE_RATE)
543 .sin() as f32;
544 let actual = block.sample(ch as u16, frame);
545 assert!(
546 (actual - expected).abs() < 1e-4,
547 "Offset mismatch at channel {ch}, frame {actual_frame}: expected {expected}, got {actual}"
548 );
549 }
550 }
551 }
552
553 #[test]
554 fn test_samples_selection() {
555 let audio1 = read::<f32>("test_data/test_1ch.wav", ReadConfig::default()).unwrap();
556 let block1 = to_block(&audio1);
557 assert_eq!(audio1.sample_rate, 48000);
558 assert_eq!(block1.num_frames(), 48000);
559 assert_eq!(block1.num_channels(), 1);
560
561 let audio2 = read::<f32>(
562 "test_data/test_1ch.wav",
563 ReadConfig {
564 start: Position::Frame(1100),
565 stop: Position::Frame(1200),
566 ..Default::default()
567 },
568 )
569 .unwrap();
570 let block2 = to_block(&audio2);
571 assert_eq!(audio2.sample_rate, 48000);
572 assert_eq!(block2.num_frames(), 100);
573 assert_eq!(block2.num_channels(), 1);
574 assert_eq!(block1.raw_data()[1100..1200], block2.raw_data()[..]);
575 }
576
577 #[test]
578 fn test_time_selection() {
579 let audio1 = read::<f32>("test_data/test_1ch.wav", ReadConfig::default()).unwrap();
580 let block1 = to_block(&audio1);
581 assert_eq!(audio1.sample_rate, 48000);
582 assert_eq!(block1.num_frames(), 48000);
583 assert_eq!(block1.num_channels(), 1);
584
585 let audio2 = read::<f32>(
586 "test_data/test_1ch.wav",
587 ReadConfig {
588 start: Position::Time(Duration::from_secs_f32(0.5)),
589 stop: Position::Time(Duration::from_secs_f32(0.6)),
590 ..Default::default()
591 },
592 )
593 .unwrap();
594 let block2 = to_block(&audio2);
595
596 assert_eq!(audio2.sample_rate, 48000);
597 assert_eq!(block2.num_frames(), 4800);
598 assert_eq!(block2.num_channels(), 1);
599 assert_eq!(block1.raw_data()[24000..28800], block2.raw_data()[..]);
600 }
601
602 #[test]
603 fn test_channel_selection() {
604 let audio1 = read::<f32>("test_data/test_4ch.wav", ReadConfig::default()).unwrap();
605 let block1 = to_block(&audio1);
606 assert_eq!(audio1.sample_rate, 48000);
607 assert_eq!(block1.num_frames(), 48000);
608 assert_eq!(block1.num_channels(), 4);
609
610 let audio2 = read::<f32>(
611 "test_data/test_4ch.wav",
612 ReadConfig {
613 start_channel: Some(1),
614 num_channels: Some(2),
615 ..Default::default()
616 },
617 )
618 .unwrap();
619 let block2 = to_block(&audio2);
620
621 assert_eq!(audio2.sample_rate, 48000);
622 assert_eq!(block2.num_frames(), 48000);
623 assert_eq!(block2.num_channels(), 2);
624
625 for frame in 0..10 {
627 assert_eq!(block2.sample(0, frame), block1.sample(1, frame));
628 assert_eq!(block2.sample(1, frame), block1.sample(2, frame));
629 }
630 }
631
632 #[test]
633 fn test_fail_selection() {
634 match read::<f32>(
635 "test_data/test_1ch.wav",
636 ReadConfig {
637 start: Position::Frame(100),
638 stop: Position::Frame(99),
639 ..Default::default()
640 },
641 ) {
642 Err(ReadError::InvalidFrameRange { start: _, end: _ }) => (),
643 _ => panic!(),
644 }
645
646 match read::<f32>(
647 "test_data/test_1ch.wav",
648 ReadConfig {
649 start: Position::Time(Duration::from_secs_f32(0.6)),
650 stop: Position::Time(Duration::from_secs_f32(0.5)),
651 ..Default::default()
652 },
653 ) {
654 Err(ReadError::InvalidFrameRange { start: _, end: _ }) => (),
655 _ => panic!(),
656 }
657
658 match read::<f32>(
659 "test_data/test_1ch.wav",
660 ReadConfig {
661 start_channel: Some(1),
662 ..Default::default()
663 },
664 ) {
665 Err(ReadError::InvalidStartChannel { start: _, total: _ }) => (),
666 _ => panic!(),
667 }
668
669 match read::<f32>(
672 "test_data/test_1ch.wav",
673 ReadConfig {
674 start_channel: Some(3),
675 ..Default::default()
676 },
677 ) {
678 Err(ReadError::InvalidStartChannel { start: 3, total: 1 }) => (),
679 other => panic!("{other:?}"),
680 }
681
682 match read::<f32>(
683 "test_data/test_1ch.wav",
684 ReadConfig {
685 num_channels: Some(0),
686 ..Default::default()
687 },
688 ) {
689 Err(ReadError::ZeroChannels) => (),
690 _ => panic!(),
691 }
692
693 match read::<f32>(
694 "test_data/test_1ch.wav",
695 ReadConfig {
696 num_channels: Some(2),
697 ..Default::default()
698 },
699 ) {
700 Err(ReadError::InvalidChannelRange {
701 start: 0,
702 count: 2,
703 total: 1,
704 }) => (),
705 other => panic!("{other:?}"),
706 }
707
708 let error = read::<f32>(
711 "test_data/test_4ch.wav",
712 ReadConfig {
713 start_channel: Some(1),
714 num_channels: Some(usize::MAX),
715 ..Default::default()
716 },
717 )
718 .expect_err("a channel count of usize::MAX must be rejected");
719
720 assert!(
721 matches!(
722 error,
723 ReadError::InvalidChannelRange {
724 start: 1,
725 count: usize::MAX,
726 total: 4,
727 }
728 ),
729 "{error:?}"
730 );
731 assert!(!error.to_string().is_empty());
732 }
733
734 #[cfg(feature = "resample")]
735 #[test]
736 fn test_resample_preserves_frequency() {
737 const FREQUENCIES: [f64; 4] = [440.0, 554.37, 659.25, 880.0];
738 let sr_out: u32 = 22050;
739
740 let audio = read::<f32>(
742 "test_data/test_4ch.wav",
743 ReadConfig {
744 sample_rate: Some(sr_out),
745 ..Default::default()
746 },
747 )
748 .unwrap();
749 let block = to_block(&audio);
750
751 assert_eq!(audio.sample_rate, sr_out); assert_eq!(block.num_channels(), 4);
753
754 let expected_frames = 22050;
756 assert_eq!(
757 block.num_frames(),
758 expected_frames,
759 "Expected {} frames, got {}",
760 expected_frames,
761 block.num_frames()
762 );
763
764 let start_frame = 100;
767 let test_frames = 1000;
768
769 for (ch, &freq) in FREQUENCIES.iter().enumerate() {
770 let mut max_error: f32 = 0.0;
771 for frame in start_frame..(start_frame + test_frames) {
772 let expected =
773 (2.0 * std::f64::consts::PI * freq * frame as f64 / sr_out as f64).sin() as f32;
774 let actual = block.sample(ch as u16, frame);
775 let error = (actual - expected).abs();
776 max_error = max_error.max(error);
777 }
778 assert!(
779 max_error < 0.02,
780 "Channel {} ({}Hz): max error {} exceeds threshold",
781 ch,
782 freq,
783 max_error
784 );
785 }
786 }
787
788 #[cfg(feature = "resample")]
789 #[test]
790 fn test_channel_selection_with_resampling() {
791 const FREQUENCIES: [f64; 4] = [440.0, 554.37, 659.25, 880.0];
793 let sr_out: u32 = 22050;
794
795 let audio = read::<f32>(
797 "test_data/test_4ch.wav",
798 ReadConfig {
799 start_channel: Some(1),
800 num_channels: Some(2),
801 sample_rate: Some(sr_out),
802 ..Default::default()
803 },
804 )
805 .unwrap();
806 let block = to_block(&audio);
807
808 assert_eq!(audio.num_channels, 2, "Should have 2 channels");
809 assert_eq!(
810 audio.sample_rate, sr_out,
811 "Sample rate should be the resampled rate"
812 );
813
814 let expected_frames = 22050;
816 assert_eq!(
817 block.num_frames(),
818 expected_frames,
819 "Expected {} frames, got {}",
820 expected_frames,
821 block.num_frames()
822 );
823
824 let selected_freqs = &FREQUENCIES[1..3];
827
828 let start_frame = 100;
829 let test_frames = 1000;
830
831 for (ch, &freq) in selected_freqs.iter().enumerate() {
832 let mut max_error: f32 = 0.0;
833 for frame in start_frame..(start_frame + test_frames) {
834 let expected =
835 (2.0 * std::f64::consts::PI * freq * frame as f64 / sr_out as f64).sin() as f32;
836 let actual = block.sample(ch as u16, frame);
837 let error = (actual - expected).abs();
838 max_error = max_error.max(error);
839 }
840 assert!(
841 max_error < 0.02,
842 "Channel {} ({}Hz): max error {} exceeds threshold",
843 ch,
844 freq,
845 max_error
846 );
847 }
848 }
849
850 #[test]
851 fn test_channel_count_must_fit_the_reported_type() {
852 assert_eq!(checked_num_channels(2).unwrap(), 2);
853 assert_eq!(
854 checked_num_channels(usize::from(u16::MAX)).unwrap(),
855 u16::MAX
856 );
857
858 let error = checked_num_channels(usize::from(u16::MAX) + 1)
859 .expect_err("more channels than u16 can hold must be rejected");
860 assert!(
861 matches!(error, ReadError::TooManyChannels(65_536)),
862 "{error:?}"
863 );
864 assert!(!error.to_string().is_empty());
865
866 assert!(matches!(
869 Plan::resolve(48_000, 65_536, &ReadConfig::default()),
870 Err(ReadError::TooManyChannels(65_536))
871 ));
872 let plan = Plan::resolve(
874 48_000,
875 65_536,
876 &ReadConfig {
877 num_channels: Some(2),
878 ..Default::default()
879 },
880 )
881 .unwrap();
882 assert_eq!(plan.layout.count, 2);
883 }
884
885 #[test]
886 fn test_plan_is_resolved_against_the_given_specification() {
887 let config = ReadConfig {
888 start: Position::Time(std::time::Duration::from_millis(10)),
889 stop: Position::Time(std::time::Duration::from_millis(20)),
890 start_channel: Some(1),
891 num_channels: Some(2),
892 #[cfg(feature = "resample")]
893 sample_rate: None,
894 };
895
896 let plan = Plan::resolve(48_000, 4, &config).unwrap();
898 assert_eq!(plan.sample_rate, 48_000);
899 assert_eq!(plan.start_frame, 480);
900 assert_eq!(plan.end_frame, Some(960));
901 assert_eq!(plan.layout.total, 4);
902 assert_eq!(plan.layout.start, 1);
903 assert_eq!(plan.layout.count, 2);
904
905 let plan = Plan::resolve(24_000, 4, &config).unwrap();
906 assert_eq!(plan.start_frame, 240);
907 assert_eq!(plan.end_frame, Some(480));
908
909 assert!(matches!(
911 Plan::resolve(48_000, 2, &config),
912 Err(ReadError::InvalidChannelRange {
913 start: 1,
914 count: 2,
915 total: 2
916 })
917 ));
918
919 let backwards = ReadConfig {
920 start: Position::Frame(100),
921 stop: Position::Frame(99),
922 ..Default::default()
923 };
924 assert!(matches!(
925 Plan::resolve(48_000, 1, &backwards),
926 Err(ReadError::InvalidFrameRange {
927 start: 100,
928 end: 99
929 })
930 ));
931 }
932
933 #[cfg(feature = "resample")]
935 #[test]
936 fn test_stop_with_resampling() {
937 let sr_out: u32 = 24000;
938
939 let audio = read::<f32>(
940 "test_data/test_4ch.wav",
941 ReadConfig {
942 stop: Position::Frame(24000),
943 sample_rate: Some(sr_out),
944 ..Default::default()
945 },
946 )
947 .unwrap();
948
949 assert_eq!(audio.sample_rate, sr_out);
950 assert_eq!(audio.num_channels, 4);
951 assert_eq!(to_block(&audio).num_frames(), 12000);
953 }
954
955 #[test]
957 fn test_start_beyond_seek_threshold() {
958 let path = crate::tmp_path("read-seek.wav");
959
960 let num_frames = 48000 * 3;
962 let mut samples = Vec::with_capacity(num_frames * 2);
963 for frame in 0..num_frames {
964 let value = frame as f32 / num_frames as f32;
965 samples.push(value);
966 samples.push(-value);
967 }
968 crate::writer::write(
969 &path,
970 &samples,
971 2,
972 48000,
973 crate::writer::WriteConfig {
974 sample_format: crate::writer::SampleFormat::Float32,
975 },
976 )
977 .unwrap();
978
979 for start in [48_001, 60_000, 100_000, 143_000] {
980 let audio = read::<f32>(
981 &path,
982 ReadConfig {
983 start: Position::Frame(start),
984 ..Default::default()
985 },
986 )
987 .unwrap();
988
989 assert_eq!(audio.num_channels, 2);
990 assert_eq!(
991 audio.samples_interleaved.len(),
992 (num_frames - start) * 2,
993 "wrong length for start frame {start}"
994 );
995 assert_eq!(
996 audio.samples_interleaved[..2],
997 samples[start * 2..start * 2 + 2],
998 "wrong first frame for start frame {start}"
999 );
1000 }
1001
1002 std::fs::remove_file(&path).unwrap();
1003 }
1004
1005 #[test]
1010 fn test_positions_beyond_the_end_of_the_file() {
1011 let path = crate::tmp_path("read-beyond-eof.wav");
1012
1013 let num_frames = 24_000;
1016 let samples: Vec<f32> = (0..num_frames * 2).map(|i| i as f32 / 1e6).collect();
1017 crate::writer::write(
1018 &path,
1019 &samples,
1020 2,
1021 48000,
1022 crate::writer::WriteConfig {
1023 sample_format: crate::writer::SampleFormat::Float32,
1024 },
1025 )
1026 .unwrap();
1027
1028 for start in [30_000, 48_001, 1_000_000] {
1033 let audio = read::<f32>(
1034 &path,
1035 ReadConfig {
1036 start: Position::Frame(start),
1037 ..Default::default()
1038 },
1039 )
1040 .unwrap();
1041
1042 assert_eq!(audio.num_channels, 2, "start frame {start}");
1043 assert_eq!(audio.sample_rate, 48000, "start frame {start}");
1044 assert!(
1045 audio.samples_interleaved.is_empty(),
1046 "start frame {start} returned {} samples",
1047 audio.samples_interleaved.len()
1048 );
1049 }
1050
1051 #[cfg(feature = "resample")]
1054 {
1055 let audio = read::<f32>(
1056 &path,
1057 ReadConfig {
1058 start: Position::Frame(1_000_000),
1059 sample_rate: Some(24_000),
1060 ..Default::default()
1061 },
1062 )
1063 .unwrap();
1064 assert_eq!(audio.sample_rate, 24_000);
1065 assert!(audio.samples_interleaved.is_empty());
1066 }
1067
1068 let audio = read::<f32>(
1069 &path,
1070 ReadConfig {
1071 stop: Position::Frame(1_000_000),
1072 ..Default::default()
1073 },
1074 )
1075 .unwrap();
1076 assert_eq!(audio.samples_interleaved, samples);
1077
1078 let audio = read::<f32>(
1079 &path,
1080 ReadConfig {
1081 stop: Position::Time(Duration::from_secs(60)),
1082 ..Default::default()
1083 },
1084 )
1085 .unwrap();
1086 assert_eq!(audio.samples_interleaved, samples);
1087
1088 std::fs::remove_file(&path).unwrap();
1089 }
1090
1091 #[test]
1095 fn test_sub_frame_time_positions_are_rounded() {
1096 let start = Duration::from_nanos(20_841_666);
1098 let stop = Duration::from_nanos(25_010_417);
1100
1101 let audio = read::<f32>(
1102 "test_data/test_1ch.wav",
1103 ReadConfig {
1104 start: Position::Time(start),
1105 stop: Position::Time(stop),
1106 ..Default::default()
1107 },
1108 )
1109 .unwrap();
1110
1111 let full = read::<f32>("test_data/test_1ch.wav", ReadConfig::default()).unwrap();
1112 assert_eq!(audio.samples_interleaved.len(), 201);
1113 assert_eq!(
1114 audio.samples_interleaved,
1115 full.samples_interleaved[1000..1201]
1116 );
1117
1118 assert_eq!(position_to_frame(Position::Time(start), 48_000), Some(1000));
1119 assert_eq!(position_to_frame(Position::Time(stop), 48_000), Some(1201));
1120 }
1121
1122 #[cfg(feature = "audio-blocks")]
1124 #[test]
1125 fn test_read_block_matches_read() {
1126 let config = || ReadConfig {
1127 start: Position::Frame(1_000),
1128 stop: Position::Frame(1_500),
1129 start_channel: Some(1),
1130 num_channels: Some(2),
1131 #[cfg(feature = "resample")]
1132 sample_rate: None,
1133 };
1134
1135 let audio = read::<f32>("test_data/test_4ch.wav", config()).unwrap();
1136 let (block, sample_rate) = read_block::<f32>("test_data/test_4ch.wav", config()).unwrap();
1137
1138 assert_eq!(sample_rate, audio.sample_rate);
1139 assert_eq!(block.num_channels(), audio.num_channels);
1140 assert_eq!(block.num_frames(), 500);
1141 assert_eq!(block.raw_data(), audio.samples_interleaved.as_slice());
1142 }
1143
1144 #[test]
1147 fn test_read_file_without_frames() {
1148 let path = crate::tmp_path("read-empty.wav");
1149 crate::writer::write::<f32>(&path, &[], 2, 48000, crate::writer::WriteConfig::default())
1150 .unwrap();
1151
1152 let audio = read::<f32>(&path, ReadConfig::default()).unwrap();
1153 assert_eq!(audio.num_channels, 2);
1154 assert_eq!(audio.sample_rate, 48000);
1155 assert!(audio.samples_interleaved.is_empty());
1156
1157 #[cfg(feature = "resample")]
1159 {
1160 let audio = read::<f32>(
1161 &path,
1162 ReadConfig {
1163 sample_rate: Some(24000),
1164 ..Default::default()
1165 },
1166 )
1167 .unwrap();
1168 assert_eq!(audio.num_channels, 2);
1169 assert_eq!(audio.sample_rate, 24000);
1170 assert!(audio.samples_interleaved.is_empty());
1171 }
1172
1173 match read::<f32>(
1175 &path,
1176 ReadConfig {
1177 num_channels: Some(99),
1178 ..Default::default()
1179 },
1180 ) {
1181 Err(ReadError::InvalidChannelRange { total: 2, .. }) => (),
1182 other => panic!("{other:?}"),
1183 }
1184
1185 std::fs::remove_file(&path).unwrap();
1186 }
1187}