Skip to content

Commit 47dab16

Browse files
authored
Merge pull request #790 from RustAudio/better_wav_output
add wav output method which takes writer
2 parents 84d77c7 + ac6e228 commit 47dab16

File tree

4 files changed

+106
-20
lines changed

4 files changed

+106
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
- Adds a new input source: Microphone.
2020
- Adds a new method on source: record which collects all samples into a
2121
SamplesBuffer.
22+
- Adds `wav_to_writer` which writes a `Source` to a writer.
2223

2324
### Fixed
2425
- docs.rs will now document all features, including those that are optional.
@@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2728
- `PeriodicAccess` is slightly more accurate for 44.1 kHz sample rate families.
2829

2930
### Changed
31+
- `output_to_wav` renamed to `wav_to_file` and now takes ownership of the `Source`.
3032
- `Blue` noise generator uses uniform instead of Gaussian noise for better performance.
3133
- `Gaussian` noise generator has standard deviation of 0.6 for perceptual equivalence.
3234

examples/into_file.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rodio::{output_to_wav, Source};
1+
use rodio::{wav_to_file, Source};
22
use std::error::Error;
33

44
/// Converts mp3 file to a wav file.
@@ -12,7 +12,7 @@ fn main() -> Result<(), Box<dyn Error>> {
1212

1313
let wav_path = "music_mp3_converted.wav";
1414
println!("Storing converted audio into {}", wav_path);
15-
output_to_wav(&mut audio, wav_path)?;
15+
wav_to_file(&mut audio, wav_path)?;
1616

1717
Ok(())
1818
}

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,7 @@ pub use crate::spatial_sink::SpatialSink;
198198
pub use crate::stream::{play, OutputStream, OutputStreamBuilder, PlayError, StreamError};
199199
#[cfg(feature = "wav_output")]
200200
#[cfg_attr(docsrs, doc(cfg(feature = "wav_output")))]
201-
pub use crate::wav_output::output_to_wav;
201+
pub use crate::wav_output::wav_to_file;
202+
#[cfg(feature = "wav_output")]
203+
#[cfg_attr(docsrs, doc(cfg(feature = "wav_output")))]
204+
pub use crate::wav_output::wav_to_writer;

src/wav_output.rs

Lines changed: 98 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,148 @@
11
use crate::common::assert_error_traits;
2+
use crate::Sample;
23
use crate::Source;
34
use hound::{SampleFormat, WavSpec};
5+
use std::io::{self, Write};
46
use std::path;
57
use std::sync::Arc;
68

79
#[derive(Debug, thiserror::Error, Clone)]
810
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")]
1014
Creating(#[source] Arc<hound::Error>),
11-
#[error("Failed to write samples to wav file")]
15+
#[error("Failed to write samples writer")]
1216
Writing(#[source] Arc<hound::Error>),
1317
#[error("Failed to update the wav header")]
1418
Finishing(#[source] Arc<hound::Error>),
19+
#[error("Failed to flush all bytes to writer")]
20+
Flushing(#[source] Arc<std::io::Error>),
1521
}
1622
assert_error_traits!(ToWavError);
1723

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
2026
/// the output without opening output stream to a real audio device.
2127
///
2228
/// 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
2534
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),
2664
) -> Result<(), ToWavError> {
2765
let format = WavSpec {
2866
channels: source.channels().get(),
2967
sample_rate: source.sample_rate().get(),
3068
bits_per_sample: 32,
3169
sample_format: SampleFormat::Float,
3270
};
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+
3785
writer
38-
.write_sample(sample)
86+
.finalize()
3987
.map_err(Arc::new)
40-
.map_err(ToWavError::Writing)?;
88+
.map_err(ToWavError::Finishing)?;
4189
}
4290
writer
43-
.finalize()
91+
.flush()
4492
.map_err(Arc::new)
45-
.map_err(ToWavError::Finishing)?;
93+
.map_err(ToWavError::Flushing)?;
4694
Ok(())
4795
}
4896

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+
49130
#[cfg(test)]
50131
mod test {
51-
use super::output_to_wav;
132+
use super::wav_to_file;
52133
use crate::Source;
53134
use std::io::BufReader;
54135
use std::time::Duration;
55136

56137
#[test]
57-
fn test_output_to_wav() {
138+
fn test_wav_to_file() {
58139
let make_source = || {
59140
crate::source::SineWave::new(745.0)
60141
.amplify(0.1)
61142
.take_duration(Duration::from_secs(1))
62143
};
63144
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");
65146

66147
let file = std::fs::File::open(wav_file_path).expect("output file can be opened");
67148
// Not using crate::Decoder bcause it is limited to i16 samples.

0 commit comments

Comments
 (0)