rg3d_sound/renderer/
mod.rs

1//! Renderer module.
2//!
3//! # Overview
4//!
5//! Renderer processes samples from each sound source before they'll be passed to output device. Exact
6//! behaviour of renderer depends of variant being used.
7
8#![allow(clippy::float_cmp)]
9
10use crate::{
11    context::DistanceModel,
12    listener::Listener,
13    math,
14    renderer::hrtf::HrtfRenderer,
15    source::{generic::GenericSource, SoundSource},
16};
17use rg3d_core::visitor::{Visit, VisitResult, Visitor};
18
19pub mod hrtf;
20
21/// See module docs.
22// This "large size difference" is not a problem because renderer
23// can be only one at a time on context.
24#[allow(clippy::large_enum_variant)]
25#[derive(Debug, Clone)]
26pub enum Renderer {
27    /// Stateless default renderer.
28    Default,
29
30    /// Can be used *only* with mono sounds, stereo sounds will be rendered through
31    /// default renderer.
32    HrtfRenderer(HrtfRenderer),
33}
34
35impl Renderer {
36    fn id(&self) -> u32 {
37        match self {
38            Renderer::Default => 0,
39            Renderer::HrtfRenderer(_) => 1,
40        }
41    }
42
43    fn from_id(id: u32) -> Result<Self, String> {
44        match id {
45            0 => Ok(Self::Default),
46            1 => Ok(Self::HrtfRenderer(HrtfRenderer::default())),
47            _ => Err(format!("Invalid renderer id {}!", id)),
48        }
49    }
50}
51
52impl Default for Renderer {
53    fn default() -> Self {
54        Self::Default
55    }
56}
57
58fn render_with_params(
59    source: &mut GenericSource,
60    left_gain: f32,
61    right_gain: f32,
62    mix_buffer: &mut [(f32, f32)],
63) {
64    let last_left_gain = *source.last_left_gain.get_or_insert(left_gain);
65    let last_right_gain = *source.last_right_gain.get_or_insert(right_gain);
66
67    if last_left_gain != left_gain || last_right_gain != right_gain {
68        let step = 1.0 / mix_buffer.len() as f32;
69        let mut t = 0.0;
70        for ((out_left, out_right), &(raw_left, raw_right)) in
71            mix_buffer.iter_mut().zip(source.frame_samples())
72        {
73            // Interpolation of gain is very important to remove clicks which appears
74            // when gain changes by significant value between frames.
75            *out_left += math::lerpf(last_left_gain, left_gain, t) * raw_left;
76            *out_right += math::lerpf(last_right_gain, right_gain, t) * raw_right;
77
78            t += step;
79        }
80    } else {
81        for ((out_left, out_right), &(raw_left, raw_right)) in
82            mix_buffer.iter_mut().zip(source.frame_samples())
83        {
84            // Optimize the common case when the gain did not change since the last call.
85            *out_left += left_gain * raw_left;
86            *out_right += right_gain * raw_right;
87        }
88    }
89}
90
91pub(in crate) fn render_source_default(
92    source: &mut SoundSource,
93    listener: &Listener,
94    distance_model: DistanceModel,
95    mix_buffer: &mut [(f32, f32)],
96) {
97    match source {
98        SoundSource::Generic(generic) => {
99            let gain = generic.gain();
100            let panning = generic.panning();
101            let left_gain = gain * (1.0 + panning);
102            let right_gain = gain * (1.0 - panning);
103            render_with_params(generic, left_gain, right_gain, mix_buffer);
104            generic.last_left_gain = Some(left_gain);
105            generic.last_right_gain = Some(right_gain);
106        }
107        SoundSource::Spatial(spatial) => {
108            let distance_gain = spatial.get_distance_gain(listener, distance_model);
109            let panning = spatial.get_panning(listener);
110            let gain = distance_gain * spatial.generic().gain();
111            let left_gain = gain * (1.0 + panning);
112            let right_gain = gain * (1.0 - panning);
113            render_with_params(spatial.generic_mut(), left_gain, right_gain, mix_buffer);
114            spatial.generic_mut().last_left_gain = Some(left_gain);
115            spatial.generic_mut().last_right_gain = Some(right_gain);
116        }
117    }
118}
119
120impl Visit for Renderer {
121    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
122        visitor.enter_region(name)?;
123
124        let mut id = self.id();
125        id.visit("Id", visitor)?;
126        if visitor.is_reading() {
127            *self = Self::from_id(id)?;
128        }
129        match self {
130            Renderer::Default => {}
131            Renderer::HrtfRenderer(hrtf) => {
132                hrtf.visit("Hrtf", visitor)?;
133            }
134        }
135
136        visitor.leave_region()
137    }
138}