namespace Sandbox.Audio; /// /// Holds up to 8 mix buffers, which usually represent output speakers. /// public sealed class MultiChannelBuffer : IDisposable { // 8 buffers, 512 floats each, about 16kb internal CAudioMixDeviceBuffers _native; internal PerChannel _buffers; /// /// How many channels do we have /// public int ChannelCount { get; } public MultiChannelBuffer( int channelCount = 8 ) { Assert.True( channelCount > 0 ); Assert.True( channelCount <= 8 ); _native = CAudioMixDeviceBuffers.Create( channelCount ); ChannelCount = channelCount; for ( int i = 0; i < ChannelCount; i++ ) { _buffers.Set( new AudioChannel( i ), new MixBuffer( _native.GetBuffer( i ) ) ); } } ~MultiChannelBuffer() { Dispose(); } /// /// Delete and release all resources. Cannot be used again. /// public void Dispose() { if ( _native.IsNull ) return; GC.SuppressFinalize( this ); for ( int i = 0; i < ChannelCount; i++ ) { _buffers.Get( new AudioChannel( i ) ).ClearPointer(); } _native.Destroy(); _native = default; } /// /// Get MixBuffer number i /// public MixBuffer Get( AudioChannel i ) { return _buffers.Get( i ); } /// /// Get MixBuffer number i /// public MixBuffer Get( int i ) => Get( new AudioChannel( i ) ); /// /// Silence all buffers /// public void Silence() { for ( int i = 0; i < ChannelCount; i++ ) { _buffers.Get( new AudioChannel( i ) ).Silence(); } } /// /// Set this buffer to this value /// public void CopyFrom( MultiChannelBuffer other ) { for ( int i = 0; i < ChannelCount && i < other.ChannelCount; i++ ) { _buffers.Get( new AudioChannel( i ) ).CopyFrom( other._buffers.Get( new AudioChannel( i ) ) ); } } /// /// Copies from one buffer to the other. If the other has less channels, we'll upmix /// public void CopyFromUpmix( MultiChannelBuffer other ) { for ( int i = 0; i < ChannelCount; i++ ) { var otherBuffer = other._buffers.Get( new AudioChannel( i % other.ChannelCount ) ); _buffers.Get( new AudioChannel( i ) ).CopyFrom( otherBuffer ); } } /// /// Mix the target buffer into this buffer /// public void MixFrom( MultiChannelBuffer samples, float mix ) { for ( int i = 0; i < ChannelCount && i < samples.ChannelCount; i++ ) { _buffers.Get( new AudioChannel( i ) ).MixFrom( samples._buffers.Get( new AudioChannel( i ) ), mix ); } } /// /// Scale volume of this buffer /// public void Scale( float volume ) { if ( volume <= 0 ) { Silence(); return; } if ( volume.AlmostEqual( 1 ) ) return; for ( int i = 0; i < ChannelCount; i++ ) { _buffers.Get( new AudioChannel( i ) ).Scale( volume ); } } /// /// Send to device output /// internal void SendToOutput() { if ( !g_pAudioDevice.IsValid() ) return; g_pAudioDevice.SendOutput( _native ); // If we are recording a screencapture we also need to send the buffer to the movie recorder if ( ScreenRecorder.IsRecording() && g_pAudioDevice.BytesPerSample() == 4 && g_pAudioDevice.ChannelCount() == 2 ) { ScreenRecorder.RecordAudioSample( _native ); } } /// /// Mix each channel into the single buffer, using passed in volume /// internal void ToMono( MixBuffer monoTarget, float volume ) { for ( int i = 0; i < ChannelCount; i++ ) { monoTarget.MixFrom( _buffers.Get( new AudioChannel( i ) ), volume ); } } }