1#[cfg(not(feature = "std"))]
2use num_traits::Float;
3
4use core::f32::consts::FRAC_PI_2;
5use core::{num::NonZeroU32, ops::Range};
6
7#[cfg(not(feature = "std"))]
8use bevy_platform::prelude::Vec;
9
10use crate::dsp::filter::smoothing_filter::{SmoothingFilter, SmoothingFilterCoeff};
11
12#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Declicker {
20 SettledAt0,
21 #[default]
22 SettledAt1,
23 FadingTo0 {
24 frames_left: usize,
25 },
26 FadingTo1 {
27 frames_left: usize,
28 },
29}
30
31impl Declicker {
32 pub fn from_enabled(enabled: bool) -> Self {
33 if enabled {
34 Self::SettledAt1
35 } else {
36 Self::SettledAt0
37 }
38 }
39
40 pub fn has_settled(&self) -> bool {
41 *self == Self::SettledAt0 || *self == Self::SettledAt1
42 }
43
44 pub fn disabled(&self) -> bool {
45 *self == Self::SettledAt0
46 }
47
48 pub fn fade_to_enabled(&mut self, enabled: bool, declick_values: &DeclickValues) {
49 if enabled {
50 self.fade_to_1(declick_values);
51 } else {
52 self.fade_to_0(declick_values);
53 }
54 }
55
56 pub fn fade_to_0(&mut self, declick_values: &DeclickValues) {
57 match self {
58 Self::SettledAt1 => {
59 *self = Self::FadingTo0 {
60 frames_left: declick_values.frames(),
61 }
62 }
63 Self::FadingTo1 { frames_left } => {
64 let frames_left = if *frames_left <= declick_values.frames() {
65 declick_values.frames() - *frames_left
66 } else {
67 declick_values.frames()
68 };
69
70 *self = Self::FadingTo0 { frames_left }
71 }
72 _ => {}
73 }
74 }
75
76 pub fn fade_to_1(&mut self, declick_values: &DeclickValues) {
77 match self {
78 Self::SettledAt0 => {
79 *self = Self::FadingTo1 {
80 frames_left: declick_values.frames(),
81 }
82 }
83 Self::FadingTo0 { frames_left } => {
84 let frames_left = if *frames_left <= declick_values.frames() {
85 declick_values.frames() - *frames_left
86 } else {
87 declick_values.frames()
88 };
89
90 *self = Self::FadingTo1 { frames_left }
91 }
92 _ => {}
93 }
94 }
95
96 pub fn reset_to_target(&mut self) {
98 *self = match &self {
99 Self::FadingTo0 { .. } => Self::SettledAt0,
100 Self::FadingTo1 { .. } => Self::SettledAt1,
101 s => **s,
102 }
103 }
104
105 pub fn reset_to_0(&mut self) {
106 *self = Self::SettledAt0;
107 }
108
109 pub fn reset_to_1(&mut self) {
110 *self = Self::SettledAt1;
111 }
112
113 pub fn process_crossfade<VA: AsRef<[f32]>, VB: AsMut<[f32]>>(
116 &mut self,
117 buffers_a: &[VA],
118 buffers_b: &mut [VB],
119 frames: usize,
120 declick_values: &DeclickValues,
121 fade_curve: DeclickFadeCurve,
122 ) {
123 let mut crossfade_buffers =
124 |declick_frames_left: &mut usize, values_a: &[f32], values_b: &[f32]| -> usize {
125 let process_frames = frames.min(*declick_frames_left);
126
127 let values_start = values_a.len() - *declick_frames_left;
128 let values_a = &values_a[values_start..values_start + process_frames];
129 let values_b = &values_b[values_start..values_start + process_frames];
130
131 for (ch_a, ch_b) in buffers_a.iter().zip(buffers_b.iter_mut()) {
132 let slice_a = &ch_a.as_ref()[..process_frames];
133 let slice_b = &mut ch_b.as_mut()[..process_frames];
134
135 for i in 0..process_frames {
136 slice_b[i] = (slice_a[i] * values_a[i]) + (slice_b[i] * values_b[i]);
137 }
138 }
139
140 *declick_frames_left -= process_frames;
141
142 process_frames
143 };
144
145 match self {
146 Self::SettledAt0 => {
147 for (ch_a, ch_b) in buffers_a.iter().zip(buffers_b.iter_mut()) {
148 let slice_a = &ch_a.as_ref()[..frames];
149 let slice_b = &mut ch_b.as_mut()[..frames];
150
151 slice_b.copy_from_slice(slice_a);
152 }
153 }
154 Self::FadingTo0 { frames_left } => {
155 let (values_a, values_b) = match fade_curve {
156 DeclickFadeCurve::Linear => (
157 &declick_values.linear_0_to_1_values,
158 &declick_values.linear_1_to_0_values,
159 ),
160 DeclickFadeCurve::EqualPower3dB => (
161 &declick_values.circular_0_to_1_values,
162 &declick_values.circular_1_to_0_values,
163 ),
164 };
165
166 let frames_processed = crossfade_buffers(frames_left, values_a, &values_b);
167
168 if frames_processed < frames {
169 for (ch_a, ch_b) in buffers_a.iter().zip(buffers_b.iter_mut()) {
170 let slice_a = &ch_a.as_ref()[frames_processed..frames];
171 let slice_b = &mut ch_b.as_mut()[frames_processed..frames];
172
173 slice_b.copy_from_slice(slice_a);
174 }
175 }
176
177 if *frames_left == 0 {
178 *self = Self::SettledAt0;
179 }
180 }
181 Self::FadingTo1 { frames_left } => {
182 let (values_a, values_b) = match fade_curve {
183 DeclickFadeCurve::Linear => (
184 &declick_values.linear_1_to_0_values,
185 &declick_values.linear_0_to_1_values,
186 ),
187 DeclickFadeCurve::EqualPower3dB => (
188 &declick_values.circular_1_to_0_values,
189 &declick_values.circular_0_to_1_values,
190 ),
191 };
192
193 crossfade_buffers(frames_left, values_a, values_b);
194
195 if *frames_left == 0 {
196 *self = Self::SettledAt1;
197 }
198 }
199 _ => {}
200 }
201 }
202
203 pub fn process<V: AsMut<[f32]>>(
204 &mut self,
205 buffers: &mut [V],
206 range_in_buffer: Range<usize>,
207 declick_values: &DeclickValues,
208 gain: f32,
209 fade_curve: DeclickFadeCurve,
210 ) {
211 let mut fade_buffers = |declick_frames_left: &mut usize, values: &[f32]| -> usize {
212 let buffer_frames = range_in_buffer.end - range_in_buffer.start;
213 let process_frames = buffer_frames.min(*declick_frames_left);
214 let start_frame = values.len() - *declick_frames_left;
215
216 if gain == 1.0 {
217 for b in buffers.iter_mut() {
218 let b = &mut b.as_mut()
219 [range_in_buffer.start..range_in_buffer.start + process_frames];
220
221 for (s, &g) in b
222 .iter_mut()
223 .zip(values[start_frame..start_frame + process_frames].iter())
224 {
225 *s *= g;
226 }
227 }
228 } else {
229 for b in buffers.iter_mut() {
230 let b = &mut b.as_mut()
231 [range_in_buffer.start..range_in_buffer.start + process_frames];
232
233 for (s, &g) in b
234 .iter_mut()
235 .zip(values[start_frame..start_frame + process_frames].iter())
236 {
237 *s *= g * gain;
238 }
239 }
240 }
241
242 *declick_frames_left -= process_frames;
243
244 process_frames
245 };
246
247 match self {
248 Self::SettledAt0 => {
249 for b in buffers.iter_mut() {
250 let b = &mut b.as_mut();
251 b[range_in_buffer.clone()].fill(0.0);
252 }
253 }
254 Self::FadingTo0 { frames_left } => {
255 let values = match fade_curve {
256 DeclickFadeCurve::Linear => &declick_values.linear_1_to_0_values,
257 DeclickFadeCurve::EqualPower3dB => &declick_values.circular_1_to_0_values,
258 };
259
260 let frames_processed = fade_buffers(frames_left, values);
261
262 if frames_processed < range_in_buffer.end - range_in_buffer.start {
263 for b in buffers.iter_mut() {
264 let b = &mut b.as_mut()
265 [range_in_buffer.start + frames_processed..range_in_buffer.end];
266 b.fill(0.0);
267 }
268 }
269
270 if *frames_left == 0 {
271 *self = Self::SettledAt0;
272 }
273 }
274 Self::FadingTo1 { frames_left } => {
275 let values = match fade_curve {
276 DeclickFadeCurve::Linear => &declick_values.linear_0_to_1_values,
277 DeclickFadeCurve::EqualPower3dB => &declick_values.circular_0_to_1_values,
278 };
279
280 let frames_processed = fade_buffers(frames_left, values);
281
282 if frames_processed < range_in_buffer.end - range_in_buffer.start && gain != 1.0 {
283 for b in buffers.iter_mut() {
284 let b = &mut b.as_mut()
285 [range_in_buffer.start + frames_processed..range_in_buffer.end];
286 for s in b.iter_mut() {
287 *s *= gain;
288 }
289 }
290 }
291
292 if *frames_left == 0 {
293 *self = Self::SettledAt1;
294 }
295 }
296 _ => {}
297 }
298 }
299
300 pub fn trending_towards_zero(&self) -> bool {
301 match self {
302 Declicker::SettledAt0 | Declicker::FadingTo0 { .. } => true,
303 _ => false,
304 }
305 }
306
307 pub fn trending_towards_one(&self) -> bool {
308 match self {
309 Declicker::SettledAt1 | Declicker::FadingTo1 { .. } => true,
310 _ => false,
311 }
312 }
313
314 pub fn frames_left(&self) -> usize {
315 match *self {
316 Declicker::FadingTo0 { frames_left } => frames_left,
317 Declicker::FadingTo1 { frames_left } => frames_left,
318 _ => 0,
319 }
320 }
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
324#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
325#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
326pub enum DeclickFadeCurve {
327 Linear,
329 EqualPower3dB,
331}
332
333pub struct DeclickValues {
338 pub linear_0_to_1_values: Vec<f32>,
339 pub linear_1_to_0_values: Vec<f32>,
340 pub circular_0_to_1_values: Vec<f32>,
341 pub circular_1_to_0_values: Vec<f32>,
342}
343
344impl DeclickValues {
345 pub const DEFAULT_FADE_SECONDS: f32 = 10.0 / 1_000.0;
346
347 pub fn new(frames: NonZeroU32) -> Self {
348 let frames = frames.get() as usize;
349 let frames_recip = (frames as f32).recip();
350
351 let mut linear_0_to_1_values = Vec::new();
352 let mut linear_1_to_0_values = Vec::new();
353 let mut circular_0_to_1_values = Vec::new();
354 let mut circular_1_to_0_values = Vec::new();
355
356 linear_0_to_1_values.reserve_exact(frames);
357 linear_1_to_0_values.reserve_exact(frames);
358 circular_0_to_1_values.reserve_exact(frames);
359 circular_1_to_0_values.reserve_exact(frames);
360
361 linear_0_to_1_values = (0..frames).map(|i| i as f32 * frames_recip).collect();
362 linear_1_to_0_values = (0..frames).rev().map(|i| i as f32 * frames_recip).collect();
363
364 circular_0_to_1_values = linear_0_to_1_values
365 .iter()
366 .map(|x| (x * FRAC_PI_2).sin())
367 .collect();
368 circular_1_to_0_values = circular_0_to_1_values.iter().rev().copied().collect();
369
370 Self {
371 linear_0_to_1_values,
372 linear_1_to_0_values,
373 circular_0_to_1_values,
374 circular_1_to_0_values,
375 }
376 }
377
378 pub fn frames(&self) -> usize {
379 self.linear_0_to_1_values.len()
380 }
381}
382
383#[derive(Debug)]
390pub struct LowpassDeclicker<const MAX_CHANNELS: usize> {
391 filters: [SmoothingFilter; MAX_CHANNELS],
392 coeff: SmoothingFilterCoeff,
393 smooth_secs: f32,
394 smooth_frames: usize,
395 smooth_frames_recip: f32,
396 frames_left: usize,
397}
398
399impl<const MAX_CHANNELS: usize> LowpassDeclicker<MAX_CHANNELS> {
400 pub fn new(sample_rate: NonZeroU32, smooth_secs: f32) -> Self {
401 let smooth_frames = ((smooth_secs * sample_rate.get() as f32).round() as usize).max(1);
402 let smooth_frames_recip = (smooth_frames as f32).recip();
403
404 Self {
405 filters: [SmoothingFilter::new(0.0); MAX_CHANNELS],
406 coeff: SmoothingFilterCoeff::new(sample_rate, smooth_secs),
407 smooth_secs,
408 smooth_frames,
409 smooth_frames_recip,
410 frames_left: 0,
411 }
412 }
413
414 pub fn is_declicking(&self) -> bool {
415 self.frames_left > 0
416 }
417
418 pub fn update_sample_rate(&mut self, sample_rate: NonZeroU32) {
419 self.coeff = SmoothingFilterCoeff::new(sample_rate, self.smooth_secs);
420 self.smooth_frames =
421 ((self.smooth_secs * sample_rate.get() as f32).round() as usize).max(1);
422 self.smooth_frames_recip = (self.smooth_frames as f32).recip();
423 }
424
425 pub fn begin(&mut self) {
426 self.frames_left = self.smooth_frames;
427 }
428
429 pub fn reset(&mut self) {
430 self.frames_left = 0;
431 }
432
433 pub fn process<V: AsMut<[f32]>>(&mut self, buffers: &mut [V], frames: usize) {
434 if frames == 0 {
435 return;
436 }
437
438 if self.frames_left == 0 {
439 for (buf, f) in buffers.iter_mut().zip(self.filters.iter_mut()) {
440 f.z1 = buf.as_mut()[frames - 1];
441 }
442
443 return;
444 }
445
446 let proc_frames = self.frames_left.min(frames);
447
448 if buffers.len().min(MAX_CHANNELS) == 2 {
449 let (buf_l, buf_r) = buffers.split_first_mut().unwrap();
452 let buf_l = &mut buf_l.as_mut()[..proc_frames];
453 let buf_r = &mut buf_r[0].as_mut()[..proc_frames];
454
455 let (f_l, f_r) = self.filters.split_first_mut().unwrap();
456 let f_r = &mut f_r[0];
457
458 for i in 0..proc_frames {
459 let filtered_l = f_l.process(buf_l[i], self.coeff);
460 let filtered_r = f_r.process(buf_r[i], self.coeff);
461
462 let filtered_mix = (self.frames_left - i) as f32 * self.smooth_frames_recip;
463
464 buf_l[i] = (filtered_l * filtered_mix) + (buf_l[i] * (1.0 - filtered_mix));
465 buf_r[i] = (filtered_r * filtered_mix) + (buf_r[i] * (1.0 - filtered_mix));
466 }
467 } else {
468 for (buf, f) in buffers.iter_mut().zip(self.filters.iter_mut()) {
469 for (i, s) in buf.as_mut()[..proc_frames].iter_mut().enumerate() {
470 let filtered_s = f.process(*s, self.coeff);
471
472 let filtered_mix = (self.frames_left - i) as f32 * self.smooth_frames_recip;
473
474 *s = (filtered_s * filtered_mix) + (*s * (1.0 - filtered_mix));
475 }
476 }
477 }
478
479 self.frames_left -= proc_frames;
480 }
481}