1#[cfg(not(feature = "std"))]
2use num_traits::Float;
3
4use core::num::NonZeroU32;
5
6use firewheel_macros::{Diff, Patch};
7
8use crate::{
9 dsp::{
10 coeff_update::{CoeffUpdateFactor, CoeffUpdateMask},
11 filter::single_pole_iir::{OnePoleIirLPFCoeff, OnePoleIirLPFCoeffSimd, OnePoleIirLPFSimd},
12 },
13 param::smoother::{DEFAULT_GAIN_SPAN, SmoothedParam, SmootherConfig},
14};
15
16pub const MUFFLE_CUTOFF_HZ_MIN: f32 = 20.0;
17pub const MUFFLE_CUTOFF_HZ_MAX: f32 = 20_480.0;
18const MUFFLE_CUTOFF_HZ_RANGE_RECIP: f32 = 1.0 / (MUFFLE_CUTOFF_HZ_MAX - MUFFLE_CUTOFF_HZ_MIN);
19
20#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Diff, Patch)]
27#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub enum DistanceModel {
30 #[default]
31 Inverse,
37 Linear,
41 Exponential,
47}
48
49impl DistanceModel {
50 fn calculate_gain(
51 &self,
52 distance: f32,
53 distance_gain_factor: f32,
54 reference_distance: f32,
55 maximum_distance: f32,
56 ) -> f32 {
57 if distance <= reference_distance || distance_gain_factor <= 0.00001 {
58 return 1.0;
59 }
60
61 match self {
62 DistanceModel::Inverse => {
63 reference_distance
64 / (reference_distance
65 + (distance_gain_factor * (distance - reference_distance)))
66 }
67 DistanceModel::Linear => {
68 if maximum_distance <= reference_distance {
69 1.0
70 } else {
71 (1.0 - (distance_gain_factor * (distance - reference_distance)
72 / (maximum_distance - reference_distance)))
73 .clamp(0.0, 1.0)
74 }
75 }
76 DistanceModel::Exponential => {
77 (distance / reference_distance).powf(-distance_gain_factor)
78 }
79 }
80 }
81}
82
83#[derive(Diff, Patch, Debug, Clone, Copy, PartialEq)]
86#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
87#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
88pub struct DistanceAttenuation {
89 pub distance_model: DistanceModel,
98
99 pub distance_gain_factor: f32,
112
113 pub reference_distance: f32,
123
124 pub max_distance: f32,
135
136 pub distance_muffle_factor: f32,
149
150 pub max_muffle_distance: f32,
162
163 pub max_distance_muffle_cutoff_hz: f32,
174}
175
176impl Default for DistanceAttenuation {
177 fn default() -> Self {
178 Self {
179 distance_model: DistanceModel::Inverse,
180 distance_gain_factor: 1.0,
181 reference_distance: 5.0,
182 max_distance: 200.0,
183 distance_muffle_factor: 1.9,
184 max_muffle_distance: 200.0,
185 max_distance_muffle_cutoff_hz: 20.0,
186 }
187 }
188}
189
190pub struct DistanceAttenuatorStereoDsp {
191 pub gain: SmoothedParam,
192 pub muffle_cutoff_hz: SmoothedParam,
193 pub damping_disabled: bool,
194
195 pub filter: OnePoleIirLPFSimd<2>,
196 coeff_update_mask: CoeffUpdateMask,
197}
198
199impl DistanceAttenuatorStereoDsp {
200 pub fn new(
201 smoother_config: SmootherConfig,
202 sample_rate: NonZeroU32,
203 coeff_update_factor: CoeffUpdateFactor,
204 ) -> Self {
205 Self {
206 gain: SmoothedParam::new(1.0, DEFAULT_GAIN_SPAN, smoother_config, sample_rate),
207 muffle_cutoff_hz: SmoothedParam::new(
208 MUFFLE_CUTOFF_HZ_MAX,
209 MUFFLE_CUTOFF_HZ_MAX - MUFFLE_CUTOFF_HZ_MIN,
210 smoother_config,
211 sample_rate,
212 ),
213 damping_disabled: true,
214 filter: OnePoleIirLPFSimd::default(),
215 coeff_update_mask: coeff_update_factor.mask(),
216 }
217 }
218
219 pub fn set_coeff_update_factor(&mut self, coeff_update_factor: CoeffUpdateFactor) {
220 self.coeff_update_mask = coeff_update_factor.mask();
221 }
222
223 pub fn is_silent(&self) -> bool {
224 self.gain.target_value() == 0.0 && !self.gain.is_smoothing()
225 }
226
227 pub fn compute_values(
228 &mut self,
229 distance: f32,
230 params: &DistanceAttenuation,
231 muffle_cutoff_hz: f32,
232 min_gain: f32,
233 ) {
234 let reference_distance = params.reference_distance.max(0.00001);
235 let max_distance = params.max_distance.max(0.0);
236 let max_distance_muffle_cutoff_hz = params
237 .max_distance_muffle_cutoff_hz
238 .max(MUFFLE_CUTOFF_HZ_MIN);
239
240 let distance_gain = params.distance_model.calculate_gain(
241 distance,
242 params.distance_gain_factor,
243 reference_distance,
244 max_distance,
245 );
246
247 let gain = if distance_gain <= min_gain {
248 0.0
249 } else {
250 distance_gain
251 };
252
253 let distance_cutoff_norm = if params.distance_muffle_factor <= 0.00001
254 || distance <= reference_distance
255 || params.max_muffle_distance <= reference_distance
256 || max_distance_muffle_cutoff_hz >= MUFFLE_CUTOFF_HZ_MAX
257 {
258 1.0
259 } else {
260 let num = distance - reference_distance;
261 let den = params.max_muffle_distance - reference_distance;
262
263 let norm = 1.0 - (num / den).powf(params.distance_muffle_factor.recip());
264
265 let min_norm = (max_distance_muffle_cutoff_hz - MUFFLE_CUTOFF_HZ_MIN)
266 * MUFFLE_CUTOFF_HZ_RANGE_RECIP;
267
268 norm.max(min_norm)
269 };
270
271 let muffle_cutoff_hz = if (muffle_cutoff_hz < MUFFLE_CUTOFF_HZ_MAX - 0.01)
272 || distance_cutoff_norm < 1.0
273 {
274 let hz = if distance_cutoff_norm < 1.0 {
275 let muffle_cutoff_norm =
276 (muffle_cutoff_hz - MUFFLE_CUTOFF_HZ_MIN) * MUFFLE_CUTOFF_HZ_RANGE_RECIP;
277 let final_norm = muffle_cutoff_norm * distance_cutoff_norm;
278
279 (final_norm * (MUFFLE_CUTOFF_HZ_MAX - MUFFLE_CUTOFF_HZ_MIN)) + MUFFLE_CUTOFF_HZ_MIN
280 } else {
281 muffle_cutoff_hz
282 };
283
284 Some(hz.clamp(MUFFLE_CUTOFF_HZ_MIN, MUFFLE_CUTOFF_HZ_MAX))
285 } else {
286 None
287 };
288
289 self.gain.set_value(gain);
290
291 if let Some(cutoff_hz) = muffle_cutoff_hz {
292 self.muffle_cutoff_hz.set_value(cutoff_hz);
293 self.damping_disabled = false;
294 } else {
295 self.muffle_cutoff_hz.set_value(MUFFLE_CUTOFF_HZ_MAX);
296 self.damping_disabled = true;
297 }
298 }
299
300 pub fn process(
303 &mut self,
304 frames: usize,
305 out1: &mut [f32],
306 out2: &mut [f32],
307 sample_rate_recip: f64,
308 ) -> bool {
309 let out1 = &mut out1[..frames];
312 let out2 = &mut out2[..frames];
313
314 if !self.gain.is_smoothing() && !self.muffle_cutoff_hz.is_smoothing() {
315 if !self.gain.is_smoothing() && self.gain.target_value() == 0.0 {
316 self.gain.reset_to_target();
317 self.muffle_cutoff_hz.reset_to_target();
318 self.filter.reset();
319
320 return true;
321 } else if self.damping_disabled {
322 for i in 0..frames {
323 out1[i] *= self.gain.target_value();
324 out2[i] *= self.gain.target_value();
325 }
326 } else {
327 let coeff = OnePoleIirLPFCoeffSimd::splat(OnePoleIirLPFCoeff::new(
330 self.muffle_cutoff_hz.target_value(),
331 sample_rate_recip as f32,
332 ));
333
334 for i in 0..frames {
335 let s = [
336 out1[i] * self.gain.target_value(),
337 out2[i] * self.gain.target_value(),
338 ];
339
340 let [l, r] = self.filter.process(s, &coeff);
341
342 out1[i] = l;
343 out2[i] = r;
344 }
345 }
346 } else {
347 if self.damping_disabled && !self.muffle_cutoff_hz.is_smoothing() {
348 for i in 0..frames {
349 let gain = self.gain.next_smoothed();
350
351 out1[i] *= gain;
352 out2[i] *= gain;
353 }
354 } else {
355 let mut coeff = OnePoleIirLPFCoeffSimd::default();
356
357 for i in 0..frames {
358 let cutoff_hz = self.muffle_cutoff_hz.next_smoothed();
359 let gain = self.gain.next_smoothed();
360
361 if self.coeff_update_mask.do_update(i) {
368 coeff = OnePoleIirLPFCoeffSimd::splat(OnePoleIirLPFCoeff::new(
369 cutoff_hz,
370 sample_rate_recip as f32,
371 ));
372 }
373
374 let s = [out1[i] * gain, out2[i] * gain];
375
376 let [l, r] = self.filter.process(s, &coeff);
377
378 out1[i] = l;
379 out2[i] = r;
380 }
381 }
382
383 self.gain.settle();
384 self.muffle_cutoff_hz.settle();
385 }
386
387 false
388 }
389
390 pub fn reset(&mut self) {
391 self.gain.reset_to_target();
392 self.muffle_cutoff_hz.reset_to_target();
393 self.filter.reset();
394 }
395
396 pub fn set_smooth_seconds(&mut self, seconds: f32, sample_rate: NonZeroU32) {
397 self.gain.set_smooth_seconds(seconds, sample_rate);
398 self.muffle_cutoff_hz
399 .set_smooth_seconds(seconds, sample_rate);
400 }
401
402 pub fn update_sample_rate(&mut self, sample_rate: NonZeroU32) {
403 self.gain.update_sample_rate(sample_rate);
404 self.muffle_cutoff_hz.update_sample_rate(sample_rate);
405 }
406}