Hound is a wav encoding and decoding library in Rust.
Hound can read and write the WAVE audio format, an ubiquitous format for raw, uncompressed audio. The main motivation to write it was to test Claxon, a FLAC decoding library written in Rust.
Examples
The following example renders a 440 Hz sine wave, and stores it as a mono wav file with a sample rate of 44.1 kHz and 16 bits per sample.
use std::f32::consts::PI;
use std::i16;
use hound;
let spec = hound::WavSpec {
channels: 1,
sample_rate: 44100,
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
};
let mut writer = hound::WavWriter::create("sine.wav", spec).unwrap();
for t in (0 .. 44100).map(|x| x as f32 / 44100.0) {
let sample = (t * 440.0 * 2.0 * PI).sin();
let amplitude = i16::MAX as f32;
writer.write_sample((sample * amplitude) as i16).unwrap();
}
The file is finalized implicitly when the writer is dropped, call writer.finalize()
to observe errors.
The following example computes the root mean square (RMS) of an audio file with at most 16 bits per sample.
use hound;
let mut reader = hound::WavReader::open("testsamples/pop.wav").unwrap();
let sqr_sum = reader.samples::<i16>()
.fold(0.0, |sqr_sum, s| {
let sample = s.unwrap() as f64;
sqr_sum + sample * sample
});
println!("RMS is {}", (sqr_sum / reader.len() as f64).sqrt());
Features
Read | Write | |
---|---|---|
Format | PCMWAVEFORMAT, WAVEFORMATEX, WAVEFORMATEXTENSIBLE | PCMWAVEFORMAT |
WAVEFORMATEX
WAVEFORMATEXTENSIBLE
PCMWAVEFORMAT, WAVEFORMATEXTENSIBLE|PCMWAVEFORMAT
WAVEFORMATEXTENSIBLE
|Encoding|Integer PCM, IEEE Float|Integer PCM, IEEE Float|
|Bits per sample|8, 16, 24, 32 (integer), 32 (float)|8, 16, 24, 32 (integer), 32 (float)|
Full Example
Let us look at a full Hound Example.
Step 1. Write Code
Finally we need to write our code as follows:
(a). append.rs
This example appends one second of a 440 Hz sine wave to the file "sine.wav". If the file does not exist, it is created instead.
use std::f32::consts::PI;
use std::i16;
use std::path::Path;
extern crate hound;
fn main() {
let spec = hound::WavSpec {
channels: 1,
sample_rate: 44100,
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
};
let path: &Path = "sine.wav".as_ref();
let mut writer = match path.is_file() {
true => hound::WavWriter::append(path).unwrap(),
false => hound::WavWriter::create(path, spec).unwrap(),
};
// We should not append blindly, we should make sure that the existing file
// has the right spec, because that is what we assume when writing.
assert_eq!(spec, writer.spec());
println!("Old duration is {} seconds.", writer.duration() / spec.sample_rate);
for t in (0 .. 44100).map(|x| x as f32 / 44100.0) {
let sample = (t * 440.0 * 2.0 * PI).sin();
let amplitude = i16::MAX as f32;
writer.write_sample((sample * amplitude) as i16).unwrap();
}
println!("New duration is {} seconds.", writer.duration() / spec.sample_rate);
writer.finalize().unwrap();
}
(b). cpal.rs
This example shows how to play a wav file using the cpal crate.
extern crate hound;
extern crate cpal;
use std::env;
use std::thread;
fn main() {
// Make a WavReader that reads the file provided as program argument.
let fname = env::args().nth(1).expect("no file given");
let mut reader = hound::WavReader::open(fname).unwrap();
let spec = reader.spec();
let endpoint = cpal::get_default_endpoint().unwrap();
// Pick a playback format supported by the endpoint, which matches the spec
// of the wav file.
let format = endpoint.get_supported_formats_list().unwrap()
.filter(|f| matches_format(f, &spec))
.next()
.expect("no supported playback format");
// A voice in cpal is used for playback.
let mut voice = cpal::Voice::new(&endpoint, &format).unwrap();
let mut samples_left = reader.len() as usize;
let mut append_data = |voice: &mut cpal::Voice| {
match voice.append_data(samples_left) {
cpal::UnknownTypeBuffer::I16(mut wrapped_buf) => {
// We cannot rely on Rust's autoderef here, because we want to
// call .len() on the buffer, which would cause a deref() of the
// buffer, not a deref_mut(), and cpal's deref() implementation
// is to panic.
let buf: &mut [i16] = &mut *wrapped_buf;
for (dst, src) in buf.iter_mut().zip(reader.samples::<i16>()) {
*dst = src.unwrap();
}
samples_left -= buf.len();
}
cpal::UnknownTypeBuffer::F32(mut wrapped_buf) => {
let buf: &mut [f32] = &mut *wrapped_buf;
for (dst, src) in buf.iter_mut().zip(reader.samples::<f32>()) {
*dst = src.unwrap();
}
samples_left -= buf.len();
}
_ => unreachable!()
}
// Loop again if there are samples left.
samples_left > 0
};
// The voice must have some data before playing for the first time.
let mut has_more = append_data(&mut voice);
voice.play();
// Then we keep providing new data until the end of the audio.
while has_more {
has_more = append_data(&mut voice);
}
// Wait for playback to complete.
while voice.underflowed() {
thread::yield_now();
}
}
fn matches_format(format: &cpal::Format, spec: &hound::WavSpec) -> bool {
let cpal::SamplesRate(sample_rate) = format.samples_rate;
if sample_rate != spec.sample_rate {
return false
}
if format.channels.len() != spec.channels as usize {
return false
}
let data_type = match (spec.bits_per_sample, spec.sample_format) {
(16, hound::SampleFormat::Int) => Some(cpal::SampleFormat::I16),
(32, hound::SampleFormat::Float) => Some(cpal::SampleFormat::F32),
_ => None
};
if Some(format.data_type) != data_type {
return false
}
// If the sample rate, channel count, and sample format match, then we can
// play back the file in this format.
true
}
(c). mean.rs
This example computes the mean value and rms of a file, where samples are first interpreted as 16-bit signed integer, and then as a 16-bit unsigned integer. This should allow us to determine whether the samples stored are signed or unsigned: for signed the average value is expected to be 0, and for unsigned the value is expected to be 2^16 - 1.
Note that this example is not a particularly good example of proper coding style; it does not handle failure properly, and it assumes that the provided file has 16 bits per sample
extern crate hound;
use std::env;
fn main() {
let fname = env::args().nth(1).expect("no file given");
let mut reader = hound::WavReader::open(&fname).unwrap();
let samples: Vec<i16> = reader.samples().map(|s| s.unwrap()).collect();
let (ts, tu, n) = samples.iter().fold((0.0, 0.0, 0.0), |(ts, tu, n), &s| {
let signed = s as f64;
let unsigned = (s as u16) as f64;
(ts + signed, tu + unsigned, n + 1.0)
});
let ms = ts / n;
let mu = tu / n;
println!("mean signed: {} (should be 0, deviation is {})", ms, ms.abs());
println!("mean unsigned: {} (should be 2^16 - 1, deviation is {})", mu, (mu - 32767.0).abs());
let (ts, tu) = samples.iter().fold((0.0, 0.0), |(ts, tu), &s| {
let ds = s as f64 - ms;
let du = (s as u16) as f64 - mu;
(ts + ds * ds, tu + du * du)
});
let rmss = (ts / n).sqrt();
let rmsu = (tu / n).sqrt();
println!("rms signed: {}", rmss);
println!("rms unsigned: {}", rmsu);
}
(d). rms.rs
This example computes the root mean square (rms) of an audio file with integer or float samples, of at most 32 bits per sample. It is a slightly more elaborate version of the example found in the readme, mostly useful for checking whether Hound can read a specific file.
extern crate hound;
use std::env;
use std::io;
/// Compute the RMS of either integers or float samples.
fn compute_rms<S, R>(reader: &mut hound::WavReader<R>) -> f64
where
f64: From<S>,
S: hound::Sample,
R: io::Read,
{
let sqr_sum = reader.samples::<S>().fold(0.0, |sqr_sum, s| {
let sample = f64::from(s.unwrap());
sqr_sum + sample * sample
});
(sqr_sum / reader.len() as f64).sqrt()
}
fn main() {
// Compute the RMS for all files given on the command line.
for fname in env::args().skip(1) {
let mut reader = hound::WavReader::open(&fname).unwrap();
let rms = match reader.spec().sample_format {
hound::SampleFormat::Float => compute_rms::<f32, _>(&mut reader),
hound::SampleFormat::Int => compute_rms::<i32, _>(&mut reader),
};
println!("{}: {:0.1} ({} samples)", fname, rms, reader.len());
}
}
(e). wavstdout.rs
Generate endless screeching noise to stdout
Usage: cargo run --example wavstdout | mpv -
.
extern crate hound;
use std::io::Write;
fn main() {
let spec = hound::WavSpec {
bits_per_sample: 16,
channels: 1,
sample_format: hound::SampleFormat::Int,
sample_rate: 16000,
};
let v = spec.into_header_for_infinite_file();
let so = std::io::stdout();
let mut so = so.lock();
so.write_all(&v[..]).unwrap();
loop {
for i in 0..126 {
use hound::Sample;
let x : i16 = (i * 256) as i16;
if x.write(&mut so, 16).is_err() {
return;
}
}
}
}
Reference
Download the code below:
No. | Link |
---|---|
1. | Download Full Code |
2. | Read more here. |
3. | Follow code author here. |