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;
}
}