Skip to main content

firewheel_nodes/noise_generator/
white.rs

1//! A simple node that generates white noise.
2
3use firewheel_core::{
4    channel_config::{ChannelConfig, ChannelCount},
5    diff::{Diff, Patch},
6    dsp::{
7        filter::smoothing_filter::DEFAULT_SMOOTH_SECONDS,
8        volume::{Volume, DEFAULT_AMP_EPSILON},
9    },
10    event::ProcEvents,
11    node::{
12        AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcBuffers,
13        ProcExtra, ProcInfo, ProcessStatus,
14    },
15    param::smoother::{SmoothedParam, SmootherConfig},
16};
17
18/// A simple node that generates white noise (Mono output only)
19#[derive(Diff, Patch, Debug, Clone, Copy, PartialEq)]
20#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
21#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub struct WhiteNoiseGenNode {
24    /// The overall volume.
25    ///
26    /// Note, white noise is really loud, so prefer to use a value like
27    /// `Volume::Linear(0.4)` or `Volume::Decibels(-18.0)`.
28    pub volume: Volume,
29    /// Whether or not this node is enabled.
30    pub enabled: bool,
31    /// The time in seconds of the internal smoothing filter.
32    ///
33    /// By default this is set to `0.015` (15ms).
34    pub smooth_seconds: f32,
35}
36
37impl Default for WhiteNoiseGenNode {
38    fn default() -> Self {
39        Self {
40            volume: Volume::Linear(0.4),
41            enabled: true,
42            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
43        }
44    }
45}
46
47/// The configuration for a [`WhiteNoiseGenNode`]
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
50#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52pub struct WhiteNoiseGenConfig {
53    /// The starting seed. This cannot be zero.
54    pub seed: i32,
55}
56
57impl Default for WhiteNoiseGenConfig {
58    fn default() -> Self {
59        Self { seed: 17 }
60    }
61}
62
63impl AudioNode for WhiteNoiseGenNode {
64    type Configuration = WhiteNoiseGenConfig;
65
66    fn info(&self, _config: &Self::Configuration) -> AudioNodeInfo {
67        AudioNodeInfo::new()
68            .debug_name("white_noise_gen")
69            .channel_config(ChannelConfig {
70                num_inputs: ChannelCount::ZERO,
71                num_outputs: ChannelCount::MONO,
72            })
73    }
74
75    fn construct_processor(
76        &self,
77        config: &Self::Configuration,
78        cx: ConstructProcessorContext,
79    ) -> impl AudioNodeProcessor {
80        // Seed cannot be zero.
81        let seed = if config.seed == 0 { 17 } else { config.seed };
82
83        Processor {
84            fpd: seed,
85            gain: SmoothedParam::new(
86                self.volume.amp_clamped(DEFAULT_AMP_EPSILON),
87                SmootherConfig {
88                    smooth_seconds: self.smooth_seconds,
89                    ..Default::default()
90                },
91                cx.stream_info.sample_rate,
92            ),
93            params: *self,
94        }
95    }
96}
97
98// The realtime processor counterpart to your node.
99struct Processor {
100    fpd: i32,
101    params: WhiteNoiseGenNode,
102    gain: SmoothedParam,
103}
104
105impl AudioNodeProcessor for Processor {
106    fn process(
107        &mut self,
108        info: &ProcInfo,
109        buffers: ProcBuffers,
110        events: &mut ProcEvents,
111        _extra: &mut ProcExtra,
112    ) -> ProcessStatus {
113        for patch in events.drain_patches::<WhiteNoiseGenNode>() {
114            match patch {
115                WhiteNoiseGenNodePatch::Volume(vol) => {
116                    self.gain.set_value(vol.amp_clamped(DEFAULT_AMP_EPSILON));
117                }
118                WhiteNoiseGenNodePatch::SmoothSeconds(seconds) => {
119                    self.gain.set_smooth_seconds(seconds, info.sample_rate);
120                }
121                _ => {}
122            }
123
124            self.params.apply(patch);
125        }
126
127        if !self.params.enabled || self.gain.has_settled_at_or_below(DEFAULT_AMP_EPSILON) {
128            self.gain.reset_to_target();
129            return ProcessStatus::ClearAllOutputs;
130        }
131
132        for s in buffers.outputs[0].iter_mut() {
133            self.fpd ^= self.fpd << 13;
134            self.fpd ^= self.fpd >> 17;
135            self.fpd ^= self.fpd << 5;
136
137            // Get a random normalized value in the range `[-1.0, 1.0]`.
138            let r = self.fpd as f32 * (1.0 / 2_147_483_648.0);
139
140            *s = r * self.gain.next_smoothed();
141        }
142
143        ProcessStatus::OutputsModified
144    }
145}