Skip to main content

firewheel_nodes/noise_generator/
white.rs

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