Skip to main content

rust_synth/math/
rnd.rs

1//! Deterministic pseudo-random scalar fields for modulation.
2//!
3//! Everything here is seeded + reproducible — the same `t` and `seed`
4//! always yield the same value. Used as organic, non-periodic modulation
5//! inside `lfo(|t| …)` to avoid the robotic feel of pure sine LFOs.
6
7/// xorshift64* — fast, deterministic. Good enough for modulation.
8#[inline]
9fn xorshift(mut x: u64) -> u64 {
10    x ^= x << 13;
11    x ^= x >> 7;
12    x ^= x << 17;
13    x.wrapping_mul(0x2545_F491_4F6C_DD1D)
14}
15
16#[inline]
17fn hash_u32(seed: u32) -> f32 {
18    let h = xorshift(seed as u64 | ((seed as u64) << 32));
19    ((h >> 40) as f32) / ((1u32 << 24) as f32) * 2.0 - 1.0
20}
21
22/// Value noise — step-interpolated lattice.
23/// Same integer → same value. Sample rate ≈ `freq` transitions per second.
24#[inline]
25pub fn value_noise(t: f32, freq: f32, seed: u32) -> f32 {
26    let idx = (t * freq).floor() as i32 as u32;
27    hash_u32(seed ^ idx)
28}
29
30/// 1-D Perlin-like noise (cubic interpolation between lattice points).
31/// Smooth, organic wobble for slow LFO modulation.
32pub fn perlin1d(t: f32, freq: f32, seed: u32) -> f32 {
33    let x = t * freq;
34    let i = x.floor();
35    let f = x - i;
36    let a = hash_u32(seed ^ (i as i32 as u32));
37    let b = hash_u32(seed ^ ((i as i32 + 1) as u32));
38    let s = f * f * (3.0 - 2.0 * f);
39    a + (b - a) * s
40}
41
42/// Brownian walk — integrated noise. Drifts over time, no periodicity.
43/// `step_hz` controls speed of drift; `scale` its amplitude.
44///
45/// NOTE: stateless approximation using summed perlin octaves — reproducible
46/// but not a true integral. For audio-rate randomness use `fundsp::noise()`.
47pub fn brown_walk(t: f32, step_hz: f32, scale: f32, seed: u32) -> f32 {
48    let mut sum = 0.0;
49    let mut amp = 1.0;
50    let mut freq = step_hz;
51    for octave in 0..4 {
52        sum += perlin1d(t, freq, seed.wrapping_add(octave * 131)) * amp;
53        amp *= 0.5;
54        freq *= 2.0;
55    }
56    sum * scale
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn deterministic() {
65        assert_eq!(value_noise(1.3, 2.0, 42), value_noise(1.3, 2.0, 42));
66        assert_eq!(perlin1d(0.7, 1.5, 7), perlin1d(0.7, 1.5, 7));
67    }
68
69    #[test]
70    fn perlin_in_range() {
71        for i in 0..1000 {
72            let v = perlin1d(i as f32 * 0.01, 3.7, 99);
73            assert!(v.abs() <= 1.01, "perlin out of range: {v}");
74        }
75    }
76}