From 6f3bba4f09c654ef81b4548db8ea2374585ecf3f Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 2 Dec 2024 22:29:03 -0800 Subject: [PATCH 01/12] Added a fix to fix #207 --- src/source/signal_generator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index 54969ed5..ac008926 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -34,7 +34,7 @@ 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; + let cycle_pos: f32 = (i as f32).rem_euclid(period) / period; match self { Self::Sine => (TAU * cycle_pos).sin(), From 0b8aecfda06db6020b1ca61d3128e95238342c0e Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Thu, 5 Dec 2024 18:41:42 -0800 Subject: [PATCH 02/12] Re-implemented SignalGenerator to use wrap SignalGenerator's internal time variable is now a f32 and will wrap to prevent loss of precision over long-running sessions. --- src/source/signal_generator.rs | 64 ++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index ac008926..a63a3718 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -30,34 +30,33 @@ pub enum Function { 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).rem_euclid(period) / 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()), - } +fn sine_signal(theta: f32) -> f32 { + (TAU * theta).sin() +} + +fn triangle_signal(theta: f32) -> f32 { + 4.0f32 * (theta - (theta + 0.5f32).floor()).abs() - 1f32 +} + +fn square_signal(theta: f32) -> f32 { + if theta % 1.0f32 < 0.5f32 { + 1.0f32 + } else { + -1.0f32 } } +fn sawtooth_signal(theta: f32) -> f32 { + 2.0f32 * (theta - (theta + 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, + function: fn(f32) -> f32, + delta_theta: f32, + theta: f32, } impl SignalGenerator { @@ -71,11 +70,20 @@ impl SignalGenerator { 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; + let delta_theta = 1.0f32 / period; + + let function: fn(f32) -> f32 = match f { + Function::Sine => sine_signal, + Function::Triangle => triangle_signal, + Function::Square => square_signal, + Function::Sawtooth => sawtooth_signal, + }; + SignalGenerator { sample_rate, - period, - function: f, - i: 0, + function, + delta_theta, + theta: 0.0f32, } } } @@ -85,8 +93,9 @@ impl Iterator for SignalGenerator { #[inline] fn next(&mut self) -> Option { - let val = Some(self.function.render(self.i, self.period)); - self.i += 1; + let f = self.function; + let val = Some(f(self.theta)); + self.theta = (self.theta + self.delta_theta).rem_euclid(1.0f32); val } } @@ -114,7 +123,8 @@ impl Source for SignalGenerator { #[inline] fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { - self.i = (self.sample_rate.0 as f32 * duration.as_secs_f32()) as u64; + self.theta = self.sample_rate.0 as f32 * duration.as_secs_f32(); + self.theta = self.theta.rem_euclid(1.0f32); Ok(()) } } From b2aae2a83d3653991fc8db07195b42fbac24aa3e Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Thu, 5 Dec 2024 18:59:32 -0800 Subject: [PATCH 03/12] Added convenience interfaces for generators Added SawtoothWave, SquareWave and TriangleWave --- src/source/mod.rs | 6 ++++ src/source/sawtooth.rs | 69 ++++++++++++++++++++++++++++++++++++++++++ src/source/square.rs | 69 ++++++++++++++++++++++++++++++++++++++++++ src/source/triangle.rs | 69 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 213 insertions(+) create mode 100644 src/source/sawtooth.rs create mode 100644 src/source/square.rs create mode 100644 src/source/triangle.rs diff --git a/src/source/mod.rs b/src/source/mod.rs index ceac73bd..0ec01122 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -28,6 +28,7 @@ pub use self::pausable::Pausable; pub use self::periodic::PeriodicAccess; pub use self::position::TrackPosition; pub use self::repeat::Repeat; +pub use self::sawtooth::SawtoothWave; pub use self::samples_converter::SamplesConverter; pub use self::signal_generator::{Function, SignalGenerator}; pub use self::sine::SineWave; @@ -36,7 +37,9 @@ pub use self::skippable::Skippable; pub use self::spatial::Spatial; pub use self::speed::Speed; pub use self::stoppable::Stoppable; +pub use self::square::SquareWave; pub use self::take::TakeDuration; +pub use self::triangle::TriangleWave; pub use self::uniform::UniformSourceIterator; pub use self::zero::Zero; @@ -62,14 +65,17 @@ mod periodic; mod position; mod repeat; mod samples_converter; +mod sawtooth; mod signal_generator; mod sine; mod skip; mod skippable; mod spatial; mod speed; +mod square; mod stoppable; mod take; +mod triangle; mod uniform; mod zero; diff --git a/src/source/sawtooth.rs b/src/source/sawtooth.rs new file mode 100644 index 00000000..a7fc30b6 --- /dev/null +++ b/src/source/sawtooth.rs @@ -0,0 +1,69 @@ +use std::time::Duration; + +use crate::source::{Function, SignalGenerator}; +use crate::Source; + +use super::SeekError; + +/// An infinite source that produces a sawtooth wave. +/// +/// Always has a rate of 48kHz and one channel. +/// +/// This source is a thin interface on top of `SignalGenerator` provided for +/// your convenience.) +#[derive(Clone, Debug)] +pub struct SawtoothWave { + test_saw: SignalGenerator, +} + +impl SawtoothWave { + const SAMPLE_RATE: u32 = 48000; + + /// The frequency of the sine. + #[inline] + pub fn new(freq: f32) -> SawtoothWave { + let sr = cpal::SampleRate(Self::SAMPLE_RATE); + SawtoothWave { + test_saw: SignalGenerator::new(sr, freq, Function::Sawtooth ), + } + } +} + +impl Iterator for SawtoothWave { + type Item = f32; + + #[inline] + fn next(&mut self) -> Option { + self.test_saw.next() + } +} + +impl Source for SawtoothWave { + #[inline] + fn current_frame_len(&self) -> Option { + None + } + + #[inline] + fn channels(&self) -> u16 { + 1 + } + + #[inline] + fn sample_rate(&self) -> u32 { + Self::SAMPLE_RATE + } + + #[inline] + fn total_duration(&self) -> Option { + 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, _: Duration) -> Result<(), SeekError> { + Ok(()) + } +} diff --git a/src/source/square.rs b/src/source/square.rs new file mode 100644 index 00000000..e16f8949 --- /dev/null +++ b/src/source/square.rs @@ -0,0 +1,69 @@ +use std::time::Duration; + +use crate::source::{Function, SignalGenerator}; +use crate::Source; + +use super::SeekError; + +/// An infinite source that produces a square wave. +/// +/// Always has a rate of 48kHz and one channel. +/// +/// This source is a thin interface on top of `SignalGenerator` provided for +/// your convenience.) +#[derive(Clone, Debug)] +pub struct SquareWave { + test_square: SignalGenerator, +} + +impl SquareWave { + const SAMPLE_RATE: u32 = 48000; + + /// The frequency of the sine. + #[inline] + pub fn new(freq: f32) -> SquareWave { + let sr = cpal::SampleRate(Self::SAMPLE_RATE); + SquareWave { + test_square: SignalGenerator::new(sr, freq, Function::Square), + } + } +} + +impl Iterator for SquareWave { + type Item = f32; + + #[inline] + fn next(&mut self) -> Option { + self.test_square.next() + } +} + +impl Source for SquareWave { + #[inline] + fn current_frame_len(&self) -> Option { + None + } + + #[inline] + fn channels(&self) -> u16 { + 1 + } + + #[inline] + fn sample_rate(&self) -> u32 { + Self::SAMPLE_RATE + } + + #[inline] + fn total_duration(&self) -> Option { + 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, _: Duration) -> Result<(), SeekError> { + Ok(()) + } +} diff --git a/src/source/triangle.rs b/src/source/triangle.rs new file mode 100644 index 00000000..7e4be6cb --- /dev/null +++ b/src/source/triangle.rs @@ -0,0 +1,69 @@ +use std::time::Duration; + +use crate::source::{Function, SignalGenerator}; +use crate::Source; + +use super::SeekError; + +/// An infinite source that produces a triangle wave. +/// +/// Always has a rate of 48kHz and one channel. +/// +/// This source is a thin interface on top of `SignalGenerator` provided for +/// your convenience.) +#[derive(Clone, Debug)] +pub struct TriangleWave { + test_tri: SignalGenerator, +} + +impl TriangleWave { + const SAMPLE_RATE: u32 = 48000; + + /// The frequency of the sine. + #[inline] + pub fn new(freq: f32) -> TriangleWave { + let sr = cpal::SampleRate(Self::SAMPLE_RATE); + TriangleWave { + test_tri: SignalGenerator::new(sr, freq, Function::Triangle), + } + } +} + +impl Iterator for TriangleWave { + type Item = f32; + + #[inline] + fn next(&mut self) -> Option { + self.test_tri.next() + } +} + +impl Source for TriangleWave { + #[inline] + fn current_frame_len(&self) -> Option { + None + } + + #[inline] + fn channels(&self) -> u16 { + 1 + } + + #[inline] + fn sample_rate(&self) -> u32 { + Self::SAMPLE_RATE + } + + #[inline] + fn total_duration(&self) -> Option { + 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, _: Duration) -> Result<(), SeekError> { + Ok(()) + } +} From 0f4b9a605e8fefe9fe649c707899c4dd7be3f942 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Thu, 5 Dec 2024 19:03:26 -0800 Subject: [PATCH 04/12] rustfmt! --- src/source/mod.rs | 4 ++-- src/source/sawtooth.rs | 6 +++--- src/source/square.rs | 4 ++-- src/source/triangle.rs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/source/mod.rs b/src/source/mod.rs index 0ec01122..3c58c509 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -28,16 +28,16 @@ pub use self::pausable::Pausable; pub use self::periodic::PeriodicAccess; pub use self::position::TrackPosition; pub use self::repeat::Repeat; -pub use self::sawtooth::SawtoothWave; pub use self::samples_converter::SamplesConverter; +pub use self::sawtooth::SawtoothWave; pub use self::signal_generator::{Function, SignalGenerator}; pub use self::sine::SineWave; pub use self::skip::SkipDuration; pub use self::skippable::Skippable; pub use self::spatial::Spatial; pub use self::speed::Speed; -pub use self::stoppable::Stoppable; pub use self::square::SquareWave; +pub use self::stoppable::Stoppable; pub use self::take::TakeDuration; pub use self::triangle::TriangleWave; pub use self::uniform::UniformSourceIterator; diff --git a/src/source/sawtooth.rs b/src/source/sawtooth.rs index a7fc30b6..0173819e 100644 --- a/src/source/sawtooth.rs +++ b/src/source/sawtooth.rs @@ -7,9 +7,9 @@ use super::SeekError; /// An infinite source that produces a sawtooth wave. /// -/// Always has a rate of 48kHz and one channel. +/// Always has a rate of 48kHz and one channel. /// -/// This source is a thin interface on top of `SignalGenerator` provided for +/// This source is a thin interface on top of `SignalGenerator` provided for /// your convenience.) #[derive(Clone, Debug)] pub struct SawtoothWave { @@ -24,7 +24,7 @@ impl SawtoothWave { pub fn new(freq: f32) -> SawtoothWave { let sr = cpal::SampleRate(Self::SAMPLE_RATE); SawtoothWave { - test_saw: SignalGenerator::new(sr, freq, Function::Sawtooth ), + test_saw: SignalGenerator::new(sr, freq, Function::Sawtooth), } } } diff --git a/src/source/square.rs b/src/source/square.rs index e16f8949..61cdb9ec 100644 --- a/src/source/square.rs +++ b/src/source/square.rs @@ -7,9 +7,9 @@ use super::SeekError; /// An infinite source that produces a square wave. /// -/// Always has a rate of 48kHz and one channel. +/// Always has a rate of 48kHz and one channel. /// -/// This source is a thin interface on top of `SignalGenerator` provided for +/// This source is a thin interface on top of `SignalGenerator` provided for /// your convenience.) #[derive(Clone, Debug)] pub struct SquareWave { diff --git a/src/source/triangle.rs b/src/source/triangle.rs index 7e4be6cb..93765083 100644 --- a/src/source/triangle.rs +++ b/src/source/triangle.rs @@ -7,9 +7,9 @@ use super::SeekError; /// An infinite source that produces a triangle wave. /// -/// Always has a rate of 48kHz and one channel. +/// Always has a rate of 48kHz and one channel. /// -/// This source is a thin interface on top of `SignalGenerator` provided for +/// This source is a thin interface on top of `SignalGenerator` provided for /// your convenience.) #[derive(Clone, Debug)] pub struct TriangleWave { From 226464e389383d5ccfc312d7ddfbc0c311ed10c6 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Thu, 5 Dec 2024 19:19:49 -0800 Subject: [PATCH 05/12] Made small doc changes A typo I left when I made the new generators, also clarified "rate" to "sample rate" just so people didn't think we were talking about cycle frequency. --- src/source/sawtooth.rs | 4 ++-- src/source/sine.rs | 5 ++++- src/source/square.rs | 4 ++-- src/source/triangle.rs | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/source/sawtooth.rs b/src/source/sawtooth.rs index 0173819e..b021d489 100644 --- a/src/source/sawtooth.rs +++ b/src/source/sawtooth.rs @@ -7,10 +7,10 @@ use super::SeekError; /// An infinite source that produces a sawtooth wave. /// -/// Always has a rate of 48kHz and one channel. +/// Always has a sample rate of 48kHz and one channel. /// /// This source is a thin interface on top of `SignalGenerator` provided for -/// your convenience.) +/// your convenience. #[derive(Clone, Debug)] pub struct SawtoothWave { test_saw: SignalGenerator, diff --git a/src/source/sine.rs b/src/source/sine.rs index da4f8b2c..5aeab8bb 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -7,7 +7,10 @@ use super::SeekError; /// An infinite source that produces a sine. /// -/// Always has a rate of 48kHz and one channel. +/// Always has a sample rate of 48kHz and one channel. +/// +/// This source is a thin interface on top of `SignalGenerator` provided for +/// your convenience. #[derive(Clone, Debug)] pub struct SineWave { test_sine: SignalGenerator, diff --git a/src/source/square.rs b/src/source/square.rs index 61cdb9ec..13f116a9 100644 --- a/src/source/square.rs +++ b/src/source/square.rs @@ -7,10 +7,10 @@ use super::SeekError; /// An infinite source that produces a square wave. /// -/// Always has a rate of 48kHz and one channel. +/// Always has a sample rate of 48kHz and one channel. /// /// This source is a thin interface on top of `SignalGenerator` provided for -/// your convenience.) +/// your convenience. #[derive(Clone, Debug)] pub struct SquareWave { test_square: SignalGenerator, diff --git a/src/source/triangle.rs b/src/source/triangle.rs index 93765083..e58f3e0e 100644 --- a/src/source/triangle.rs +++ b/src/source/triangle.rs @@ -7,10 +7,10 @@ use super::SeekError; /// An infinite source that produces a triangle wave. /// -/// Always has a rate of 48kHz and one channel. +/// Always has a sample rate of 48kHz and one channel. /// /// This source is a thin interface on top of `SignalGenerator` provided for -/// your convenience.) +/// your convenience. #[derive(Clone, Debug)] pub struct TriangleWave { test_tri: SignalGenerator, From 723d2e354a7893321a88ab6f03e979a2a2d64ec3 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 6 Dec 2024 06:57:57 -0800 Subject: [PATCH 06/12] Removed try_seek implementation --- src/source/sawtooth.rs | 4 ++-- src/source/signal_generator.rs | 7 ++++--- src/source/square.rs | 4 ++-- src/source/triangle.rs | 5 ++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/source/sawtooth.rs b/src/source/sawtooth.rs index b021d489..ce260cf2 100644 --- a/src/source/sawtooth.rs +++ b/src/source/sawtooth.rs @@ -59,8 +59,8 @@ impl Source for SawtoothWave { 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 + /// `try_seek()` does nothing on the sawtooth generator. If you need to + /// generate a test signal with a precise phase or sample offset, consider /// using `skip::skip_samples()`. #[inline] fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index a63a3718..3dac950c 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -121,10 +121,11 @@ impl Source for SignalGenerator { None } + /// `try_seek()` does nothing on the signal generator. If you need to + /// generate a test signal with a precise phase or sample offset, consider + /// using `skip::skip_samples()`. #[inline] - fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { - self.theta = self.sample_rate.0 as f32 * duration.as_secs_f32(); - self.theta = self.theta.rem_euclid(1.0f32); + fn try_seek(&mut self, _duration: Duration) -> Result<(), SeekError> { Ok(()) } } diff --git a/src/source/square.rs b/src/source/square.rs index 13f116a9..d3db7dcb 100644 --- a/src/source/square.rs +++ b/src/source/square.rs @@ -59,8 +59,8 @@ impl Source for SquareWave { 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 + /// `try_seek()` does nothing on the squarewave generator. If you need to + /// generate a test signal with a precise phase or sample offset, consider /// using `skip::skip_samples()`. #[inline] fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { diff --git a/src/source/triangle.rs b/src/source/triangle.rs index e58f3e0e..d7c74403 100644 --- a/src/source/triangle.rs +++ b/src/source/triangle.rs @@ -59,9 +59,8 @@ impl Source for TriangleWave { 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()`. + /// `try_seek()` does nothing on the triangle wave generator. If you need to generate a test + /// signal tone with a precise phase or sample offset, consider using `skip::skip_samples()`. #[inline] fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { Ok(()) From 5eaa67b6cd44d255040e057e5dcb9a277fefdc6e Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 6 Dec 2024 07:24:16 -0800 Subject: [PATCH 07/12] Re-implemented try_seek() Also passed this implementation through to `TriangleWave`, `SquareWave` etc. --- src/source/sawtooth.rs | 7 ++----- src/source/signal_generator.rs | 9 +++++---- src/source/sine.rs | 7 ++----- src/source/square.rs | 7 ++----- src/source/triangle.rs | 6 ++---- 5 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/source/sawtooth.rs b/src/source/sawtooth.rs index ce260cf2..d34970af 100644 --- a/src/source/sawtooth.rs +++ b/src/source/sawtooth.rs @@ -59,11 +59,8 @@ impl Source for SawtoothWave { None } - /// `try_seek()` does nothing on the sawtooth generator. If you need to - /// generate a test signal with a precise phase or sample offset, consider - /// using `skip::skip_samples()`. #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { - Ok(()) + fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { + self.test_saw.try_seek(duration) } } diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index 3dac950c..bdaea286 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -57,6 +57,7 @@ pub struct SignalGenerator { function: fn(f32) -> f32, delta_theta: f32, theta: f32, + period: f32, } impl SignalGenerator { @@ -84,6 +85,7 @@ impl SignalGenerator { function, delta_theta, theta: 0.0f32, + period, } } } @@ -121,11 +123,10 @@ impl Source for SignalGenerator { None } - /// `try_seek()` does nothing on the signal generator. If you need to - /// generate a test signal with a precise phase or sample offset, consider - /// using `skip::skip_samples()`. #[inline] - fn try_seek(&mut self, _duration: Duration) -> Result<(), SeekError> { + fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { + let seek = duration.as_secs_f32() * (self.sample_rate.0 as f32) / self.period; + self.theta = seek.rem_euclid(1.0f32); Ok(()) } } diff --git a/src/source/sine.rs b/src/source/sine.rs index 5aeab8bb..f8b054c7 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -59,11 +59,8 @@ 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, _: Duration) -> Result<(), SeekError> { - Ok(()) + fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { + self.test_sine.try_seek(duration) } } diff --git a/src/source/square.rs b/src/source/square.rs index d3db7dcb..ea529653 100644 --- a/src/source/square.rs +++ b/src/source/square.rs @@ -59,11 +59,8 @@ impl Source for SquareWave { None } - /// `try_seek()` does nothing on the squarewave generator. If you need to - /// generate a test signal with a precise phase or sample offset, consider - /// using `skip::skip_samples()`. #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { - Ok(()) + fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { + self.test_square.try_seek(duration) } } diff --git a/src/source/triangle.rs b/src/source/triangle.rs index d7c74403..d77a8ccd 100644 --- a/src/source/triangle.rs +++ b/src/source/triangle.rs @@ -59,10 +59,8 @@ impl Source for TriangleWave { None } - /// `try_seek()` does nothing on the triangle wave generator. If you need to generate a test - /// signal tone with a precise phase or sample offset, consider using `skip::skip_samples()`. #[inline] - fn try_seek(&mut self, _: Duration) -> Result<(), SeekError> { - Ok(()) + fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { + self.test_tri.try_seek(duration) } } From 1354fc37fc5136ee499eb5003f83d73d333f6c0d Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 6 Dec 2024 07:27:15 -0800 Subject: [PATCH 08/12] Updated CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee9544e1..61a98b07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Output audio stream buffer size can now be adjusted. +- Sources for directly generating square waves, traingle waves, square waves and + sawtooths have been added. ### Changed - Breaking: `OutputStreamBuilder` should now be used to initialize audio output stream. @@ -20,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Symphonia decoder `total_duration` incorrect value caused by conversion from `Time` to `Duration`. +- An issue with `SignalGenerator` that caused it to create increasingly distorted waveforms + over long run times has been corrected. # Version 0.20.1 (2024-11-08) From f882a39e5809d3fadcdf245d8f05edaa437dc6c4 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 6 Dec 2024 09:52:34 -0800 Subject: [PATCH 09/12] Renamed struct fields --- src/source/signal_generator.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index bdaea286..ad2de3b0 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -55,8 +55,8 @@ fn sawtooth_signal(theta: f32) -> f32 { pub struct SignalGenerator { sample_rate: cpal::SampleRate, function: fn(f32) -> f32, - delta_theta: f32, - theta: f32, + phase_step: f32, + phase: f32, period: f32, } @@ -83,8 +83,8 @@ impl SignalGenerator { SignalGenerator { sample_rate, function, - delta_theta, - theta: 0.0f32, + phase_step: delta_theta, + phase: 0.0f32, period, } } @@ -96,8 +96,8 @@ impl Iterator for SignalGenerator { #[inline] fn next(&mut self) -> Option { let f = self.function; - let val = Some(f(self.theta)); - self.theta = (self.theta + self.delta_theta).rem_euclid(1.0f32); + let val = Some(f(self.phase)); + self.phase = (self.phase + self.phase_step).rem_euclid(1.0f32); val } } @@ -126,7 +126,7 @@ impl Source for SignalGenerator { #[inline] fn try_seek(&mut self, duration: Duration) -> Result<(), SeekError> { let seek = duration.as_secs_f32() * (self.sample_rate.0 as f32) / self.period; - self.theta = seek.rem_euclid(1.0f32); + self.phase = seek.rem_euclid(1.0f32); Ok(()) } } From 59a7fb93c076cba927caf81cc3b851731c5160a3 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 6 Dec 2024 10:12:54 -0800 Subject: [PATCH 10/12] Extracted GeneratorFunction type Also elaborated documentation, changed some param names. --- src/source/signal_generator.rs | 69 +++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index ad2de3b0..91cbd631 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -17,6 +17,24 @@ use std::time::Duration; use super::SeekError; use crate::Source; +/// Generator function. +/// +/// A generator function is the core of a signal generator, the `SignalGenerator` type uses these +/// function to create periodic waveforms. +/// +/// # Arguments +/// * An `f32` representing a time in the signal to generate. The scale of this variable is +/// normalized to the period of the signal, such that "0.0" is time zero, "1.0" is one period of +/// the signal, "2.0" is two periods and so on. This function should be written to accept any +/// float in the range (f32::MIN, f32::MAX) but `SignalGenerator` will only pass values in +/// (0.0, 1.0) to mitigate floating point error. +/// +/// # Returns +/// +/// An `f32` representing the signal level at the passed time. This value should be normalized +/// in the range [-1.0,1.0]. +type GeneratorFunction = fn(f32) -> f32; + /// Waveform functions. #[derive(Clone, Debug)] pub enum Function { @@ -30,60 +48,75 @@ pub enum Function { Sawtooth, } -fn sine_signal(theta: f32) -> f32 { - (TAU * theta).sin() +fn sine_signal(phase: f32) -> f32 { + (TAU * phase).sin() } -fn triangle_signal(theta: f32) -> f32 { - 4.0f32 * (theta - (theta + 0.5f32).floor()).abs() - 1f32 +fn triangle_signal(phase: f32) -> f32 { + 4.0f32 * (phase - (phase + 0.5f32).floor()).abs() - 1f32 } -fn square_signal(theta: f32) -> f32 { - if theta % 1.0f32 < 0.5f32 { +fn square_signal(phase: f32) -> f32 { + if phase % 1.0f32 < 0.5f32 { 1.0f32 } else { -1.0f32 } } -fn sawtooth_signal(theta: f32) -> f32 { - 2.0f32 * (theta - (theta + 0.5f32).floor()) +fn sawtooth_signal(phase: f32) -> f32 { + 2.0f32 * (phase - (phase + 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, - function: fn(f32) -> f32, + function: GeneratorFunction, phase_step: f32, phase: f32, period: f32, } impl SignalGenerator { - /// Create a new `TestWaveform` object that generates an endless waveform + /// Create a new `SignalGenerator` 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; - let delta_theta = 1.0f32 / period; - - let function: fn(f32) -> f32 = match f { + pub fn new(sample_rate: cpal::SampleRate, frequency: f32, f: Function) -> Self { + let function: GeneratorFunction = match f { Function::Sine => sine_signal, Function::Triangle => triangle_signal, Function::Square => square_signal, Function::Sawtooth => sawtooth_signal, }; + Self::with_function(sample_rate, frequency, function) + } + + /// Create a new `SignalGenerator` object that generates an endless waveform + /// from the [generator function](GeneratorFunction) `generator_function`. + /// + /// # Panics + /// + /// Will panic if `frequency` is equal to zero. + #[inline] + pub fn with_function( + sample_rate: cpal::SampleRate, + frequency: f32, + generator_function: GeneratorFunction, + ) -> Self { + assert!(frequency != 0.0, "frequency must be greater than zero"); + let period = sample_rate.0 as f32 / frequency; + let phase_step = 1.0f32 / period; + SignalGenerator { sample_rate, - function, - phase_step: delta_theta, + function: generator_function, + phase_step, phase: 0.0f32, period, } From 8130c5b4149bec94d90e3ff05c57a4810144a972 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 6 Dec 2024 10:21:40 -0800 Subject: [PATCH 11/12] Doc fixes for clippy --- src/source/signal_generator.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index 91cbd631..a66dc0bf 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -24,10 +24,10 @@ use crate::Source; /// /// # Arguments /// * An `f32` representing a time in the signal to generate. The scale of this variable is -/// normalized to the period of the signal, such that "0.0" is time zero, "1.0" is one period of -/// the signal, "2.0" is two periods and so on. This function should be written to accept any -/// float in the range (f32::MIN, f32::MAX) but `SignalGenerator` will only pass values in -/// (0.0, 1.0) to mitigate floating point error. +/// normalized to the period of the signal, such that "0.0" is time zero, "1.0" is one period of +/// the signal, "2.0" is two periods and so on. This function should be written to accept any +/// float in the range (`f32::MIN`, `f32::MAX`) but `SignalGenerator` will only pass values in +/// (0.0, 1.0) to mitigate floating point error. /// /// # Returns /// From a49ec7911e00f5deaac159822fc98e2554e4b265 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Fri, 6 Dec 2024 11:23:53 -0800 Subject: [PATCH 12/12] Added `GeneratorFunction` note to CHANGELOG And made the type `pub` --- CHANGELOG.md | 2 ++ src/source/signal_generator.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61a98b07..107ba47e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Output audio stream buffer size can now be adjusted. - Sources for directly generating square waves, traingle waves, square waves and sawtooths have been added. +- An interface for defining `SignalGenerator` patterns with an `fn`, see + `GeneratorFunction`. ### Changed - Breaking: `OutputStreamBuilder` should now be used to initialize audio output stream. diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index a66dc0bf..3997d167 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -33,7 +33,7 @@ use crate::Source; /// /// An `f32` representing the signal level at the passed time. This value should be normalized /// in the range [-1.0,1.0]. -type GeneratorFunction = fn(f32) -> f32; +pub type GeneratorFunction = fn(f32) -> f32; /// Waveform functions. #[derive(Clone, Debug)]