Skip to main content

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, Default)]
49pub enum Renderer {
50    /// Stateless default renderer.
51    #[default]
52    Default,
53
54    /// Can be used *only* with mono sounds, stereo sounds will be rendered through
55    /// default renderer.
56    HrtfRenderer(HrtfRenderer),
57}
58
59uuid_provider!(Renderer = "13bf8432-987a-4216-b6aa-f5c0e8914a31");
60
61fn render_with_params(
62    source: &mut SoundSource,
63    left_gain: f32,
64    right_gain: f32,
65    mix_buffer: &mut [(f32, f32)],
66) {
67    let last_left_gain = *source.last_left_gain.get_or_insert(left_gain);
68    let last_right_gain = *source.last_right_gain.get_or_insert(right_gain);
69
70    if last_left_gain != left_gain || last_right_gain != right_gain {
71        let step = 1.0 / mix_buffer.len() as f32;
72        let mut t = 0.0;
73        for ((out_left, out_right), &(raw_left, raw_right)) in
74            mix_buffer.iter_mut().zip(source.frame_samples())
75        {
76            // Interpolation of gain is very important to remove clicks which appears
77            // when gain changes by significant value between frames.
78            *out_left += math::lerpf(last_left_gain, left_gain, t) * raw_left;
79            *out_right += math::lerpf(last_right_gain, right_gain, t) * raw_right;
80
81            t += step;
82        }
83    } else {
84        for ((out_left, out_right), &(raw_left, raw_right)) in
85            mix_buffer.iter_mut().zip(source.frame_samples())
86        {
87            // Optimize the common case when the gain did not change since the last call.
88            *out_left += left_gain * raw_left;
89            *out_right += right_gain * raw_right;
90        }
91    }
92}
93
94pub(crate) fn render_source_default(
95    source: &mut SoundSource,
96    listener: &Listener,
97    distance_model: DistanceModel,
98    mix_buffer: &mut [(f32, f32)],
99) {
100    let distance_gain = lerpf(
101        1.0,
102        source.calculate_distance_gain(listener, distance_model),
103        source.spatial_blend(),
104    );
105    let panning = lerpf(
106        source.panning(),
107        source.calculate_panning(listener),
108        source.spatial_blend(),
109    );
110    let gain = distance_gain * source.gain();
111    let left_gain = gain * (1.0 + panning);
112    let right_gain = gain * (1.0 - panning);
113    render_with_params(source, left_gain, right_gain, mix_buffer);
114    source.last_left_gain = Some(left_gain);
115    source.last_right_gain = Some(right_gain);
116}
117
118pub(crate) fn render_source_2d_only(source: &mut SoundSource, mix_buffer: &mut [(f32, f32)]) {
119    let gain = (1.0 - source.spatial_blend()) * source.gain();
120    let left_gain = gain * (1.0 + source.panning());
121    let right_gain = gain * (1.0 - source.panning());
122    render_with_params(source, left_gain, right_gain, mix_buffer);
123    source.last_left_gain = Some(left_gain);
124    source.last_right_gain = Some(right_gain);
125}