Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Synthesizer Waveforms #602

Merged
merged 33 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
fc22b37
Implemented synthesizer waveform source
iluvcapra Aug 8, 2024
128fa74
More synth work, but think I'm going to startover
iluvcapra Aug 8, 2024
8b08d12
Merge branch 'master' of https://github.com/RustAudio/rodio into synt…
iluvcapra Aug 9, 2024
eef2eda
Recent synth changes caught up with master
iluvcapra Aug 9, 2024
107454e
Implementation of synth waveforms, tests
iluvcapra Aug 9, 2024
7d4c9d7
Implemented `sine` source as a Synth
iluvcapra Aug 10, 2024
2783904
Dumb typo on sine sample rate
iluvcapra Aug 10, 2024
b6c8a7e
Implemented White and Pink noise generators
iluvcapra Aug 11, 2024
1eabb21
Some clippy and style changes
iluvcapra Aug 11, 2024
84a008d
Added noise_generator example
iluvcapra Aug 11, 2024
a015eb4
Using SmallRng for white noise generation
iluvcapra Aug 13, 2024
56d087a
Renamed Synth to Test
iluvcapra Aug 15, 2024
0907857
Documentation updates
iluvcapra Aug 15, 2024
87cd605
More documentation, WhiteNoise creation option.
iluvcapra Aug 15, 2024
59a5b12
Several code review fixes
iluvcapra Aug 19, 2024
a012ac1
Changes for code reviews
iluvcapra Aug 19, 2024
0c2c194
Made noise feature
iluvcapra Aug 19, 2024
e060bdd
Implemented chirp generator
iluvcapra Sep 14, 2024
f3e77f4
Removed `try_seek()` from chirp.
iluvcapra Sep 14, 2024
28b7fc1
Merge branch 'master' of https://github.com/RustAudio/rodio into synt…
iluvcapra Sep 15, 2024
c912f56
Removed white space for rustfmt
iluvcapra Sep 15, 2024
cde724a
Removed white space for rustfmt
iluvcapra Sep 15, 2024
56fcce7
Fixed a typo from @UnknownSuperficialNight
iluvcapra Sep 29, 2024
09c5f31
Merge branch 'master' of https://github.com/RustAudio/rodio into synt…
iluvcapra Sep 29, 2024
b373f52
Moved "noise" feature documentation
iluvcapra Sep 29, 2024
5fa0188
Added signal_generator.rs example
iluvcapra Sep 29, 2024
371bbda
Some cosmetic changes for rustfmt
iluvcapra Sep 29, 2024
9d20ea2
One more tweak for rustfmt
iluvcapra Sep 29, 2024
44cb217
Code review changes
iluvcapra Sep 29, 2024
8899e67
Renamed TestWaveform to SignalGenerator
iluvcapra Oct 1, 2024
a1fa144
Adding renamed signal_generator.rs
iluvcapra Oct 1, 2024
322bdd5
Rustfmt and a doc comment
iluvcapra Oct 1, 2024
1350246
rustfmt
iluvcapra Oct 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Support for *ALAC/AIFF*
- New sources:
- Support for *ALAC/AIFF*
- 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 linearly period over a given
iluvcapra marked this conversation as resolved.
Show resolved Hide resolved
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"
iluvcapra marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ minimp3_fixed = { version = "0.5.4", optional = true}
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 }

[features]
default = ["flac", "vorbis", "wav", "mp3"]
Expand All @@ -27,6 +28,7 @@ vorbis = ["lewton"]
wav = ["hound"]
mp3 = ["symphonia-mp3"]
minimp3 = ["dep:minimp3_fixed"]
noise = ["rand"]
iluvcapra marked this conversation as resolved.
Show resolved Hide resolved
wasm-bindgen = ["cpal/wasm-bindgen"]
cpal-shared-stdcxx = ["cpal/oboe-shared-stdcxx"]
symphonia-aac = ["symphonia/aac"]
Expand All @@ -48,3 +50,7 @@ approx = "0.5.1"
[[example]]
name = "music_m4a"
required-features = ["symphonia-isomp4", "symphonia-aac"]

[[example]]
name = "noise_generator"
required-features = ["noise"]
41 changes: 41 additions & 0 deletions examples/noise_generator.rs
Original file line number Diff line number Diff line change
@@ -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);
}

iluvcapra marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(not(feature = "noise"))]
fn main() {
println!("rodio has not been compiled with noise sources, use `--features noise` to enable this feature.");
println!("Exiting...");
}
9 changes: 9 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@
//! the number of sinks that can be created (except for the fact that creating too many will slow
//! down your program).
//!
//!
//! ## Features
//!
//! Rodio provides several optional features that are guarded with feature gates.
iluvcapra marked this conversation as resolved.
Show resolved Hide resolved
//!
//! ### Feature "Noise"
//!
//! The "noise" feature adds support for white and pink noise sources. This feature requires the
//! "rand" crate.
#![cfg_attr(test, deny(missing_docs))]
pub use cpal::{
self, traits::DeviceTrait, Device, Devices, DevicesError, InputDevices, OutputDevices,
Expand Down
80 changes: 80 additions & 0 deletions src/source/chirp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//! 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))
}

fn try_seek(&mut self, pos: Duration) -> Result<(), super::SeekError> {
todo!()
}
}
9 changes: 9 additions & 0 deletions src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,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;
Expand All @@ -33,13 +34,15 @@ pub use self::spatial::Spatial;
pub use self::speed::Speed;
pub use self::stoppable::Stoppable;
pub use self::take::TakeDuration;
pub use self::test_waveform::{Function, TestWaveform};
pub use self::uniform::UniformSourceIterator;
pub use self::zero::Zero;

mod amplify;
mod blt;
mod buffered;
mod channel_volume;
mod chirp;
mod crossfade;
mod delay;
mod done;
Expand All @@ -63,9 +66,15 @@ mod spatial;
mod speed;
mod stoppable;
mod take;
mod test_waveform;
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
Expand Down
158 changes: 158 additions & 0 deletions src/source/noise.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
}
Loading
Loading