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::{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, smoother_config, sample_rate),
207 muffle_cutoff_hz: SmoothedParam::new(
208 MUFFLE_CUTOFF_HZ_MAX,
209 smoother_config,
210 sample_rate,
211 ),
212 damping_disabled: true,
213 filter: OnePoleIirLPFSimd::default(),
214 coeff_update_mask: coeff_update_factor.mask(),
215 }
216 }
217
218 pub fn set_coeff_update_factor(&mut self, coeff_update_factor: CoeffUpdateFactor) {
219 self.coeff_update_mask = coeff_update_factor.mask();
220 }
221
222 pub fn is_silent(&self) -> bool {
223 self.gain.target_value() == 0.0 && !self.gain.is_smoothing()
224 }
225
226 pub fn compute_values(
227 &mut self,
228 distance: f32,
229 params: &DistanceAttenuation,
230 muffle_cutoff_hz: f32,
231 min_gain: f32,
232 ) {
233 let reference_distance = params.reference_distance.max(0.00001);
234 let max_distance = params.max_distance.max(0.0);
235 let max_distance_muffle_cutoff_hz = params
236 .max_distance_muffle_cutoff_hz
237 .max(MUFFLE_CUTOFF_HZ_MIN);
238
239 let distance_gain = params.distance_model.calculate_gain(
240 distance,
241 params.distance_gain_factor,
242 reference_distance,
243 max_distance,
244 );
245
246 let gain = if distance_gain <= min_gain {
247 0.0
248 } else {
249 distance_gain
250 };
251
252 let distance_cutoff_norm = if params.distance_muffle_factor <= 0.00001
253 || distance <= reference_distance
254 || params.max_muffle_distance <= reference_distance
255 || max_distance_muffle_cutoff_hz >= MUFFLE_CUTOFF_HZ_MAX
256 {
257 1.0
258 } else {
259 let num = distance - reference_distance;
260 let den = params.max_muffle_distance - reference_distance;
261
262 let norm = 1.0 - (num / den).powf(params.distance_muffle_factor.recip());
263
264 let min_norm = (max_distance_muffle_cutoff_hz - MUFFLE_CUTOFF_HZ_MIN)
265 * MUFFLE_CUTOFF_HZ_RANGE_RECIP;
266
267 norm.max(min_norm)
268 };
269
270 let muffle_cutoff_hz = if (muffle_cutoff_hz < MUFFLE_CUTOFF_HZ_MAX - 0.01)
271 || distance_cutoff_norm < 1.0
272 {
273 let hz = if distance_cutoff_norm < 1.0 {
274 let muffle_cutoff_norm =
275 (muffle_cutoff_hz - MUFFLE_CUTOFF_HZ_MIN) * MUFFLE_CUTOFF_HZ_RANGE_RECIP;
276 let final_norm = muffle_cutoff_norm * distance_cutoff_norm;
277
278 (final_norm * (MUFFLE_CUTOFF_HZ_MAX - MUFFLE_CUTOFF_HZ_MIN)) + MUFFLE_CUTOFF_HZ_MIN
279 } else {
280 muffle_cutoff_hz
281 };
282
283 Some(hz.clamp(MUFFLE_CUTOFF_HZ_MIN, MUFFLE_CUTOFF_HZ_MAX))
284 } else {
285 None
286 };
287
288 self.gain.set_value(gain);
289
290 if let Some(cutoff_hz) = muffle_cutoff_hz {
291 self.muffle_cutoff_hz.set_value(cutoff_hz);
292 self.damping_disabled = false;
293 } else {
294 self.muffle_cutoff_hz.set_value(MUFFLE_CUTOFF_HZ_MAX);
295 self.damping_disabled = true;
296 }
297 }
298
299 pub fn process(
302 &mut self,
303 frames: usize,
304 out1: &mut [f32],
305 out2: &mut [f32],
306 sample_rate_recip: f64,
307 ) -> bool {
308 let out1 = &mut out1[..frames];
311 let out2 = &mut out2[..frames];
312
313 if !self.gain.is_smoothing() && !self.muffle_cutoff_hz.is_smoothing() {
314 if !self.gain.is_smoothing() && self.gain.target_value() == 0.0 {
315 self.gain.reset_to_target();
316 self.muffle_cutoff_hz.reset_to_target();
317 self.filter.reset();
318
319 return true;
320 } else if self.damping_disabled {
321 for i in 0..frames {
322 out1[i] = out1[i] * self.gain.target_value();
323 out2[i] = out2[i] * self.gain.target_value();
324 }
325 } else {
326 let coeff = OnePoleIirLPFCoeffSimd::splat(OnePoleIirLPFCoeff::new(
329 self.muffle_cutoff_hz.target_value(),
330 sample_rate_recip as f32,
331 ));
332
333 for i in 0..frames {
334 let s = [
335 out1[i] * self.gain.target_value(),
336 out2[i] * self.gain.target_value(),
337 ];
338
339 let [l, r] = self.filter.process(s, &coeff);
340
341 out1[i] = l;
342 out2[i] = r;
343 }
344 }
345 } else {
346 if self.damping_disabled && !self.muffle_cutoff_hz.is_smoothing() {
347 for i in 0..frames {
348 let gain = self.gain.next_smoothed();
349
350 out1[i] = out1[i] * gain;
351 out2[i] = out2[i] * gain;
352 }
353 } else {
354 let mut coeff = OnePoleIirLPFCoeffSimd::default();
355
356 for i in 0..frames {
357 let cutoff_hz = self.muffle_cutoff_hz.next_smoothed();
358 let gain = self.gain.next_smoothed();
359
360 if self.coeff_update_mask.do_update(i) {
367 coeff = OnePoleIirLPFCoeffSimd::splat(OnePoleIirLPFCoeff::new(
368 cutoff_hz,
369 sample_rate_recip as f32,
370 ));
371 }
372
373 let s = [out1[i] * gain, out2[i] * gain];
374
375 let [l, r] = self.filter.process(s, &coeff);
376
377 out1[i] = l;
378 out2[i] = r;
379 }
380 }
381
382 self.gain.settle();
383 self.muffle_cutoff_hz.settle();
384 }
385
386 false
387 }
388
389 pub fn reset(&mut self) {
390 self.gain.reset_to_target();
391 self.muffle_cutoff_hz.reset_to_target();
392 self.filter.reset();
393 }
394
395 pub fn set_smooth_seconds(&mut self, seconds: f32, sample_rate: NonZeroU32) {
396 self.gain.set_smooth_seconds(seconds, sample_rate);
397 self.muffle_cutoff_hz
398 .set_smooth_seconds(seconds, sample_rate);
399 }
400
401 pub fn update_sample_rate(&mut self, sample_rate: NonZeroU32) {
402 self.gain.update_sample_rate(sample_rate);
403 self.muffle_cutoff_hz.update_sample_rate(sample_rate);
404 }
405}