namespace TravelEar.Core; /// /// The two AudioVolumes the game adds to a remote voice's AudioSourceController /// volume chain (docs/reference/big-walk-voice-effects-catalog.md sections 5.1b and 5.5, /// PlayerVoicePlaybackControl.PlayVoice / Update): plain linear gains on the source, /// applied before every mixer-side effect. /// /// Indoor attenuation: Outdoorness * 0.5 + 0.5 from the listener's /// AudioDynamicReverb.Outdoorness (1 with no dynamic reverb), so a voice is 6 dB down when /// the listener is fully indoors; smoothed by clamp01(dt * 3) per frame. /// Speechlessness (the red bells): 1 - speaker.speechless.speechlessness, lerped /// by dt * 5; silent at the centre of a zone. /// /// At the Self-Ear the local player is both speaker and listener, so both read local state. /// Main-thread model; the encoder thread multiplies by . /// public sealed class SourceVolume { public const float IndoorSmoothingPerSecond = 3f; public const float SpeechlessSmoothingPerSecond = 5f; /// The indoor attenuation term, 0.5..1. public float IndoorGain { get; private set; } = 1f; /// The speechlessness term, 0..1. public float SpeechlessGain { get; private set; } = 1f; public bool IndoorEnabled { get; } public bool SpeechlessEnabled { get; } /// The product of the enabled terms. public float Gain => (IndoorEnabled ? IndoorGain : 1f) * (SpeechlessEnabled ? SpeechlessGain : 1f); public SourceVolume(bool indoorEnabled, bool speechlessEnabled) { IndoorEnabled = indoorEnabled; SpeechlessEnabled = speechlessEnabled; } // [impl->REQ-MIXER-RESYNTH] /// One frame: the listener's outdoorness (1 when unknown), the speaker's speechlessness (0 outside a zone), the frame time. public void Step(float listenerOutdoorness, float speakerSpeechlessness, float dt) { var outdoor = float.IsNaN(listenerOutdoorness) ? 1f : Math.Clamp(listenerOutdoorness, 0f, 1f); var sp = float.IsNaN(speakerSpeechlessness) ? 0f : Math.Clamp(speakerSpeechlessness, 0f, 1f); var indoorTarget = outdoor * 0.5f + 0.5f; IndoorGain += (indoorTarget - IndoorGain) * Math.Clamp(dt * IndoorSmoothingPerSecond, 0f, 1f); SpeechlessGain += (1f - sp - SpeechlessGain) * Math.Clamp(dt * SpeechlessSmoothingPerSecond, 0f, 1f); } public void Reset() { IndoorGain = 1f; SpeechlessGain = 1f; } }