firewheel_nodes/freeverb/
mod.rs1#![allow(missing_docs)]
5#![allow(clippy::module_inception)]
6
7use firewheel_core::{
8 channel_config::{ChannelConfig, ChannelCount},
9 diff::{Diff, Notify, Patch},
10 dsp::declick::{DeclickFadeCurve, DeclickValues, Declicker},
11 event::ProcEvents,
12 node::{
13 AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, EmptyConfig,
14 ProcBuffers, ProcExtra, ProcInfo, ProcStreamCtx, ProcessStatus,
15 },
16 param::smoother::{SmoothedParam, SmootherConfig},
17};
18
19mod all_pass;
20mod comb;
21mod delay_line;
22mod freeverb;
23
24#[derive(Diff, Patch, Clone, Copy, Debug, PartialEq)]
29#[cfg_attr(feature = "bevy", derive(bevy_ecs::component::Component))]
30#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct FreeverbNode {
33 pub room_size: f32,
38
39 pub damping: f32,
44
45 pub width: f32,
47
48 pub pause: bool,
53
54 #[cfg_attr(feature = "serde", serde(skip))]
56 pub reset: Notify<()>,
57
58 pub smooth_seconds: f32,
62}
63
64impl Default for FreeverbNode {
65 fn default() -> Self {
66 FreeverbNode {
67 room_size: 0.5,
68 damping: 0.5,
69 width: 0.5,
70 pause: false,
71 reset: Notify::new(()),
72 smooth_seconds: 0.015,
73 }
74 }
75}
76
77impl AudioNode for FreeverbNode {
78 type Configuration = EmptyConfig;
79
80 fn info(&self, _: &Self::Configuration) -> AudioNodeInfo {
81 AudioNodeInfo::new()
82 .debug_name("freeverb")
83 .channel_config(ChannelConfig {
84 num_inputs: ChannelCount::STEREO,
85 num_outputs: ChannelCount::STEREO,
86 })
87 }
88
89 fn construct_processor(
90 &self,
91 _: &Self::Configuration,
92 cx: ConstructProcessorContext,
93 ) -> impl AudioNodeProcessor {
94 let freeverb = freeverb::Freeverb::new(cx.stream_info.sample_rate.get() as usize);
95 let smoother_config = SmootherConfig {
96 smooth_seconds: self.smooth_seconds,
97 ..Default::default()
98 };
99
100 let mut processor = FreeverbProcessor {
101 freeverb,
102 damping: SmoothedParam::new(
103 self.damping.clamp(0.0, 1.0),
104 smoother_config,
105 cx.stream_info.sample_rate,
106 ),
107 width: SmoothedParam::new(
108 self.width.clamp(0.0, 1.0),
109 smoother_config,
110 cx.stream_info.sample_rate,
111 ),
112 room_size: SmoothedParam::new(
113 self.room_size.clamp(0.0, 1.0),
114 smoother_config,
115 cx.stream_info.sample_rate,
116 ),
117 paused: self.pause,
118 declicker: if self.pause {
119 Declicker::SettledAt0
120 } else {
121 Declicker::SettledAt1
122 },
123 values: DeclickValues::new(cx.stream_info.declick_frames),
124 };
125
126 processor.apply_parameters();
127
128 processor
129 }
130}
131
132struct FreeverbProcessor {
133 freeverb: freeverb::Freeverb,
134 damping: SmoothedParam,
135 width: SmoothedParam,
136 room_size: SmoothedParam,
137 paused: bool,
138 declicker: Declicker,
139 values: DeclickValues,
140}
141
142impl AudioNodeProcessor for FreeverbProcessor {
143 fn process(
144 &mut self,
145 proc_info: &ProcInfo,
146 buffers: ProcBuffers,
147 events: &mut ProcEvents,
148 _: &mut ProcExtra,
149 ) -> ProcessStatus {
150 for patch in events.drain_patches::<FreeverbNode>() {
151 match patch {
152 FreeverbNodePatch::Damping(value) => {
153 self.damping.set_value(value.clamp(0.0, 1.0));
154 }
155 FreeverbNodePatch::RoomSize(value) => {
156 self.room_size.set_value(value.clamp(0.0, 1.0));
157 }
158 FreeverbNodePatch::Width(value) => {
159 self.width.set_value(value.clamp(0.0, 1.0));
160 }
161 FreeverbNodePatch::Reset(_) => {
162 self.freeverb.reset();
163 }
164 FreeverbNodePatch::Pause(value) => {
165 self.paused = value;
166
167 if value {
168 self.declicker.fade_to_0(&self.values);
169 } else {
170 self.apply_parameters();
171 self.declicker.fade_to_1(&self.values);
172 }
173 }
174 FreeverbNodePatch::SmoothSeconds(value) => {
175 self.room_size
176 .set_smooth_seconds(value, proc_info.sample_rate);
177 self.width.set_smooth_seconds(value, proc_info.sample_rate);
178 self.damping
179 .set_smooth_seconds(value, proc_info.sample_rate);
180 }
181 }
182 }
183
184 if self.paused && self.declicker.has_settled() {
185 self.damping.reset_to_target();
186 self.room_size.reset_to_target();
187 self.width.reset_to_target();
188
189 return ProcessStatus::ClearAllOutputs;
190 }
191
192 let all_silent = proc_info.in_silence_mask.all_channels_silent(2);
193 if all_silent && proc_info.prev_output_was_silent {
194 self.declicker.reset_to_target();
195 self.damping.reset_to_target();
196 self.room_size.reset_to_target();
197 self.width.reset_to_target();
198
199 return ProcessStatus::ClearAllOutputs;
200 }
201
202 if !all_silent && proc_info.prev_output_was_silent {
203 self.apply_parameters();
205 }
206
207 if self.damping.is_smoothing() || self.room_size.is_smoothing() || self.width.is_smoothing()
209 {
210 for frame in 0..proc_info.frames {
211 let damping = self.damping.next_smoothed();
212 let room_size = self.room_size.next_smoothed();
213 let width = self.width.next_smoothed();
214
215 if frame.is_multiple_of(4) {
218 self.freeverb.set_dampening(damping as f64);
219 self.freeverb.set_room_size(room_size as f64);
220 self.freeverb.set_width(width as f64);
221
222 self.freeverb.update_combs();
223 }
224
225 let (left, right) = self.freeverb.tick((
226 buffers.inputs[0][frame] as f64,
227 buffers.inputs[1][frame] as f64,
228 ));
229
230 buffers.outputs[0][frame] = left as f32;
231 buffers.outputs[1][frame] = right as f32;
232 }
233
234 self.damping.settle();
235 self.room_size.settle();
236 self.width.settle();
237 } else {
238 for frame in 0..proc_info.frames {
239 let (left, right) = self.freeverb.tick((
240 buffers.inputs[0][frame] as f64,
241 buffers.inputs[1][frame] as f64,
242 ));
243
244 buffers.outputs[0][frame] = left as f32;
245 buffers.outputs[1][frame] = right as f32;
246 }
247 }
248
249 if all_silent && !proc_info.prev_output_was_silent {
253 let threshold = 0.0001;
258 if matches!(
259 buffers.check_for_silence_on_outputs(threshold),
260 ProcessStatus::ClearAllOutputs
261 ) {
262 return ProcessStatus::ClearAllOutputs;
263 }
264 }
265
266 if !self.declicker.has_settled() {
267 self.declicker.process(
268 &mut buffers.outputs[..2],
269 0..proc_info.frames,
270 &self.values,
271 1.0,
272 DeclickFadeCurve::EqualPower3dB,
273 );
274 }
275
276 ProcessStatus::OutputsModified
277 }
278
279 fn new_stream(&mut self, stream_info: &firewheel_core::StreamInfo, _proc: &mut ProcStreamCtx) {
280 self.freeverb.resize(stream_info.sample_rate.get() as usize);
282 self.damping.update_sample_rate(stream_info.sample_rate);
283 self.width.update_sample_rate(stream_info.sample_rate);
284 self.room_size.update_sample_rate(stream_info.sample_rate);
285 }
286}
287
288impl FreeverbProcessor {
289 fn apply_parameters(&mut self) {
290 self.freeverb
291 .set_dampening(self.damping.target_value() as f64);
292 self.freeverb
293 .set_room_size(self.room_size.target_value() as f64);
294 self.freeverb.set_width(self.width.target_value() as f64);
295 self.freeverb.update_combs();
296 }
297}