Skip to content

Commit

Permalink
Merge pull request #499 from iluvcapra/ramp-linear
Browse files Browse the repository at this point in the history
Added linear gain filter
  • Loading branch information
dvdsk authored Aug 9, 2024
2 parents e3d5612 + c541f35 commit 3c18b53
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 29 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Support for *ALAC/AIFF*
- New sources:
- `fade_out` fades an input out using a linear gain fade.
- `linear_gain_ramp` applies a linear gain change to a sound over a
given duration. `fade_out` is implemented as a `linear_gain_ramp` and
`fade_in` has been refactored to use the `linear_gain_ramp`
implementation.

### Changed
- `SamplesBuffer` is now `Clone`
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ symphonia-aiff = ["symphonia/aiff", "symphonia/pcm"]
quickcheck = "0.9.2"
rstest = "0.18.2"
rstest_reuse = "0.6.0"
approx = "0.5.1"

[[example]]
name = "music_m4a"
Expand Down
2 changes: 1 addition & 1 deletion src/conversions/sample.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ where
/// - For `u16`, silence corresponds to the value `u16::max_value() / 2`. The minimum and maximum
/// amplitudes are represented by `0` and `u16::max_value()` respectively.
/// - For `f32`, silence corresponds to the value `0.0`. The minimum and maximum amplitudes are
/// represented by `-1.0` and `1.0` respectively.
/// represented by `-1.0` and `1.0` respectively.
///
/// You can implement this trait on your own type as well if you wish so.
///
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,7 @@ pub use crate::sink::Sink;
pub use crate::source::Source;
pub use crate::spatial_sink::SpatialSink;
pub use crate::stream::{OutputStream, OutputStreamHandle, PlayError, StreamError};

#[cfg(test)]
#[macro_use]
extern crate approx;
11 changes: 8 additions & 3 deletions src/source/crossfade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use cpal::FromSample;
use crate::source::{FadeIn, Mix, TakeDuration};
use crate::{Sample, Source};

/// Mixes one sound fading out with another sound fading in for the given duration.
/// Mixes one sound fading out with another sound fading in for the given
/// duration.
///
/// Only the crossfaded portion (beginning of fadeout, beginning of fadein) is returned.
/// Only the crossfaded portion (beginning of fadeout, beginning of fadein) is
/// returned.
pub fn crossfade<I1, I2>(
input_fadeout: I1,
input_fadein: I2,
Expand Down Expand Up @@ -37,7 +39,7 @@ mod tests {
}

#[test]
fn test_crossfade() {
fn test_crossfade_with_self() {
let source1 = dummysource(10);
let source2 = dummysource(10);
let mut mixed = crossfade(
Expand All @@ -51,7 +53,10 @@ mod tests {
assert_eq!(mixed.next(), Some(4.0));
assert_eq!(mixed.next(), Some(5.0));
assert_eq!(mixed.next(), None);
}

#[test]
fn test_crossfade() {
let source1 = dummysource(10);
let source2 = dummysource(10).amplify(0.0);
let mut mixed = crossfade(
Expand Down
37 changes: 12 additions & 25 deletions src/source/fadein.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,23 @@ use std::time::Duration;

use crate::{Sample, Source};

use super::SeekError;
use super::{linear_ramp::linear_gain_ramp, LinearGainRamp, SeekError};

/// Internal function that builds a `FadeIn` object.
pub fn fadein<I>(input: I, duration: Duration) -> FadeIn<I>
where
I: Source,
I::Item: Sample,
{
let duration = duration.as_secs() * 1000000000 + duration.subsec_nanos() as u64;

FadeIn {
input,
remaining_ns: duration as f32,
total_ns: duration as f32,
input: linear_gain_ramp(input, duration, 0.0f32, 1.0f32, false),
}
}

/// Filter that modifies raises the volume from silence over a time period.
#[derive(Clone, Debug)]
pub struct FadeIn<I> {
input: I,
remaining_ns: f32,
total_ns: f32,
input: LinearGainRamp<I>,
}

impl<I> FadeIn<I>
Expand All @@ -35,19 +29,19 @@ where
/// Returns a reference to the inner source.
#[inline]
pub fn inner(&self) -> &I {
&self.input
self.input.inner()
}

/// Returns a mutable reference to the inner source.
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
&mut self.input
self.input.inner_mut()
}

/// Returns the inner source.
#[inline]
pub fn into_inner(self) -> I {
self.input
self.input.into_inner()
}
}

Expand All @@ -60,14 +54,7 @@ where

#[inline]
fn next(&mut self) -> Option<I::Item> {
if self.remaining_ns <= 0.0 {
return self.input.next();
}

let factor = 1.0 - self.remaining_ns / self.total_ns;
self.remaining_ns -=
1000000000.0 / (self.input.sample_rate() as f32 * self.channels() as f32);
self.input.next().map(|value| value.amplify(factor))
self.input.next()
}

#[inline]
Expand All @@ -90,26 +77,26 @@ where
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
self.input.current_frame_len()
self.inner().current_frame_len()
}

#[inline]
fn channels(&self) -> u16 {
self.input.channels()
self.inner().channels()
}

#[inline]
fn sample_rate(&self) -> u32 {
self.input.sample_rate()
self.inner().sample_rate()
}

#[inline]
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
self.inner().total_duration()
}

#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
self.input.try_seek(pos)
self.inner_mut().try_seek(pos)
}
}
102 changes: 102 additions & 0 deletions src/source/fadeout.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use std::time::Duration;

use crate::{Sample, Source};

use super::{linear_ramp::linear_gain_ramp, LinearGainRamp, SeekError};

/// Internal function that builds a `FadeOut` object.
pub fn fadeout<I>(input: I, duration: Duration) -> FadeOut<I>
where
I: Source,
I::Item: Sample,
{
FadeOut {
input: linear_gain_ramp(input, duration, 1.0f32, 0.0f32, true),
}
}

/// Filter that modifies lowers the volume to silence over a time period.
#[derive(Clone, Debug)]
pub struct FadeOut<I> {
input: LinearGainRamp<I>,
}

impl<I> FadeOut<I>
where
I: Source,
I::Item: Sample,
{
/// Returns a reference to the inner source.
#[inline]
pub fn inner(&self) -> &I {
self.input.inner()
}

/// Returns a mutable reference to the inner source.
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
self.input.inner_mut()
}

/// Returns the inner source.
#[inline]
pub fn into_inner(self) -> I {
self.input.into_inner()
}
}

impl<I> Iterator for FadeOut<I>
where
I: Source,
I::Item: Sample,
{
type Item = I::Item;

#[inline]
fn next(&mut self) -> Option<I::Item> {
self.input.next()
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.input.size_hint()
}
}

impl<I> ExactSizeIterator for FadeOut<I>
where
I: Source + ExactSizeIterator,
I::Item: Sample,
{
}

impl<I> Source for FadeOut<I>
where
I: Source,
I::Item: Sample,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
self.inner().current_frame_len()
}

#[inline]
fn channels(&self) -> u16 {
self.inner().channels()
}

#[inline]
fn sample_rate(&self) -> u32 {
self.inner().sample_rate()
}

#[inline]
fn total_duration(&self) -> Option<Duration> {
self.inner().total_duration()
}

#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
self.inner_mut().try_seek(pos)
}
}
Loading

0 comments on commit 3c18b53

Please sign in to comment.