mirror of
https://github.com/RustAudio/rodio
synced 2024-12-12 13:12:30 +00:00
Merge branch 'master' into feature/automatic-gain-control
This commit is contained in:
commit
fd94703dc7
11 changed files with 589 additions and 16 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -10,7 +10,16 @@ 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 sources:
|
||||
- New test signal generator sources:
|
||||
- `TestSignal` 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
|
||||
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"
|
||||
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
|
||||
|
|
|
@ -19,6 +19,7 @@ symphonia = { version = "0.5.4", optional = true, default-features = false }
|
|||
crossbeam-channel = { version = "0.5.8", optional = true }
|
||||
|
||||
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 }
|
||||
|
@ -33,6 +34,7 @@ vorbis = ["lewton"]
|
|||
wav = ["hound"]
|
||||
mp3 = ["symphonia-mp3"]
|
||||
minimp3 = ["dep:minimp3_fixed"]
|
||||
noise = ["rand"]
|
||||
wasm-bindgen = ["cpal/wasm-bindgen"]
|
||||
cpal-shared-stdcxx = ["cpal/oboe-shared-stdcxx"]
|
||||
symphonia-aac = ["symphonia/aac"]
|
||||
|
@ -64,3 +66,7 @@ harness = false
|
|||
[[example]]
|
||||
name = "music_m4a"
|
||||
required-features = ["symphonia-isomp4", "symphonia-aac"]
|
||||
|
||||
[[example]]
|
||||
name = "noise_generator"
|
||||
required-features = ["noise"]
|
||||
|
|
|
@ -20,6 +20,10 @@ See [the docs](https://docs.rs/rodio/latest/rodio/#alternative-decoder-backends)
|
|||
|
||||
[The documentation](http://docs.rs/rodio) contains an introduction to the library.
|
||||
|
||||
## Dependencies(Linux only)
|
||||
|
||||
Rodio uses `cpal` to send audio to the OS for playback. On Linux `cpal` needs the ALSA development files. These are provided as part of the libasound2-dev package on Debian and Ubuntu distributions and alsa-lib-devel on Fedora.
|
||||
|
||||
## License
|
||||
[License]: #license
|
||||
|
||||
|
|
41
examples/noise_generator.rs
Normal file
41
examples/noise_generator.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
//! Noise generator example. Use the "noise" feature to enable the noise generator sources.
|
||||
|
||||
#[cfg(feature = "noise")]
|
||||
fn main() {
|
||||
use rodio::source::{pink, white, Source};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
|
||||
|
||||
let noise_duration = Duration::from_millis(1000);
|
||||
let interval_duration = Duration::from_millis(1500);
|
||||
|
||||
stream_handle
|
||||
.play_raw(
|
||||
white(cpal::SampleRate(48000))
|
||||
.amplify(0.1)
|
||||
.take_duration(noise_duration),
|
||||
)
|
||||
.unwrap();
|
||||
println!("Playing white noise");
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
|
||||
stream_handle
|
||||
.play_raw(
|
||||
pink(cpal::SampleRate(48000))
|
||||
.amplify(0.1)
|
||||
.take_duration(noise_duration),
|
||||
)
|
||||
.unwrap();
|
||||
println!("Playing pink noise");
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "noise"))]
|
||||
fn main() {
|
||||
println!("rodio has not been compiled with noise sources, use `--features noise` to enable this feature.");
|
||||
println!("Exiting...");
|
||||
}
|
83
examples/signal_generator.rs
Normal file
83
examples/signal_generator.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
//! Test signal generator example.
|
||||
|
||||
fn main() {
|
||||
use rodio::source::{chirp, Function, SignalGenerator, Source};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
|
||||
|
||||
let test_signal_duration = Duration::from_millis(1000);
|
||||
let interval_duration = Duration::from_millis(1500);
|
||||
|
||||
println!("Playing 1000 Hz tone");
|
||||
stream_handle
|
||||
.play_raw(
|
||||
SignalGenerator::new(cpal::SampleRate(48000), 1000.0, Function::Sine)
|
||||
.amplify(0.1)
|
||||
.take_duration(test_signal_duration),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
|
||||
println!("Playing 10,000 Hz tone");
|
||||
stream_handle
|
||||
.play_raw(
|
||||
SignalGenerator::new(cpal::SampleRate(48000), 10000.0, Function::Sine)
|
||||
.amplify(0.1)
|
||||
.take_duration(test_signal_duration),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
|
||||
println!("Playing 440 Hz Triangle Wave");
|
||||
stream_handle
|
||||
.play_raw(
|
||||
SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Triangle)
|
||||
.amplify(0.1)
|
||||
.take_duration(test_signal_duration),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
|
||||
println!("Playing 440 Hz Sawtooth Wave");
|
||||
stream_handle
|
||||
.play_raw(
|
||||
SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Sawtooth)
|
||||
.amplify(0.1)
|
||||
.take_duration(test_signal_duration),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
|
||||
println!("Playing 440 Hz Square Wave");
|
||||
stream_handle
|
||||
.play_raw(
|
||||
SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Square)
|
||||
.amplify(0.1)
|
||||
.take_duration(test_signal_duration),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
|
||||
println!("Playing 20-10000 Hz Sweep");
|
||||
stream_handle
|
||||
.play_raw(
|
||||
chirp(
|
||||
cpal::SampleRate(48000),
|
||||
20.0,
|
||||
10000.0,
|
||||
Duration::from_secs(1),
|
||||
)
|
||||
.amplify(0.1)
|
||||
.take_duration(test_signal_duration),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
}
|
|
@ -110,6 +110,11 @@
|
|||
//! The "tracing" feature replaces the print to stderr when a stream error happens with a
|
||||
//! recording an error event with tracing.
|
||||
//!
|
||||
//! ### Feature "Noise"
|
||||
//!
|
||||
//! The "noise" feature adds support for white and pink noise sources. This feature requires the
|
||||
//! "rand" crate.
|
||||
//!
|
||||
//! ## How it works under the hood
|
||||
//!
|
||||
//! Rodio spawns a background thread that is dedicated to reading from the sources and sending
|
||||
|
@ -120,7 +125,7 @@
|
|||
//! hardware. Therefore there is no restriction on the number of sounds that play simultaneously or
|
||||
//! the number of sinks that can be created (except for the fact that creating too many will slow
|
||||
//! down your program).
|
||||
//!
|
||||
|
||||
#![cfg_attr(test, deny(missing_docs))]
|
||||
pub use cpal::{
|
||||
self, traits::DeviceTrait, Device, Devices, DevicesError, InputDevices, OutputDevices,
|
||||
|
|
76
src/source/chirp.rs
Normal file
76
src/source/chirp.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
//! Chirp/sweep source.
|
||||
|
||||
use std::{f32::consts::TAU, time::Duration};
|
||||
|
||||
use crate::Source;
|
||||
|
||||
/// Convenience function to create a new `Chirp` source.
|
||||
#[inline]
|
||||
pub fn chirp(
|
||||
sample_rate: cpal::SampleRate,
|
||||
start_frequency: f32,
|
||||
end_frequency: f32,
|
||||
duration: Duration,
|
||||
) -> Chirp {
|
||||
Chirp::new(sample_rate, start_frequency, end_frequency, duration)
|
||||
}
|
||||
|
||||
/// Generate a sine wave with an instantaneous frequency that changes/sweeps linearly over time.
|
||||
/// At the end of the chirp, once the `end_frequency` is reached, the source is exhausted.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Chirp {
|
||||
start_frequency: f32,
|
||||
end_frequency: f32,
|
||||
sample_rate: cpal::SampleRate,
|
||||
total_samples: u64,
|
||||
elapsed_samples: u64,
|
||||
}
|
||||
|
||||
impl Chirp {
|
||||
fn new(
|
||||
sample_rate: cpal::SampleRate,
|
||||
start_frequency: f32,
|
||||
end_frequency: f32,
|
||||
duration: Duration,
|
||||
) -> Self {
|
||||
Self {
|
||||
sample_rate,
|
||||
start_frequency,
|
||||
end_frequency,
|
||||
total_samples: (duration.as_secs_f64() * (sample_rate.0 as f64)) as u64,
|
||||
elapsed_samples: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Chirp {
|
||||
type Item = f32;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let i = self.elapsed_samples;
|
||||
let ratio = self.elapsed_samples as f32 / self.total_samples as f32;
|
||||
self.elapsed_samples += 1;
|
||||
let freq = self.start_frequency * (1.0 - ratio) + self.end_frequency * ratio;
|
||||
let t = (i as f32 / self.sample_rate() as f32) * TAU * freq;
|
||||
Some(t.sin())
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for Chirp {
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
fn channels(&self) -> u16 {
|
||||
1
|
||||
}
|
||||
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.sample_rate.0
|
||||
}
|
||||
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
let secs: f64 = self.total_samples as f64 / self.sample_rate.0 as f64;
|
||||
Some(Duration::new(1, 0).mul_f64(secs))
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ pub use self::amplify::Amplify;
|
|||
pub use self::blt::BltFilter;
|
||||
pub use self::buffered::Buffered;
|
||||
pub use self::channel_volume::ChannelVolume;
|
||||
pub use self::chirp::{chirp, Chirp};
|
||||
pub use self::crossfade::Crossfade;
|
||||
pub use self::delay::Delay;
|
||||
pub use self::done::Done;
|
||||
|
@ -27,6 +28,7 @@ pub use self::periodic::PeriodicAccess;
|
|||
pub use self::position::TrackPosition;
|
||||
pub use self::repeat::Repeat;
|
||||
pub use self::samples_converter::SamplesConverter;
|
||||
pub use self::signal_generator::{Function, SignalGenerator};
|
||||
pub use self::sine::SineWave;
|
||||
pub use self::skip::SkipDuration;
|
||||
pub use self::skippable::Skippable;
|
||||
|
@ -42,6 +44,7 @@ mod amplify;
|
|||
mod blt;
|
||||
mod buffered;
|
||||
mod channel_volume;
|
||||
mod chirp;
|
||||
mod crossfade;
|
||||
mod delay;
|
||||
mod done;
|
||||
|
@ -58,6 +61,7 @@ mod periodic;
|
|||
mod position;
|
||||
mod repeat;
|
||||
mod samples_converter;
|
||||
mod signal_generator;
|
||||
mod sine;
|
||||
mod skip;
|
||||
mod skippable;
|
||||
|
@ -68,6 +72,11 @@ mod take;
|
|||
mod uniform;
|
||||
mod zero;
|
||||
|
||||
#[cfg(feature = "noise")]
|
||||
mod noise;
|
||||
#[cfg(feature = "noise")]
|
||||
pub use self::noise::{pink, white, PinkNoise, WhiteNoise};
|
||||
|
||||
/// A source of samples.
|
||||
///
|
||||
/// # A quick lesson about sounds
|
||||
|
|
158
src/source/noise.rs
Normal file
158
src/source/noise.rs
Normal file
|
@ -0,0 +1,158 @@
|
|||
//! Noise sources.
|
||||
//!
|
||||
//!
|
||||
|
||||
use crate::Source;
|
||||
|
||||
use super::SeekError;
|
||||
|
||||
use rand::{rngs::SmallRng, RngCore, SeedableRng};
|
||||
|
||||
/// Convenience function to create a new `WhiteNoise` noise source.
|
||||
#[inline]
|
||||
pub fn white(sample_rate: cpal::SampleRate) -> WhiteNoise {
|
||||
WhiteNoise::new(sample_rate)
|
||||
}
|
||||
|
||||
/// Convenience function to create a new `PinkNoise` noise source.
|
||||
#[inline]
|
||||
pub fn pink(sample_rate: cpal::SampleRate) -> PinkNoise {
|
||||
PinkNoise::new(sample_rate)
|
||||
}
|
||||
|
||||
/// Generates an infinite stream of random samples in [-1.0, 1.0]. This source generates random
|
||||
/// samples as provided by the `rand::rngs::SmallRng` randomness source.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WhiteNoise {
|
||||
sample_rate: cpal::SampleRate,
|
||||
rng: SmallRng,
|
||||
}
|
||||
|
||||
impl WhiteNoise {
|
||||
/// Create a new white noise generator, seeding the RNG with `seed`.
|
||||
pub fn new_with_seed(sample_rate: cpal::SampleRate, seed: u64) -> Self {
|
||||
Self {
|
||||
sample_rate,
|
||||
rng: SmallRng::seed_from_u64(seed),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new white noise generator, seeding the RNG with system entropy.
|
||||
pub fn new(sample_rate: cpal::SampleRate) -> Self {
|
||||
Self {
|
||||
sample_rate,
|
||||
rng: SmallRng::from_entropy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for WhiteNoise {
|
||||
type Item = f32;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let rand = self.rng.next_u32() as f32 / u32::MAX as f32;
|
||||
let scaled = rand * 2.0 - 1.0;
|
||||
Some(scaled)
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for WhiteNoise {
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.sample_rate.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<std::time::Duration> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: std::time::Duration) -> Result<(), SeekError> {
|
||||
// Does nothing, should do nothing
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates an infinite stream of pink noise samples in [-1.0, 1.0].
|
||||
///
|
||||
/// The output of the source is the result of taking the output of the `WhiteNoise` source and
|
||||
/// filtering it according to a weighted-sum of seven FIR filters after [Paul Kellett's
|
||||
/// method][pk_method] from *musicdsp.org*.
|
||||
///
|
||||
/// [pk_method]: https://www.musicdsp.org/en/latest/Filters/76-pink-noise-filter.html
|
||||
pub struct PinkNoise {
|
||||
white_noise: WhiteNoise,
|
||||
b: [f32; 7],
|
||||
}
|
||||
|
||||
impl PinkNoise {
|
||||
pub fn new(sample_rate: cpal::SampleRate) -> Self {
|
||||
Self {
|
||||
white_noise: WhiteNoise::new(sample_rate),
|
||||
b: [0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for PinkNoise {
|
||||
type Item = f32;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let white = self.white_noise.next().unwrap();
|
||||
self.b[0] = 0.99886 * self.b[0] + white * 0.0555179;
|
||||
self.b[1] = 0.99332 * self.b[1] + white * 0.0750759;
|
||||
self.b[2] = 0.969 * self.b[2] + white * 0.153852;
|
||||
self.b[3] = 0.8665 * self.b[3] + white * 0.3104856;
|
||||
self.b[4] = 0.550 * self.b[4] + white * 0.5329522;
|
||||
self.b[5] = -0.7616 * self.b[5] - white * 0.016898;
|
||||
|
||||
let pink = self.b[0]
|
||||
+ self.b[1]
|
||||
+ self.b[2]
|
||||
+ self.b[3]
|
||||
+ self.b[4]
|
||||
+ self.b[5]
|
||||
+ self.b[6]
|
||||
+ white * 0.5362;
|
||||
|
||||
self.b[6] = white * 0.115926;
|
||||
|
||||
Some(pink)
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for PinkNoise {
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
fn channels(&self) -> u16 {
|
||||
1
|
||||
}
|
||||
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.white_noise.sample_rate()
|
||||
}
|
||||
|
||||
fn total_duration(&self) -> Option<std::time::Duration> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: std::time::Duration) -> Result<(), SeekError> {
|
||||
// Does nothing, should do nothing
|
||||
Ok(())
|
||||
}
|
||||
}
|
185
src/source/signal_generator.rs
Normal file
185
src/source/signal_generator.rs
Normal file
|
@ -0,0 +1,185 @@
|
|||
//! Generator sources for various periodic test waveforms.
|
||||
//!
|
||||
//! This module provides several periodic, deterministic waveforms for testing other sources and
|
||||
//! for simple additive sound synthesis. Every source is monoaural and in the codomain [-1.0f32,
|
||||
//! 1.0f32].
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! use rodio::source::{SignalGenerator,Function};
|
||||
//!
|
||||
//! let tone = SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Sine);
|
||||
//! ```
|
||||
use std::f32::consts::TAU;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::SeekError;
|
||||
use crate::Source;
|
||||
|
||||
/// Waveform functions.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Function {
|
||||
/// A sinusoidal waveform.
|
||||
Sine,
|
||||
/// A triangle waveform.
|
||||
Triangle,
|
||||
/// A square wave, rising edge at t=0.
|
||||
Square,
|
||||
/// A rising sawtooth wave.
|
||||
Sawtooth,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
/// Create a single sample for the given waveform
|
||||
#[inline]
|
||||
fn render(&self, i: u64, period: f32) -> f32 {
|
||||
let cycle_pos: f32 = i as f32 / period;
|
||||
|
||||
match self {
|
||||
Self::Sine => (TAU * cycle_pos).sin(),
|
||||
Self::Triangle => 4.0f32 * (cycle_pos - (cycle_pos + 0.5f32).floor()).abs() - 1f32,
|
||||
Self::Square => {
|
||||
if cycle_pos % 1.0f32 < 0.5f32 {
|
||||
1.0f32
|
||||
} else {
|
||||
-1.0f32
|
||||
}
|
||||
}
|
||||
Self::Sawtooth => 2.0f32 * (cycle_pos - (cycle_pos + 0.5f32).floor()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An infinite source that produces one of a selection of test waveforms.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SignalGenerator {
|
||||
sample_rate: cpal::SampleRate,
|
||||
period: f32,
|
||||
function: Function,
|
||||
i: u64,
|
||||
}
|
||||
|
||||
impl SignalGenerator {
|
||||
/// Create a new `TestWaveform` object that generates an endless waveform
|
||||
/// `f`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if `frequency` is equal to zero.
|
||||
#[inline]
|
||||
pub fn new(sample_rate: cpal::SampleRate, frequency: f32, f: Function) -> SignalGenerator {
|
||||
assert!(frequency != 0.0, "frequency must be greater than zero");
|
||||
let period = sample_rate.0 as f32 / frequency;
|
||||
SignalGenerator {
|
||||
sample_rate,
|
||||
period,
|
||||
function: f,
|
||||
i: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SignalGenerator {
|
||||
type Item = f32;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<f32> {
|
||||
let val = Some(self.function.render(self.i, self.period));
|
||||
self.i += 1;
|
||||
val
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for SignalGenerator {
|
||||
#[inline]
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn channels(&self) -> u16 {
|
||||
1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self.sample_rate.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> {
|
||||
self.i = (self.sample_rate.0 as f32 * duration.as_secs_f32()) as u64;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::source::{Function, SignalGenerator};
|
||||
use approx::assert_abs_diff_eq;
|
||||
|
||||
#[test]
|
||||
fn square() {
|
||||
let mut wf = SignalGenerator::new(cpal::SampleRate(2000), 500.0f32, Function::Square);
|
||||
assert_eq!(wf.next(), Some(1.0f32));
|
||||
assert_eq!(wf.next(), Some(1.0f32));
|
||||
assert_eq!(wf.next(), Some(-1.0f32));
|
||||
assert_eq!(wf.next(), Some(-1.0f32));
|
||||
assert_eq!(wf.next(), Some(1.0f32));
|
||||
assert_eq!(wf.next(), Some(1.0f32));
|
||||
assert_eq!(wf.next(), Some(-1.0f32));
|
||||
assert_eq!(wf.next(), Some(-1.0f32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triangle() {
|
||||
let mut wf = SignalGenerator::new(cpal::SampleRate(8000), 1000.0f32, Function::Triangle);
|
||||
assert_eq!(wf.next(), Some(-1.0f32));
|
||||
assert_eq!(wf.next(), Some(-0.5f32));
|
||||
assert_eq!(wf.next(), Some(0.0f32));
|
||||
assert_eq!(wf.next(), Some(0.5f32));
|
||||
assert_eq!(wf.next(), Some(1.0f32));
|
||||
assert_eq!(wf.next(), Some(0.5f32));
|
||||
assert_eq!(wf.next(), Some(0.0f32));
|
||||
assert_eq!(wf.next(), Some(-0.5f32));
|
||||
assert_eq!(wf.next(), Some(-1.0f32));
|
||||
assert_eq!(wf.next(), Some(-0.5f32));
|
||||
assert_eq!(wf.next(), Some(0.0f32));
|
||||
assert_eq!(wf.next(), Some(0.5f32));
|
||||
assert_eq!(wf.next(), Some(1.0f32));
|
||||
assert_eq!(wf.next(), Some(0.5f32));
|
||||
assert_eq!(wf.next(), Some(0.0f32));
|
||||
assert_eq!(wf.next(), Some(-0.5f32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saw() {
|
||||
let mut wf = SignalGenerator::new(cpal::SampleRate(200), 50.0f32, Function::Sawtooth);
|
||||
assert_eq!(wf.next(), Some(0.0f32));
|
||||
assert_eq!(wf.next(), Some(0.5f32));
|
||||
assert_eq!(wf.next(), Some(-1.0f32));
|
||||
assert_eq!(wf.next(), Some(-0.5f32));
|
||||
assert_eq!(wf.next(), Some(0.0f32));
|
||||
assert_eq!(wf.next(), Some(0.5f32));
|
||||
assert_eq!(wf.next(), Some(-1.0f32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sine() {
|
||||
let mut wf = SignalGenerator::new(cpal::SampleRate(1000), 100f32, Function::Sine);
|
||||
|
||||
assert_abs_diff_eq!(wf.next().unwrap(), 0.0f32);
|
||||
assert_abs_diff_eq!(wf.next().unwrap(), 0.58778525f32);
|
||||
assert_abs_diff_eq!(wf.next().unwrap(), 0.95105652f32);
|
||||
assert_abs_diff_eq!(wf.next().unwrap(), 0.95105652f32);
|
||||
assert_abs_diff_eq!(wf.next().unwrap(), 0.58778525f32);
|
||||
assert_abs_diff_eq!(wf.next().unwrap(), 0.0f32);
|
||||
assert_abs_diff_eq!(wf.next().unwrap(), -0.58778554f32);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use std::f32::consts::PI;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::source::{Function, SignalGenerator};
|
||||
use crate::Source;
|
||||
|
||||
use super::SeekError;
|
||||
|
@ -10,17 +10,18 @@ use super::SeekError;
|
|||
/// Always has a rate of 48kHz and one channel.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SineWave {
|
||||
freq: f32,
|
||||
num_sample: usize,
|
||||
test_sine: SignalGenerator,
|
||||
}
|
||||
|
||||
impl SineWave {
|
||||
const SAMPLE_RATE: u32 = 48000;
|
||||
|
||||
/// The frequency of the sine.
|
||||
#[inline]
|
||||
pub fn new(freq: f32) -> SineWave {
|
||||
let sr = cpal::SampleRate(Self::SAMPLE_RATE);
|
||||
SineWave {
|
||||
freq,
|
||||
num_sample: 0,
|
||||
test_sine: SignalGenerator::new(sr, freq, Function::Sine),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,10 +31,7 @@ impl Iterator for SineWave {
|
|||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<f32> {
|
||||
self.num_sample = self.num_sample.wrapping_add(1);
|
||||
|
||||
let value = 2.0 * PI * self.freq * self.num_sample as f32 / 48000.0;
|
||||
Some(value.sin())
|
||||
self.test_sine.next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,7 +48,7 @@ impl Source for SineWave {
|
|||
|
||||
#[inline]
|
||||
fn sample_rate(&self) -> u32 {
|
||||
48000
|
||||
Self::SAMPLE_RATE
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -58,12 +56,11 @@ impl Source for SineWave {
|
|||
None
|
||||
}
|
||||
|
||||
/// `try_seek()` does nothing on the sine generator. If you need to
|
||||
/// generate a sine tone with a precise phase or sample offset, consider
|
||||
/// using `skip::skip_samples()`.
|
||||
#[inline]
|
||||
fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> {
|
||||
// This is a constant sound, normal seeking would not have any effect.
|
||||
// While changing the phase of the sine wave could change how it sounds in
|
||||
// combination with another sound (beating) such precision is not the intend
|
||||
// of seeking
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue