From fc22b37f1b3641dc161bf91436ac51b94330ddff Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Wed, 7 Aug 2024 20:30:57 -0700 Subject: [PATCH 01/30] Implemented synthesizer waveform source --- src/source/mod.rs | 2 + src/source/synth_waveforms.rs | 156 ++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 src/source/synth_waveforms.rs diff --git a/src/source/mod.rs b/src/source/mod.rs index 528e6fe8..f44df89f 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -30,6 +30,7 @@ pub use self::skippable::Skippable; pub use self::spatial::Spatial; pub use self::speed::Speed; pub use self::stoppable::Stoppable; +pub use self::synth_waveforms::SynthWaveform; pub use self::take::TakeDuration; pub use self::uniform::UniformSourceIterator; pub use self::zero::Zero; @@ -58,6 +59,7 @@ mod skippable; mod spatial; mod speed; mod stoppable; +mod synth_waveforms; mod take; mod uniform; mod zero; diff --git a/src/source/synth_waveforms.rs b/src/source/synth_waveforms.rs new file mode 100644 index 00000000..b335b91a --- /dev/null +++ b/src/source/synth_waveforms.rs @@ -0,0 +1,156 @@ +use std::f32::consts::TAU; +use std::time::Duration; + +use super::SeekError; +use crate::{buffer::SamplesBuffer, source::Repeat, Source}; + +/// Syntheizer waveform functions. All of the synth waveforms are in the +/// codomain [-1.0, 1.0]. +#[derive(Clone, Debug)] +pub enum SynthWaveformFunction { + Sine, + Triangle, + Square, + Sawtooth, +} + +impl SynthWaveformFunction { + /// Create a `SamplesBuffer` containing one period of `self` with the given + /// sample rate and frequency. + pub fn create_buffer(&self, sample_rate: u32, frequency: u32) -> SamplesBuffer { + let p: usize = (sample_rate / frequency) as usize; + let mut samples_vec = vec![0.0f32; p]; + + fn _pwm_impl(duty: f32, t: f32) -> f32 { + if t < duty.abs() { + 1f32 + } else { + -1f32 + } + } + + for i in 0..p { + let i_div_p: f32 = i as f32 / p as f32; + samples_vec[i] = match self { + Self::Sine => (TAU * i_div_p).sin(), + Self::Triangle => 4.0f32 * (i_div_p - (i_div_p + 0.5f32).floor()).abs() - 1f32, + Self::Square => { + if i_div_p < 0.5f32 { + 1.0f32 + } else { + -1.0f32 + } + } + Self::Sawtooth => 2.0f32 * (i_div_p - (i_div_p + 0.5f32).floor()), + } + } + + SamplesBuffer::new(1, sample_rate, samples_vec) + } +} + +/// An infinite source that produces one of a selection of synthesizer +/// waveforms from a buffered source. +#[derive(Clone)] +pub struct SynthWaveform { + input: Repeat>, +} + +impl SynthWaveform { + /// Create a new `SynthWaveform` object that generates an endless waveform + /// `f`. + #[inline] + pub fn new(sample_rate: u32, frequency: u32, f: SynthWaveformFunction) -> SynthWaveform { + let buffer = f.create_buffer(sample_rate, frequency); + SynthWaveform { + input: buffer.repeat_infinite(), + } + } +} + +impl Iterator for SynthWaveform { + type Item = f32; + + #[inline] + fn next(&mut self) -> Option { + self.input.next() + } +} + +impl Source for SynthWaveform { + #[inline] + fn current_frame_len(&self) -> Option { + self.input.current_frame_len() + } + + #[inline] + fn channels(&self) -> u16 { + 1 + } + + #[inline] + fn sample_rate(&self) -> u32 { + self.input.sample_rate() + } + + #[inline] + fn total_duration(&self) -> Option { + None + } + + #[inline] + fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { + self.input.try_seek(duration) + } +} + +#[cfg(test)] +mod tests { + use crate::source::synth_waveforms::*; + + #[test] + fn square() { + let mut wf = SynthWaveform::new(1000, 250, SynthWaveformFunction::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 = SynthWaveform::new(8000, 1000, SynthWaveformFunction::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 = SynthWaveform::new(200, 50, SynthWaveformFunction::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)); + } +} From 128fa745545595cb29019aad077e00c8b6e17cb0 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Thu, 8 Aug 2024 16:39:35 -0700 Subject: [PATCH 02/30] More synth work, but think I'm going to startover --- src/source/mod.rs | 2 +- src/source/synth_waveforms.rs | 89 ++++++++++++++++++++++------------- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/src/source/mod.rs b/src/source/mod.rs index f44df89f..c173ea88 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -30,7 +30,7 @@ pub use self::skippable::Skippable; pub use self::spatial::Spatial; pub use self::speed::Speed; pub use self::stoppable::Stoppable; -pub use self::synth_waveforms::SynthWaveform; +pub use self::synth_waveforms::{BufferedSynthWaveform, SynthWaveformFunction}; pub use self::take::TakeDuration; pub use self::uniform::UniformSourceIterator; pub use self::zero::Zero; diff --git a/src/source/synth_waveforms.rs b/src/source/synth_waveforms.rs index b335b91a..ad910c03 100644 --- a/src/source/synth_waveforms.rs +++ b/src/source/synth_waveforms.rs @@ -4,6 +4,16 @@ use std::time::Duration; use super::SeekError; use crate::{buffer::SamplesBuffer, source::Repeat, Source}; + +/// Express a frequency as a rational number. +/// .0 Cycles per time quanta +/// .1 Time quanta per second +/// +/// Examples: +/// 1000,1 1000 Hz +/// 12345,100 123.45 Hz +pub struct RationalFrequency(u32, u32); + /// Syntheizer waveform functions. All of the synth waveforms are in the /// codomain [-1.0, 1.0]. #[derive(Clone, Debug)] @@ -15,60 +25,67 @@ pub enum SynthWaveformFunction { } impl SynthWaveformFunction { - /// Create a `SamplesBuffer` containing one period of `self` with the given - /// sample rate and frequency. - pub fn create_buffer(&self, sample_rate: u32, frequency: u32) -> SamplesBuffer { - let p: usize = (sample_rate / frequency) as usize; - let mut samples_vec = vec![0.0f32; p]; - - fn _pwm_impl(duty: f32, t: f32) -> f32 { - if t < duty.abs() { - 1f32 - } else { - -1f32 - } - } - - for i in 0..p { - let i_div_p: f32 = i as f32 / p as f32; - samples_vec[i] = match self { - Self::Sine => (TAU * i_div_p).sin(), - Self::Triangle => 4.0f32 * (i_div_p - (i_div_p + 0.5f32).floor()).abs() - 1f32, - Self::Square => { + /// Create a single sample for the given waveform + #[inline] + pub fn render(&self, sample: u32, period: u32) -> f32 { + let i_div_p: f32 = sample as f32 / period as f32; + + match self { + Self::Sine => (TAU * i_div_p).sin(), + Self::Triangle => 04.0f32 * (i_div_p - (i_div_p + 0.5f32).floor()).abs() - 1f32, + Self::Square => { if i_div_p < 0.5f32 { 1.0f32 } else { -1.0f32 } - } - Self::Sawtooth => 2.0f32 * (i_div_p - (i_div_p + 0.5f32).floor()), - } + }, + Self::Sawtooth => 2.0f32 * (i_div_p - (i_div_p + 0.5f32).floor()), + } + } + + /// Create a `SamplesBuffer` containing one period of `self` with the given + /// sample rate and frequency. + pub fn create_buffer( + &self, + sample_rate: cpal::SampleRate, + frequency: u32, + ) -> SamplesBuffer { + let period: usize = (sample_rate.0 / frequency) as usize; + let mut samples_vec = vec![0.0f32; period]; + + for i in 0..period { + samples_vec[i] = self.render(i as u32, period as u32); } - SamplesBuffer::new(1, sample_rate, samples_vec) + SamplesBuffer::new(1, sample_rate.0, samples_vec) } } /// An infinite source that produces one of a selection of synthesizer /// waveforms from a buffered source. #[derive(Clone)] -pub struct SynthWaveform { +pub struct BufferedSynthWaveform { input: Repeat>, } -impl SynthWaveform { +impl BufferedSynthWaveform { /// Create a new `SynthWaveform` object that generates an endless waveform /// `f`. #[inline] - pub fn new(sample_rate: u32, frequency: u32, f: SynthWaveformFunction) -> SynthWaveform { + pub fn new( + sample_rate: cpal::SampleRate, + frequency: u32, + f: SynthWaveformFunction, + ) -> BufferedSynthWaveform { let buffer = f.create_buffer(sample_rate, frequency); - SynthWaveform { + BufferedSynthWaveform { input: buffer.repeat_infinite(), } } } -impl Iterator for SynthWaveform { +impl Iterator for BufferedSynthWaveform { type Item = f32; #[inline] @@ -77,7 +94,7 @@ impl Iterator for SynthWaveform { } } -impl Source for SynthWaveform { +impl Source for BufferedSynthWaveform { #[inline] fn current_frame_len(&self) -> Option { self.input.current_frame_len() @@ -110,7 +127,8 @@ mod tests { #[test] fn square() { - let mut wf = SynthWaveform::new(1000, 250, SynthWaveformFunction::Square); + let mut wf = + BufferedSynthWaveform::new(cpal::SampleRate(1000), 250, SynthWaveformFunction::Square); assert_eq!(wf.next(), Some(1.0f32)); assert_eq!(wf.next(), Some(1.0f32)); assert_eq!(wf.next(), Some(-1.0f32)); @@ -123,7 +141,11 @@ mod tests { #[test] fn triangle() { - let mut wf = SynthWaveform::new(8000, 1000, SynthWaveformFunction::Triangle); + let mut wf = BufferedSynthWaveform::new( + cpal::SampleRate(8000), + 1000, + SynthWaveformFunction::Triangle, + ); assert_eq!(wf.next(), Some(-1.0f32)); assert_eq!(wf.next(), Some(-0.5f32)); assert_eq!(wf.next(), Some(0.0f32)); @@ -144,7 +166,8 @@ mod tests { #[test] fn saw() { - let mut wf = SynthWaveform::new(200, 50, SynthWaveformFunction::Sawtooth); + let mut wf = + BufferedSynthWaveform::new(cpal::SampleRate(200), 50, SynthWaveformFunction::Sawtooth); assert_eq!(wf.next(), Some(0.0f32)); assert_eq!(wf.next(), Some(0.5f32)); assert_eq!(wf.next(), Some(-1.0f32)); From eef2edaa3deb87e3af932825aa98b69a3c2983ec Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 9 Aug 2024 12:40:46 -0700 Subject: [PATCH 03/30] Recent synth changes caught up with master ...and upstream. --- src/source/mod.rs | 4 +- src/source/{synth_waveforms.rs => synth.rs} | 113 ++++++++++---------- 2 files changed, 56 insertions(+), 61 deletions(-) rename src/source/{synth_waveforms.rs => synth.rs} (60%) diff --git a/src/source/mod.rs b/src/source/mod.rs index 2da10677..84ce2f35 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -32,7 +32,7 @@ pub use self::skippable::Skippable; pub use self::spatial::Spatial; pub use self::speed::Speed; pub use self::stoppable::Stoppable; -pub use self::synth_waveforms::{BufferedSynthWaveform, SynthWaveformFunction}; +pub use self::synth::{SynthWaveform, SynthWaveformFunction}; pub use self::take::TakeDuration; pub use self::uniform::UniformSourceIterator; pub use self::zero::Zero; @@ -63,7 +63,7 @@ mod skippable; mod spatial; mod speed; mod stoppable; -mod synth_waveforms; +mod synth; mod take; mod uniform; mod zero; diff --git a/src/source/synth_waveforms.rs b/src/source/synth.rs similarity index 60% rename from src/source/synth_waveforms.rs rename to src/source/synth.rs index ad910c03..1531b6d1 100644 --- a/src/source/synth_waveforms.rs +++ b/src/source/synth.rs @@ -2,17 +2,7 @@ use std::f32::consts::TAU; use std::time::Duration; use super::SeekError; -use crate::{buffer::SamplesBuffer, source::Repeat, Source}; - - -/// Express a frequency as a rational number. -/// .0 Cycles per time quanta -/// .1 Time quanta per second -/// -/// Examples: -/// 1000,1 1000 Hz -/// 12345,100 123.45 Hz -pub struct RationalFrequency(u32, u32); +use crate::Source; /// Syntheizer waveform functions. All of the synth waveforms are in the /// codomain [-1.0, 1.0]. @@ -27,77 +17,68 @@ pub enum SynthWaveformFunction { impl SynthWaveformFunction { /// Create a single sample for the given waveform #[inline] - pub fn render(&self, sample: u32, period: u32) -> f32 { - let i_div_p: f32 = sample as f32 / period as f32; - + pub fn render(&self, i: u64, period: f32) -> f32 { + let i_div_p: f32 = i as f32 / period; + match self { Self::Sine => (TAU * i_div_p).sin(), Self::Triangle => 04.0f32 * (i_div_p - (i_div_p + 0.5f32).floor()).abs() - 1f32, Self::Square => { - if i_div_p < 0.5f32 { - 1.0f32 - } else { - -1.0f32 - } - }, + if i_div_p % 1.0f32 < 0.5f32 { + 1.0f32 + } else { + -1.0f32 + } + } Self::Sawtooth => 2.0f32 * (i_div_p - (i_div_p + 0.5f32).floor()), } } - - /// Create a `SamplesBuffer` containing one period of `self` with the given - /// sample rate and frequency. - pub fn create_buffer( - &self, - sample_rate: cpal::SampleRate, - frequency: u32, - ) -> SamplesBuffer { - let period: usize = (sample_rate.0 / frequency) as usize; - let mut samples_vec = vec![0.0f32; period]; - - for i in 0..period { - samples_vec[i] = self.render(i as u32, period as u32); - } - - SamplesBuffer::new(1, sample_rate.0, samples_vec) - } } /// An infinite source that produces one of a selection of synthesizer -/// waveforms from a buffered source. +/// waveforms. #[derive(Clone)] -pub struct BufferedSynthWaveform { - input: Repeat>, +pub struct SynthWaveform { + sample_rate: cpal::SampleRate, + period: f32, + f: SynthWaveformFunction, + i: u64, } -impl BufferedSynthWaveform { +impl SynthWaveform { /// Create a new `SynthWaveform` object that generates an endless waveform /// `f`. #[inline] pub fn new( sample_rate: cpal::SampleRate, - frequency: u32, + frequency: f32, f: SynthWaveformFunction, - ) -> BufferedSynthWaveform { - let buffer = f.create_buffer(sample_rate, frequency); - BufferedSynthWaveform { - input: buffer.repeat_infinite(), + ) -> SynthWaveform { + let period = sample_rate.0 as f32 / frequency; + SynthWaveform { + sample_rate, + period, + f, + i: 0, } } } -impl Iterator for BufferedSynthWaveform { +impl Iterator for SynthWaveform { type Item = f32; #[inline] fn next(&mut self) -> Option { - self.input.next() + let this_i = self.i; + self.i += 1; + Some(self.f.render(this_i, self.period)) } } -impl Source for BufferedSynthWaveform { +impl Source for SynthWaveform { #[inline] fn current_frame_len(&self) -> Option { - self.input.current_frame_len() + None } #[inline] @@ -107,7 +88,7 @@ impl Source for BufferedSynthWaveform { #[inline] fn sample_rate(&self) -> u32 { - self.input.sample_rate() + self.sample_rate.0 } #[inline] @@ -117,18 +98,22 @@ impl Source for BufferedSynthWaveform { #[inline] fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { - self.input.try_seek(duration) + self.i = (self.sample_rate.0 as f32 * duration.as_secs_f32()) as u64; + Ok(()) } } #[cfg(test)] mod tests { - use crate::source::synth_waveforms::*; + use crate::source::synth::*; #[test] fn square() { - let mut wf = - BufferedSynthWaveform::new(cpal::SampleRate(1000), 250, SynthWaveformFunction::Square); + let mut wf = SynthWaveform::new( + cpal::SampleRate(2000), + 500.0f32, + SynthWaveformFunction::Square, + ); assert_eq!(wf.next(), Some(1.0f32)); assert_eq!(wf.next(), Some(1.0f32)); assert_eq!(wf.next(), Some(-1.0f32)); @@ -141,9 +126,9 @@ mod tests { #[test] fn triangle() { - let mut wf = BufferedSynthWaveform::new( + let mut wf = SynthWaveform::new( cpal::SampleRate(8000), - 1000, + 1000.0f32, SynthWaveformFunction::Triangle, ); assert_eq!(wf.next(), Some(-1.0f32)); @@ -166,8 +151,11 @@ mod tests { #[test] fn saw() { - let mut wf = - BufferedSynthWaveform::new(cpal::SampleRate(200), 50, SynthWaveformFunction::Sawtooth); + let mut wf = SynthWaveform::new( + cpal::SampleRate(200), + 50.0f32, + SynthWaveformFunction::Sawtooth, + ); assert_eq!(wf.next(), Some(0.0f32)); assert_eq!(wf.next(), Some(0.5f32)); assert_eq!(wf.next(), Some(-1.0f32)); @@ -176,4 +164,11 @@ mod tests { assert_eq!(wf.next(), Some(0.5f32)); assert_eq!(wf.next(), Some(-1.0f32)); } + + // #[test] + // fn sine() { + // let mut wf = + // SynthWaveform::new(cpal::SampleRate(1000), 100f32, SynthWaveformFunction::Sine); + // + // } } From 107454efded065091990fe32ea921a155ce143bd Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 9 Aug 2024 12:54:41 -0700 Subject: [PATCH 04/30] Implementation of synth waveforms, tests --- src/source/synth.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/source/synth.rs b/src/source/synth.rs index 1531b6d1..499827c6 100644 --- a/src/source/synth.rs +++ b/src/source/synth.rs @@ -106,6 +106,7 @@ impl Source for SynthWaveform { #[cfg(test)] mod tests { use crate::source::synth::*; + use approx::assert_abs_diff_eq; #[test] fn square() { @@ -165,10 +166,17 @@ mod tests { assert_eq!(wf.next(), Some(-1.0f32)); } - // #[test] - // fn sine() { - // let mut wf = - // SynthWaveform::new(cpal::SampleRate(1000), 100f32, SynthWaveformFunction::Sine); - // - // } + #[test] + fn sine() { + let mut wf = + SynthWaveform::new(cpal::SampleRate(1000), 100f32, SynthWaveformFunction::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); + } } From 7d4c9d78fa84ce4298e541fe18542c53caf9bae3 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 9 Aug 2024 18:46:44 -0700 Subject: [PATCH 05/30] Implemented `sine` source as a Synth --- src/source/sine.rs | 26 ++++++++++---------------- src/source/synth.rs | 2 +- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/source/sine.rs b/src/source/sine.rs index 44aa48ee..f263dccd 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -1,26 +1,27 @@ -use std::f32::consts::PI; use std::time::Duration; +use crate::source::{SynthWaveform, SynthWaveformFunction}; use crate::Source; use super::SeekError; +const SAMPLE_RATE: u32 = 46000; + /// An infinite source that produces a sine. /// /// Always has a rate of 48kHz and one channel. #[derive(Clone, Debug)] pub struct SineWave { - freq: f32, - num_sample: usize, + synth: SynthWaveform, } impl SineWave { /// The frequency of the sine. #[inline] pub fn new(freq: f32) -> SineWave { + let sr = cpal::SampleRate(SAMPLE_RATE); SineWave { - freq, - num_sample: 0, + synth: SynthWaveform::new(sr, freq, SynthWaveformFunction::Sine), } } } @@ -30,10 +31,7 @@ impl Iterator for SineWave { #[inline] fn next(&mut self) -> Option { - 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.synth.next() } } @@ -50,7 +48,7 @@ impl Source for SineWave { #[inline] fn sample_rate(&self) -> u32 { - 48000 + SAMPLE_RATE } #[inline] @@ -59,11 +57,7 @@ impl Source for SineWave { } #[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(()) + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + self.synth.try_seek(pos) } } diff --git a/src/source/synth.rs b/src/source/synth.rs index 499827c6..e87dfe9e 100644 --- a/src/source/synth.rs +++ b/src/source/synth.rs @@ -37,7 +37,7 @@ impl SynthWaveformFunction { /// An infinite source that produces one of a selection of synthesizer /// waveforms. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct SynthWaveform { sample_rate: cpal::SampleRate, period: f32, From 278390471538cdd26b3428bd303a77b17dcab68e Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 9 Aug 2024 20:17:42 -0700 Subject: [PATCH 06/30] Dumb typo on sine sample rate --- src/source/sine.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source/sine.rs b/src/source/sine.rs index f263dccd..6e78ec69 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -5,7 +5,7 @@ use crate::Source; use super::SeekError; -const SAMPLE_RATE: u32 = 46000; +const SAMPLE_RATE: u32 = 48000; /// An infinite source that produces a sine. /// From b6c8a7ee48b5f87ae0653e217992599a5c68a416 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 11 Aug 2024 14:13:42 -0700 Subject: [PATCH 07/30] Implemented White and Pink noise generators Need to write an example app to test them. --- Cargo.toml | 1 + src/source/mod.rs | 1 + src/source/noise.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 src/source/noise.rs diff --git a/Cargo.toml b/Cargo.toml index aa75167a..2c5ed858 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 = "0.8.5" [features] default = ["flac", "vorbis", "wav", "mp3"] diff --git a/src/source/mod.rs b/src/source/mod.rs index 84ce2f35..d86c8334 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -52,6 +52,7 @@ mod from_factory; mod from_iter; mod linear_ramp; mod mix; +mod noise; mod pausable; mod periodic; mod position; diff --git a/src/source/noise.rs b/src/source/noise.rs new file mode 100644 index 00000000..cbbbbd12 --- /dev/null +++ b/src/source/noise.rs @@ -0,0 +1,106 @@ +use crate::Source; + +use rand::prelude::*; + +/// Generates an infinite stream of random samples in [=1.0, 1.0] +#[derive(Clone, Debug)] +pub struct WhiteNoise { + sample_rate: cpal::SampleRate, + rng: rand::rngs::ThreadRng, +} + +impl WhiteNoise { + /// Create a new white noise generator. + pub fn new(sample_rate: cpal::SampleRate) -> Self { + Self { + sample_rate, + rng: rand::thread_rng(), + } + } +} + +impl Iterator for WhiteNoise { + type Item = f32; + + #[inline] + fn next(&mut self) -> Option { + let ru32: i64 = self.rng.next_u32().into(); + let scaled: i64 = 2 * ru32 - (u32::MAX / 2) as i64; + let sample: f32 = scaled as f32 / u32::MAX as f32; + Some(sample) + } +} + +impl Source for WhiteNoise { + fn current_frame_len(&self) -> Option { + None + } + + fn channels(&self) -> u16 { + 1 + } + + fn sample_rate(&self) -> u32 { + self.sample_rate.0 + } + + fn total_duration(&self) -> Option { + None + } +} + +// https://www.musicdsp.org/en/latest/Filters/76-pink-noise-filter.html +// +/// Generate an infinite stream of pink noise samples in [-1.0, 1.0]. +struct PinkNoise { + noise: WhiteNoise, + b: [f32; 7], +} + +impl PinkNoise { + pub fn new(sample_rate: cpal::SampleRate) -> Self { + Self { + 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 { + let white = self.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.96900 * self.b[2] + white * 0.1538520; + self.b[3] = 0.86650 * self.b[3] + white * 0.3104856; + self.b[4] = 0.55000 * self.b[4] + white * 0.5329522; + self.b[5] = -0.7616 * self.b[5] - white * 0.0168980; + + 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 { + None + } + + fn channels(&self) -> u16 { + 1 + } + + fn sample_rate(&self) -> u32 { + self.noise.sample_rate() + } + + fn total_duration(&self) -> Option { + None + } +} From 1eabb212ac21b7ac7d303eff09c3bb1463c9537f Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 11 Aug 2024 14:29:24 -0700 Subject: [PATCH 08/30] Some clippy and style changes --- src/source/mod.rs | 1 + src/source/noise.rs | 38 ++++++++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/source/mod.rs b/src/source/mod.rs index d86c8334..059c84ed 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -21,6 +21,7 @@ pub use self::from_factory::{from_factory, FromFactoryIter}; pub use self::from_iter::{from_iter, FromIter}; pub use self::linear_ramp::LinearGainRamp; pub use self::mix::Mix; +pub use self::noise::{pink, white, PinkNoise, WhiteNoise}; pub use self::pausable::Pausable; pub use self::periodic::PeriodicAccess; pub use self::position::TrackPosition; diff --git a/src/source/noise.rs b/src/source/noise.rs index cbbbbd12..f8b55aa9 100644 --- a/src/source/noise.rs +++ b/src/source/noise.rs @@ -2,6 +2,18 @@ use crate::Source; use rand::prelude::*; +/// Create a new white noise source. +#[inline] +pub fn white(sample_rate: cpal::SampleRate) -> WhiteNoise { + WhiteNoise::new(sample_rate) +} + +/// Create a new pink 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] #[derive(Clone, Debug)] pub struct WhiteNoise { @@ -32,18 +44,22 @@ impl Iterator for WhiteNoise { } impl Source for WhiteNoise { + #[inline] fn current_frame_len(&self) -> Option { None } + #[inline] fn channels(&self) -> u16 { 1 } + #[inline] fn sample_rate(&self) -> u32 { self.sample_rate.0 } + #[inline] fn total_duration(&self) -> Option { None } @@ -52,7 +68,7 @@ impl Source for WhiteNoise { // https://www.musicdsp.org/en/latest/Filters/76-pink-noise-filter.html // /// Generate an infinite stream of pink noise samples in [-1.0, 1.0]. -struct PinkNoise { +pub struct PinkNoise { noise: WhiteNoise, b: [f32; 7], } @@ -73,13 +89,19 @@ impl Iterator for PinkNoise { let white = self.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.96900 * self.b[2] + white * 0.1538520; - self.b[3] = 0.86650 * self.b[3] + white * 0.3104856; - self.b[4] = 0.55000 * self.b[4] + white * 0.5329522; - self.b[5] = -0.7616 * self.b[5] - white * 0.0168980; - - 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[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; From 84a008dd2743531afc9c0ba1fbd255ff38ec0da1 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 11 Aug 2024 15:28:15 -0700 Subject: [PATCH 09/30] Added noise_generator example --- examples/noise_generator.rs | 33 +++++++++++++++++++++++++++++++++ src/source/noise.rs | 27 +++++++++++++++++---------- 2 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 examples/noise_generator.rs diff --git a/examples/noise_generator.rs b/examples/noise_generator.rs new file mode 100644 index 00000000..49f1f88e --- /dev/null +++ b/examples/noise_generator.rs @@ -0,0 +1,33 @@ +use std::thread; +use std::time::Duration; + +use rodio::source::{pink, white, Source}; + +fn main() { + 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); +} diff --git a/src/source/noise.rs b/src/source/noise.rs index f8b55aa9..25f42266 100644 --- a/src/source/noise.rs +++ b/src/source/noise.rs @@ -1,6 +1,6 @@ use crate::Source; -use rand::prelude::*; +use super::SeekError; /// Create a new white noise source. #[inline] @@ -18,16 +18,12 @@ pub fn pink(sample_rate: cpal::SampleRate) -> PinkNoise { #[derive(Clone, Debug)] pub struct WhiteNoise { sample_rate: cpal::SampleRate, - rng: rand::rngs::ThreadRng, } impl WhiteNoise { /// Create a new white noise generator. pub fn new(sample_rate: cpal::SampleRate) -> Self { - Self { - sample_rate, - rng: rand::thread_rng(), - } + Self { sample_rate } } } @@ -36,10 +32,9 @@ impl Iterator for WhiteNoise { #[inline] fn next(&mut self) -> Option { - let ru32: i64 = self.rng.next_u32().into(); - let scaled: i64 = 2 * ru32 - (u32::MAX / 2) as i64; - let sample: f32 = scaled as f32 / u32::MAX as f32; - Some(sample) + let randf = rand::random::(); + let scaled = randf * 2.0 - 1.0; + Some(scaled) } } @@ -63,6 +58,12 @@ impl Source for WhiteNoise { fn total_duration(&self) -> Option { None } + + #[inline] + fn try_seek(&mut self, _: std::time::Duration) -> Result<(), SeekError> { + // Does nothing, should do nothing + Ok(()) + } } // https://www.musicdsp.org/en/latest/Filters/76-pink-noise-filter.html @@ -125,4 +126,10 @@ impl Source for PinkNoise { fn total_duration(&self) -> Option { None } + + #[inline] + fn try_seek(&mut self, _: std::time::Duration) -> Result<(), SeekError> { + // Does nothing, should do nothing + Ok(()) + } } From a015eb49a20bf851fad0150f72709282ecd6933c Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 12 Aug 2024 20:28:25 -0700 Subject: [PATCH 10/30] Using SmallRng for white noise generation This resolves a PR code review note and should make the source run much faster. --- Cargo.toml | 2 +- src/source/noise.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2c5ed858..a75b74c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +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 = "0.8.5" +rand = {version = "0.8.5", features = ["small_rng"]} [features] default = ["flac", "vorbis", "wav", "mp3"] diff --git a/src/source/noise.rs b/src/source/noise.rs index 25f42266..dfee9172 100644 --- a/src/source/noise.rs +++ b/src/source/noise.rs @@ -2,6 +2,9 @@ use crate::Source; use super::SeekError; +use rand::rngs::SmallRng; +use rand::{RngCore, SeedableRng}; + /// Create a new white noise source. #[inline] pub fn white(sample_rate: cpal::SampleRate) -> WhiteNoise { @@ -18,12 +21,16 @@ pub fn pink(sample_rate: cpal::SampleRate) -> PinkNoise { #[derive(Clone, Debug)] pub struct WhiteNoise { sample_rate: cpal::SampleRate, + rng: SmallRng, } impl WhiteNoise { /// Create a new white noise generator. pub fn new(sample_rate: cpal::SampleRate) -> Self { - Self { sample_rate } + Self { + sample_rate, + rng: SmallRng::from_entropy(), + } } } @@ -32,7 +39,7 @@ impl Iterator for WhiteNoise { #[inline] fn next(&mut self) -> Option { - let randf = rand::random::(); + let randf = self.rng.next_u32() as f32 / u32::MAX as f32; let scaled = randf * 2.0 - 1.0; Some(scaled) } From 56d087afbc8b1accdbf6a5090f88f81ad2d20f2b Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Thu, 15 Aug 2024 00:33:02 -0700 Subject: [PATCH 11/30] Renamed Synth to Test Modules, files and types renamed --- src/source/mod.rs | 4 +-- src/source/sine.rs | 6 ++-- src/source/{synth.rs => test_waveform.rs} | 36 +++++++++++------------ 3 files changed, 23 insertions(+), 23 deletions(-) rename src/source/{synth.rs => test_waveform.rs} (86%) diff --git a/src/source/mod.rs b/src/source/mod.rs index 059c84ed..6043318c 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -33,7 +33,7 @@ pub use self::skippable::Skippable; pub use self::spatial::Spatial; pub use self::speed::Speed; pub use self::stoppable::Stoppable; -pub use self::synth::{SynthWaveform, SynthWaveformFunction}; +pub use self::test_waveform::{TestWaveform, TestWaveformFunction}; pub use self::take::TakeDuration; pub use self::uniform::UniformSourceIterator; pub use self::zero::Zero; @@ -65,8 +65,8 @@ mod skippable; mod spatial; mod speed; mod stoppable; -mod synth; mod take; +mod test_waveform; mod uniform; mod zero; diff --git a/src/source/sine.rs b/src/source/sine.rs index 6e78ec69..b9c11a05 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::source::{SynthWaveform, SynthWaveformFunction}; +use crate::source::{TestWaveform, TestWaveformFunction}; use crate::Source; use super::SeekError; @@ -12,7 +12,7 @@ const SAMPLE_RATE: u32 = 48000; /// Always has a rate of 48kHz and one channel. #[derive(Clone, Debug)] pub struct SineWave { - synth: SynthWaveform, + synth: TestWaveform, } impl SineWave { @@ -21,7 +21,7 @@ impl SineWave { pub fn new(freq: f32) -> SineWave { let sr = cpal::SampleRate(SAMPLE_RATE); SineWave { - synth: SynthWaveform::new(sr, freq, SynthWaveformFunction::Sine), + synth: TestWaveform::new(sr, freq, TestWaveformFunction::Sine), } } } diff --git a/src/source/synth.rs b/src/source/test_waveform.rs similarity index 86% rename from src/source/synth.rs rename to src/source/test_waveform.rs index e87dfe9e..6919bead 100644 --- a/src/source/synth.rs +++ b/src/source/test_waveform.rs @@ -7,14 +7,14 @@ use crate::Source; /// Syntheizer waveform functions. All of the synth waveforms are in the /// codomain [-1.0, 1.0]. #[derive(Clone, Debug)] -pub enum SynthWaveformFunction { +pub enum TestWaveformFunction { Sine, Triangle, Square, Sawtooth, } -impl SynthWaveformFunction { +impl TestWaveformFunction { /// Create a single sample for the given waveform #[inline] pub fn render(&self, i: u64, period: f32) -> f32 { @@ -38,24 +38,24 @@ impl SynthWaveformFunction { /// An infinite source that produces one of a selection of synthesizer /// waveforms. #[derive(Clone, Debug)] -pub struct SynthWaveform { +pub struct TestWaveform { sample_rate: cpal::SampleRate, period: f32, - f: SynthWaveformFunction, + f: TestWaveformFunction, i: u64, } -impl SynthWaveform { +impl TestWaveform { /// Create a new `SynthWaveform` object that generates an endless waveform /// `f`. #[inline] pub fn new( sample_rate: cpal::SampleRate, frequency: f32, - f: SynthWaveformFunction, - ) -> SynthWaveform { + f: TestWaveformFunction, + ) -> TestWaveform { let period = sample_rate.0 as f32 / frequency; - SynthWaveform { + TestWaveform { sample_rate, period, f, @@ -64,7 +64,7 @@ impl SynthWaveform { } } -impl Iterator for SynthWaveform { +impl Iterator for TestWaveform { type Item = f32; #[inline] @@ -75,7 +75,7 @@ impl Iterator for SynthWaveform { } } -impl Source for SynthWaveform { +impl Source for TestWaveform { #[inline] fn current_frame_len(&self) -> Option { None @@ -105,15 +105,15 @@ impl Source for SynthWaveform { #[cfg(test)] mod tests { - use crate::source::synth::*; + use crate::source::{TestWaveform, TestWaveformFunction}; use approx::assert_abs_diff_eq; #[test] fn square() { - let mut wf = SynthWaveform::new( + let mut wf = TestWaveform::new( cpal::SampleRate(2000), 500.0f32, - SynthWaveformFunction::Square, + TestWaveformFunction::Square, ); assert_eq!(wf.next(), Some(1.0f32)); assert_eq!(wf.next(), Some(1.0f32)); @@ -127,10 +127,10 @@ mod tests { #[test] fn triangle() { - let mut wf = SynthWaveform::new( + let mut wf = TestWaveform::new( cpal::SampleRate(8000), 1000.0f32, - SynthWaveformFunction::Triangle, + TestWaveformFunction::Triangle, ); assert_eq!(wf.next(), Some(-1.0f32)); assert_eq!(wf.next(), Some(-0.5f32)); @@ -152,10 +152,10 @@ mod tests { #[test] fn saw() { - let mut wf = SynthWaveform::new( + let mut wf = TestWaveform::new( cpal::SampleRate(200), 50.0f32, - SynthWaveformFunction::Sawtooth, + TestWaveformFunction::Sawtooth, ); assert_eq!(wf.next(), Some(0.0f32)); assert_eq!(wf.next(), Some(0.5f32)); @@ -169,7 +169,7 @@ mod tests { #[test] fn sine() { let mut wf = - SynthWaveform::new(cpal::SampleRate(1000), 100f32, SynthWaveformFunction::Sine); + TestWaveform::new(cpal::SampleRate(1000), 100f32, TestWaveformFunction::Sine); assert_abs_diff_eq!(wf.next().unwrap(), 0.0f32); assert_abs_diff_eq!(wf.next().unwrap(), 0.58778525f32); From 0907857501f68aa0e9b020e13232a35fd6c6ba62 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Thu, 15 Aug 2024 01:01:53 -0700 Subject: [PATCH 12/30] Documentation updates --- src/source/mod.rs | 2 +- src/source/test_waveform.rs | 29 +++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/source/mod.rs b/src/source/mod.rs index 6043318c..3448529e 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -33,8 +33,8 @@ pub use self::skippable::Skippable; pub use self::spatial::Spatial; pub use self::speed::Speed; pub use self::stoppable::Stoppable; -pub use self::test_waveform::{TestWaveform, TestWaveformFunction}; pub use self::take::TakeDuration; +pub use self::test_waveform::{TestWaveform, TestWaveformFunction}; pub use self::uniform::UniformSourceIterator; pub use self::zero::Zero; diff --git a/src/source/test_waveform.rs b/src/source/test_waveform.rs index 6919bead..1b2214bd 100644 --- a/src/source/test_waveform.rs +++ b/src/source/test_waveform.rs @@ -1,23 +1,38 @@ +//! Generator sources for various periodic test waveforms. +//! +//! This module provides several periodic, deterministic waveforms for testing other sources. Every +//! source oscillates in the codomain `[-1.0f32, 1.0f32]`. +//! +//! # Example +//! +//! ``` +//! use rodio::source::{TestWaveform,TestWaveformFunction}; +//! +//! let tone = TestWaveform::new(cpal::SampleRate(48000), 440.0, TestWaveformFunction::Sine); +//! ``` use std::f32::consts::TAU; use std::time::Duration; use super::SeekError; use crate::Source; -/// Syntheizer waveform functions. All of the synth waveforms are in the -/// codomain [-1.0, 1.0]. +/// Test waveform functions. #[derive(Clone, Debug)] pub enum TestWaveformFunction { + /// A sinusoidal waveform. Sine, + /// A triangle wave. Triangle, + /// A square wave, rising edge at t=0. Square, + /// A rising swatooth wave. Sawtooth, } impl TestWaveformFunction { /// Create a single sample for the given waveform #[inline] - pub fn render(&self, i: u64, period: f32) -> f32 { + fn render(&self, i: u64, period: f32) -> f32 { let i_div_p: f32 = i as f32 / period; match self { @@ -35,8 +50,7 @@ impl TestWaveformFunction { } } -/// An infinite source that produces one of a selection of synthesizer -/// waveforms. +/// An infinite source that produces one of a selection of test waveforms. #[derive(Clone, Debug)] pub struct TestWaveform { sample_rate: cpal::SampleRate, @@ -46,7 +60,7 @@ pub struct TestWaveform { } impl TestWaveform { - /// Create a new `SynthWaveform` object that generates an endless waveform + /// Create a new `TestWaveform` object that generates an endless waveform /// `f`. #[inline] pub fn new( @@ -168,8 +182,7 @@ mod tests { #[test] fn sine() { - let mut wf = - TestWaveform::new(cpal::SampleRate(1000), 100f32, TestWaveformFunction::Sine); + let mut wf = TestWaveform::new(cpal::SampleRate(1000), 100f32, TestWaveformFunction::Sine); assert_abs_diff_eq!(wf.next().unwrap(), 0.0f32); assert_abs_diff_eq!(wf.next().unwrap(), 0.58778525f32); From 87cd605d73060181eb5a8b3129028b5d3ef6f114 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Thu, 15 Aug 2024 01:32:46 -0700 Subject: [PATCH 13/30] More documentation, WhiteNoise creation option. --- src/source/noise.rs | 25 +++++++++++++++++++------ src/source/test_waveform.rs | 5 +++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/source/noise.rs b/src/source/noise.rs index dfee9172..04b450ed 100644 --- a/src/source/noise.rs +++ b/src/source/noise.rs @@ -1,3 +1,6 @@ +//! Noise sources. +//! +//! use crate::Source; use super::SeekError; @@ -5,19 +8,20 @@ use super::SeekError; use rand::rngs::SmallRng; use rand::{RngCore, SeedableRng}; -/// Create a new white noise source. +/// Create a new `WhiteNoise` noise source. #[inline] pub fn white(sample_rate: cpal::SampleRate) -> WhiteNoise { WhiteNoise::new(sample_rate) } -/// Create a new pink noise source. +/// 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] +/// 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, @@ -25,7 +29,15 @@ pub struct WhiteNoise { } impl WhiteNoise { - /// Create a new white noise generator. + /// 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, @@ -73,9 +85,10 @@ impl Source for WhiteNoise { } } -// https://www.musicdsp.org/en/latest/Filters/76-pink-noise-filter.html -// /// Generate an infinite stream of pink noise samples in [-1.0, 1.0]. +/// +/// The output of this 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](https://www.musicdsp.org/en/latest/Filters/76-pink-noise-filter.html). pub struct PinkNoise { noise: WhiteNoise, b: [f32; 7], diff --git a/src/source/test_waveform.rs b/src/source/test_waveform.rs index 1b2214bd..b9615aea 100644 --- a/src/source/test_waveform.rs +++ b/src/source/test_waveform.rs @@ -1,7 +1,8 @@ //! Generator sources for various periodic test waveforms. //! -//! This module provides several periodic, deterministic waveforms for testing other sources. Every -//! source oscillates in the codomain `[-1.0f32, 1.0f32]`. +//! 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 //! From 59a5b1236738888699b32b161a894479b50741ca Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 19 Aug 2024 17:14:06 -0500 Subject: [PATCH 14/30] Several code review fixes --- src/source/chirp.rs | 79 +++++++++++++++++++++++++++++++++++++ src/source/mod.rs | 2 + src/source/noise.rs | 25 ++++++------ src/source/sine.rs | 8 ++-- src/source/test_waveform.rs | 37 +++++++++-------- 5 files changed, 120 insertions(+), 31 deletions(-) create mode 100644 src/source/chirp.rs diff --git a/src/source/chirp.rs b/src/source/chirp.rs new file mode 100644 index 00000000..43d2b30d --- /dev/null +++ b/src/source/chirp.rs @@ -0,0 +1,79 @@ +//! Chirp/sweep source. + +use std::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 { + let i = self.elapsed_samples; + self.elapsed_samples += 1; + + + todo!() + } +} + +impl Source for Chirp { + fn current_frame_len(&self) -> Option { + None + } + + fn channels(&self) -> u16 { + 1 + } + + fn sample_rate(&self) -> u32 { + self.sample_rate.0 + } + + fn total_duration(&self) -> Option { + 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!() + } +} diff --git a/src/source/mod.rs b/src/source/mod.rs index 3448529e..23f058de 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -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; @@ -41,6 +42,7 @@ pub use self::zero::Zero; mod amplify; mod blt; mod buffered; +mod chirp; mod channel_volume; mod crossfade; mod delay; diff --git a/src/source/noise.rs b/src/source/noise.rs index 04b450ed..434cc998 100644 --- a/src/source/noise.rs +++ b/src/source/noise.rs @@ -8,13 +8,13 @@ use super::SeekError; use rand::rngs::SmallRng; use rand::{RngCore, SeedableRng}; -/// Create a new `WhiteNoise` noise source. +/// Convenience function to create a new `WhiteNoise` noise source. #[inline] pub fn white(sample_rate: cpal::SampleRate) -> WhiteNoise { WhiteNoise::new(sample_rate) } -/// Create a new `PinkNoise` noise source. +/// Convenience function to create a new `PinkNoise` noise source. #[inline] pub fn pink(sample_rate: cpal::SampleRate) -> PinkNoise { PinkNoise::new(sample_rate) @@ -51,8 +51,8 @@ impl Iterator for WhiteNoise { #[inline] fn next(&mut self) -> Option { - let randf = self.rng.next_u32() as f32 / u32::MAX as f32; - let scaled = randf * 2.0 - 1.0; + let rand = self.rng.next_u32() as f32 / u32::MAX as f32; + let scaled = rand * 2.0 - 1.0; Some(scaled) } } @@ -85,19 +85,22 @@ impl Source for WhiteNoise { } } -/// Generate an infinite stream of pink noise samples in [-1.0, 1.0]. +/// Generates an infinite stream of pink noise samples in [-1.0, 1.0]. /// -/// The output of this 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](https://www.musicdsp.org/en/latest/Filters/76-pink-noise-filter.html). +/// 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 { - noise: WhiteNoise, + white_noise: WhiteNoise, b: [f32; 7], } impl PinkNoise { pub fn new(sample_rate: cpal::SampleRate) -> Self { Self { - noise: WhiteNoise::new(sample_rate), + white_noise: WhiteNoise::new(sample_rate), b: [0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32], } } @@ -107,7 +110,7 @@ impl Iterator for PinkNoise { type Item = f32; fn next(&mut self) -> Option { - let white = self.noise.next().unwrap(); + 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; @@ -140,7 +143,7 @@ impl Source for PinkNoise { } fn sample_rate(&self) -> u32 { - self.noise.sample_rate() + self.white_noise.sample_rate() } fn total_duration(&self) -> Option { diff --git a/src/source/sine.rs b/src/source/sine.rs index b9c11a05..0fafcf00 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -12,7 +12,7 @@ const SAMPLE_RATE: u32 = 48000; /// Always has a rate of 48kHz and one channel. #[derive(Clone, Debug)] pub struct SineWave { - synth: TestWaveform, + test_sine: TestWaveform, } impl SineWave { @@ -21,7 +21,7 @@ impl SineWave { pub fn new(freq: f32) -> SineWave { let sr = cpal::SampleRate(SAMPLE_RATE); SineWave { - synth: TestWaveform::new(sr, freq, TestWaveformFunction::Sine), + test_sine: TestWaveform::new(sr, freq, TestWaveformFunction::Sine), } } } @@ -31,7 +31,7 @@ impl Iterator for SineWave { #[inline] fn next(&mut self) -> Option { - self.synth.next() + self.test_sine.next() } } @@ -58,6 +58,6 @@ impl Source for SineWave { #[inline] fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { - self.synth.try_seek(pos) + self.test_sine.try_seek(pos) } } diff --git a/src/source/test_waveform.rs b/src/source/test_waveform.rs index b9615aea..420eac58 100644 --- a/src/source/test_waveform.rs +++ b/src/source/test_waveform.rs @@ -1,8 +1,8 @@ //! 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]` +//! for simple additive sound synthesis. Every source is monoaural and in the codomain [-1.0f32, +//! 1.0f32]. //! //! # Example //! @@ -17,12 +17,12 @@ use std::time::Duration; use super::SeekError; use crate::Source; -/// Test waveform functions. +/// Waveform functions. #[derive(Clone, Debug)] -pub enum TestWaveformFunction { +pub enum Function { /// A sinusoidal waveform. Sine, - /// A triangle wave. + /// A triangle waveform. Triangle, /// A square wave, rising edge at t=0. Square, @@ -30,7 +30,7 @@ pub enum TestWaveformFunction { Sawtooth, } -impl TestWaveformFunction { +impl Function { /// Create a single sample for the given waveform #[inline] fn render(&self, i: u64, period: f32) -> f32 { @@ -38,7 +38,7 @@ impl TestWaveformFunction { match self { Self::Sine => (TAU * i_div_p).sin(), - Self::Triangle => 04.0f32 * (i_div_p - (i_div_p + 0.5f32).floor()).abs() - 1f32, + Self::Triangle => 4.0f32 * (i_div_p - (i_div_p + 0.5f32).floor()).abs() - 1f32, Self::Square => { if i_div_p % 1.0f32 < 0.5f32 { 1.0f32 @@ -56,24 +56,29 @@ impl TestWaveformFunction { pub struct TestWaveform { sample_rate: cpal::SampleRate, period: f32, - f: TestWaveformFunction, + function: Function, i: u64, } impl TestWaveform { /// 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: TestWaveformFunction, + f: Function, ) -> TestWaveform { + assert!(frequency != 0.0, "frequency must be greater than zero"); let period = sample_rate.0 as f32 / frequency; TestWaveform { sample_rate, period, - f, + function: f, i: 0, } } @@ -86,7 +91,7 @@ impl Iterator for TestWaveform { fn next(&mut self) -> Option { let this_i = self.i; self.i += 1; - Some(self.f.render(this_i, self.period)) + Some(self.function.render(this_i, self.period)) } } @@ -120,7 +125,7 @@ impl Source for TestWaveform { #[cfg(test)] mod tests { - use crate::source::{TestWaveform, TestWaveformFunction}; + use crate::source::{TestWaveform, Function}; use approx::assert_abs_diff_eq; #[test] @@ -128,7 +133,7 @@ mod tests { let mut wf = TestWaveform::new( cpal::SampleRate(2000), 500.0f32, - TestWaveformFunction::Square, + Function::Square, ); assert_eq!(wf.next(), Some(1.0f32)); assert_eq!(wf.next(), Some(1.0f32)); @@ -145,7 +150,7 @@ mod tests { let mut wf = TestWaveform::new( cpal::SampleRate(8000), 1000.0f32, - TestWaveformFunction::Triangle, + Function::Triangle, ); assert_eq!(wf.next(), Some(-1.0f32)); assert_eq!(wf.next(), Some(-0.5f32)); @@ -170,7 +175,7 @@ mod tests { let mut wf = TestWaveform::new( cpal::SampleRate(200), 50.0f32, - TestWaveformFunction::Sawtooth, + Function::Sawtooth, ); assert_eq!(wf.next(), Some(0.0f32)); assert_eq!(wf.next(), Some(0.5f32)); @@ -183,7 +188,7 @@ mod tests { #[test] fn sine() { - let mut wf = TestWaveform::new(cpal::SampleRate(1000), 100f32, TestWaveformFunction::Sine); + let mut wf = TestWaveform::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); From a012ac1a48ae5e96b118384d8f820b15037e30ab Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 19 Aug 2024 17:15:27 -0500 Subject: [PATCH 15/30] Changes for code reviews --- Cargo.toml | 3 ++- src/source/mod.rs | 2 +- src/source/sine.rs | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a75b74c0..b93973d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +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"]} +rand = { version = "0.8.5", features = ["small_rng"], optional = true } [features] default = ["flac", "vorbis", "wav", "mp3"] @@ -28,6 +28,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"] diff --git a/src/source/mod.rs b/src/source/mod.rs index 23f058de..14e252e7 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -35,7 +35,7 @@ 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::{TestWaveform, TestWaveformFunction}; +pub use self::test_waveform::{TestWaveform, Function}; pub use self::uniform::UniformSourceIterator; pub use self::zero::Zero; diff --git a/src/source/sine.rs b/src/source/sine.rs index 0fafcf00..9a92b20b 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::source::{TestWaveform, TestWaveformFunction}; +use crate::source::{TestWaveform, Function}; use crate::Source; use super::SeekError; @@ -21,7 +21,7 @@ impl SineWave { pub fn new(freq: f32) -> SineWave { let sr = cpal::SampleRate(SAMPLE_RATE); SineWave { - test_sine: TestWaveform::new(sr, freq, TestWaveformFunction::Sine), + test_sine: TestWaveform::new(sr, freq, Function::Sine), } } } From 0c2c1941d6edec698e6b7baeb59275081a9f8051 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 19 Aug 2024 17:39:25 -0500 Subject: [PATCH 16/30] Made noise feature Noise source and example guarded by it. --- Cargo.toml | 4 ++++ examples/noise_generator.rs | 16 ++++++++++++---- src/source/chirp.rs | 3 +-- src/source/mod.rs | 11 +++++++---- src/source/noise.rs | 4 ++-- src/source/sine.rs | 2 +- src/source/test_waveform.rs | 34 +++++++++------------------------- 7 files changed, 36 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b93973d1..fc29e7fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,3 +50,7 @@ approx = "0.5.1" [[example]] name = "music_m4a" required-features = ["symphonia-isomp4", "symphonia-aac"] + +[[example]] +name = "noise_generator" +required-features = ["noise"] diff --git a/examples/noise_generator.rs b/examples/noise_generator.rs index 49f1f88e..a72bcb7c 100644 --- a/examples/noise_generator.rs +++ b/examples/noise_generator.rs @@ -1,9 +1,11 @@ -use std::thread; -use std::time::Duration; - -use rodio::source::{pink, white, Source}; +//! 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); @@ -31,3 +33,9 @@ fn main() { 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..."); +} diff --git a/src/source/chirp.rs b/src/source/chirp.rs index 43d2b30d..8fabc292 100644 --- a/src/source/chirp.rs +++ b/src/source/chirp.rs @@ -50,7 +50,6 @@ impl Iterator for Chirp { let i = self.elapsed_samples; self.elapsed_samples += 1; - todo!() } } @@ -70,7 +69,7 @@ impl Source for Chirp { fn total_duration(&self) -> Option { let secs: f64 = self.total_samples as f64 / self.sample_rate.0 as f64; - Some(Duration::new(1,0).mul_f64(secs)) + Some(Duration::new(1, 0).mul_f64(secs)) } fn try_seek(&mut self, pos: Duration) -> Result<(), super::SeekError> { diff --git a/src/source/mod.rs b/src/source/mod.rs index 14e252e7..5e9b6f39 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -22,7 +22,6 @@ pub use self::from_factory::{from_factory, FromFactoryIter}; pub use self::from_iter::{from_iter, FromIter}; pub use self::linear_ramp::LinearGainRamp; pub use self::mix::Mix; -pub use self::noise::{pink, white, PinkNoise, WhiteNoise}; pub use self::pausable::Pausable; pub use self::periodic::PeriodicAccess; pub use self::position::TrackPosition; @@ -35,15 +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::{TestWaveform, Function}; +pub use self::test_waveform::{Function, TestWaveform}; pub use self::uniform::UniformSourceIterator; pub use self::zero::Zero; mod amplify; mod blt; mod buffered; -mod chirp; mod channel_volume; +mod chirp; mod crossfade; mod delay; mod done; @@ -55,7 +54,6 @@ mod from_factory; mod from_iter; mod linear_ramp; mod mix; -mod noise; mod pausable; mod periodic; mod position; @@ -72,6 +70,11 @@ 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 diff --git a/src/source/noise.rs b/src/source/noise.rs index 434cc998..42ae0e61 100644 --- a/src/source/noise.rs +++ b/src/source/noise.rs @@ -1,12 +1,12 @@ //! Noise sources. //! //! + use crate::Source; use super::SeekError; -use rand::rngs::SmallRng; -use rand::{RngCore, SeedableRng}; +use rand::{rngs::SmallRng, RngCore, SeedableRng}; /// Convenience function to create a new `WhiteNoise` noise source. #[inline] diff --git a/src/source/sine.rs b/src/source/sine.rs index 9a92b20b..73db5eba 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::source::{TestWaveform, Function}; +use crate::source::{Function, TestWaveform}; use crate::Source; use super::SeekError; diff --git a/src/source/test_waveform.rs b/src/source/test_waveform.rs index 420eac58..fea4f508 100644 --- a/src/source/test_waveform.rs +++ b/src/source/test_waveform.rs @@ -7,9 +7,9 @@ //! # Example //! //! ``` -//! use rodio::source::{TestWaveform,TestWaveformFunction}; +//! use rodio::source::{TestWaveform,Function}; //! -//! let tone = TestWaveform::new(cpal::SampleRate(48000), 440.0, TestWaveformFunction::Sine); +//! let tone = TestWaveform::new(cpal::SampleRate(48000), 440.0, Function::Sine); //! ``` use std::f32::consts::TAU; use std::time::Duration; @@ -68,11 +68,7 @@ impl TestWaveform { /// /// Will panic if `frequency` is equal to zero. #[inline] - pub fn new( - sample_rate: cpal::SampleRate, - frequency: f32, - f: Function, - ) -> TestWaveform { + pub fn new(sample_rate: cpal::SampleRate, frequency: f32, f: Function) -> TestWaveform { assert!(frequency != 0.0, "frequency must be greater than zero"); let period = sample_rate.0 as f32 / frequency; TestWaveform { @@ -89,9 +85,9 @@ impl Iterator for TestWaveform { #[inline] fn next(&mut self) -> Option { - let this_i = self.i; + let val = Some(self.function.render(self.i, self.period)); self.i += 1; - Some(self.function.render(this_i, self.period)) + val } } @@ -125,16 +121,12 @@ impl Source for TestWaveform { #[cfg(test)] mod tests { - use crate::source::{TestWaveform, Function}; + use crate::source::{Function, TestWaveform}; use approx::assert_abs_diff_eq; #[test] fn square() { - let mut wf = TestWaveform::new( - cpal::SampleRate(2000), - 500.0f32, - Function::Square, - ); + let mut wf = TestWaveform::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)); @@ -147,11 +139,7 @@ mod tests { #[test] fn triangle() { - let mut wf = TestWaveform::new( - cpal::SampleRate(8000), - 1000.0f32, - Function::Triangle, - ); + let mut wf = TestWaveform::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)); @@ -172,11 +160,7 @@ mod tests { #[test] fn saw() { - let mut wf = TestWaveform::new( - cpal::SampleRate(200), - 50.0f32, - Function::Sawtooth, - ); + let mut wf = TestWaveform::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)); From e060bdd35c9a5fa5ce91bdf08a3cbacac0454b5d Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 14 Sep 2024 09:32:51 -0700 Subject: [PATCH 17/30] Implemented chirp generator --- CHANGELOG.md | 13 +++++++++++-- src/lib.rs | 9 +++++++++ src/source/chirp.rs | 8 +++++--- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63faa801..03cf51f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/lib.rs b/src/lib.rs index 6f6c505b..c47eafab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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. +//! +//! ### 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, diff --git a/src/source/chirp.rs b/src/source/chirp.rs index 8fabc292..1971c70f 100644 --- a/src/source/chirp.rs +++ b/src/source/chirp.rs @@ -1,6 +1,6 @@ //! Chirp/sweep source. -use std::time::Duration; +use std::{f32::consts::TAU, time::Duration}; use crate::Source; @@ -48,9 +48,11 @@ impl Iterator for Chirp { fn next(&mut self) -> Option { let i = self.elapsed_samples; + let ratio = self.elapsed_samples as f32 / self.total_samples as f32; self.elapsed_samples += 1; - - todo!() + 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() ) } } From f3e77f43ec52dac082d0a362508d01706a767f0c Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 14 Sep 2024 09:39:02 -0700 Subject: [PATCH 18/30] Removed `try_seek()` from chirp. --- src/source/chirp.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/source/chirp.rs b/src/source/chirp.rs index 1971c70f..9942cab4 100644 --- a/src/source/chirp.rs +++ b/src/source/chirp.rs @@ -52,7 +52,7 @@ impl Iterator for Chirp { 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() ) + Some(t.sin()) } } @@ -73,8 +73,4 @@ impl Source for Chirp { 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!() - } } From c912f567a66bd2a0aa21a0b058087e31c1e47878 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 15 Sep 2024 13:26:43 -0700 Subject: [PATCH 19/30] Removed white space for rustfmt --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c47eafab..6889bd62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,7 +113,7 @@ //! down your program). //! //! -//! ## Features +//! ## Features //! //! Rodio provides several optional features that are guarded with feature gates. //! From cde724a429a45345200ae654c67644f900077a49 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 15 Sep 2024 13:29:15 -0700 Subject: [PATCH 20/30] Removed white space for rustfmt --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 6889bd62..f9dde615 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,7 +112,7 @@ //! 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. From 56fcce74f3d93517c45886097eafdd588ac99a4f Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 29 Sep 2024 15:40:36 -0700 Subject: [PATCH 21/30] Fixed a typo from @UnknownSuperficialNight --- src/source/test_waveform.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source/test_waveform.rs b/src/source/test_waveform.rs index fea4f508..ec8db6c2 100644 --- a/src/source/test_waveform.rs +++ b/src/source/test_waveform.rs @@ -26,7 +26,7 @@ pub enum Function { Triangle, /// A square wave, rising edge at t=0. Square, - /// A rising swatooth wave. + /// A rising sawtooth wave. Sawtooth, } From b373f5253afdfeb51e14b409abc3fcc0cc561aa7 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 29 Sep 2024 16:02:07 -0700 Subject: [PATCH 22/30] Moved "noise" feature documentation ...into the previous "Optional Features" block --- src/lib.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d6742eab..ecff98a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 @@ -121,15 +126,8 @@ //! 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. -//! -//! ### 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, From 5fa0188cc361a7a36b511b6781d49b4496dad648 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 29 Sep 2024 16:15:41 -0700 Subject: [PATCH 23/30] Added signal_generator.rs example --- examples/signal_generator.rs | 83 ++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 examples/signal_generator.rs diff --git a/examples/signal_generator.rs b/examples/signal_generator.rs new file mode 100644 index 00000000..ca761937 --- /dev/null +++ b/examples/signal_generator.rs @@ -0,0 +1,83 @@ +//! Noise generator example. Use the "noise" feature to enable the noise generator sources. + +fn main() { + use rodio::source::{chirp, Function, Source, TestWaveform}; + 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); + + println!("Playing 1000 Hz tone"); + stream_handle + .play_raw( + TestWaveform::new(cpal::SampleRate(48000), 1000.0, Function::Sine) + .amplify(0.1) + .take_duration(noise_duration), + ) + .unwrap(); + + thread::sleep(interval_duration); + + println!("Playing 10,000 Hz tone"); + stream_handle + .play_raw( + TestWaveform::new(cpal::SampleRate(48000), 10000.0, Function::Sine) + .amplify(0.1) + .take_duration(noise_duration), + ) + .unwrap(); + + thread::sleep(interval_duration); + + println!("Playing 440 Hz Triangle Wave"); + stream_handle + .play_raw( + TestWaveform::new(cpal::SampleRate(48000), 440.0, Function::Triangle) + .amplify(0.1) + .take_duration(noise_duration), + ) + .unwrap(); + + thread::sleep(interval_duration); + + println!("Playing 440 Hz Sawtooth Wave"); + stream_handle + .play_raw( + TestWaveform::new(cpal::SampleRate(48000), 440.0, Function::Sawtooth) + .amplify(0.1) + .take_duration(noise_duration), + ) + .unwrap(); + + thread::sleep(interval_duration); + + println!("Playing 440 Hz Square Wave"); + stream_handle + .play_raw( + TestWaveform::new(cpal::SampleRate(48000), 440.0, Function::Square) + .amplify(0.1) + .take_duration(noise_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(noise_duration), + ) + .unwrap(); + + thread::sleep(interval_duration); +} From 371bbda49e4df18276b7fe91be2525cd5bc40065 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 29 Sep 2024 16:18:42 -0700 Subject: [PATCH 24/30] Some cosmetic changes for rustfmt --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ecff98a8..822d5e35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,7 +114,7 @@ //! //! 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 @@ -125,7 +125,6 @@ //! 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))] From 9d20ea21f197753f60d6d1ac2f22ba6360beede3 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 29 Sep 2024 16:23:52 -0700 Subject: [PATCH 25/30] One more tweak for rustfmt --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 822d5e35..10ef60dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,7 +126,6 @@ //! 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, From 44cb217d48e65f3a2484b865a2f3b0b94ea45333 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 29 Sep 2024 16:45:29 -0700 Subject: [PATCH 26/30] Code review changes --- CHANGELOG.md | 6 +++--- examples/signal_generator.rs | 16 ++++++++-------- src/source/sine.rs | 8 ++++---- src/source/test_waveform.rs | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 390bfa03..b1567d48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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. + - `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" + 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: diff --git a/examples/signal_generator.rs b/examples/signal_generator.rs index ca761937..d17f1675 100644 --- a/examples/signal_generator.rs +++ b/examples/signal_generator.rs @@ -1,4 +1,4 @@ -//! Noise generator example. Use the "noise" feature to enable the noise generator sources. +//! Test signal generator example. fn main() { use rodio::source::{chirp, Function, Source, TestWaveform}; @@ -7,7 +7,7 @@ fn main() { let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap(); - let noise_duration = Duration::from_millis(1000); + let test_signal_duration = Duration::from_millis(1000); let interval_duration = Duration::from_millis(1500); println!("Playing 1000 Hz tone"); @@ -15,7 +15,7 @@ fn main() { .play_raw( TestWaveform::new(cpal::SampleRate(48000), 1000.0, Function::Sine) .amplify(0.1) - .take_duration(noise_duration), + .take_duration(test_signal_duration), ) .unwrap(); @@ -26,7 +26,7 @@ fn main() { .play_raw( TestWaveform::new(cpal::SampleRate(48000), 10000.0, Function::Sine) .amplify(0.1) - .take_duration(noise_duration), + .take_duration(test_signal_duration), ) .unwrap(); @@ -37,7 +37,7 @@ fn main() { .play_raw( TestWaveform::new(cpal::SampleRate(48000), 440.0, Function::Triangle) .amplify(0.1) - .take_duration(noise_duration), + .take_duration(test_signal_duration), ) .unwrap(); @@ -48,7 +48,7 @@ fn main() { .play_raw( TestWaveform::new(cpal::SampleRate(48000), 440.0, Function::Sawtooth) .amplify(0.1) - .take_duration(noise_duration), + .take_duration(test_signal_duration), ) .unwrap(); @@ -59,7 +59,7 @@ fn main() { .play_raw( TestWaveform::new(cpal::SampleRate(48000), 440.0, Function::Square) .amplify(0.1) - .take_duration(noise_duration), + .take_duration(test_signal_duration), ) .unwrap(); @@ -75,7 +75,7 @@ fn main() { Duration::from_secs(1), ) .amplify(0.1) - .take_duration(noise_duration), + .take_duration(test_signal_duration), ) .unwrap(); diff --git a/src/source/sine.rs b/src/source/sine.rs index 73db5eba..b9b97caa 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -5,8 +5,6 @@ use crate::Source; use super::SeekError; -const SAMPLE_RATE: u32 = 48000; - /// An infinite source that produces a sine. /// /// Always has a rate of 48kHz and one channel. @@ -16,10 +14,12 @@ pub struct SineWave { } impl SineWave { + const SAMPLE_RATE: u32 = 48000; + /// The frequency of the sine. #[inline] pub fn new(freq: f32) -> SineWave { - let sr = cpal::SampleRate(SAMPLE_RATE); + let sr = cpal::SampleRate(Self::SAMPLE_RATE); SineWave { test_sine: TestWaveform::new(sr, freq, Function::Sine), } @@ -48,7 +48,7 @@ impl Source for SineWave { #[inline] fn sample_rate(&self) -> u32 { - SAMPLE_RATE + Self::SAMPLE_RATE } #[inline] diff --git a/src/source/test_waveform.rs b/src/source/test_waveform.rs index ec8db6c2..bcd4799c 100644 --- a/src/source/test_waveform.rs +++ b/src/source/test_waveform.rs @@ -34,19 +34,19 @@ impl Function { /// Create a single sample for the given waveform #[inline] fn render(&self, i: u64, period: f32) -> f32 { - let i_div_p: f32 = i as f32 / period; + let cycle_pos: f32 = i as f32 / period; match self { - Self::Sine => (TAU * i_div_p).sin(), - Self::Triangle => 4.0f32 * (i_div_p - (i_div_p + 0.5f32).floor()).abs() - 1f32, + Self::Sine => (TAU * cycle_pos).sin(), + Self::Triangle => 4.0f32 * (cycle_pos - (cycle_pos + 0.5f32).floor()).abs() - 1f32, Self::Square => { - if i_div_p % 1.0f32 < 0.5f32 { + if cycle_pos % 1.0f32 < 0.5f32 { 1.0f32 } else { -1.0f32 } } - Self::Sawtooth => 2.0f32 * (i_div_p - (i_div_p + 0.5f32).floor()), + Self::Sawtooth => 2.0f32 * (cycle_pos - (cycle_pos + 0.5f32).floor()), } } } From 8899e6709fa50e7d70136ea8029217e9feafefd2 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 30 Sep 2024 21:45:30 -0700 Subject: [PATCH 27/30] Renamed TestWaveform to SignalGenerator --- examples/signal_generator.rs | 12 +-- src/source/mod.rs | 4 +- src/source/sine.rs | 6 +- src/source/test_waveform.rs | 185 ----------------------------------- 4 files changed, 11 insertions(+), 196 deletions(-) delete mode 100644 src/source/test_waveform.rs diff --git a/examples/signal_generator.rs b/examples/signal_generator.rs index d17f1675..802fd582 100644 --- a/examples/signal_generator.rs +++ b/examples/signal_generator.rs @@ -1,7 +1,7 @@ //! Test signal generator example. fn main() { - use rodio::source::{chirp, Function, Source, TestWaveform}; + use rodio::source::{chirp, Function, Source, SignalGenerator}; use std::thread; use std::time::Duration; @@ -13,7 +13,7 @@ fn main() { println!("Playing 1000 Hz tone"); stream_handle .play_raw( - TestWaveform::new(cpal::SampleRate(48000), 1000.0, Function::Sine) + SignalGenerator::new(cpal::SampleRate(48000), 1000.0, Function::Sine) .amplify(0.1) .take_duration(test_signal_duration), ) @@ -24,7 +24,7 @@ fn main() { println!("Playing 10,000 Hz tone"); stream_handle .play_raw( - TestWaveform::new(cpal::SampleRate(48000), 10000.0, Function::Sine) + SignalGenerator::new(cpal::SampleRate(48000), 10000.0, Function::Sine) .amplify(0.1) .take_duration(test_signal_duration), ) @@ -35,7 +35,7 @@ fn main() { println!("Playing 440 Hz Triangle Wave"); stream_handle .play_raw( - TestWaveform::new(cpal::SampleRate(48000), 440.0, Function::Triangle) + SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Triangle) .amplify(0.1) .take_duration(test_signal_duration), ) @@ -46,7 +46,7 @@ fn main() { println!("Playing 440 Hz Sawtooth Wave"); stream_handle .play_raw( - TestWaveform::new(cpal::SampleRate(48000), 440.0, Function::Sawtooth) + SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Sawtooth) .amplify(0.1) .take_duration(test_signal_duration), ) @@ -57,7 +57,7 @@ fn main() { println!("Playing 440 Hz Square Wave"); stream_handle .play_raw( - TestWaveform::new(cpal::SampleRate(48000), 440.0, Function::Square) + SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Square) .amplify(0.1) .take_duration(test_signal_duration), ) diff --git a/src/source/mod.rs b/src/source/mod.rs index 8a2f728f..d91cabf2 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -34,7 +34,7 @@ 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::signal_generator::{Function, SignalGenerator}; pub use self::uniform::UniformSourceIterator; pub use self::zero::Zero; @@ -66,7 +66,7 @@ mod spatial; mod speed; mod stoppable; mod take; -mod test_waveform; +mod signal_generator; mod uniform; mod zero; diff --git a/src/source/sine.rs b/src/source/sine.rs index b9b97caa..b7591900 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::source::{Function, TestWaveform}; +use crate::source::{Function, SignalGenerator}; use crate::Source; use super::SeekError; @@ -10,7 +10,7 @@ use super::SeekError; /// Always has a rate of 48kHz and one channel. #[derive(Clone, Debug)] pub struct SineWave { - test_sine: TestWaveform, + test_sine: SignalGenerator, } impl SineWave { @@ -21,7 +21,7 @@ impl SineWave { pub fn new(freq: f32) -> SineWave { let sr = cpal::SampleRate(Self::SAMPLE_RATE); SineWave { - test_sine: TestWaveform::new(sr, freq, Function::Sine), + test_sine: SignalGenerator::new(sr, freq, Function::Sine), } } } diff --git a/src/source/test_waveform.rs b/src/source/test_waveform.rs deleted file mode 100644 index bcd4799c..00000000 --- a/src/source/test_waveform.rs +++ /dev/null @@ -1,185 +0,0 @@ -//! 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::{TestWaveform,Function}; -//! -//! let tone = TestWaveform::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 TestWaveform { - sample_rate: cpal::SampleRate, - period: f32, - function: Function, - i: u64, -} - -impl TestWaveform { - /// 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) -> TestWaveform { - assert!(frequency != 0.0, "frequency must be greater than zero"); - let period = sample_rate.0 as f32 / frequency; - TestWaveform { - sample_rate, - period, - function: f, - i: 0, - } - } -} - -impl Iterator for TestWaveform { - type Item = f32; - - #[inline] - fn next(&mut self) -> Option { - let val = Some(self.function.render(self.i, self.period)); - self.i += 1; - val - } -} - -impl Source for TestWaveform { - #[inline] - fn current_frame_len(&self) -> Option { - None - } - - #[inline] - fn channels(&self) -> u16 { - 1 - } - - #[inline] - fn sample_rate(&self) -> u32 { - self.sample_rate.0 - } - - #[inline] - fn total_duration(&self) -> Option { - 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, TestWaveform}; - use approx::assert_abs_diff_eq; - - #[test] - fn square() { - let mut wf = TestWaveform::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 = TestWaveform::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 = TestWaveform::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 = TestWaveform::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); - } -} From a1fa144f579243e052e71b399907c0b46adbae0b Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 30 Sep 2024 21:50:30 -0700 Subject: [PATCH 28/30] Adding renamed signal_generator.rs --- src/source/signal_generator.rs | 185 +++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 src/source/signal_generator.rs diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs new file mode 100644 index 00000000..54969ed5 --- /dev/null +++ b/src/source/signal_generator.rs @@ -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 { + 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 { + None + } + + #[inline] + fn channels(&self) -> u16 { + 1 + } + + #[inline] + fn sample_rate(&self) -> u32 { + self.sample_rate.0 + } + + #[inline] + fn total_duration(&self) -> Option { + 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); + } +} From 322bdd5f5f66d1930f09e193fc1f48314ec11626 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 30 Sep 2024 21:59:44 -0700 Subject: [PATCH 29/30] Rustfmt and a doc comment --- src/source/mod.rs | 4 ++-- src/source/sine.rs | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/source/mod.rs b/src/source/mod.rs index d91cabf2..932bec4c 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -27,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; @@ -34,7 +35,6 @@ pub use self::spatial::Spatial; pub use self::speed::Speed; pub use self::stoppable::Stoppable; pub use self::take::TakeDuration; -pub use self::signal_generator::{Function, SignalGenerator}; pub use self::uniform::UniformSourceIterator; pub use self::zero::Zero; @@ -59,6 +59,7 @@ mod periodic; mod position; mod repeat; mod samples_converter; +mod signal_generator; mod sine; mod skip; mod skippable; @@ -66,7 +67,6 @@ mod spatial; mod speed; mod stoppable; mod take; -mod signal_generator; mod uniform; mod zero; diff --git a/src/source/sine.rs b/src/source/sine.rs index b7591900..da4f8b2c 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -56,8 +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, pos: Duration) -> Result<(), SeekError> { - self.test_sine.try_seek(pos) + fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { + Ok(()) } } From 135024650ebeac215a36e0876a8d52dde4c597fa Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 30 Sep 2024 22:02:46 -0700 Subject: [PATCH 30/30] rustfmt --- examples/signal_generator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/signal_generator.rs b/examples/signal_generator.rs index 802fd582..08fd4769 100644 --- a/examples/signal_generator.rs +++ b/examples/signal_generator.rs @@ -1,7 +1,7 @@ //! Test signal generator example. fn main() { - use rodio::source::{chirp, Function, Source, SignalGenerator}; + use rodio::source::{chirp, Function, SignalGenerator, Source}; use std::thread; use std::time::Duration;