Merge pull request #8 from tomaka/clean-convert-samples

Cleanup and add tests for convert_samples_rate
This commit is contained in:
tomaka 2014-12-23 15:02:26 +01:00
commit 0f76134359
3 changed files with 65 additions and 18 deletions

View File

@ -4,34 +4,48 @@ This module contains function that will convert from one PCM format to another.
This includes conversion between samples formats, channels or sample rates. This includes conversion between samples formats, channels or sample rates.
*/ */
pub fn convert_samples_rate<T>(input: &[T], from: ::SamplesRate, to: ::SamplesRate) -> Vec<T> use samples_formats::Sample;
where T: Copy
/// Converts between samples rates while preserving the pitch.
pub fn convert_samples_rate<T>(input: &[T], from: ::SamplesRate, to: ::SamplesRate,
channels: ::ChannelsCount) -> Vec<T>
where T: Sample
{ {
let from = from.0; let from = from.0;
let to = to.0; let to = to.0;
// if `from` is a multiple of `to` (for example `from` is 44100 and `to` is 22050), // if `from` is a multiple of `to` (for example `from` is 44100 and `to` is 22050),
// then we simply skip some samples // then we simply skip some samples
if from % to == 0 { if from % to == 0 {
let mut result = Vec::new(); let mut result = Vec::new();
for element in input.chunks((from / to) as uint) { for element in input.chunks(channels as uint * (from / to) as uint) {
result.push(element[0]); for i in range(0, channels) {
result.push(element[i as uint]);
}
} }
return result; return result;
} }
// if `to` is a multiple of `from` (for example `to` is 44100 and `from` is 22050) // if `to` is twice `from` (for example `to` is 44100 and `from` is 22050)
// TODO: dumb algorithm // TODO: more generic
// FIXME: doesn't take channels into account if to == from * 2 {
if to % from == 0 {
let mut result = Vec::new(); let mut result = Vec::new();
for element in input.windows(2) { let mut previous: Option<Vec<T>> = None;
for _ in range(0, (to / from) as uint) { for element in input.chunks(channels as uint) {
result.push(element[0]); if let Some(previous) = previous.take() {
for (prev, curr) in previous.into_iter().zip(element.iter()) {
result.push(prev.interpolate(*curr));
}
for curr in element.iter() {
result.push(*curr);
}
} else {
for e in element.iter() {
result.push(*e);
}
} }
}
for _ in range(0, (to / from) as uint) { previous = Some(element.to_vec());
result.push(*input.last().unwrap());
} }
return result; return result;
} }
@ -50,7 +64,7 @@ pub fn convert_samples_rate<T>(input: &[T], from: ::SamplesRate, to: ::SamplesRa
/// ///
/// Panics if `from` is 0, `to` is 0, or if the data length is not a multiple of `from`. /// Panics if `from` is 0, `to` is 0, or if the data length is not a multiple of `from`.
pub fn convert_channels<T>(input: &[T], from: ::ChannelsCount, to: ::ChannelsCount) -> Vec<T> pub fn convert_channels<T>(input: &[T], from: ::ChannelsCount, to: ::ChannelsCount) -> Vec<T>
where T: Copy where T: Sample
{ {
assert!(from != 0); assert!(from != 0);
assert!(to != 0); assert!(to != 0);
@ -78,6 +92,7 @@ pub fn convert_channels<T>(input: &[T], from: ::ChannelsCount, to: ::ChannelsCou
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::convert_channels; use super::convert_channels;
use super::convert_samples_rate;
#[test] #[test]
fn remove_channels() { fn remove_channels() {
@ -102,4 +117,20 @@ mod test {
fn convert_channels_wrong_data_len() { fn convert_channels_wrong_data_len() {
convert_channels(&[1u16, 2, 3], 2, 1); convert_channels(&[1u16, 2, 3], 2, 1);
} }
#[test]
fn half_samples_rate() {
let result = convert_samples_rate(&[1u16, 16, 2, 17, 3, 18, 4, 19],
::SamplesRate(44100), ::SamplesRate(22050), 2);
assert_eq!(result.as_slice(), [1, 16, 3, 18]);
}
#[test]
fn double_samples_rate() {
let result = convert_samples_rate(&[2u16, 16, 4, 18, 6, 20, 8, 22],
::SamplesRate(22050), ::SamplesRate(44100), 2);
assert_eq!(result.as_slice(), [2, 16, 3, 17, 4, 18, 5, 19, 6, 20, 7, 21, 8, 22]);
}
} }

View File

@ -245,7 +245,8 @@ impl<'a, T> Drop for Buffer<'a, T> where T: Sample {
let buffer = if conversion.from_sample_rate != conversion.to_sample_rate { let buffer = if conversion.from_sample_rate != conversion.to_sample_rate {
conversions::convert_samples_rate(buffer.as_slice(), conversion.from_sample_rate, conversions::convert_samples_rate(buffer.as_slice(), conversion.from_sample_rate,
conversion.to_sample_rate) conversion.to_sample_rate,
conversion.to_channels)
} else { } else {
buffer buffer
}; };

View File

@ -25,9 +25,12 @@ impl SampleFormat {
/// Trait for containers that contain PCM data. /// Trait for containers that contain PCM data.
#[unstable = "Will be rewritten with associated types"] #[unstable = "Will be rewritten with associated types"]
pub trait Sample: Copy { pub trait Sample: Copy + Clone {
fn get_format(Option<Self>) -> SampleFormat; fn get_format(Option<Self>) -> SampleFormat;
/// Returns `(self + other) / 2`.
fn interpolate(self, other: Self) -> Self;
/// Turns the data into samples of type `I16`. /// Turns the data into samples of type `I16`.
fn to_vec_i16(&[Self]) -> Cow<Vec<i16>, [i16]>; fn to_vec_i16(&[Self]) -> Cow<Vec<i16>, [i16]>;
/// Turns the data into samples of type `U16`. /// Turns the data into samples of type `U16`.
@ -41,6 +44,10 @@ impl Sample for u16 {
SampleFormat::U16 SampleFormat::U16
} }
fn interpolate(self, other: u16) -> u16 {
(self + other) / 2
}
fn to_vec_i16(input: &[u16]) -> Cow<Vec<i16>, [i16]> { fn to_vec_i16(input: &[u16]) -> Cow<Vec<i16>, [i16]> {
Cow::Owned(input.iter().map(|&value| { Cow::Owned(input.iter().map(|&value| {
if value >= 32768 { if value >= 32768 {
@ -65,6 +72,10 @@ impl Sample for i16 {
SampleFormat::I16 SampleFormat::I16
} }
fn interpolate(self, other: i16) -> i16 {
(self + other) / 2
}
fn to_vec_i16(input: &[i16]) -> Cow<Vec<i16>, [i16]> { fn to_vec_i16(input: &[i16]) -> Cow<Vec<i16>, [i16]> {
Cow::Borrowed(input) Cow::Borrowed(input)
} }
@ -95,6 +106,10 @@ impl Sample for f32 {
SampleFormat::F32 SampleFormat::F32
} }
fn interpolate(self, other: f32) -> f32 {
(self + other) / 2.0
}
fn to_vec_i16(input: &[f32]) -> Cow<Vec<i16>, [i16]> { fn to_vec_i16(input: &[f32]) -> Cow<Vec<i16>, [i16]> {
Cow::Owned(input.iter().map(|&value| { Cow::Owned(input.iter().map(|&value| {
if value >= 0.0 { if value >= 0.0 {