Skip to main content

rill_core_dsp/generators/
noise.rs

1//! Noise generators (White, Pink, Brown, Blue, Violet)
2
3use super::Generator;
4use crate::filters::{FilterParams, FilterType, OnePole};
5use crate::vector::prelude::*;
6use rill_core::math::vector::scalar::ScalarVector4;
7use rill_core::math::vector::traits::Vector as VecTrait;
8use rill_core::traits::algorithm::{Algorithm, AlgorithmCategory, AlgorithmMetadata};
9use rill_core::traits::ProcessResult;
10use rill_core::Transcendental;
11
12/// Noise colour / spectral shape.
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum NoiseType {
15    /// Equal energy per Hz (flat spectrum).
16    White,
17    /// Equal energy per octave (3 dB/oct roll-off, 1/f).
18    Pink,
19    /// Brownian motion (6 dB/oct roll-off, 1/f²).
20    Brown,
21    /// Increasing with frequency (3 dB/oct rise).
22    Blue,
23    /// Strongly increasing (6 dB/oct rise).
24    Violet,
25}
26
27impl NoiseType {
28    /// Human-readable name of the noise type.
29    pub fn name(&self) -> &'static str {
30        match self {
31            NoiseType::White => "White Noise",
32            NoiseType::Pink => "Pink Noise",
33            NoiseType::Brown => "Brown Noise",
34            NoiseType::Blue => "Blue Noise",
35            NoiseType::Violet => "Violet Noise",
36        }
37    }
38
39    /// Short description of the noise type's spectral characteristic.
40    pub fn description(&self) -> &'static str {
41        match self {
42            NoiseType::White => "Equal energy per Hz",
43            NoiseType::Pink => "Equal energy per octave (1/f)",
44            NoiseType::Brown => "Brownian motion (1/f²)",
45            NoiseType::Blue => "Increasing with frequency (+3dB/oct)",
46            NoiseType::Violet => "Strongly increasing (+6dB/oct)",
47        }
48    }
49}
50
51/// Coloured noise generator (white, pink, brown, blue, violet).
52///
53/// Uses a Xorshift RNG for the white noise source and applies
54/// filtering / integration for the coloured variants.
55pub struct NoiseGenerator<T: Transcendental> {
56    noise_type: NoiseType,
57    amplitude: ScalarVector1<T>,
58    state: u32,
59    pink_filters: [OnePole<T>; 6],
60    brown_state: ScalarVector1<T>,
61    sample_rate: f32,
62    last_white: ScalarVector1<T>,
63    last_white1: ScalarVector1<T>,
64    last_white2: ScalarVector1<T>,
65}
66
67impl<T: Transcendental> NoiseGenerator<T> {
68    /// Create a new noise generator with the given colour and amplitude.
69    pub fn new(noise_type: NoiseType, amplitude: T) -> Self {
70        // Create OnePole filters via new with correct parameters
71        let filter_params = FilterParams {
72            filter_type: FilterType::LowPass,
73            cutoff: 1.0,
74            q: 0.707,
75            gain_db: 0.0,
76        };
77
78        Self {
79            noise_type,
80            amplitude: ScalarVector1::splat(amplitude),
81            state: 123456789,
82            pink_filters: [
83                OnePole::new(filter_params.clone()),
84                OnePole::new(filter_params.clone()),
85                OnePole::new(filter_params.clone()),
86                OnePole::new(filter_params.clone()),
87                OnePole::new(filter_params.clone()),
88                OnePole::new(filter_params),
89            ],
90            brown_state: ScalarVector1::splat(T::ZERO),
91            sample_rate: 44100.0,
92            last_white: ScalarVector1::splat(T::ZERO),
93            last_white1: ScalarVector1::splat(T::ZERO),
94            last_white2: ScalarVector1::splat(T::ZERO),
95        }
96    }
97
98    /// Xorshift RNG (operates on u32, returns f32 via Transcendental)
99    #[inline(always)]
100    fn xorshift(&mut self) -> T {
101        let mut x = self.state;
102
103        x ^= x << 13;
104        x ^= x >> 17;
105        x ^= x << 5;
106
107        self.state = x;
108
109        // Convert u32 to f32 in range [-1, 1]
110        // Use upper 24 bits for uniform distribution
111        let float_val = (x as f32 / 2147483648.0) - 1.0; // 2^31
112        T::from_f32(float_val)
113    }
114
115    /// Generate white noise — batched xorshift + SIMD amplitude
116    fn generate_white_block(&mut self, out: &mut [T]) {
117        let chunks = out.len() / 4;
118        let amp = ScalarVector4::splat(self.amplitude.extract(0));
119
120        for chunk in 0..chunks {
121            let offset = chunk * 4;
122            // Run xorshift 4 times in a batch
123            let w0 = self.xorshift();
124            let w1 = self.xorshift();
125            let w2 = self.xorshift();
126            let w3 = self.xorshift();
127
128            let v = ScalarVector4::load(&[w0, w1, w2, w3]);
129            v.mul(&amp).store(&mut out[offset..offset + 4]);
130        }
131
132        // Scalar remainder
133        for out in out[chunks * 4..].iter_mut() {
134            *out = self.generate_white().extract(0);
135        }
136    }
137
138    /// Generate white noise
139    #[inline(always)]
140    fn generate_white(&mut self) -> ScalarVector1<T> {
141        ScalarVector1::splat(self.xorshift()) * self.amplitude
142    }
143
144    /// Generate pink noise (1/f) — scalar path
145    fn generate_pink_scalar(&mut self) -> ScalarVector1<T> {
146        let white = self.xorshift();
147
148        // 6-band filter for 1/f approximation
149        let mut output = T::ZERO;
150        for filter in &mut self.pink_filters {
151            output = output.add(filter.process_sample(white));
152        }
153
154        ScalarVector1::splat(output) * self.amplitude / ScalarVector1::splat(T::from_f32(3.0))
155        // normalization
156    }
157
158    /// Generate brown noise — batched integrator with SIMD amplitude
159    fn generate_brown_block(&mut self, out: &mut [T]) {
160        let chunks = out.len() / 4;
161        let amp = self.amplitude.extract(0);
162        let factor = T::from_f32(0.1);
163        let one = T::ONE;
164        let neg_one = -T::ONE;
165        let mut state = self.brown_state.extract(0);
166
167        for chunk in 0..chunks {
168            let offset = chunk * 4;
169            let w0 = self.xorshift();
170            let w1 = self.xorshift();
171            let w2 = self.xorshift();
172            let w3 = self.xorshift();
173
174            // Unrolled integrator with per-step clamping
175            state = (state + w0 * factor).clamp(neg_one, one);
176            out[offset] = state * amp;
177
178            state = (state + w1 * factor).clamp(neg_one, one);
179            out[offset + 1] = state * amp;
180
181            state = (state + w2 * factor).clamp(neg_one, one);
182            out[offset + 2] = state * amp;
183
184            state = (state + w3 * factor).clamp(neg_one, one);
185            out[offset + 3] = state * amp;
186        }
187
188        self.brown_state = ScalarVector1::splat(state);
189
190        for out in out[chunks * 4..].iter_mut() {
191            *out = self.generate_brown_scalar().extract(0);
192        }
193    }
194
195    /// Generate brown noise — scalar path
196    fn generate_brown_scalar(&mut self) -> ScalarVector1<T> {
197        let white = self.xorshift();
198        // Integrator with clipping
199        self.brown_state =
200            self.brown_state + ScalarVector1::splat(white) * ScalarVector1::splat(T::from_f32(0.1));
201        let one_vec = ScalarVector1::splat(T::ONE);
202        let neg_one_vec = ScalarVector1::splat(-T::ONE);
203        self.brown_state = self.brown_state.clamp(&neg_one_vec, &one_vec);
204        self.brown_state * self.amplitude
205    }
206
207    /// Generate blue noise — SIMD batch of 4
208    fn generate_blue_block(&mut self, out: &mut [T]) {
209        let chunks = out.len() / 4;
210        let amp = self.amplitude.extract(0);
211        let mut last = self.last_white.extract(0);
212
213        for chunk in 0..chunks {
214            let offset = chunk * 4;
215            let w0 = self.xorshift();
216            let w1 = self.xorshift();
217            let w2 = self.xorshift();
218            let w3 = self.xorshift();
219
220            let white_v = ScalarVector4::load(&[w0, w1, w2, w3]);
221            let shifted_v = ScalarVector4::load(&[last, w0, w1, w2]);
222            let diff = white_v.sub(&shifted_v);
223            diff.mul(&ScalarVector4::splat(amp))
224                .store(&mut out[offset..offset + 4]);
225            last = w3;
226        }
227
228        self.last_white = ScalarVector1::splat(last);
229
230        // Scalar remainder
231        for out in out[chunks * 4..].iter_mut() {
232            *out = self.generate_blue_scalar().extract(0);
233        }
234    }
235
236    /// Generate violet noise — SIMD batch of 4
237    fn generate_violet_block(&mut self, out: &mut [T]) {
238        let chunks = out.len() / 4;
239        let amp = self.amplitude.extract(0);
240        let mut l1 = self.last_white1.extract(0);
241        let mut l2 = self.last_white2.extract(0);
242
243        for chunk in 0..chunks {
244            let offset = chunk * 4;
245            let w0 = self.xorshift();
246            let w1 = self.xorshift();
247            let w2 = self.xorshift();
248            let w3 = self.xorshift();
249
250            // First differentiator: diff1_v = [w0-l1, w1-w0, w2-w1, w3-w2]
251            let white_v = ScalarVector4::load(&[w0, w1, w2, w3]);
252            let s1_v = ScalarVector4::load(&[l1, w0, w1, w2]);
253            let diff1 = white_v.sub(&s1_v);
254
255            // Second differentiator: diff2_v = [d1_0-l2, d1_1-d1_0, d1_2-d1_1, d1_3-d1_2]
256            let s2_v =
257                ScalarVector4::load(&[l2, diff1.extract(0), diff1.extract(1), diff1.extract(2)]);
258            let diff2 = diff1.sub(&s2_v);
259
260            diff2
261                .mul(&ScalarVector4::splat(amp))
262                .store(&mut out[offset..offset + 4]);
263            l1 = w3;
264            l2 = diff1.extract(3);
265        }
266
267        self.last_white1 = ScalarVector1::splat(l1);
268        self.last_white2 = ScalarVector1::splat(l2);
269
270        for out in out[chunks * 4..].iter_mut() {
271            *out = self.generate_violet_scalar().extract(0);
272        }
273    }
274
275    /// Generate blue noise — scalar path
276    fn generate_blue_scalar(&mut self) -> ScalarVector1<T> {
277        let white = self.xorshift();
278        let white_vec = ScalarVector1::splat(white);
279        let diff = white_vec - self.last_white;
280        self.last_white = white_vec;
281        diff * self.amplitude
282    }
283
284    /// Generate violet noise — scalar path
285    fn generate_violet_scalar(&mut self) -> ScalarVector1<T> {
286        let white = self.xorshift();
287        let white_vec = ScalarVector1::splat(white);
288        let diff1 = white_vec - self.last_white1;
289        let diff2 = diff1 - self.last_white2;
290        self.last_white2 = diff1;
291        self.last_white1 = white_vec;
292        diff2 * self.amplitude
293    }
294}
295
296impl<T: Transcendental> Algorithm<T> for NoiseGenerator<T> {
297    fn init(&mut self, sample_rate: f32) {
298        self.sample_rate = sample_rate;
299
300        // Configure filters for pink noise
301        let freqs = [5.0, 15.0, 45.0, 135.0, 405.0, 1215.0];
302        for (i, &freq) in freqs.iter().enumerate() {
303            // Update filter parameters via set_cutoff
304            // Import the Filter trait for this
305            use crate::filters::Filter;
306            self.pink_filters[i].set_cutoff(freq);
307        }
308
309        self.reset();
310    }
311
312    fn reset(&mut self) {
313        self.state = 123456789;
314        self.brown_state = ScalarVector1::splat(T::ZERO);
315        self.last_white = ScalarVector1::splat(T::ZERO);
316        self.last_white1 = ScalarVector1::splat(T::ZERO);
317        self.last_white2 = ScalarVector1::splat(T::ZERO);
318        for filter in &mut self.pink_filters {
319            filter.reset();
320        }
321    }
322
323    fn process(&mut self, _input: Option<&[T]>, output: &mut [T]) -> ProcessResult<()> {
324        match self.noise_type {
325            NoiseType::White => self.generate_white_block(output),
326            NoiseType::Brown => self.generate_brown_block(output),
327            NoiseType::Blue => self.generate_blue_block(output),
328            NoiseType::Violet => self.generate_violet_block(output),
329            _ => {
330                for out in output.iter_mut() {
331                    *out = match self.noise_type {
332                        NoiseType::Pink => self.generate_pink_scalar().extract(0),
333                        _ => unreachable!(),
334                    };
335                }
336            }
337        }
338        Ok(())
339    }
340
341    fn metadata(&self) -> AlgorithmMetadata {
342        AlgorithmMetadata {
343            name: self.noise_type.name(),
344            category: AlgorithmCategory::Generator,
345            description: self.noise_type.description(),
346            author: "Rill",
347            version: env!("CARGO_PKG_VERSION"),
348        }
349    }
350}
351
352impl<T: Transcendental> Generator<T> for NoiseGenerator<T> {
353    fn phase(&self) -> T {
354        T::ZERO
355    } // Noise has no phase
356
357    fn set_phase(&mut self, _phase: T) {}
358
359    fn frequency(&self) -> f32 {
360        0.0
361    }
362
363    fn set_frequency(&mut self, _freq: f32) {}
364
365    fn amplitude(&self) -> T {
366        self.amplitude.extract(0)
367    }
368
369    fn set_amplitude(&mut self, amp: T) {
370        let one = T::from_f32(1.0);
371        let clamped = if amp > one {
372            one
373        } else if amp < T::ZERO {
374            T::ZERO
375        } else {
376            amp
377        };
378        self.amplitude = ScalarVector1::splat(clamped);
379    }
380}