using System.Runtime.InteropServices; using System.Text.Json.Nodes; namespace Sandbox.Audio; /// /// Takes a bunch of samples and processes them. It's common for these to be chained together. /// It's also common for the processor to store state between calls. /// public abstract partial class AudioProcessor { /// /// Is this processor active? /// [Group( "Processor Settings" )] public bool Enabled { get; set; } = true; /// /// Should we fade the influence of this processor in? /// [Range( 0, 1 )] [Group( "Processor Settings" )] public float Mix { get; set; } = 1; private MultiChannelBuffer scratch = new MultiChannelBuffer( 8 ); internal Transform _listener; /// /// The listener's position in this frame. /// [Hide] protected Transform Listener => _listener; /// /// Should process input into output /// internal virtual void Process( MultiChannelBuffer input, MultiChannelBuffer output ) { Assert.True( input.ChannelCount <= output.ChannelCount ); output.CopyFrom( input ); ProcessEachChannel( output ); } /// /// Will process the buffer, and copy it back to output /// internal void ProcessInPlace( MultiChannelBuffer inputoutput ) { scratch.Silence(); Process( inputoutput, scratch ); for ( int i = 0; i < inputoutput.ChannelCount; i++ ) { inputoutput.Get( i ).CopyFrom( scratch.Get( i ) ); } } /// /// Called internally to process each channel in a buffer /// private unsafe void ProcessEachChannel( MultiChannelBuffer buffer ) { for ( int i = 0; i < buffer.ChannelCount; i++ ) { using ( buffer.Get( i ).DataPointer( out var ptr ) ) { Span memory = new Span( (float*)ptr, AudioEngine.MixBufferSize ); ProcessSingleChannel( new AudioChannel( i ), memory ); } } } /// /// For implementations that process each channel individually /// protected virtual unsafe void ProcessSingleChannel( AudioChannel channel, Span input ) { } public JsonObject Serialize() { var js = Json.SerializeAsObject( this ); js["__type"] = GetType().Name; return js; } public void Deserialize( JsonObject node ) { Json.DeserializeToObject( this, node ); } public override string ToString() => GetType().Name; protected virtual void OnDestroy() { } internal virtual void OnRemovedInternal() { OnDestroy(); } } /// /// Represents an audio channel, between 0 and 7. This is used to index into buffers. /// This is used rather than an int to avoid unfortuate bugs. /// public struct AudioChannel { public static AudioChannel Left => new AudioChannel( 0 ); public static AudioChannel Right => new AudioChannel( 1 ); internal int channel; public AudioChannel( int i ) { channel = i; } public int Get() => channel; } /// /// Stores a variable per channel /// [System.Diagnostics.CodeAnalysis.SuppressMessage( "Compiler", "CS0169" )] public struct PerChannel { #pragma warning disable CS0169, CS0649 private T _0, _1, _2, _3, _4, _5, _6, _7; #pragma warning restore CS0169, CS0649 private Span Values => MemoryMarshal.CreateSpan( ref _0, 8 ); [Obsolete] public T[] Value => Values.ToArray(); public PerChannel() { } public static implicit operator PerChannel( T v ) { PerChannel x = new PerChannel(); for ( int i = 0; i < 8; i++ ) { x.Values[i] = v; } return x; } /// /// Get the value in a channel /// public T Get( AudioChannel i ) => Values[i.channel]; /// /// Set the value in a channel /// public void Set( AudioChannel i, T value ) => Values[i.channel] = value; }