firewheel_nodes/noise_generator/
pink.rs1use firewheel_core::{
6 channel_config::{ChannelConfig, ChannelCount},
7 diff::{Diff, Patch},
8 dsp::{
9 filter::smoothing_filter::DEFAULT_SMOOTH_SECONDS,
10 volume::{Volume, DEFAULT_AMP_EPSILON},
11 },
12 event::ProcEvents,
13 node::{
14 AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcBuffers,
15 ProcExtra, ProcInfo, ProcessStatus,
16 },
17 param::smoother::{SmoothedParam, SmootherConfig},
18};
19
20const COEFF_A: [i32; 5] = [14055, 12759, 10733, 12273, 15716];
21const COEFF_SUM: [i16; 5] = [22347, 27917, 29523, 29942, 30007];
22
23#[derive(Diff, Patch, Debug, Clone, Copy, PartialEq)]
25#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
26#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct PinkNoiseGenNode {
29 pub volume: Volume,
34 pub enabled: bool,
36 pub smooth_seconds: f32,
40}
41
42impl Default for PinkNoiseGenNode {
43 fn default() -> Self {
44 Self {
45 volume: Volume::Linear(0.4),
46 enabled: true,
47 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
48 }
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
55#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub struct PinkNoiseGenConfig {
58 pub seed: i32,
60}
61
62impl Default for PinkNoiseGenConfig {
63 fn default() -> Self {
64 Self { seed: 17 }
65 }
66}
67
68impl AudioNode for PinkNoiseGenNode {
69 type Configuration = PinkNoiseGenConfig;
70
71 fn info(&self, _config: &Self::Configuration) -> AudioNodeInfo {
72 AudioNodeInfo::new()
73 .debug_name("pink_noise_gen")
74 .channel_config(ChannelConfig {
75 num_inputs: ChannelCount::ZERO,
76 num_outputs: ChannelCount::MONO,
77 })
78 }
79
80 fn construct_processor(
81 &self,
82 config: &Self::Configuration,
83 cx: ConstructProcessorContext,
84 ) -> impl AudioNodeProcessor {
85 let seed = if config.seed == 0 { 17 } else { config.seed };
87
88 Processor {
89 gain: SmoothedParam::new(
90 self.volume.amp_clamped(DEFAULT_AMP_EPSILON),
91 SmootherConfig {
92 smooth_seconds: self.smooth_seconds,
93 ..Default::default()
94 },
95 cx.stream_info.sample_rate,
96 ),
97 params: *self,
98 fpd: seed,
99 contrib: [0; 5],
100 accum: 0,
101 }
102 }
103}
104
105struct Processor {
107 params: PinkNoiseGenNode,
108 gain: SmoothedParam,
109
110 fpd: i32,
112
113 contrib: [i32; 5],
115 accum: i32,
116}
117
118impl AudioNodeProcessor for Processor {
119 fn process(
120 &mut self,
121 info: &ProcInfo,
122 buffers: ProcBuffers,
123 events: &mut ProcEvents,
124 _extra: &mut ProcExtra,
125 ) -> ProcessStatus {
126 for patch in events.drain_patches::<PinkNoiseGenNode>() {
127 match patch {
128 PinkNoiseGenNodePatch::Volume(vol) => {
129 self.gain.set_value(vol.amp_clamped(DEFAULT_AMP_EPSILON));
130 }
131 PinkNoiseGenNodePatch::SmoothSeconds(seconds) => {
132 self.gain.set_smooth_seconds(seconds, info.sample_rate);
133 }
134 _ => {}
135 }
136
137 self.params.apply(patch);
138 }
139
140 if !self.params.enabled || self.gain.has_settled_at_or_below(DEFAULT_AMP_EPSILON) {
141 self.gain.reset_to_target();
142 return ProcessStatus::ClearAllOutputs;
143 }
144
145 for s in buffers.outputs[0].iter_mut() {
146 let randu: i16 = (rng(&mut self.fpd) & 0x7fff) as i16;
148
149 let r_bytes = rng(&mut self.fpd).to_ne_bytes();
151 let randv: i32 = i16::from_ne_bytes([r_bytes[0], r_bytes[1]]) as i32;
152
153 if randu < COEFF_SUM[0] {
154 update_contrib::<0>(&mut self.accum, &mut self.contrib, randv);
155 } else if randu < COEFF_SUM[1] {
156 update_contrib::<1>(&mut self.accum, &mut self.contrib, randv);
157 } else if randu < COEFF_SUM[2] {
158 update_contrib::<2>(&mut self.accum, &mut self.contrib, randv);
159 } else if randu < COEFF_SUM[3] {
160 update_contrib::<3>(&mut self.accum, &mut self.contrib, randv);
161 } else if randu < COEFF_SUM[4] {
162 update_contrib::<4>(&mut self.accum, &mut self.contrib, randv);
163 }
164
165 let r = self.accum as f32 * (1.0 / 2_147_483_648.0);
167
168 *s = r * self.gain.next_smoothed();
169 }
170
171 ProcessStatus::OutputsModified
172 }
173}
174
175#[inline(always)]
176fn rng(fpd: &mut i32) -> i32 {
177 *fpd ^= *fpd << 13;
178 *fpd ^= *fpd >> 17;
179 *fpd ^= *fpd << 5;
180
181 *fpd
182}
183
184#[inline(always)]
185fn update_contrib<const I: usize>(accum: &mut i32, contrib: &mut [i32; 5], randv: i32) {
186 *accum = accum.wrapping_sub(contrib[I]);
187 contrib[I] = randv * COEFF_A[I];
188 *accum = accum.wrapping_add(contrib[I]);
189}