1pub mod error;
2pub mod traits;
3pub mod types;
4
5#[cfg(feature = "wav")]
6pub mod wav;
7
8#[cfg(feature = "wav")]
9pub use crate::wav::{
10 StreamedWavFile, StreamedWavWriter, build_wav_header, wav_data_len, wav_file::WavFile, wav_file_len, wav_header_len,
11};
12
13#[cfg(feature = "flac")]
14pub mod flac;
15
16#[cfg(feature = "flac")]
17pub use crate::flac::{CompressionLevel, StreamedFlacFile, StreamedFlacWriter};
18
19#[cfg(any(feature = "wav", feature = "flac"))]
20pub mod streaming;
21#[cfg(any(feature = "wav", feature = "flac"))]
22pub use crate::streaming::StreamedAudioWriter;
23
24#[cfg(feature = "numpy")]
25pub mod python;
26
27#[cfg(feature = "resampling")]
28use std::num::NonZeroU32;
29use std::{
30 any::TypeId,
31 fs::File,
32 io::{BufReader, BufWriter, Read, Seek, Write},
33 path::Path,
34};
35
36#[cfg(feature = "resampling")]
37pub use audio_samples::operations::ResamplingQuality;
38#[cfg(feature = "resampling")]
39pub use audio_samples::operations::resample;
40use audio_samples::{AudioSamples, traits::StandardSample};
41
42#[cfg(feature = "numpy")]
43pub use crate::python::read_pyarray;
44#[cfg(all(feature = "numpy", target_endian = "little"))]
45pub use crate::python::{NativeAudioArray, read_pyarray_native};
46pub use crate::{
47 error::{AudioIOError, AudioIOResult},
48 traits::{AudioFile, AudioFileMetadata, AudioFileRead, AudioStreamReader},
49 types::{BaseAudioInfo, FileType, OpenOptions, ValidatedSampleType, WriteOptions},
50};
51
52pub(crate) const MAX_WAV_SIZE: u64 = 2 * 1024 * 1024 * 1024; pub(crate) const MAX_MMAP_SIZE: u64 = 512 * 1024 * 1024; pub trait ReadSeek: Read + Seek {}
57
58impl<RS> ReadSeek for RS where RS: Read + Seek {}
59
60pub trait WriteSeek: Write + Seek {}
61
62impl<WS> WriteSeek for WS where WS: Write + Seek {}
63
64pub fn peek_native_type<P: AsRef<Path>>(fp: P) -> AudioIOResult<ValidatedSampleType> {
72 let path = fp.as_ref();
73
74 match FileType::from_path(path) {
75 FileType::WAV => {
76 #[cfg(not(feature = "wav"))]
77 return Err(crate::error::AudioIOError::missing_feature(
78 "'wav' feature must be enabled to peek WAV files",
79 ));
80
81 #[cfg(feature = "wav")]
82 {
83 use crate::wav::wav_file::parse_wav_header_streaming;
84 let file = File::open(path)?;
85 let mut reader = BufReader::with_capacity(65536, file);
86 let (info, _) = parse_wav_header_streaming(&mut reader)?;
87 ValidatedSampleType::try_from(info.sample_type).map_err(|_| {
88 AudioIOError::unsupported_format(format!("Unsupported native sample type: {:?}", info.sample_type))
89 })
90 }
91 },
92 FileType::FLAC => {
93 #[cfg(not(feature = "flac"))]
94 return Err(crate::error::AudioIOError::missing_feature(
95 "'flac' feature must be enabled to peek FLAC files",
96 ));
97
98 #[cfg(feature = "flac")]
99 {
100 use crate::flac::FlacFile;
101 use crate::traits::{AudioFile, AudioFileMetadata};
102 let flac_file = FlacFile::open_with_options(path, OpenOptions::default())?;
103 let info = flac_file.base_info()?;
104 ValidatedSampleType::try_from(info.sample_type).map_err(|_| {
105 AudioIOError::unsupported_format(format!("Unsupported native sample type: {:?}", info.sample_type))
106 })
107 }
108 },
109 other => Err(crate::error::AudioIOError::unsupported_format(format!(
110 "peek_native_type does not support: {other:?}"
111 ))),
112 }
113}
114
115pub fn info<P: AsRef<Path>>(fp: P) -> AudioIOResult<BaseAudioInfo> {
120 let path = fp.as_ref();
121
122 match FileType::from_path(path) {
123 FileType::WAV => {
124 #[cfg(not(feature = "wav"))]
125 return Err(crate::error::AudioIOError::missing_feature(
126 "'wav' feature must be enabled to read WAV files",
127 ));
128
129 #[cfg(feature = "wav")]
130 {
131 let wav_file = WavFile::open_metadata(path)?;
132 wav_file.base_info()
133 }
134 },
135 FileType::FLAC => {
136 #[cfg(not(feature = "flac"))]
137 return Err(crate::error::AudioIOError::missing_feature(
138 "'flac' feature must be enabled to read FLAC files",
139 ));
140
141 #[cfg(feature = "flac")]
142 {
143 use crate::flac::FlacFile;
144 use crate::traits::AudioFileMetadata;
145 let flac_file = FlacFile::open_metadata(path)?;
146 flac_file.base_info()
147 }
148 },
149 other => Err(crate::error::AudioIOError::unsupported_format(format!(
150 "Unsupported file format: {other:?}"
151 ))),
152 }
153}
154
155pub fn read<P, T>(fp: P) -> AudioIOResult<AudioSamples<'static, T>>
161where
162 P: AsRef<Path>,
163 T: StandardSample + 'static,
164{
165 let path = fp.as_ref();
166
167 match FileType::from_path(path) {
168 FileType::WAV => {
169 #[cfg(not(feature = "wav"))]
170 return Err(crate::error::AudioIOError::missing_feature(
171 "'wav' feature must be enabled to read WAV files",
172 ));
173
174 #[cfg(feature = "wav")]
175 {
176 let wav_file = WavFile::open_with_options(path, OpenOptions::default())?;
177 let samples = wav_file.read::<T>()?;
178 Ok(samples.into_owned())
179 }
180 },
181 FileType::FLAC => {
182 #[cfg(not(feature = "flac"))]
183 return Err(crate::error::AudioIOError::missing_feature(
184 "'flac' feature must be enabled to read FLAC files",
185 ));
186
187 #[cfg(feature = "flac")]
188 {
189 use crate::flac::FlacFile;
190 use crate::traits::{AudioFile, AudioFileRead};
191 let flac_file = FlacFile::open_with_options(path, OpenOptions::default())?;
192 let samples = flac_file.read::<T>()?;
193 Ok(samples.into_owned())
194 }
195 },
196 other => Err(crate::error::AudioIOError::unsupported_format(format!(
197 "Unsupported file format: {other:?}"
198 ))),
199 }
200}
201
202#[cfg(feature = "resampling")]
203pub fn read_and_resample<P, T>(
204 fp: P,
205 target_sr: NonZeroU32,
206 quality: Option<ResamplingQuality>,
207) -> AudioIOResult<AudioSamples<'static, T>>
208where
209 P: AsRef<Path>,
210 T: StandardSample,
211{
212 let signal = read(fp)?;
213 resample::<T>(&signal, target_sr, quality.unwrap_or(ResamplingQuality::Fast)).map_err(AudioIOError::AudioSamples)
214}
215
216#[cfg(feature = "wav")]
243pub fn open_streamed<P>(fp: P) -> AudioIOResult<StreamedWavFile<BufReader<File>>>
244where
245 P: AsRef<Path>,
246{
247 let path = fp.as_ref();
248
249 match FileType::from_path(path) {
250 FileType::WAV => {
251 let file = File::open(path)?;
252 let reader = BufReader::new(file);
253 StreamedWavFile::new_with_path(reader, path.to_path_buf())
254 },
255 other => Err(crate::error::AudioIOError::unsupported_format(format!(
256 "Unsupported file format for streaming: {other:?}"
257 ))),
258 }
259}
260
261#[cfg(feature = "wav")]
280pub fn open_streamed_reader<R>(reader: R) -> AudioIOResult<wav::StreamedWavFile<R>>
281where
282 R: ReadSeek,
283{
284 wav::StreamedWavFile::new(reader)
285}
286
287#[cfg(feature = "flac")]
314pub fn open_streamed_flac<P>(fp: P) -> AudioIOResult<StreamedFlacFile<BufReader<File>>>
315where
316 P: AsRef<Path>,
317{
318 let path = fp.as_ref();
319 match FileType::from_path(path) {
320 FileType::FLAC => {
321 let file = File::open(path)?;
322 let reader = BufReader::new(file);
323 StreamedFlacFile::new_with_path(reader, path.to_path_buf())
324 },
325 other => Err(crate::error::AudioIOError::unsupported_format(format!(
326 "Unsupported file format for FLAC streaming: {other:?}"
327 ))),
328 }
329}
330
331#[cfg(feature = "flac")]
348pub fn open_streamed_flac_reader<R>(reader: R) -> AudioIOResult<flac::StreamedFlacFile<R>>
349where
350 R: ReadSeek,
351{
352 flac::StreamedFlacFile::new(reader)
353}
354
355pub fn open_streamed_dyn<P>(fp: P) -> AudioIOResult<Box<dyn AudioStreamReader>>
383where
384 P: AsRef<Path>,
385{
386 let path = fp.as_ref();
387
388 match FileType::from_path(path) {
389 FileType::WAV => {
390 #[cfg(not(feature = "wav"))]
391 return Err(crate::error::AudioIOError::missing_feature(
392 "'wav' feature must be enabled for WAV streaming",
393 ));
394
395 #[cfg(feature = "wav")]
396 {
397 let file = File::open(path)?;
398 let reader = BufReader::new(file);
399 let streamed = StreamedWavFile::new_with_path(reader, path.to_path_buf())?;
400 Ok(Box::new(streamed))
401 }
402 },
403 FileType::FLAC => {
404 #[cfg(not(feature = "flac"))]
405 return Err(crate::error::AudioIOError::missing_feature(
406 "'flac' feature must be enabled for FLAC streaming",
407 ));
408
409 #[cfg(feature = "flac")]
410 {
411 let file = File::open(path)?;
412 let reader = BufReader::new(file);
413 let streamed = StreamedFlacFile::new_with_path(reader, path.to_path_buf())?;
414 Ok(Box::new(streamed))
415 }
416 },
417 other => Err(crate::error::AudioIOError::unsupported_format(format!(
418 "Unsupported file format for streaming: {other:?}"
419 ))),
420 }
421}
422
423#[cfg(any(feature = "wav", feature = "flac"))]
447pub fn create_streamed<P, T>(
448 fp: P,
449 channels: u16,
450 sample_rate: u32,
451) -> AudioIOResult<StreamedAudioWriter<BufWriter<File>>>
452where
453 P: AsRef<Path>,
454 T: StandardSample + 'static,
455{
456 let path = fp.as_ref();
457 let format = FileType::from_path(path);
458 let file = File::create(path)?;
459 let writer = BufWriter::with_capacity(256 * 1024, file);
462 create_streamed_with::<_, T>(writer, channels, sample_rate, format)
463}
464
465#[cfg(any(feature = "wav", feature = "flac"))]
488pub fn create_streamed_with_options<P, T>(
489 fp: P,
490 channels: u16,
491 sample_rate: u32,
492 opts: WriteOptions,
493) -> AudioIOResult<StreamedAudioWriter<BufWriter<File>>>
494where
495 P: AsRef<Path>,
496 T: StandardSample + 'static,
497{
498 let path = fp.as_ref();
499 let format = FileType::from_path(path);
500 let file = File::create(path)?;
501 let writer = BufWriter::with_capacity(opts.write_buf_capacity, file);
502 create_streamed_with::<_, T>(writer, channels, sample_rate, format)
503}
504
505#[cfg(any(feature = "wav", feature = "flac"))]
524pub fn create_streamed_with<W, T>(
525 writer: W,
526 channels: u16,
527 sample_rate: u32,
528 format: FileType,
529) -> AudioIOResult<StreamedAudioWriter<W>>
530where
531 W: WriteSeek,
532 T: StandardSample + 'static,
533{
534 match format {
535 FileType::WAV => {
536 #[cfg(not(feature = "wav"))]
537 {
538 let _ = (writer, channels, sample_rate);
539 Err(AudioIOError::missing_feature(
540 "'wav' feature must be enabled for WAV streaming writes",
541 ))
542 }
543 #[cfg(feature = "wav")]
544 {
545 Ok(StreamedAudioWriter::Wav(wav_writer_for_type::<T, W>(
546 writer,
547 channels,
548 sample_rate,
549 )?))
550 }
551 },
552 FileType::FLAC => {
553 #[cfg(not(feature = "flac"))]
554 {
555 let _ = (writer, channels, sample_rate);
556 Err(AudioIOError::missing_feature(
557 "'flac' feature must be enabled for FLAC streaming writes",
558 ))
559 }
560 #[cfg(feature = "flac")]
561 {
562 Ok(StreamedAudioWriter::Flac(flac_writer_for_type::<T, W>(
563 writer,
564 channels,
565 sample_rate,
566 )?))
567 }
568 },
569 other => Err(AudioIOError::unsupported_format(format!(
570 "Unsupported output format for streaming write: {other:?}"
571 ))),
572 }
573}
574
575#[cfg(feature = "flac")]
602pub fn create_streamed_flac<P, T>(
603 fp: P,
604 channels: u16,
605 sample_rate: u32,
606) -> AudioIOResult<StreamedFlacWriter<BufWriter<File>>>
607where
608 P: AsRef<Path>,
609 T: StandardSample + 'static,
610{
611 let path = fp.as_ref();
612 match FileType::from_path(path) {
613 FileType::FLAC => {
614 let file = File::create(path)?;
615 let writer = BufWriter::with_capacity(256 * 1024, file);
616 flac_writer_for_type::<T, _>(writer, channels, sample_rate)
617 },
618 other => Err(crate::error::AudioIOError::unsupported_format(format!(
619 "Unsupported output format for streaming FLAC write: {other:?}"
620 ))),
621 }
622}
623
624#[cfg(feature = "flac")]
627pub fn create_streamed_flac_writer<W, T>(
628 writer: W,
629 channels: u16,
630 sample_rate: u32,
631) -> AudioIOResult<StreamedFlacWriter<W>>
632where
633 W: WriteSeek,
634 T: StandardSample + 'static,
635{
636 flac_writer_for_type::<T, W>(writer, channels, sample_rate)
637}
638
639#[cfg(feature = "flac")]
643fn flac_writer_for_type<T, W>(writer: W, channels: u16, sample_rate: u32) -> AudioIOResult<StreamedFlacWriter<W>>
644where
645 T: StandardSample + 'static,
646 W: WriteSeek,
647{
648 let sample_type = validated_sample_type_of::<T>()?;
649 StreamedFlacWriter::new(writer, channels, sample_rate, sample_type, CompressionLevel::default())
650}
651
652#[cfg(feature = "wav")]
657pub fn create_streamed_writer<W, T>(writer: W, channels: u16, sample_rate: u32) -> AudioIOResult<StreamedWavWriter<W>>
658where
659 W: WriteSeek,
660 T: StandardSample + 'static,
661{
662 wav_writer_for_type::<T, W>(writer, channels, sample_rate)
663}
664
665#[cfg(feature = "wav")]
669fn wav_writer_for_type<T, W>(writer: W, channels: u16, sample_rate: u32) -> AudioIOResult<StreamedWavWriter<W>>
670where
671 T: StandardSample + 'static,
672 W: WriteSeek,
673{
674 use audio_samples::I24;
675 let type_id = TypeId::of::<T>();
676 match type_id {
677 id if id == TypeId::of::<u8>() || id == TypeId::of::<i16>() => {
678 StreamedWavWriter::new_i16(writer, channels, sample_rate)
679 },
680 id if id == TypeId::of::<I24>() => StreamedWavWriter::new_i24(writer, channels, sample_rate),
681 id if id == TypeId::of::<i32>() => StreamedWavWriter::new_i32(writer, channels, sample_rate),
682 id if id == TypeId::of::<f32>() => StreamedWavWriter::new_f32(writer, channels, sample_rate),
683 id if id == TypeId::of::<f64>() => StreamedWavWriter::new_f64(writer, channels, sample_rate),
684 _ => Err(AudioIOError::unsupported_format(format!(
685 "No WAV encoding for sample type (TypeId: {type_id:?})"
686 ))),
687 }
688}
689
690#[cfg(any(feature = "wav", feature = "flac"))]
692fn validated_sample_type_of<T>() -> AudioIOResult<ValidatedSampleType>
693where
694 T: StandardSample + 'static,
695{
696 use audio_samples::I24;
697 let id = TypeId::of::<T>();
698 if id == TypeId::of::<u8>() {
699 Ok(ValidatedSampleType::U8)
700 } else if id == TypeId::of::<i16>() {
701 Ok(ValidatedSampleType::I16)
702 } else if id == TypeId::of::<I24>() {
703 Ok(ValidatedSampleType::I24)
704 } else if id == TypeId::of::<i32>() {
705 Ok(ValidatedSampleType::I32)
706 } else if id == TypeId::of::<f32>() {
707 Ok(ValidatedSampleType::F32)
708 } else if id == TypeId::of::<f64>() {
709 Ok(ValidatedSampleType::F64)
710 } else {
711 Err(AudioIOError::unsupported_format(format!(
712 "No WAV encoding for sample type (TypeId: {id:?})"
713 )))
714 }
715}
716
717#[cfg(feature = "wav")]
740pub fn create_streamed_sink<W, T>(
741 writer: W,
742 channels: u16,
743 sample_rate: u32,
744 total_frames: Option<usize>,
745) -> AudioIOResult<wav::WavSink<W>>
746where
747 W: Write,
748 T: StandardSample + 'static,
749{
750 let sample_type = validated_sample_type_of::<T>()?;
751 wav::WavSink::new(writer, channels, sample_rate, sample_type, total_frames)
752}
753
754pub fn open<P>(fp: P) -> AudioIOResult<Box<dyn AudioFile>>
760where
761 P: AsRef<Path>,
762{
763 let path = fp.as_ref();
764
765 match FileType::from_path(path) {
766 FileType::WAV => {
767 #[cfg(not(feature = "wav"))]
768 return Err(crate::error::AudioIOError::missing_feature(
769 "'wav' feature must be enabled to open WAV files",
770 ));
771
772 #[cfg(feature = "wav")]
773 {
774 let wav_file = WavFile::open_with_options(path, OpenOptions::default())?;
775 Ok(Box::new(wav_file))
776 }
777 },
778 FileType::FLAC => {
779 #[cfg(not(feature = "flac"))]
780 return Err(crate::error::AudioIOError::missing_feature(
781 "'flac' feature must be enabled to open FLAC files",
782 ));
783
784 #[cfg(feature = "flac")]
785 {
786 use crate::flac::FlacFile;
787 use crate::traits::AudioFile;
788 let flac_file = FlacFile::open_with_options(path, OpenOptions::default())?;
789 Ok(Box::new(flac_file))
790 }
791 },
792 other => Err(crate::error::AudioIOError::unsupported_format(format!(
793 "Unsupported file format: {other:?}"
794 ))),
795 }
796}
797
798pub fn write<P, T>(fp: P, audio: &AudioSamples<T>) -> AudioIOResult<()>
799where
800 P: AsRef<Path>,
801 T: StandardSample + 'static,
802{
803 write_with_options(fp, audio, WriteOptions::default())
804}
805
806pub fn write_with_options<P, T>(fp: P, audio: &AudioSamples<T>, opts: WriteOptions) -> AudioIOResult<()>
822where
823 P: AsRef<Path>,
824 T: StandardSample + 'static,
825{
826 let path = fp.as_ref();
827
828 match FileType::from_path(path) {
829 FileType::WAV => {
830 #[cfg(not(feature = "wav"))]
831 return Err(crate::error::AudioIOError::missing_feature(
832 "'wav' feature must be enabled to write WAV files",
833 ));
834
835 #[cfg(feature = "wav")]
836 {
837 let file = std::fs::File::create(path)?;
838 crate::wav::wav_file::write_wav(file, audio, opts)
839 }
840 },
841 FileType::FLAC => {
842 #[cfg(not(feature = "flac"))]
843 return Err(crate::error::AudioIOError::missing_feature(
844 "'flac' feature must be enabled to write FLAC files",
845 ));
846
847 #[cfg(feature = "flac")]
848 {
849 let file = std::fs::File::create(path)?;
850 let buf_writer = std::io::BufWriter::with_capacity(opts.write_buf_capacity, file);
851 crate::flac::write_flac(buf_writer, audio, CompressionLevel::default())
852 }
853 },
854 other => Err(crate::error::AudioIOError::unsupported_format(format!(
855 "Unsupported format: {other:?}"
856 ))),
857 }
858}
859
860#[cfg(feature = "wav")]
880pub fn write_with_metadata<P, T>(
881 fp: P,
882 audio: &AudioSamples<T>,
883 metadata: &crate::wav::WavMetadata,
884) -> AudioIOResult<()>
885where
886 P: AsRef<Path>,
887 T: StandardSample + 'static,
888{
889 let file = std::fs::File::create(fp)?;
890 crate::wav::wav_file::write_wav_with_metadata(file, audio, WriteOptions::default(), metadata)
891}
892
893#[cfg(feature = "wav")]
895pub fn write_with_metadata_to<T, W>(
896 writer: W,
897 audio: &AudioSamples<T>,
898 metadata: &crate::wav::WavMetadata,
899) -> AudioIOResult<()>
900where
901 T: StandardSample + 'static,
902 W: Write,
903{
904 crate::wav::wav_file::write_wav_with_metadata(writer, audio, WriteOptions::default(), metadata)
905}
906
907pub fn write_with<T, W>(writer: W, audio: &AudioSamples<T>, format: FileType) -> AudioIOResult<()>
936where
937 T: StandardSample + 'static,
938 W: Write,
939{
940 write_with_writer_options(writer, audio, format, WriteOptions::default())
941}
942
943pub fn write_with_writer_options<T, W>(
947 writer: W,
948 audio: &AudioSamples<T>,
949 format: FileType,
950 opts: WriteOptions,
951) -> AudioIOResult<()>
952where
953 T: StandardSample + 'static,
954 W: Write,
955{
956 match format {
957 FileType::WAV => {
958 #[cfg(not(feature = "wav"))]
959 return Err(crate::error::AudioIOError::missing_feature(
960 "'wav' feature must be enabled to write WAV files",
961 ));
962
963 #[cfg(feature = "wav")]
964 {
965 crate::wav::wav_file::write_wav(writer, audio, opts)
966 }
967 },
968 FileType::FLAC => {
969 #[cfg(not(feature = "flac"))]
970 return Err(crate::error::AudioIOError::missing_feature(
971 "'flac' feature must be enabled to write FLAC files",
972 ));
973
974 #[cfg(feature = "flac")]
975 {
976 crate::flac::write_flac(writer, audio, CompressionLevel::default())
977 }
978 },
979 other => Err(crate::error::AudioIOError::unsupported_format(format!(
980 "Unsupported format for write_with: {other:?}"
981 ))),
982 }
983}
984
985#[cfg(all(test, feature = "wav"))]
986mod lib_tests {
987 use std::time::Duration;
988
989 use audio_samples::sample_rate;
990
991 use super::*;
992
993 #[test]
994 fn test_info_function() {
995 let info_result = info("resources/test.wav");
996 assert!(info_result.is_ok(), "Failed to get info from test WAV file");
997
998 let audio_info = info_result.expect("Expected successful info retrieval");
999 assert_eq!(audio_info.file_type, FileType::WAV);
1000 assert!(audio_info.sample_rate.get() > 0, "Sample rate should be positive");
1001 assert!(audio_info.channels > 0, "Channel count should be positive");
1002 println!("Audio info: {audio_info:#}");
1003 }
1004
1005 #[test]
1006 fn test_read_function() {
1007 let audio_result = read::<_, f32>("resources/test.wav");
1008 assert!(audio_result.is_ok(), "Failed to read test WAV file");
1009
1010 let audio_samples = audio_result.expect("Expected successful audio read");
1011 println!(
1012 "Read {} samples at {} Hz",
1013 audio_samples.len(),
1014 audio_samples.sample_rate()
1015 );
1016 }
1017
1018 #[test]
1019 fn test_open_function() {
1020 let file_result = open("resources/test.wav");
1021 assert!(file_result.is_ok(), "Failed to open test WAV file");
1022 }
1023
1024 #[test]
1025 fn test_write_function() {
1026 use std::fs;
1027
1028 use audio_samples::sine_wave;
1029
1030 let sample_rate = sample_rate!(44100);
1032 let sine_samples = sine_wave::<f32>(440.0, Duration::from_secs_f64(0.1), sample_rate, 0.5);
1033
1034 let output_path = std::env::temp_dir().join("test_lib_write.wav");
1036 write(&output_path, &sine_samples).expect("Failed to write WAV file");
1037
1038 assert!(fs::metadata(&output_path).is_ok(), "Output file should exist");
1040
1041 let read_back = read::<_, f32>(&output_path).expect("Failed to read back WAV file");
1042 assert_eq!(read_back.sample_rate(), sample_rate);
1043 assert_eq!(read_back.total_samples(), sine_samples.total_samples());
1044 assert_eq!(read_back.num_channels(), sine_samples.num_channels());
1045
1046 let original_bytes = sine_samples.bytes().expect("bytes should be available");
1048 let read_bytes = read_back.bytes().expect("bytes should be available");
1049 assert_eq!(
1050 original_bytes.as_slice().len(),
1051 read_bytes.as_slice().len(),
1052 "Audio data size should match"
1053 );
1054
1055 let original_samples: &[f32] = bytemuck::cast_slice(original_bytes.as_slice());
1057 let read_samples: &[f32] = bytemuck::cast_slice(read_bytes.as_slice());
1058
1059 for (i, (orig, read)) in original_samples.iter().zip(read_samples.iter()).enumerate() {
1060 let diff = (orig - read).abs();
1061 assert!(
1062 diff < 1e-6,
1063 "Sample {i} differs too much: {orig} vs {read} (diff: {diff})"
1064 );
1065 }
1066
1067 fs::remove_file(&output_path).ok();
1069 }
1070
1071 #[test]
1072 fn test_write_with_function() {
1073 use std::io::Cursor;
1074
1075 use audio_samples::sine_wave;
1076
1077 let sample_rate = sample_rate!(22050);
1079 let sine_samples = sine_wave::<i16>(880.0, Duration::from_secs_f64(0.05), sample_rate, 0.8);
1080
1081 let mut buffer = Vec::new();
1083 let cursor = Cursor::new(&mut buffer);
1084 write_with(cursor, &sine_samples, FileType::WAV).expect("Failed to write with cursor");
1085
1086 assert!(buffer.len() > 44, "Buffer should contain WAV header and data");
1088 assert_eq!(&buffer[0..4], b"RIFF", "Should start with RIFF header");
1089 assert_eq!(&buffer[8..12], b"WAVE", "Should contain WAVE identifier");
1090
1091 let temp_file = std::env::temp_dir().join("test_write_with_buffer.wav");
1093 std::fs::write(&temp_file, &buffer).expect("Failed to write buffer to temp file");
1094
1095 let read_back = read::<_, i16>(&temp_file).expect("Failed to read back WAV from buffer");
1096 assert_eq!(read_back.sample_rate(), sample_rate);
1097 assert_eq!(read_back.total_samples(), sine_samples.total_samples());
1098 assert_eq!(read_back.num_channels(), sine_samples.num_channels());
1099
1100 let original_bytes = sine_samples.bytes().expect("bytes should be available");
1102 let read_bytes = read_back.bytes().expect("bytes should be available");
1103 assert_eq!(
1104 original_bytes.as_slice(),
1105 read_bytes.as_slice(),
1106 "Audio data should match exactly for i16"
1107 );
1108
1109 std::fs::remove_file(&temp_file).ok();
1111 }
1112
1113 #[test]
1114 fn test_write_with_format_parameter() {
1115 use std::io::Cursor;
1116
1117 use audio_samples::sine_wave;
1118
1119 let sample_rate = sample_rate!(44100);
1121 let sine_samples = sine_wave::<f32>(440.0, Duration::from_secs_f64(0.01), sample_rate, 0.5);
1122
1123 let mut wav_buffer = Vec::new();
1125 let wav_cursor = Cursor::new(&mut wav_buffer);
1126 write_with(wav_cursor, &sine_samples, FileType::WAV).expect("Failed to write WAV format");
1127
1128 assert!(wav_buffer.len() > 44, "WAV buffer should contain header and data");
1130 assert_eq!(&wav_buffer[0..4], b"RIFF", "Should start with RIFF header");
1131 assert_eq!(&wav_buffer[8..12], b"WAVE", "Should contain WAVE identifier");
1132
1133 let mut buffer = Vec::new();
1135 let cursor = Cursor::new(&mut buffer);
1136 let result = write_with(cursor, &sine_samples, FileType::MP3);
1137 assert!(result.is_err(), "Should return error for unsupported format");
1138
1139 let error_msg = format!("{}", result.expect_err("Expected error"));
1140 assert!(
1141 error_msg.contains("Unsupported format"),
1142 "Error should mention unsupported format"
1143 );
1144 }
1145
1146 #[test]
1147 fn test_write_different_formats() {
1148 use std::fs;
1149
1150 use audio_samples::{AudioTypeConversion, sine_wave};
1151
1152 let sample_rate = sample_rate!(48000);
1153 let sine_base = sine_wave::<f32>(1000.0, Duration::from_secs_f64(0.02), sample_rate, 0.3);
1154
1155 let test_cases = vec![
1157 ("i16", std::env::temp_dir().join("test_format_i16.wav")),
1158 ("f32", std::env::temp_dir().join("test_format_f32.wav")),
1159 ];
1160
1161 for (format_name, output_path) in test_cases {
1162 match format_name {
1163 "i16" => {
1164 let samples_i16 = sine_base.to_format::<i16>();
1165 write(&output_path, &samples_i16).expect("Failed to write i16 WAV");
1166
1167 let read_back = read::<_, i16>(&output_path).expect("Failed to read back i16 WAV");
1169 assert_eq!(read_back.sample_rate(), sample_rate, "Sample rate mismatch for i16");
1170 assert_eq!(
1171 read_back.total_samples(),
1172 samples_i16.total_samples(),
1173 "Sample count mismatch for i16"
1174 );
1175
1176 let wav_info = info(&output_path).expect("Failed to get WAV info for i16");
1178 assert_eq!(wav_info.bits_per_sample, 16, "Bits per sample should be 16 for i16");
1179 assert_eq!(
1180 wav_info.sample_type,
1181 audio_samples::SampleType::I16,
1182 "Sample type should be I16"
1183 );
1184 },
1185 "f32" => {
1186 write(&output_path, &sine_base).expect("Failed to write f32 WAV");
1187
1188 let read_back = read::<_, f32>(&output_path).expect("Failed to read back f32 WAV");
1190 assert_eq!(read_back.sample_rate(), sample_rate, "Sample rate mismatch for f32");
1191 assert_eq!(
1192 read_back.total_samples(),
1193 sine_base.total_samples(),
1194 "Sample count mismatch for f32"
1195 );
1196
1197 let wav_info = info(&output_path).expect("Failed to get WAV info for f32");
1199 assert_eq!(wav_info.bits_per_sample, 32, "Bits per sample should be 32 for f32");
1200 assert_eq!(
1201 wav_info.sample_type,
1202 audio_samples::SampleType::F32,
1203 "Sample type should be F32"
1204 );
1205 },
1206 _ => unreachable!("Unknown format"),
1207 }
1208
1209 assert!(
1211 fs::metadata(&output_path).is_ok(),
1212 "File should exist for {format_name}"
1213 );
1214
1215 fs::remove_file(&output_path).ok();
1217 }
1218 }
1219
1220 #[test]
1221 fn test_unsupported_format_error() {
1222 use audio_samples::sine_wave;
1223
1224 let sample_rate = sample_rate!(44100);
1225 let sine_samples = sine_wave::<f32>(440.0, Duration::from_secs_f64(0.01), sample_rate, 0.1);
1226
1227 let result = write(std::env::temp_dir().join("test.mp3"), &sine_samples);
1229 assert!(result.is_err(), "Should fail for unsupported format");
1230
1231 let error_msg = format!("{}", result.expect_err("Expected error"));
1232 assert!(
1233 error_msg.contains("Unsupported"),
1234 "Error should mention unsupported format"
1235 );
1236 }
1237}