From c8f9625cf6c11d7db01c1d6bea7370ae6096462b Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Thu, 10 Nov 2016 15:22:37 -0800 Subject: [PATCH 1/3] Remove rate module --- examples/wav.rs | 6 +- src/interpolate.rs | 1 - src/lib.rs | 1 - src/rate.rs | 230 --------------------------------------------- src/signal.rs | 50 ++++++---- 5 files changed, 37 insertions(+), 251 deletions(-) delete mode 100644 src/rate.rs diff --git a/examples/wav.rs b/examples/wav.rs index a43cdac8..5d738620 100644 --- a/examples/wav.rs +++ b/examples/wav.rs @@ -4,6 +4,7 @@ extern crate portaudio as pa; extern crate sample; use sample::{signal, Signal, ToFrameSliceMut}; +use sample::interpolate::Linear; // Thumb piano. mod wav { @@ -63,7 +64,8 @@ fn frames(file_name: &'static str) -> Vec { let mut reader = hound::WavReader::open(&sample_file).unwrap(); let spec = reader.spec(); let samples = reader.samples().map(|s| s.unwrap()); - signal::from_interleaved_samples::<_, wav::Frame>(samples) - .from_hz_to_hz(spec.sample_rate as f64, SAMPLE_RATE as f64) + let mut signal = signal::from_interleaved_samples::<_, wav::Frame>(samples); + let interp = Linear::from_source(&mut signal).unwrap(); + signal.from_hz_to_hz(interp, spec.sample_rate as f64, SAMPLE_RATE as f64) .collect() } diff --git a/src/interpolate.rs b/src/interpolate.rs index f6d1e8eb..7baa082f 100644 --- a/src/interpolate.rs +++ b/src/interpolate.rs @@ -150,7 +150,6 @@ impl Converter impl Iterator for Converter where T: Iterator, ::Item: Frame, - <::Item as Frame>::Sample: Duplex, I: Interpolator::Item> { type Item = ::Item; diff --git a/src/lib.rs b/src/lib.rs index b4fb395c..0069160f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,6 @@ pub mod slice; pub mod conv; pub mod frame; pub mod signal; -pub mod rate; pub mod types; pub mod window; pub mod interpolate; diff --git a/src/rate.rs b/src/rate.rs deleted file mode 100644 index 3bb9861c..00000000 --- a/src/rate.rs +++ /dev/null @@ -1,230 +0,0 @@ -//! The **rate** module provides a [**Converter** type](./struct.Converter.html), for converting -//! and interpolating the rate of **Signal**s. This can be useful for both sample rate conversion -//! and playback rate multiplication. - -use {Duplex, Frame, Sample}; - -/// An iterator that converts the rate at which frames are yielded from some given frame Iterator -/// via some given ratio. -/// -/// Other names for `sample::rate::Converter` might include: -/// -/// - Sample rate converter -/// - {Up/Down}sampler -/// - Sample interpolater -/// - Sample decimator -/// -/// At the moment, `Converter` only supports basic linear amplitude interpolation between -/// frames and is far from the most precise algorithm available. The current form of interpolation -/// also requires that samples are either in `f64` format or can be converted to and from `f64` -/// format. In terms of audio quality, it is not recommended for use in pro-audio applications or -/// professional sound design. However if you are simply reading audio files and need to do a -/// single conversion from their sample rate to your own domain for basic playback, `Converter` -/// might be sufficient and fast at the very least. -/// -/// That said, the aim is to provide higher quality interpolation types soon and change -/// `Converter`s interface to a generic API compatible with a range of interpolation types. -#[derive(Clone)] -pub struct Converter - where I: Iterator, - I::Item: Frame, -{ - /// The frames at the old rate which we need to convert. - source_frames: I, - /// The ratio between the target and source sample rates. - /// - /// This value is equal to `source_sample_rate / target_sample_rate` and `target_playback_rate - /// / source_playback_rate`. - source_to_target_ratio: f64, - /// The "left" side of the source frame window that is used for interpolation when calculating - /// new target frames. - source_window_left: Option, - /// The "right" side of the source frame window that is used for interpolation when calculating - /// new target frames. - source_window_right: Option, - /// Represents the interpolation between the `source_window_left` and `source_window_right`. - /// - /// This can be thought of as the "playhead" over the source frames. - /// - /// This is stepped forward by the `source_to_target_ratio` each time a new target sample is - /// yielded. - /// - /// Whenever `source_interpolation` surpasses `1.0`, the "source window" is stepped forward and - /// the `source_interpolation` reduced by `1.0` accordingly until the "source window" falls - /// under the `source_interpolation`. The resulting `source_interpolation` is then used to - /// interpolate the window. - source_interpolation: f64, -} - -impl Converter - where I: Iterator, - I::Item: Frame, -{ - - /// Construct a new `Converter` from the source frames and the source and target sample rates - /// (in Hz). - #[inline] - pub fn from_hz_to_hz(source_frames: I, source_hz: f64, target_hz: f64) -> Self { - Self::scale_playback_hz(source_frames, source_hz / target_hz) - } - - /// Construct a new `Converter` from the source frames and the amount by which the current - /// ***playback*** **rate** (not sample rate) should be multiplied to reach the new playback - /// rate. - /// - /// For example, if our `source_frames` is a sine wave oscillating at a frequency of 2hz and - /// we wanted to convert it to a frequency of 3hz, the given `scale` should be `1.5`. - #[inline] - pub fn scale_playback_hz(source_frames: I, scale: f64) -> Self { - assert!(scale > 0.0, "We can't yield any frames at 0 times a second!"); - Converter { - source_frames: source_frames, - source_to_target_ratio: scale, - source_interpolation: 0.0, - source_window_left: None, - source_window_right: None, - } - } - - /// Construct a new `Converter` from the source frames and the amount by which the current - /// ***sample*** **rate** (not playback rate) should be multiplied to reach the new sample - /// rate. - /// - /// If our `source_frames` are being sampled at a rate of 44_100hz and we want to - /// convert to a sample rate of 96_000hz, the given `scale` should be `96_000.0 / 44_100.0`. - /// - /// This is the same as calling `Converter::scale_playback_hz(source_frames, 1.0 / scale)`. - #[inline] - pub fn scale_sample_hz(source_frames: I, scale: f64) -> Self { - Self::scale_playback_hz(source_frames, 1.0 / scale) - } - - /// Update the `source_to_target_ratio` internally given the source and target hz. - /// - /// This method might be useful for changing the sample rate during playback. - #[inline] - pub fn set_hz_to_hz(&mut self, source_hz: f64, target_hz: f64) { - self.set_playback_hz_scale(source_hz / target_hz) - } - - /// Update the `source_to_target_ratio` internally given a new **playback rate** multiplier. - /// - /// This method is useful for dynamically changing rates. - #[inline] - pub fn set_playback_hz_scale(&mut self, scale: f64) { - self.source_to_target_ratio = scale; - } - - /// Update the `source_to_target_ratio` internally given a new **sample rate** multiplier. - /// - /// This method is useful for dynamically changing rates. - #[inline] - pub fn set_sample_hz_scale(&mut self, scale: f64) { - self.set_playback_hz_scale(1.0 / scale); - } - - /// Borrow the `source_frames` Iterator from the `Converter`. - #[inline] - pub fn source(&self) -> &I { - &self.source_frames - } - - /// Mutably borrow the `source_frames` Iterator from the `Converter`. - #[inline] - pub fn source_mut(&mut self) -> &mut I { - &mut self.source_frames - } - - /// Drop `self` and return the internal `source_frames` Iterator. - #[inline] - pub fn into_source(self) -> I { - self.source_frames - } - - /// Yields the next interpolated target frame. - #[inline] - pub fn next_frame(&mut self) -> Option - where ::Sample: Duplex, - { - let Converter { - ref mut source_frames, - source_to_target_ratio, - ref mut source_interpolation, - ref mut source_window_left, - ref mut source_window_right, - } = *self; - - // Retrieve the source_window_left. - // - // If we have no source_window_left, we can assume this is the first iteration and - // simply assign and yield the first source_frame. - let mut left_frame = match *source_window_left { - Some(frame) => frame, - None => match source_frames.next() { - Some(frame) => { - *source_window_left = Some(frame); - *source_interpolation += source_to_target_ratio; - return *source_window_left; - }, - None => return None, - }, - }; - - // Retrieve the source_window_right. - let mut right_frame = match *source_window_right { - Some(frame) => frame, - None => match source_frames.next() { - Some(frame) => frame, - None => return None, - }, - }; - - // Step forward in our source_frames until our `source_interpolation` is 0.0...1.0. - while *source_interpolation > 1.0 { - left_frame = right_frame; - right_frame = match source_frames.next() { - Some(frame) => frame, - // If there are no more frames we have finished our conversion. - None => return None, - }; - *source_interpolation -= 1.0; - } - - // Calculate the target frame by interpolating between `left_frame` and `right_frame` by - // the `source_interpolation`. - let target_frame = left_frame.zip_map(right_frame, |current, next| { - let current_f = current.to_sample::(); - let next_f = next.to_sample::(); - let diff = next_f - current_f; - let amp = current_f + diff * *source_interpolation; - amp.to_sample::<::Sample>() - }); - - *source_window_left = Some(left_frame); - *source_window_right = Some(right_frame); - *source_interpolation += source_to_target_ratio; - - Some(target_frame) - } - -} - -impl Iterator for Converter - where I: Iterator, - I::Item: Frame, - ::Sample: Duplex, -{ - type Item = I::Item; - #[inline] - fn next(&mut self) -> Option { - self.next_frame() - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - let len_multiplier = self.source_to_target_ratio / 1.0; - let (source_lower, source_upper) = self.source_frames.size_hint(); - let lower = (source_lower as f64 * len_multiplier) as usize; - let upper = source_upper.map(|upper| (upper as f64 * len_multiplier) as usize); - (lower, upper) - } -} diff --git a/src/signal.rs b/src/signal.rs index a5d037c1..d0630350 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -20,7 +20,7 @@ //! a simple and familiar API. use {Duplex, Frame, Sample, Vec, Rc, VecDeque}; -use rate; +use interpolate::{Converter, Interpolator}; use core; @@ -219,19 +219,23 @@ pub trait Signal: Iterator + Sized /// extern crate sample; /// /// use sample::Signal; + /// use sample::interpolate::Linear; /// /// fn main() { /// let foo = [[0.0], [1.0], [0.0], [-1.0]]; /// let mul = [1.0, 1.0, 0.5, 0.5, 0.5, 0.5]; - /// let frames: Vec<_> = foo.iter().cloned().mul_hz(mul.iter().cloned()).collect(); - /// assert_eq!(&frames[..], &[[0.0], [1.0], [0.0], [-0.5], [-1.0]][..]); + /// let mut source = foo.iter().cloned(); + /// let interp = Linear::from_source(&mut source).unwrap(); + /// let frames: Vec<_> = source.mul_hz(interp, mul.iter().cloned()).collect(); + /// assert_eq!(&frames[..], &[[0.0], [1.0], [0.0], [-0.5], [-1.0], [-0.5]][..]); /// } /// ``` - fn mul_hz(self, mul_per_frame: I) -> MulHz - where I: Iterator, + fn mul_hz(self, interpolator: I, mul_per_frame: M) -> MulHz + where M: Iterator, + I: Interpolator, { MulHz { - signal: rate::Converter::scale_playback_hz(self, 1.0), + signal: Converter::scale_playback_hz(self, interpolator, 1.0), mul_per_frame: mul_per_frame, } } @@ -244,15 +248,20 @@ pub trait Signal: Iterator + Sized /// extern crate sample; /// /// use sample::Signal; + /// use sample::interpolate::Linear; /// /// fn main() { /// let foo = [[0.0], [1.0], [0.0], [-1.0]]; - /// let frames: Vec<_> = foo.iter().cloned().from_hz_to_hz(1.0, 2.0).collect(); - /// assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0]][..]); + /// let mut source = foo.iter().cloned(); + /// let interp = Linear::from_source(&mut source).unwrap(); + /// let frames: Vec<_> = source.from_hz_to_hz(interp, 1.0, 2.0).collect(); + /// assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); /// } /// ``` - fn from_hz_to_hz(self, source_hz: f64, target_hz: f64) -> rate::Converter { - rate::Converter::from_hz_to_hz(self, source_hz, target_hz) + fn from_hz_to_hz(self, interpolator: I, source_hz: f64, target_hz: f64) -> Converter + where I: Interpolator, + { + Converter::from_hz_to_hz(self, interpolator, source_hz, target_hz) } /// Multiplies the rate at which frames of the `Signal` are yielded by the given value. @@ -263,15 +272,20 @@ pub trait Signal: Iterator + Sized /// extern crate sample; /// /// use sample::Signal; + /// use sample::interpolate::Linear; /// /// fn main() { /// let foo = [[0.0], [1.0], [0.0], [-1.0]]; - /// let frames: Vec<_> = foo.iter().cloned().scale_hz(0.5).collect(); - /// assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0]][..]); + /// let mut source = foo.iter().cloned(); + /// let interp = Linear::from_source(&mut source).unwrap(); + /// let frames: Vec<_> = source.scale_hz(interp, 0.5).collect(); + /// assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); /// } /// ``` - fn scale_hz(self, multi: f64) -> rate::Converter { - rate::Converter::scale_playback_hz(self, multi) + fn scale_hz(self, interpolator: I, multi: f64) -> Converter + where I: Interpolator, + { + Converter::scale_playback_hz(self, interpolator, multi) } /// Delays the `Signal` by the given number of frames. @@ -560,11 +574,12 @@ pub struct ScaleAmpPerChannel { /// This happens by wrapping `self` in a `rate::Converter` and calling `set_playback_hz_scale` /// with the value yielded by `signal` #[derive(Clone)] -pub struct MulHz +pub struct MulHz where S: Signal, S::Item: Frame, + I: Interpolator, { - signal: rate::Converter, + signal: Converter, mul_per_frame: M, } @@ -1522,11 +1537,12 @@ impl ExactSizeIterator for OffsetAmpPerChannel } -impl Iterator for MulHz +impl Iterator for MulHz where S: Signal, S::Item: Frame, ::Sample: Duplex, M: Iterator, + I: Interpolator::Item>, { type Item = S::Item; #[inline] From b663126b4b2f668c2001eec6fb48d05cc2cb120d Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Wed, 16 Nov 2016 22:36:10 -0800 Subject: [PATCH 2/3] Fix out-of-bounds error when Sinc only has one sample left --- src/interpolate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpolate.rs b/src/interpolate.rs index 7baa082f..29159798 100644 --- a/src/interpolate.rs +++ b/src/interpolate.rs @@ -395,7 +395,7 @@ impl Interpolator for Sinc } fn is_exhausted(&self) -> bool { - self.frames.len() <= self.depth + self.frames.len() <= (self.depth + 1) && self.idx == self.depth } } From 19e6d8042d84471a5820efe5c5b7e32a0732c4ca Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Mon, 26 Jun 2017 14:56:17 -0500 Subject: [PATCH 3/3] Fix #49: Revise the README doc Change example to illustrate that the interpolated sample tapers to 0 at the end (to fill the expected length) and to ensure that it does not use an old API. --- README.md | 13 +++++++++---- tests/interpolate.rs | 10 ++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1a737f46..07432587 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,10 @@ assert_eq!(added, vec![[0.4], [-0.5], [-0.3]]); // Scale the playback rate by `0.5`. let foo = [[0.0], [1.0], [0.0], [-1.0]]; -let frames: Vec<_> = foo.iter().cloned().scale_hz(0.5).collect(); -assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0]][..]); +let mut source = foo.iter().cloned(); +let interp = Linear::from_source(&mut source).unwrap(); +let frames: Vec<_> = source.scale_hz(interp, 0.5).collect(); +assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); ``` The **signal** module also provides a series of **Signal** source types, @@ -120,9 +122,12 @@ of sample types. Traits include: - `FromFrameSliceMut`, `ToFrameSliceMut`, `DuplexFrameSliceMut`, - `DuplexSlice`, `DuplexSliceMut`, -The **rate** module provides a **Converter** type, for converting and +The **interpolate** module provides a **Converter** type, for converting and interpolating the rate of **Signal**s. This can be useful for both sample rate -conversion and playback rate multiplication. +conversion and playback rate multiplication. **Converter**s can use a range of +interpolation methods, with Floor, Linear, and Sinc interpolation provided in +the library. (NB: Sinc interpolation currently requires heap allocation, as it +uses VecDeque.) Using in a `no_std` environment ------------------------------- diff --git a/tests/interpolate.rs b/tests/interpolate.rs index a66b35bf..fc374bf8 100644 --- a/tests/interpolate.rs +++ b/tests/interpolate.rs @@ -3,6 +3,7 @@ extern crate sample; use sample::interpolate::{Converter, Floor, Linear}; +use sample::Signal; #[test] fn test_floor_converter() { @@ -42,3 +43,12 @@ fn test_linear_converter() { assert_eq!(conv.next(), None); } +#[test] +fn test_scale_playback_rate() { + // Scale the playback rate by `0.5` + let foo = [[0.0], [1.0], [0.0], [-1.0]]; + let mut source = foo.iter().cloned(); + let interp = Linear::from_source(&mut source).unwrap(); + let frames: Vec<_> = source.scale_hz(interp, 0.5).collect(); + assert_eq!(&frames[..], &[[0.0], [0.5], [1.0], [0.5], [0.0], [-0.5], [-1.0], [-0.5]][..]); +}