-
Notifications
You must be signed in to change notification settings - Fork 262
Add noise generators and improve their distribution #755
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
should it? I've never seen seek as reproducing something rather navigating in an underlying stream. I would say the seek implementation for noise like this should do nothing. But if we do go on with struct DisableSeek;
impl Source for DisableSeek {
....
fn try_seek() -> Result<() , _> {
Ok(())
}
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did not have time to look at the implementation in depth I do have some feedback/questions regarding the API and tests.
src/source/mod.rs
Outdated
@@ -87,7 +87,11 @@ mod zero; | |||
#[cfg(feature = "noise")] | |||
mod noise; | |||
#[cfg(feature = "noise")] | |||
pub use self::noise::{pink, white, PinkNoise, WhiteNoise}; | |||
pub use self::noise::{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could consider making the noise module public in source. Then the end user could access them like:
use rodio::source::noise;
let a = noise::brownian();
The advantage is that it makes it possible to have the word noise next to the noise generator. Alternative would be naming them all: <color>_noise
.
If we do make the noise folder public we could also remove the Noise
suffix from the structs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I'll do that.
tests/noise_generators.rs
Outdated
} | ||
|
||
#[test] | ||
fn test_pink_noise_with_seed_deterministic() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the equality tests can be de-duplicated using rstest
by making the functions generic over the noise kind.
See the seek.rs
tests for how rstest works
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll clean up the tests.
tests/noise_generators.rs
Outdated
} | ||
|
||
#[test] | ||
fn test_convenience_functions_work() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this tests anything not already tested by the above or verified by the compiler.
tests/noise_generators.rs
Outdated
} | ||
|
||
#[test] | ||
fn test_source_trait_properties() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, there's not really any complex logic that leads to these values. (They are hard-coded in the two macros).
tests/noise_generators.rs
Outdated
} | ||
|
||
#[test] | ||
fn test_different_seeds_produce_different_outputs() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels more like a unit test to me. Though one could argue this test prevents a regression I cant really imagine that happening. I think the compilers unused variable warnings would catch that right?
src/source/noise.rs
Outdated
//! let triangular = triangular_white(44100); // For TPDF dithering | ||
//! let blue = blue(44100); // For high-passed dithering applications | ||
//! | ||
//! // Advanced: create with custom RNG (useful for deterministic output) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the use-case for bringing a custom RNG? Is it reproducibility in future versions, or performance vs quality?
Having these generic adds a small amount of friction to the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As developer of downstream apps and libraries myself, I often have random generators in my own code. To create message or session UUIDs for example. Then, I want to keep it clean and have my entire codebase draw from that single generator. I dislike the bloat and redundancy of dependencies pulling in different Rng
s, increasing binary size and memory usage.
I believe the slight complexity in the API is OK, by keeping the simple white
, pink
, etc. functions that hide this from the user who is either novice or doesn't care.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dislike the bloat and redundancy of dependencies pulling in different Rngs, increasing binary size and memory usage.
I had no idea the increase was that big. I would think a RNG uses maybe a kB (for both). Especially if we use a small fast RNG.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may be right that it's mostly a mindset thing.
w.r.t. various RNGs: rand
's default Rng
is ThreadRng
and at some point SmallRng
was behind a feature gate. Then there are a couple more that users can experiment with.
We can still have best of both worlds:
impl WhiteNoise<SmallRng> {
pub fn new(sample_rate: SampleRate) -> Self {
Self::new_with_rng(sample_rate, SmallRng::from_os_rng())
}
}
impl<R: Rng + SeedableRng> NoiseGenerator<R> for WhiteNoise<R> {
fn new_with_rng(sample_rate: SampleRate, rng: R) -> Self {
// ...
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then there are a couple more that users can experiment with.
What is the effect of different rng's on the noise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Audibly nothing. Just pointing out that there's more than one or two Rng
s out there that users care for because of speed/uniformity/academia/fun/belief/whatever, and not difficult to support with both new
and new_with_rng
.
src/source/noise.rs
Outdated
/// use rodio::source::white; | ||
/// let white_noise = white(44100); // 44.1kHz sample rate | ||
/// ``` | ||
pub fn white(sample_rate: SampleRate) -> WhiteNoise<SmallRng> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if we want to continue having these factories. Maybe we should just have the structs with a new method each. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no particular preference. The current way doesn't break the API, is not super idiomatic, but also not super un-idiomatic. We could also keep just the existing white
and pink
ones and tag them as deprecated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SignalGenerator uses the struct::new instead of factory so it would align with those. I like the idea of keep white and pink and deprecating them. We can throw them out later.
src/source/noise.rs
Outdated
/// Trait providing common constructor patterns for noise generators. | ||
/// Provides default implementations for `new()` and `new_with_seed()` | ||
/// that delegate to `new_with_rng()`. | ||
pub trait NoiseGenerator<R: Rng + SeedableRng> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a use-case for a unified API for noise generating? If not then a macro might be a better fit. Then the user does not need to have the trait in scope. That will be especially relevant if we decided to drop the factory functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's a good point. I just did it for the purpose of DRY.
Here is an example of what would happen if we would "eat" seeks: // Create with a fixed seed for determinism
let mut noise = pink::new_with_seed(44100, 12345);
// Play for a while, changing internal state
for _ in 0..1000 { noise.next(); }
// Rewind to the beginning
noise.try_seek(Duration::from_secs(0)).unwrap();
// Create another generator with the same seed
let mut noise2 = pink::new_with_seed(44100, 12345);
// Noises should be the same, but are not
assert_eq!(noise.next(), noise2.next()); ...where you've got me however is that this is also true for my proposed implementation of Thinking out loud: maybe we should remove |
I think a deterministic noise gen is pretty neat but I struggle to think of a use case for it. Noise pretty much sounds the same regardless of seed right? If we can not find a good use case now I would propose we scrap determinism. We can always add |
Yes, let's scrap it. |
The commit improves and expands the noise generation capabilities by adding new types and fixing distribution issues in existing ones. Key changes: - Add more generators: Gaussian, triangular, blue, brownian, violet, velvet - Fix white noise uniform distribution - Fix pink noise sampling rate issues - Make noise generators properly deterministic with seeds - Add comprehensive tests for noise generator properties - Improve documentation with detailed usage guidance This provides a complete suite of high-quality noise generators for audio synthesis, testing, and dithering applications.
- Rename noise generators to PascalCase types (e.g. WhiteUniform, Pink) - Move all noise generators under `source::noise` module - Deprecate `white()` and `pink()` in favor of `noise::WhiteUniform::new()` and `noise::Pink::new()` - Remove legacy noise generator functions/types from prelude - Update `rstest`, `rstest_reuse` to fix templates in unit tests - Refactor and expand noise generator documentation and examples - Move and consolidate noise generator tests into `src/source/noise.rs` - Update `examples/noise_generator.rs` to use new API and add descriptions
94f0f53
to
377fbb9
Compare
Fixes
The current
WhiteNoise
generator has a problem where its distribution is not at all in range of[-1.0, 1.0]
due to precision loss, truncation and bias to zero from doing this:This PR fixes that by using a proper
f32
generated uniform distribution.Then the current
PinkNoise
builds on top of the incorrectWhiteNoise
but worse: has coefficients that are valid for 44.1 kHz only. This PR fixes that by algorithmically generating proper pink noise.Finally,
try_seek
should provide deterministic seeking (same results after seeking) andPinkNoise
cannot provide that. This PR correctly implementstry_seek
for noise generators that are deterministic (or uniformly random).New noise generators
Threw in some freebies:
Including documentation on when you'd want to use which.
I noticed that docs.rs didn't document the feature-gated noise generators, so I've set it to generate documentation for all features.
Advanced construction options
The current API remains as-is:
source::white()
will give you aWhiteGenerator<SmallRng>
. But you see that I made the generators generic for more choice to the user:source::WhiteNoise::<R: Rng>::new{_with_seed}()
.This now provides a rather complete suite of high-quality noise generators for audio synthesis, testing, and dithering.