|
1 | 1 | use crate::common::assert_error_traits; |
| 2 | +use crate::Sample; |
2 | 3 | use crate::Source; |
3 | 4 | use hound::{SampleFormat, WavSpec}; |
| 5 | +use std::io::{self, Write}; |
4 | 6 | use std::path; |
5 | 7 | use std::sync::Arc; |
6 | 8 |
|
7 | 9 | #[derive(Debug, thiserror::Error, Clone)] |
8 | 10 | pub enum ToWavError { |
9 | | - #[error("Could not create wav file")] |
| 11 | + #[error("Opening file for writing")] |
| 12 | + OpenFile(#[source] Arc<std::io::Error>), |
| 13 | + #[error("Could not create wav writer")] |
10 | 14 | Creating(#[source] Arc<hound::Error>), |
11 | | - #[error("Failed to write samples to wav file")] |
| 15 | + #[error("Failed to write samples writer")] |
12 | 16 | Writing(#[source] Arc<hound::Error>), |
13 | 17 | #[error("Failed to update the wav header")] |
14 | 18 | Finishing(#[source] Arc<hound::Error>), |
| 19 | + #[error("Failed to flush all bytes to writer")] |
| 20 | + Flushing(#[source] Arc<std::io::Error>), |
15 | 21 | } |
16 | 22 | assert_error_traits!(ToWavError); |
17 | 23 |
|
18 | | -/// This procedure saves Source's output into a wav file. The output samples format is 32-bit float. |
19 | | -/// This function is intended primarily for testing and diagnostics. It can be used to see |
| 24 | +/// Saves Source's output into a wav file. The output samples format is 32-bit |
| 25 | +/// float. This function is intended primarily for testing and diagnostics. It can be used to see |
20 | 26 | /// the output without opening output stream to a real audio device. |
21 | 27 | /// |
22 | 28 | /// If the file already exists it will be overwritten. |
23 | | -pub fn output_to_wav( |
24 | | - source: &mut impl Source, |
| 29 | +/// |
| 30 | +/// # Note |
| 31 | +/// This is a convenience wrapper around `wav_to_writer` |
| 32 | +pub fn wav_to_file( |
| 33 | + source: impl Source, // TODO make this take a spanless source |
25 | 34 | wav_file: impl AsRef<path::Path>, |
| 35 | +) -> Result<(), ToWavError> { |
| 36 | + let mut file = std::fs::File::create(wav_file) |
| 37 | + .map_err(Arc::new) |
| 38 | + .map_err(ToWavError::OpenFile)?; |
| 39 | + wav_to_writer(source, &mut file) |
| 40 | +} |
| 41 | + |
| 42 | +/// Saves Source's output into a writer. The output samples format is 32-bit float. This function |
| 43 | +/// is intended primarily for testing and diagnostics. It can be used to see the output without |
| 44 | +/// opening output stream to a real audio device. |
| 45 | +/// |
| 46 | +/// # Example |
| 47 | +/// ```rust |
| 48 | +/// # use rodio::static_buffer::StaticSamplesBuffer; |
| 49 | +/// # use rodio::collect_to_wav; |
| 50 | +/// # const SAMPLES: [rodio::Sample; 5] = [0.0, 1.0, 2.0, 3.0, 4.0]; |
| 51 | +/// # let source = StaticSamplesBuffer::new( |
| 52 | +/// # 1.try_into().unwrap(), |
| 53 | +/// # 1.try_into().unwrap(), |
| 54 | +/// # &SAMPLES |
| 55 | +/// # ); |
| 56 | +/// let mut writer = std::io::Cursor::new(Vec::new()); |
| 57 | +/// wav_to_writer(source, &mut writer)?; |
| 58 | +/// let wav_bytes: Vec<u8> = writer.into_inner(); |
| 59 | +/// # Ok::<(), Box<dyn std::error::Error>>(()) |
| 60 | +/// ``` |
| 61 | +pub fn wav_to_writer( |
| 62 | + source: impl Source, // TODO make this take a spanless source |
| 63 | + writer: &mut (impl io::Write + io::Seek), |
26 | 64 | ) -> Result<(), ToWavError> { |
27 | 65 | let format = WavSpec { |
28 | 66 | channels: source.channels().get(), |
29 | 67 | sample_rate: source.sample_rate().get(), |
30 | 68 | bits_per_sample: 32, |
31 | 69 | sample_format: SampleFormat::Float, |
32 | 70 | }; |
33 | | - let mut writer = hound::WavWriter::create(wav_file, format) |
34 | | - .map_err(Arc::new) |
35 | | - .map_err(ToWavError::Creating)?; |
36 | | - for sample in source { |
| 71 | + let mut writer = io::BufWriter::new(writer); |
| 72 | + { |
| 73 | + let mut writer = hound::WavWriter::new(&mut writer, format) |
| 74 | + .map_err(Arc::new) |
| 75 | + .map_err(ToWavError::Creating)?; |
| 76 | + |
| 77 | + let whole_frames = WholeFrames::new(source); |
| 78 | + for sample in whole_frames { |
| 79 | + writer |
| 80 | + .write_sample(sample) |
| 81 | + .map_err(Arc::new) |
| 82 | + .map_err(ToWavError::Writing)?; |
| 83 | + } |
| 84 | + |
37 | 85 | writer |
38 | | - .write_sample(sample) |
| 86 | + .finalize() |
39 | 87 | .map_err(Arc::new) |
40 | | - .map_err(ToWavError::Writing)?; |
| 88 | + .map_err(ToWavError::Finishing)?; |
41 | 89 | } |
42 | 90 | writer |
43 | | - .finalize() |
| 91 | + .flush() |
44 | 92 | .map_err(Arc::new) |
45 | | - .map_err(ToWavError::Finishing)?; |
| 93 | + .map_err(ToWavError::Flushing)?; |
46 | 94 | Ok(()) |
47 | 95 | } |
48 | 96 |
|
| 97 | +struct WholeFrames<I: Iterator<Item = Sample>> { |
| 98 | + buffer: Vec<Sample>, |
| 99 | + pos: usize, |
| 100 | + source: I, |
| 101 | +} |
| 102 | + |
| 103 | +impl<S: Source> WholeFrames<S> { |
| 104 | + fn new(source: S) -> Self { |
| 105 | + Self { |
| 106 | + buffer: vec![0.0; source.channels().get().into()], |
| 107 | + pos: source.channels().get().into(), |
| 108 | + source, |
| 109 | + } |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +impl<I: Iterator<Item = Sample>> Iterator for WholeFrames<I> { |
| 114 | + type Item = Sample; |
| 115 | + |
| 116 | + fn next(&mut self) -> Option<Sample> { |
| 117 | + if self.pos >= self.buffer.len() { |
| 118 | + for sample in &mut self.buffer { |
| 119 | + *sample = self.source.next()?; |
| 120 | + } |
| 121 | + self.pos = 0; |
| 122 | + } |
| 123 | + |
| 124 | + let to_yield = self.buffer[self.pos]; |
| 125 | + self.pos += 1; |
| 126 | + Some(to_yield) |
| 127 | + } |
| 128 | +} |
| 129 | + |
49 | 130 | #[cfg(test)] |
50 | 131 | mod test { |
51 | | - use super::output_to_wav; |
| 132 | + use super::wav_to_file; |
52 | 133 | use crate::Source; |
53 | 134 | use std::io::BufReader; |
54 | 135 | use std::time::Duration; |
55 | 136 |
|
56 | 137 | #[test] |
57 | | - fn test_output_to_wav() { |
| 138 | + fn test_wav_to_file() { |
58 | 139 | let make_source = || { |
59 | 140 | crate::source::SineWave::new(745.0) |
60 | 141 | .amplify(0.1) |
61 | 142 | .take_duration(Duration::from_secs(1)) |
62 | 143 | }; |
63 | 144 | let wav_file_path = "target/tmp/save-to-wav-test.wav"; |
64 | | - output_to_wav(&mut make_source(), wav_file_path).expect("output file can be written"); |
| 145 | + wav_to_file(&mut make_source(), wav_file_path).expect("output file can be written"); |
65 | 146 |
|
66 | 147 | let file = std::fs::File::open(wav_file_path).expect("output file can be opened"); |
67 | 148 | // Not using crate::Decoder bcause it is limited to i16 samples. |
|
0 commit comments