namespace VoiceCat.Dsp; public sealed class EnergyVadProcessor { private readonly TimeProvider timeProvider; private long lastVoiceTimestamp; private bool hasVoice; private float threshold; public float Threshold { get => Volatile.Read(ref threshold); set { if (!float.IsFinite(value) || value is < 0 or > 1) throw new ArgumentOutOfRangeException(nameof(value)); Volatile.Write(ref threshold, value); } } public TimeSpan HangTime { get; } public EnergyVadProcessor(float threshold = 0.02f, TimeSpan? hangTime = null, TimeProvider? timeProvider = null) { Threshold = threshold; HangTime = hangTime ?? TimeSpan.FromMilliseconds(300); if (HangTime < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(hangTime)); this.timeProvider = timeProvider ?? TimeProvider.System; } public bool Process(ReadOnlySpan pcm) { long now = timeProvider.GetTimestamp(); if (!pcm.IsEmpty) { double sum = 0; foreach (short sample in pcm) { double normalized = sample / 32768.0; sum += normalized * normalized; } if (Math.Sqrt(sum / pcm.Length) >= Threshold) { lastVoiceTimestamp = now; hasVoice = true; } } return hasVoice && timeProvider.GetElapsedTime(lastVoiceTimestamp, now) < HangTime; } }