Skip to content

Commit

Permalink
Merge pull request #602 from iluvcapra/synth-waveforms
Browse files Browse the repository at this point in the history
Synthesizer Waveforms
  • Loading branch information
dvdsk authored Oct 1, 2024
2 parents 15cca73 + 1350246 commit 1d95a5c
Show file tree
Hide file tree
Showing 10 changed files with 586 additions and 17 deletions.
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 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
Expand Down
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

[features]
Expand All @@ -30,6 +31,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"]
Expand Down Expand Up @@ -61,3 +63,7 @@ harness = false
[[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);
}

#[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 changes: 83 additions & 0 deletions examples/signal_generator.rs
Original file line number Diff line number Diff line change
@@ -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);
}
7 changes: 6 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
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 @@ -26,6 +27,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;
Expand All @@ -40,6 +42,7 @@ mod amplify;
mod blt;
mod buffered;
mod channel_volume;
mod chirp;
mod crossfade;
mod delay;
mod done;
Expand All @@ -56,6 +59,7 @@ mod periodic;
mod position;
mod repeat;
mod samples_converter;
mod signal_generator;
mod sine;
mod skip;
mod skippable;
Expand All @@ -66,6 +70,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
Expand Down
Loading

0 comments on commit 1d95a5c

Please sign in to comment.