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::filter::single_pole_iir::{OnePoleIirLPF, OnePoleIirLPFCoeff},
10 param::smoother::{SmoothedParam, SmootherConfig},
11};
12
13pub const MUFFLE_CUTOFF_HZ_MIN: f32 = 20.0;
14pub const MUFFLE_CUTOFF_HZ_MAX: f32 = 20_480.0;
15const MUFFLE_CUTOFF_HZ_RANGE_RECIP: f32 = 1.0 / (MUFFLE_CUTOFF_HZ_MAX - MUFFLE_CUTOFF_HZ_MIN);
16const CALC_FILTER_COEFF_INTERVAL: usize = 8;
17
18#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Diff, Patch)]
25#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
26#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
27pub enum DistanceModel {
28 #[default]
29 Inverse,
35 Linear,
39 Exponential,
45}
46
47impl DistanceModel {
48 fn calculate_gain(
49 &self,
50 distance: f32,
51 distance_gain_factor: f32,
52 reference_distance: f32,
53 maximum_distance: f32,
54 ) -> f32 {
55 if distance <= reference_distance || distance_gain_factor <= 0.00001 {
56 return 1.0;
57 }
58
59 match self {
60 DistanceModel::Inverse => {
61 reference_distance
62 / (reference_distance
63 + (distance_gain_factor * (distance - reference_distance)))
64 }
65 DistanceModel::Linear => {
66 if maximum_distance <= reference_distance {
67 1.0
68 } else {
69 (1.0 - (distance_gain_factor * (distance - reference_distance)
70 / (maximum_distance - reference_distance)))
71 .clamp(0.0, 1.0)
72 }
73 }
74 DistanceModel::Exponential => {
75 (distance / reference_distance).powf(-distance_gain_factor)
76 }
77 }
78 }
79}
80
81#[derive(Diff, Patch, Debug, Clone, Copy, PartialEq)]
84#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
85#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
86pub struct DistanceAttenuation {
87 pub distance_model: DistanceModel,
96
97 pub distance_gain_factor: f32,
110
111 pub reference_distance: f32,
121
122 pub max_distance: f32,
133
134 pub distance_muffle_factor: f32,
147
148 pub max_muffle_distance: f32,
160
161 pub max_distance_muffle_cutoff_hz: f32,
172}
173
174impl Default for DistanceAttenuation {
175 fn default() -> Self {
176 Self {
177 distance_model: DistanceModel::Inverse,
178 distance_gain_factor: 1.0,
179 reference_distance: 5.0,
180 max_distance: 200.0,
181 distance_muffle_factor: 1.9,
182 max_muffle_distance: 200.0,
183 max_distance_muffle_cutoff_hz: 20.0,
184 }
185 }
186}
187
188pub struct DistanceAttenuatorStereoDsp {
189 pub gain: SmoothedParam,
190 pub muffle_cutoff_hz: SmoothedParam,
191 pub damping_disabled: bool,
192
193 pub filter_l: OnePoleIirLPF,
194 pub filter_r: OnePoleIirLPF,
195}
196
197impl DistanceAttenuatorStereoDsp {
198 pub fn new(smoother_config: SmootherConfig, sample_rate: NonZeroU32) -> Self {
199 Self {
200 gain: SmoothedParam::new(1.0, smoother_config, sample_rate),
201 muffle_cutoff_hz: SmoothedParam::new(
202 MUFFLE_CUTOFF_HZ_MAX,
203 smoother_config,
204 sample_rate,
205 ),
206 damping_disabled: true,
207 filter_l: OnePoleIirLPF::default(),
208 filter_r: OnePoleIirLPF::default(),
209 }
210 }
211
212 pub fn is_silent(&self) -> bool {
213 self.gain.target_value() == 0.0 && !self.gain.is_smoothing()
214 }
215
216 pub fn compute_values(
217 &mut self,
218 distance: f32,
219 params: &DistanceAttenuation,
220 muffle_cutoff_hz: f32,
221 min_gain: f32,
222 ) {
223 let reference_distance = params.reference_distance.max(0.00001);
224 let max_distance = params.max_distance.max(0.0);
225 let max_distance_muffle_cutoff_hz = params
226 .max_distance_muffle_cutoff_hz
227 .max(MUFFLE_CUTOFF_HZ_MIN);
228
229 let distance_gain = params.distance_model.calculate_gain(
230 distance,
231 params.distance_gain_factor,
232 reference_distance,
233 max_distance,
234 );
235
236 let gain = if distance_gain <= min_gain {
237 0.0
238 } else {
239 distance_gain
240 };
241
242 let distance_cutoff_norm = if params.distance_muffle_factor <= 0.00001
243 || distance <= reference_distance
244 || params.max_muffle_distance <= reference_distance
245 || max_distance_muffle_cutoff_hz >= MUFFLE_CUTOFF_HZ_MAX
246 {
247 1.0
248 } else {
249 let num = distance - reference_distance;
250 let den = params.max_muffle_distance - reference_distance;
251
252 let norm = 1.0 - (num / den).powf(params.distance_muffle_factor.recip());
253
254 let min_norm = (max_distance_muffle_cutoff_hz - MUFFLE_CUTOFF_HZ_MIN)
255 * MUFFLE_CUTOFF_HZ_RANGE_RECIP;
256
257 norm.max(min_norm)
258 };
259
260 let muffle_cutoff_hz = if (muffle_cutoff_hz < MUFFLE_CUTOFF_HZ_MAX - 0.01)
261 || distance_cutoff_norm < 1.0
262 {
263 let hz = if distance_cutoff_norm < 1.0 {
264 let muffle_cutoff_norm =
265 (muffle_cutoff_hz - MUFFLE_CUTOFF_HZ_MIN) * MUFFLE_CUTOFF_HZ_RANGE_RECIP;
266 let final_norm = muffle_cutoff_norm * distance_cutoff_norm;
267
268 (final_norm * (MUFFLE_CUTOFF_HZ_MAX - MUFFLE_CUTOFF_HZ_MIN)) + MUFFLE_CUTOFF_HZ_MIN
269 } else {
270 muffle_cutoff_hz
271 };
272
273 Some(hz.clamp(MUFFLE_CUTOFF_HZ_MIN, MUFFLE_CUTOFF_HZ_MAX))
274 } else {
275 None
276 };
277
278 self.gain.set_value(gain);
279
280 if let Some(cutoff_hz) = muffle_cutoff_hz {
281 self.muffle_cutoff_hz.set_value(cutoff_hz);
282 self.damping_disabled = false;
283 } else {
284 self.muffle_cutoff_hz.set_value(MUFFLE_CUTOFF_HZ_MAX);
285 self.damping_disabled = true;
286 }
287 }
288
289 pub fn process(
292 &mut self,
293 frames: usize,
294 out1: &mut [f32],
295 out2: &mut [f32],
296 sample_rate_recip: f64,
297 ) -> bool {
298 let out1 = &mut out1[..frames];
301 let out2 = &mut out2[..frames];
302
303 if !self.gain.is_smoothing() && !self.muffle_cutoff_hz.is_smoothing() {
304 if !self.gain.is_smoothing() && self.gain.target_value() == 0.0 {
305 self.gain.reset();
306 self.muffle_cutoff_hz.reset();
307 self.filter_l.reset();
308 self.filter_r.reset();
309
310 return true;
311 } else if self.damping_disabled {
312 for i in 0..frames {
313 out1[i] = out1[i] * self.gain.target_value();
314 out2[i] = out2[i] * self.gain.target_value();
315 }
316 } else {
317 let coeff = OnePoleIirLPFCoeff::new(
320 self.muffle_cutoff_hz.target_value(),
321 sample_rate_recip as f32,
322 );
323
324 for i in 0..frames {
325 let l = out1[i] * self.gain.target_value();
326 let r = out2[i] * self.gain.target_value();
327
328 out1[i] = self.filter_l.process(l, coeff);
329 out2[i] = self.filter_r.process(r, coeff);
330 }
331 }
332 } else {
333 if self.damping_disabled && !self.muffle_cutoff_hz.is_smoothing() {
334 for i in 0..frames {
335 let gain = self.gain.next_smoothed();
336
337 out1[i] = out1[i] * gain;
338 out2[i] = out2[i] * gain;
339 }
340 } else {
341 let mut coeff = OnePoleIirLPFCoeff::default();
342
343 for i in 0..frames {
344 let cutoff_hz = self.muffle_cutoff_hz.next_smoothed();
345 let gain = self.gain.next_smoothed();
346
347 let l = out1[i] * gain;
348 let r = out2[i] * gain;
349
350 if i & (CALC_FILTER_COEFF_INTERVAL - 1) == 0 {
354 coeff = OnePoleIirLPFCoeff::new(cutoff_hz, sample_rate_recip as f32);
355 }
356
357 out1[i] = self.filter_l.process(l, coeff);
358 out2[i] = self.filter_r.process(r, coeff);
359 }
360 }
361
362 self.gain.settle();
363 self.muffle_cutoff_hz.settle();
364 }
365
366 false
367 }
368
369 pub fn reset(&mut self) {
370 self.gain.reset();
371 self.muffle_cutoff_hz.reset();
372 self.filter_l.reset();
373 self.filter_r.reset();
374 }
375
376 pub fn set_smooth_seconds(&mut self, seconds: f32, sample_rate: NonZeroU32) {
377 self.gain.set_smooth_seconds(seconds, sample_rate);
378 self.muffle_cutoff_hz
379 .set_smooth_seconds(seconds, sample_rate);
380 }
381
382 pub fn update_sample_rate(&mut self, sample_rate: NonZeroU32) {
383 self.gain.update_sample_rate(sample_rate);
384 self.muffle_cutoff_hz.update_sample_rate(sample_rate);
385 }
386}