From 59a5b1236738888699b32b161a894479b50741ca Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 19 Aug 2024 17:14:06 -0500 Subject: [PATCH] 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 0000000..43d2b30 --- /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 3448529..23f058d 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 04b450e..434cc99 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 b9c11a0..0fafcf0 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 b9615ae..420eac5 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);