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

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 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
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
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
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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only thats not a decoder that lives behind a feature gate. So its not several right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dunno I expected all the features to eventually be in here, I'll change the language to make it singular.

//!
//! ### 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
76 changes: 76 additions & 0 deletions src/source/chirp.rs
Original file line number Diff line number Diff line change
@@ -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))
}
}
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