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