firewheel_nodes/freeverb/
mod.rs1#![allow(missing_docs)]
5#![allow(clippy::module_inception)]
6
7use firewheel_core::dsp::coeff_update::{CoeffUpdateFactor, CoeffUpdateMask};
8use firewheel_core::dsp::filter::smoothing_filter::DEFAULT_SMOOTH_SECONDS;
9use firewheel_core::node::NodeError;
10use firewheel_core::{
11 channel_config::{ChannelConfig, ChannelCount},
12 diff::{Diff, Notify, Patch},
13 dsp::{
14 declick::{DeclickFadeCurve, DeclickValues, Declicker},
15 volume::DEFAULT_MIN_AMP,
16 },
17 event::ProcEvents,
18 node::{
19 AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, EmptyConfig,
20 ProcBuffers, ProcExtra, ProcInfo, ProcStreamCtx, ProcessStatus,
21 },
22 param::smoother::{SmoothedParam, SmootherConfig},
23};
24
25use crate::freeverb::freeverb::Freeverb;
26
27mod all_pass;
28mod comb;
29mod delay_line;
30mod freeverb;
31
32#[derive(Diff, Patch, Clone, Copy, Debug, PartialEq)]
37#[cfg_attr(feature = "bevy", derive(bevy_ecs::component::Component))]
38#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub struct FreeverbNode {
41 pub room_size: f32,
46
47 pub damping: f32,
52
53 pub width: f32,
55
56 pub pause: bool,
61
62 #[cfg_attr(feature = "serde", serde(skip))]
64 pub reset: Notify<()>,
65
66 pub smooth_seconds: f32,
72
73 pub coeff_update_factor: CoeffUpdateFactor,
85}
86
87impl Default for FreeverbNode {
88 fn default() -> Self {
89 FreeverbNode {
90 room_size: 0.5,
91 damping: 0.5,
92 width: 0.5,
93 pause: false,
94 reset: Notify::new(()),
95 smooth_seconds: DEFAULT_SMOOTH_SECONDS,
96 coeff_update_factor: CoeffUpdateFactor::default(),
97 }
98 }
99}
100
101impl AudioNode for FreeverbNode {
102 type Configuration = EmptyConfig;
103
104 fn info(&self, _: &Self::Configuration) -> Result<AudioNodeInfo, NodeError> {
105 Ok(AudioNodeInfo::new()
106 .debug_name("freeverb")
107 .channel_config(ChannelConfig {
108 num_inputs: ChannelCount::STEREO,
109 num_outputs: ChannelCount::STEREO,
110 }))
111 }
114
115 fn construct_processor(
116 &self,
117 _: &Self::Configuration,
118 cx: ConstructProcessorContext,
119 ) -> Result<impl AudioNodeProcessor, NodeError> {
120 let freeverb = freeverb::Freeverb::new(cx.stream_info.sample_rate.get() as usize);
121 let smoother_config = SmootherConfig {
122 smooth_seconds: self.smooth_seconds,
123 ..Default::default()
124 };
125
126 let mut processor = FreeverbProcessor {
127 freeverb,
128 damping: SmoothedParam::new(
129 self.damping.clamp(0.0, 1.0),
130 1.0,
131 smoother_config,
132 cx.stream_info.sample_rate,
133 ),
134 width: SmoothedParam::new(
135 self.width.clamp(0.0, 1.0),
136 1.0,
137 smoother_config,
138 cx.stream_info.sample_rate,
139 ),
140 room_size: SmoothedParam::new(
141 self.room_size.clamp(0.0, 1.0),
142 1.0,
143 smoother_config,
144 cx.stream_info.sample_rate,
145 ),
146 paused: self.pause,
147 pause_declicker: if self.pause {
148 Declicker::SettledAt0
149 } else {
150 Declicker::SettledAt1
151 },
152 values: DeclickValues::new(cx.stream_info.declick_frames),
153 coeff_update_mask: self.coeff_update_factor.mask(),
154 prev_output_was_silent: true,
155 };
156
157 processor.apply_parameters();
158
159 Ok(processor)
160 }
161}
162
163struct FreeverbProcessor {
164 freeverb: freeverb::Freeverb,
165 damping: SmoothedParam,
166 width: SmoothedParam,
167 room_size: SmoothedParam,
168 paused: bool,
169 pause_declicker: Declicker,
170 values: DeclickValues,
171 coeff_update_mask: CoeffUpdateMask,
172 prev_output_was_silent: bool,
173}
174
175impl FreeverbProcessor {
176 fn reset(&mut self, reset_reverb: bool) {
177 self.pause_declicker.reset_to_target();
178 self.damping.reset_to_target();
179 self.room_size.reset_to_target();
180 self.width.reset_to_target();
181
182 if reset_reverb {
183 self.freeverb.reset();
184 }
185 }
186}
187
188impl AudioNodeProcessor for FreeverbProcessor {
189 fn events(&mut self, info: &ProcInfo, events: &mut ProcEvents, _extra: &mut ProcExtra) {
190 for patch in events.drain_patches::<FreeverbNode>() {
191 match patch {
192 FreeverbNodePatch::Damping(value) => {
193 self.damping.set_value(value.clamp(0.0, 1.0));
194 }
195 FreeverbNodePatch::RoomSize(value) => {
196 self.room_size.set_value(value.clamp(0.0, 1.0));
197 }
198 FreeverbNodePatch::Width(value) => {
199 self.width.set_value(value.clamp(0.0, 1.0));
200 }
201 FreeverbNodePatch::Reset(_) => {
202 self.freeverb.reset();
203 }
204 FreeverbNodePatch::Pause(value) => {
205 self.paused = value;
206
207 if value {
208 self.pause_declicker.fade_to_0(&self.values);
209 } else {
210 self.apply_parameters();
211 self.pause_declicker.fade_to_1(&self.values);
212 }
213 }
214 FreeverbNodePatch::SmoothSeconds(value) => {
215 self.room_size.set_smooth_seconds(value, info.sample_rate);
216 self.width.set_smooth_seconds(value, info.sample_rate);
217 self.damping.set_smooth_seconds(value, info.sample_rate);
218 }
219 FreeverbNodePatch::CoeffUpdateFactor(value) => {
220 self.coeff_update_mask = value.mask();
221 }
222 }
223 }
224 }
225
226 fn bypassed(&mut self, bypassed: bool) {
227 if !bypassed {
228 self.reset(true);
229 }
230 }
231
232 fn process(
233 &mut self,
234 info: &ProcInfo,
235 buffers: ProcBuffers,
236 _: &mut ProcExtra,
237 ) -> ProcessStatus {
238 let all_silent = info.in_silence_mask.all_channels_silent(2);
239
240 if (self.paused && self.pause_declicker.has_settled())
241 || (all_silent && self.prev_output_was_silent)
242 {
243 self.reset(false);
244
245 self.prev_output_was_silent = true;
246 return ProcessStatus::ClearAllOutputs;
247 }
248
249 if !all_silent && self.prev_output_was_silent {
250 self.apply_parameters();
252 }
253
254 assert!(buffers.inputs[0].len() >= info.frames);
255 assert!(buffers.inputs[1].len() >= info.frames);
256 assert!(buffers.outputs[0].len() >= info.frames);
257 assert!(buffers.outputs[1].len() >= info.frames);
258
259 if self.damping.is_smoothing() || self.room_size.is_smoothing() || self.width.is_smoothing()
261 {
262 for frame in 0..info.frames {
263 let damping = self.damping.next_smoothed();
264 let room_size = self.room_size.next_smoothed();
265 let width = self.width.next_smoothed();
266
267 if self.coeff_update_mask.do_update(frame) {
270 calc_coeffs(&mut self.freeverb, damping, room_size, width);
271 }
272
273 let (left, right) = self.freeverb.tick((
274 buffers.inputs[0][frame] as f64,
275 buffers.inputs[1][frame] as f64,
276 ));
277
278 buffers.outputs[0][frame] = left as f32;
279 buffers.outputs[1][frame] = right as f32;
280 }
281
282 self.damping.settle();
283 self.room_size.settle();
284 self.width.settle();
285 } else {
286 for frame in 0..info.frames {
287 let (left, right) = self.freeverb.tick((
288 buffers.inputs[0][frame] as f64,
289 buffers.inputs[1][frame] as f64,
290 ));
291
292 buffers.outputs[0][frame] = left as f32;
293 buffers.outputs[1][frame] = right as f32;
294 }
295 }
296
297 if all_silent && !self.prev_output_was_silent {
301 if matches!(
305 buffers.check_for_silence_on_outputs(DEFAULT_MIN_AMP),
306 ProcessStatus::ClearAllOutputs
307 ) {
308 self.prev_output_was_silent = true;
309 return ProcessStatus::ClearAllOutputs;
310 }
311 }
312
313 if !self.pause_declicker.has_settled() {
314 self.pause_declicker.process(
315 &mut buffers.outputs[..2],
316 0..info.frames,
317 &self.values,
318 1.0,
319 DeclickFadeCurve::EqualPower3dB,
320 );
321 }
322
323 self.prev_output_was_silent = false;
324
325 ProcessStatus::OutputsModified
326 }
327
328 fn new_stream(&mut self, stream_info: &firewheel_core::StreamInfo, _proc: &mut ProcStreamCtx) {
329 self.freeverb.resize(stream_info.sample_rate.get() as usize);
330 self.damping.update_sample_rate(stream_info.sample_rate);
331 self.width.update_sample_rate(stream_info.sample_rate);
332 self.room_size.update_sample_rate(stream_info.sample_rate);
333 self.reset(true);
334 self.prev_output_was_silent = true;
335 }
336}
337
338impl FreeverbProcessor {
339 fn apply_parameters(&mut self) {
340 self.freeverb
341 .set_dampening(self.damping.target_value() as f64);
342 self.freeverb
343 .set_room_size(self.room_size.target_value() as f64);
344 self.freeverb.set_width(self.width.target_value() as f64);
345 self.freeverb.update_combs();
346 }
347}
348
349#[cold]
350#[inline(never)]
351fn calc_coeffs(freeverb: &mut Freeverb, damping: f32, room_size: f32, width: f32) {
352 freeverb.set_dampening(damping as f64);
353 freeverb.set_room_size(room_size as f64);
354 freeverb.set_width(width as f64);
355
356 freeverb.update_combs();
357}