using Sandbox.Utility; using System.Threading; namespace Sandbox.Audio; /// /// This is a real thread! We need to be very careful about what this accesses, and how. /// static class MixingThread { static readonly Superluminal _sampleVoices = new Superluminal( "SampleVoices", "#4d5e73" ); static readonly Superluminal _mixVoices = new Superluminal( "Mix", "#4d5e73" ); static readonly Superluminal _finishVoices = new Superluminal( "FinishVoices", "#4d5e73" ); static readonly Lock _lockObject = new Lock(); class MixingSettings { public List Listeners; public float MasterVolume = 0.1f; } static MixingSettings _settings = new MixingSettings(); internal static Lock LockObject => _lockObject; internal static void UpdateGlobals() { var settings = new MixingSettings { Listeners = new(), MasterVolume = Sound.MasterVolume }; Listener.GetActive( settings.Listeners ); Interlocked.Exchange( ref _settings, settings ); } internal static void MixOneBuffer() { try { using ( PerformanceStats.Timings.AudioMixingThread.Scope() ) { lock ( LockObject ) { Mix(); } } } catch ( Exception e ) { Log.Error( e, $"Sound Mixer Exception: {e.Message}" ); } } private static readonly List _voiceCollectList = []; private static readonly List _removedListeners = []; private static void Mix() { _voiceCollectList.Clear(); SoundHandle.GetActive( _voiceCollectList ); Mix( _voiceCollectList ); } static float Mix( List voices ) { var settings = Volatile.Read( ref _settings ); _removedListeners.Clear(); while ( Listener.RemoveQueue.TryDequeue( out var id ) ) { _removedListeners.Add( id ); } SampleVoices( voices ); // // At the moment we're running each mixer then copying their contents to output. // What we will have in the future is a sort of dependancy list, because a mixer // might want to send its output to another mixer. For now this is fine. // lock ( Mixer.Master.Lock ) { using ( _mixVoices.Start() ) { // MIX CHILDREN Mixer.Master.StartMixing( settings.Listeners, _removedListeners ); Mixer.Master.MixChildren( voices ); Mixer.Master.MixVoices( voices ); Mixer.Master.FinishMixing(); } } FinishVoices( voices ); // output { using MultiChannelBuffer buffer = new( AudioEngine.ChannelCount ); buffer.Silence(); buffer.MixFrom( Mixer.Master.Output, settings.MasterVolume ); buffer.SendToOutput(); } return 512 * AudioEngine.SecondsPerSample; } /// /// Read one sample from each voice /// static void SampleVoices( List voices ) { using var scope = _sampleVoices.Start( $"{voices.Count()}" ); System.Threading.Tasks.Parallel.ForEach( voices, voice => { if ( !voice.IsValid ) return; if ( voice.sampler is null ) return; if ( voice.Finished ) return; voice.sampler.Sample( voice.Pitch ); } ); } /// /// Mark any finished voices as finished /// static void FinishVoices( List voices ) { using var scope = _finishVoices.Start(); System.Threading.Tasks.Parallel.ForEach( voices, voice => { if ( voice.sampler is null ) return; if ( voice.Finished ) return; if ( voice.sampler.ShouldContinueMixing && !voice.IsFading ) return; if ( voice.TimeUntilFaded == false ) return; voice.Finished = true; } ); } }