rg3d_sound/effects/
mod.rs

1//! Effects module
2//!
3//! # Overview
4//!
5//! Provides unified way of creating and using effects.
6
7use crate::{
8    context::DistanceModel,
9    dsp::filters::Biquad,
10    effects::reverb::Reverb,
11    listener::Listener,
12    source::{SoundSource, Status},
13};
14use rg3d_core::{
15    math,
16    pool::{Handle, Pool},
17    visitor::{Visit, VisitResult, Visitor},
18};
19use std::ops::{Deref, DerefMut};
20
21pub mod reverb;
22
23/// Stub effect that does nothing.
24#[derive(Default, Debug, Clone)]
25pub struct StubEffect {
26    base: BaseEffect,
27}
28
29impl Visit for StubEffect {
30    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
31        visitor.enter_region(name)?;
32
33        self.base.visit("Base", visitor)?;
34
35        visitor.leave_region()
36    }
37}
38
39impl EffectRenderTrait for StubEffect {
40    fn render(
41        &mut self,
42        _sources: &Pool<SoundSource>,
43        _listener: &Listener,
44        _distance_model: DistanceModel,
45        _mix_buf: &mut [(f32, f32)],
46    ) {
47    }
48}
49
50impl Deref for StubEffect {
51    type Target = BaseEffect;
52
53    fn deref(&self) -> &Self::Target {
54        &self.base
55    }
56}
57
58impl DerefMut for StubEffect {
59    fn deref_mut(&mut self) -> &mut Self::Target {
60        &mut self.base
61    }
62}
63
64/// See module docs.
65#[derive(Debug, Clone)]
66pub enum Effect {
67    /// Stub effect that does nothing.
68    Stub(StubEffect),
69    /// Reverberation effect. See corresponding module for more info.
70    Reverb(Reverb),
71}
72
73impl Default for Effect {
74    fn default() -> Self {
75        Effect::Stub(Default::default())
76    }
77}
78
79impl Effect {
80    fn id(&self) -> u32 {
81        match self {
82            Effect::Stub(_) => 0,
83            Effect::Reverb(_) => 1,
84        }
85    }
86
87    fn from_id(id: u32) -> Result<Self, String> {
88        match id {
89            0 => Ok(Effect::Stub(Default::default())),
90            1 => Ok(Effect::Reverb(Default::default())),
91            _ => Err(format!("Unknown effect id {}", id)),
92        }
93    }
94}
95
96impl Visit for Effect {
97    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
98        visitor.enter_region(name)?;
99
100        let mut id = self.id();
101        id.visit("Id", visitor)?;
102        if visitor.is_reading() {
103            *self = Self::from_id(id)?;
104        }
105
106        match self {
107            Effect::Stub(v) => v.visit("Data", visitor)?,
108            Effect::Reverb(v) => v.visit("Data", visitor)?,
109        }
110
111        visitor.leave_region()
112    }
113}
114
115pub(in crate) trait EffectRenderTrait {
116    fn render(
117        &mut self,
118        sources: &Pool<SoundSource>,
119        listener: &Listener,
120        distance_model: DistanceModel,
121        mix_buf: &mut [(f32, f32)],
122    );
123}
124
125/// Base effect for all other kinds of effects. It contains set of inputs (direct
126/// or filtered), provides some basic methods to control them.
127#[derive(Debug, Clone)]
128pub struct BaseEffect {
129    gain: f32,
130    filters: Pool<InputFilter>,
131    inputs: Vec<EffectInput>,
132    frame_samples: Vec<(f32, f32)>,
133}
134
135impl Default for BaseEffect {
136    fn default() -> Self {
137        Self {
138            gain: 1.0,
139            filters: Default::default(),
140            inputs: Default::default(),
141            frame_samples: Default::default(),
142        }
143    }
144}
145
146impl BaseEffect {
147    pub(in crate) fn render(
148        &mut self,
149        sources: &Pool<SoundSource>,
150        listener: &Listener,
151        distance_model: DistanceModel,
152        amount: usize,
153    ) {
154        // First of all check that inputs are still lead to valid sound sources.
155        // We use some sort of weak coupling here - it is ok to leave sound source
156        // connected to effect and delete source, such "dangling" inputs will be
157        // automatically removed.
158        self.inputs
159            .retain(|input| sources.is_valid_handle(input.source));
160
161        // Accumulate samples from inputs into accumulation buffer.
162        if self.frame_samples.capacity() < amount {
163            self.frame_samples = Vec::with_capacity(amount)
164        }
165
166        self.frame_samples.clear();
167        for _ in 0..amount {
168            self.frame_samples.push((0.0, 0.0));
169        }
170
171        for input in self.inputs.iter_mut() {
172            let source = sources.borrow(input.source);
173
174            if source.status() != Status::Playing {
175                continue;
176            }
177
178            let distance_gain = match source {
179                SoundSource::Generic(_) => 1.0,
180                SoundSource::Spatial(spatial) => {
181                    spatial.get_distance_gain(listener, distance_model)
182                }
183            };
184
185            let prev_distance_gain = input.last_distance_gain.unwrap_or(distance_gain);
186
187            input.last_distance_gain = Some(distance_gain);
188
189            let mut k = 0.0;
190            let step = 1.0 / amount as f32;
191
192            match self.filters.try_borrow_mut(input.filter) {
193                None => {
194                    for ((accum_left, accum_right), &(input_left, input_right)) in
195                        self.frame_samples.iter_mut().zip(source.frame_samples())
196                    {
197                        let g = math::lerpf(prev_distance_gain, distance_gain, k);
198                        *accum_left += input_left * g;
199                        *accum_right += input_right * g;
200                        k += step;
201                    }
202                }
203                Some(filter) => {
204                    for ((accum_left, accum_right), &(input_left, input_right)) in
205                        self.frame_samples.iter_mut().zip(source.frame_samples())
206                    {
207                        let (filtered_left, filtered_right) = filter.feed(input_left, input_right);
208                        let g = math::lerpf(prev_distance_gain, distance_gain, k);
209                        *accum_left += filtered_left * g;
210                        *accum_right += filtered_right * g;
211                        k += step;
212                    }
213                }
214            }
215        }
216    }
217
218    /// Returns current gain of effect.
219    pub fn gain(&self) -> f32 {
220        self.gain
221    }
222
223    /// Sets effect gain. It should be in (0;1) range, but larger values still fine -
224    /// they can be used to achieve "overdrive" effect if needed. Basically this value
225    /// defines how "loud" effect will be.
226    pub fn set_gain(&mut self, gain: f32) {
227        self.gain = gain.max(0.0);
228    }
229
230    /// Adds new filter to effect and returns its handle. Filter handle then can be
231    /// used to add input to effect (if it is filtered input).
232    pub fn add_filter(&mut self, filter: InputFilter) -> Handle<InputFilter> {
233        self.filters.spawn(filter)
234    }
235
236    /// Adds new input to effect.
237    pub fn add_input(&mut self, input: EffectInput) {
238        self.inputs.push(input)
239    }
240
241    /// Returns shared reference to filter.
242    pub fn filter(&self, handle: Handle<InputFilter>) -> &InputFilter {
243        self.filters.borrow(handle)
244    }
245
246    /// Returns mutable reference to filter.
247    pub fn filter_mut(&mut self, handle: Handle<InputFilter>) -> &mut InputFilter {
248        self.filters.borrow_mut(handle)
249    }
250}
251
252impl Visit for BaseEffect {
253    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
254        visitor.enter_region(name)?;
255
256        self.gain.visit("Gain", visitor)?;
257        self.filters.visit("Filters", visitor)?;
258        self.inputs.visit("Inputs", visitor)?;
259
260        visitor.leave_region()
261    }
262}
263
264/// Input filter is used to transform samples in desired manner, it is based
265/// on generic second order biquad filter. See docs for Biquad filter.
266#[derive(Default, Debug, Clone)]
267pub struct InputFilter {
268    left: Biquad,
269    right: Biquad,
270}
271
272impl InputFilter {
273    /// Creates new instance of input filter using given biquad filter.
274    pub fn new(biquad: Biquad) -> Self {
275        Self {
276            left: biquad.clone(),
277            right: biquad,
278        }
279    }
280}
281
282impl InputFilter {
283    fn feed(&mut self, left_sample: f32, right_sample: f32) -> (f32, f32) {
284        (self.left.feed(left_sample), self.right.feed(right_sample))
285    }
286}
287
288impl Visit for InputFilter {
289    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
290        visitor.enter_region(name)?;
291
292        self.left.visit("Left", visitor)?;
293        self.right.visit("Right", visitor)?;
294
295        visitor.leave_region()
296    }
297}
298
299/// Input is a "reference" to a sound source. Samples of sound source will be
300/// either passed directly to effect or will be transformed by filter if one
301/// is set.
302#[derive(Default, Debug, Clone)]
303pub struct EffectInput {
304    /// Handle of source from which effect will take samples each render frame.
305    source: Handle<SoundSource>,
306
307    /// Handle of filter that will be used to transform samples. Can be NONE if no
308    /// filtering is needed.
309    filter: Handle<InputFilter>,
310
311    /// Distance gain from last frame, it is used to interpolate distance gain from
312    /// frame to frame to prevent clicks in output signal.
313    last_distance_gain: Option<f32>,
314}
315
316impl EffectInput {
317    /// Creates new effect input using specified handle of sound source.
318    pub fn direct(source: Handle<SoundSource>) -> Self {
319        Self {
320            source,
321            filter: Handle::NONE,
322            last_distance_gain: None,
323        }
324    }
325
326    /// Creates new filtered effect input using specified handles of source and filter.
327    ///
328    /// Filtered inputs are suitable for emulating occlusion of sound. For example you
329    /// can add filter to input and then modify its parameters in runtime: if there is
330    /// no direct path from listener to sound source - make it lowpass, otherwise -
331    /// allpass.
332    pub fn filtered(source: Handle<SoundSource>, filter: Handle<InputFilter>) -> Self {
333        Self {
334            source,
335            filter,
336            last_distance_gain: None,
337        }
338    }
339}
340
341impl Visit for EffectInput {
342    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
343        visitor.enter_region(name)?;
344
345        self.source.visit("Source", visitor)?;
346        self.filter.visit("Filter", visitor)?;
347
348        visitor.leave_region()
349    }
350}
351
352macro_rules! static_dispatch {
353    ($self:ident, $func:ident, $($args:expr),*) => {
354        match $self {
355            Effect::Stub(v) => v.$func($($args),*),
356            Effect::Reverb(v) => v.$func($($args),*),
357        }
358    };
359}
360
361impl EffectRenderTrait for Effect {
362    fn render(
363        &mut self,
364        sources: &Pool<SoundSource>,
365        listener: &Listener,
366        distance_model: DistanceModel,
367        mix_buf: &mut [(f32, f32)],
368    ) {
369        static_dispatch!(self, render, sources, listener, distance_model, mix_buf)
370    }
371}
372
373impl Deref for Effect {
374    type Target = BaseEffect;
375
376    fn deref(&self) -> &Self::Target {
377        match self {
378            Effect::Stub(v) => v,
379            Effect::Reverb(v) => v,
380        }
381    }
382}
383
384impl DerefMut for Effect {
385    fn deref_mut(&mut self) -> &mut Self::Target {
386        match self {
387            Effect::Stub(v) => v,
388            Effect::Reverb(v) => v,
389        }
390    }
391}