Skip to content

Commit

Permalink
Merge pull request #621 from UnknownSuperficialNight/feature/automati…
Browse files Browse the repository at this point in the history
…c-gain-control

Add Automatic Gain Control
  • Loading branch information
dvdsk authored Oct 3, 2024
2 parents 4aa0fd4 + bdbc159 commit c29fa1b
Show file tree
Hide file tree
Showing 7 changed files with 655 additions and 10 deletions.
15 changes: 8 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Support for *ALAC/AIFF*
- Add `automatic_gain_control` source for dynamic audio level adjustment.
- New test signal generator sources:
- `SignalGenerator` source generates a sine, triangle, square wave or sawtooth
of a given frequency and sample rate.
- `Chirp` source generates a sine wave with a linearly-increasing
- `Chirp` source generates a sine wave with a linearly-increasing
frequency over a given frequency range and duration.
- `white` and `pink` generate white or pink noise, respectively. These
sources depend on the `rand` crate and are guarded with the "noise"
- `white` and `pink` generate white or pink noise, respectively. These
sources depend on the `rand` crate and are guarded with the "noise"
feature.
- Documentation for the "noise" feature has been added to `lib.rs`.
- New Fade and Crossfade 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`
`fade_in` has been refactored to use the `linear_gain_ramp`
implementation.

### Fixed
- `Sink.try_seek` now updates `controls.position` before returning. Calls to `Sink.get_pos`
done immediately after a seek will now return the correct value.
done immediately after a seek will now return the correct value.

### Changed
- `SamplesBuffer` is now `Clone`
Expand All @@ -53,15 +54,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Source` trait is now also implemented for `Box<dyn Source>` and `&mut Source`
- `fn new_vorbis` is now also available when the `symphonia-vorbis` feature is enabled

### Added
### Added
- Adds a new method `try_seek` to all sources. It returns either an error or
seeks to the given position. A few sources are "unsupported" they return the
error `Unsupported`.
- Adds `SpatialSink::clear()` bringing it in line with `Sink`

### Fixed
- channel upscaling now follows the 'WAVEFORMATEXTENSIBLE' format and no longer
repeats the last source channel on all extra output channels.
repeats the last source channel on all extra output channels.
Stereo content playing on a 5.1 speaker set will now only use the front left
and front right speaker instead of repeating the right sample on all speakers
except the front left one.
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ thiserror = "1.0.49"
rand = { version = "0.8.5", features = ["small_rng"], optional = true }
tracing = { version = "0.1.40", optional = true }

atomic_float = { version = "1.1.0", optional = true }

[features]
default = ["flac", "vorbis", "wav", "mp3"]
tracing = ["dep:tracing"]
experimental = ["dep:atomic_float"]

flac = ["claxon"]
vorbis = ["lewton"]
Expand Down
39 changes: 39 additions & 0 deletions benches/effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,42 @@ fn amplify(bencher: Bencher) {
.with_inputs(|| TestSource::music_wav().to_f32s())
.bench_values(|source| source.amplify(0.8).for_each(divan::black_box_drop))
}

#[divan::bench]
fn agc_enabled(bencher: Bencher) {
bencher
.with_inputs(|| TestSource::music_wav().to_f32s())
.bench_values(|source| {
source
.automatic_gain_control(
1.0, // target_level
4.0, // attack_time (in seconds)
0.005, // release_time (in seconds)
5.0, // absolute_max_gain
)
.for_each(divan::black_box_drop)
})
}

#[cfg(feature = "experimental")]
#[divan::bench]
fn agc_disabled(bencher: Bencher) {
bencher
.with_inputs(|| TestSource::music_wav().to_f32s())
.bench_values(|source| {
// Create the AGC source
let amplified_source = source.automatic_gain_control(
1.0, // target_level
4.0, // attack_time (in seconds)
0.005, // release_time (in seconds)
5.0, // absolute_max_gain
);

// Get the control handle and disable AGC
let agc_control = amplified_source.get_agc_control();
agc_control.store(false, std::sync::atomic::Ordering::Relaxed);

// Process the audio stream with AGC disabled
amplified_source.for_each(divan::black_box_drop)
})
}
21 changes: 21 additions & 0 deletions src/conversions/sample.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ pub trait Sample: CpalSample {
/// Multiplies the value of this sample by the given amount.
fn amplify(self, value: f32) -> Self;

/// Converts the sample to an f32 value.
fn to_f32(self) -> f32;

/// Calls `saturating_add` on the sample.
fn saturating_add(self, other: Self) -> Self;

Expand All @@ -102,6 +105,12 @@ impl Sample for u16 {
((self as f32) * value) as u16
}

#[inline]
fn to_f32(self) -> f32 {
// Convert u16 to f32 in the range [-1.0, 1.0]
(self as f32 - 32768.0) / 32768.0
}

#[inline]
fn saturating_add(self, other: u16) -> u16 {
self.saturating_add(other)
Expand All @@ -125,6 +134,12 @@ impl Sample for i16 {
((self as f32) * value) as i16
}

#[inline]
fn to_f32(self) -> f32 {
// Convert i16 to f32 in the range [-1.0, 1.0]
self as f32 / 32768.0
}

#[inline]
fn saturating_add(self, other: i16) -> i16 {
self.saturating_add(other)
Expand All @@ -147,6 +162,12 @@ impl Sample for f32 {
self * value
}

#[inline]
fn to_f32(self) -> f32 {
// f32 is already in the correct format
self
}

#[inline]
fn saturating_add(self, other: f32) -> f32 {
self + other
Expand Down
4 changes: 2 additions & 2 deletions src/sink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,10 @@ impl Sink {
///
/// # Errors
/// This function will return [`SeekError::NotSupported`] if one of the underlying
/// sources does not support seeking.
/// sources does not support seeking.
///
/// It will return an error if an implementation ran
/// into one during the seek.
/// into one during the seek.
///
/// When seeking beyond the end of a source this
/// function might return an error if the duration of the source is not known.
Expand Down
Loading

0 comments on commit c29fa1b

Please sign in to comment.