fyrox_sound/renderer/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Renderer module.
22//!
23//! # Overview
24//!
25//! Renderer processes samples from each sound source before they'll be passed to output device. Exact
26//! behaviour of renderer depends of variant being used.
27
28#![allow(clippy::float_cmp)]
29
30use crate::{
31    context::DistanceModel, listener::Listener, math, renderer::hrtf::HrtfRenderer,
32    source::SoundSource,
33};
34use fyrox_core::math::lerpf;
35use fyrox_core::{
36    reflect::prelude::*,
37    uuid_provider,
38    visitor::{Visit, VisitResult, Visitor},
39};
40use strum_macros::{AsRefStr, EnumString, VariantNames};
41
42pub mod hrtf;
43
44/// See module docs.
45// This "large size difference" is not a problem because renderer
46// can be only one at a time on context.
47#[allow(clippy::large_enum_variant)]
48#[derive(Debug, Clone, AsRefStr, EnumString, VariantNames, Visit, Reflect)]
49pub enum Renderer {
50    /// Stateless default renderer.
51    Default,
52
53    /// Can be used *only* with mono sounds, stereo sounds will be rendered through
54    /// default renderer.
55    HrtfRenderer(HrtfRenderer),
56}
57
58uuid_provider!(Renderer = "13bf8432-987a-4216-b6aa-f5c0e8914a31");
59
60impl Default for Renderer {
61    fn default() -> Self {
62        Self::Default
63    }
64}
65
66fn render_with_params(
67    source: &mut SoundSource,
68    left_gain: f32,
69    right_gain: f32,
70    mix_buffer: &mut [(f32, f32)],
71) {
72    let last_left_gain = *source.last_left_gain.get_or_insert(left_gain);
73    let last_right_gain = *source.last_right_gain.get_or_insert(right_gain);
74
75    if last_left_gain != left_gain || last_right_gain != right_gain {
76        let step = 1.0 / mix_buffer.len() as f32;
77        let mut t = 0.0;
78        for ((out_left, out_right), &(raw_left, raw_right)) in
79            mix_buffer.iter_mut().zip(source.frame_samples())
80        {
81            // Interpolation of gain is very important to remove clicks which appears
82            // when gain changes by significant value between frames.
83            *out_left += math::lerpf(last_left_gain, left_gain, t) * raw_left;
84            *out_right += math::lerpf(last_right_gain, right_gain, t) * raw_right;
85
86            t += step;
87        }
88    } else {
89        for ((out_left, out_right), &(raw_left, raw_right)) in
90            mix_buffer.iter_mut().zip(source.frame_samples())
91        {
92            // Optimize the common case when the gain did not change since the last call.
93            *out_left += left_gain * raw_left;
94            *out_right += right_gain * raw_right;
95        }
96    }
97}
98
99pub(crate) fn render_source_default(
100    source: &mut SoundSource,
101    listener: &Listener,
102    distance_model: DistanceModel,
103    mix_buffer: &mut [(f32, f32)],
104) {
105    let distance_gain = lerpf(
106        1.0,
107        source.calculate_distance_gain(listener, distance_model),
108        source.spatial_blend(),
109    );
110    let panning = lerpf(
111        source.panning(),
112        source.calculate_panning(listener),
113        source.spatial_blend(),
114    );
115    let gain = distance_gain * source.gain();
116    let left_gain = gain * (1.0 + panning);
117    let right_gain = gain * (1.0 - panning);
118    render_with_params(source, left_gain, right_gain, mix_buffer);
119    source.last_left_gain = Some(left_gain);
120    source.last_right_gain = Some(right_gain);
121}
122
123pub(crate) fn render_source_2d_only(source: &mut SoundSource, mix_buffer: &mut [(f32, f32)]) {
124    let gain = (1.0 - source.spatial_blend()) * source.gain();
125    let left_gain = gain * (1.0 + source.panning());
126    let right_gain = gain * (1.0 - source.panning());
127    render_with_params(source, left_gain, right_gain, mix_buffer);
128    source.last_left_gain = Some(left_gain);
129    source.last_right_gain = Some(right_gain);
130}