diff options
| author | gdkchan <gab.dark.100@gmail.com> | 2024-05-17 16:46:43 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-05-17 16:46:43 -0300 |
| commit | 4d84df94873a070f6f5c199438f957b24d8cf8a9 (patch) | |
| tree | 3e4b4cc9585526c63f3a9fdb3a150bfd721a5030 /src/Ryujinx.Audio/Renderer/Server/Splitter | |
| parent | 9ec8b2c01a0b00f3a33d9a23b9f5ff3758520484 (diff) | |
Update audio renderer to REV12: Add support for splitter biquad filter (#6813)
* Update audio renderer to REV12: Add support for splitter biquad filter
* Formatting
* Official names
* Update BiquadFilterState size + other fixes
* Update tests
* Update comment for version 2
* Size test for SplitterDestinationVersion2
* Should use Volume1 if no ramp
Diffstat (limited to 'src/Ryujinx.Audio/Renderer/Server/Splitter')
5 files changed, 926 insertions, 161 deletions
diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs index 3efa783c..a7b82a6b 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs @@ -1,4 +1,5 @@ using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Common; @@ -16,14 +17,34 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter public class SplitterContext { /// <summary> + /// Amount of biquad filter states per splitter destination. + /// </summary> + public const int BqfStatesPerDestination = 4; + + /// <summary> /// Storage for <see cref="SplitterState"/>. /// </summary> private Memory<SplitterState> _splitters; /// <summary> - /// Storage for <see cref="SplitterDestination"/>. + /// Storage for <see cref="SplitterDestinationVersion1"/>. + /// </summary> + private Memory<SplitterDestinationVersion1> _splitterDestinationsV1; + + /// <summary> + /// Storage for <see cref="SplitterDestinationVersion2"/>. + /// </summary> + private Memory<SplitterDestinationVersion2> _splitterDestinationsV2; + + /// <summary> + /// Splitter biquad filtering states. + /// </summary> + private Memory<BiquadFilterState> _splitterBqfStates; + + /// <summary> + /// Version of the splitter context that is being used, currently can be 1 or 2. /// </summary> - private Memory<SplitterDestination> _splitterDestinations; + public int Version { get; private set; } /// <summary> /// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>. @@ -36,12 +57,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// <param name="behaviourContext">The behaviour context.</param> /// <param name="parameter">The audio renderer configuration.</param> /// <param name="workBufferAllocator">The <see cref="WorkBufferAllocator"/>.</param> + /// <param name="splitterBqfStates">Memory to store the biquad filtering state for splitters during processing.</param> /// <returns>Return true if the initialization was successful.</returns> - public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator) + public bool Initialize( + ref BehaviourContext behaviourContext, + ref AudioRendererConfiguration parameter, + WorkBufferAllocator workBufferAllocator, + Memory<BiquadFilterState> splitterBqfStates) { if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0) { - Setup(Memory<SplitterState>.Empty, Memory<SplitterDestination>.Empty, false); + Setup(Memory<SplitterState>.Empty, Memory<SplitterDestinationVersion1>.Empty, Memory<SplitterDestinationVersion2>.Empty, false); return true; } @@ -60,23 +86,62 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter splitter = new SplitterState(splitterId++); } - Memory<SplitterDestination> splitterDestinations = workBufferAllocator.Allocate<SplitterDestination>(parameter.SplitterDestinationCount, - SplitterDestination.Alignment); + Memory<SplitterDestinationVersion1> splitterDestinationsV1 = Memory<SplitterDestinationVersion1>.Empty; + Memory<SplitterDestinationVersion2> splitterDestinationsV2 = Memory<SplitterDestinationVersion2>.Empty; - if (splitterDestinations.IsEmpty) + if (!behaviourContext.IsBiquadFilterParameterForSplitterEnabled()) { - return false; - } + Version = 1; + + splitterDestinationsV1 = workBufferAllocator.Allocate<SplitterDestinationVersion1>(parameter.SplitterDestinationCount, + SplitterDestinationVersion1.Alignment); + + if (splitterDestinationsV1.IsEmpty) + { + return false; + } - int splitterDestinationId = 0; - foreach (ref SplitterDestination data in splitterDestinations.Span) + int splitterDestinationId = 0; + foreach (ref SplitterDestinationVersion1 data in splitterDestinationsV1.Span) + { + data = new SplitterDestinationVersion1(splitterDestinationId++); + } + } + else { - data = new SplitterDestination(splitterDestinationId++); + Version = 2; + + splitterDestinationsV2 = workBufferAllocator.Allocate<SplitterDestinationVersion2>(parameter.SplitterDestinationCount, + SplitterDestinationVersion2.Alignment); + + if (splitterDestinationsV2.IsEmpty) + { + return false; + } + + int splitterDestinationId = 0; + foreach (ref SplitterDestinationVersion2 data in splitterDestinationsV2.Span) + { + data = new SplitterDestinationVersion2(splitterDestinationId++); + } + + if (parameter.SplitterDestinationCount > 0) + { + // Official code stores it in the SplitterDestinationVersion2 struct, + // but we don't to avoid using unsafe code. + + splitterBqfStates.Span.Clear(); + _splitterBqfStates = splitterBqfStates; + } + else + { + _splitterBqfStates = Memory<BiquadFilterState>.Empty; + } } SplitterState.InitializeSplitters(splitters.Span); - Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed()); + Setup(splitters, splitterDestinationsV1, splitterDestinationsV2, behaviourContext.IsSplitterBugFixed()); return true; } @@ -93,7 +158,15 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter if (behaviourContext.IsSplitterSupported()) { size = WorkBufferAllocator.GetTargetSize<SplitterState>(size, parameter.SplitterCount, SplitterState.Alignment); - size = WorkBufferAllocator.GetTargetSize<SplitterDestination>(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment); + + if (behaviourContext.IsBiquadFilterParameterForSplitterEnabled()) + { + size = WorkBufferAllocator.GetTargetSize<SplitterDestinationVersion2>(size, parameter.SplitterDestinationCount, SplitterDestinationVersion2.Alignment); + } + else + { + size = WorkBufferAllocator.GetTargetSize<SplitterDestinationVersion1>(size, parameter.SplitterDestinationCount, SplitterDestinationVersion1.Alignment); + } if (behaviourContext.IsSplitterBugFixed()) { @@ -110,12 +183,18 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// Setup the <see cref="SplitterContext"/> instance. /// </summary> /// <param name="splitters">The <see cref="SplitterState"/> storage.</param> - /// <param name="splitterDestinations">The <see cref="SplitterDestination"/> storage.</param> + /// <param name="splitterDestinationsV1">The <see cref="SplitterDestinationVersion1"/> storage.</param> + /// <param name="splitterDestinationsV2">The <see cref="SplitterDestinationVersion2"/> storage.</param> /// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.</param> - private void Setup(Memory<SplitterState> splitters, Memory<SplitterDestination> splitterDestinations, bool isBugFixed) + private void Setup( + Memory<SplitterState> splitters, + Memory<SplitterDestinationVersion1> splitterDestinationsV1, + Memory<SplitterDestinationVersion2> splitterDestinationsV2, + bool isBugFixed) { _splitters = splitters; - _splitterDestinations = splitterDestinations; + _splitterDestinationsV1 = splitterDestinationsV1; + _splitterDestinationsV2 = splitterDestinationsV2; IsBugFixed = isBugFixed; } @@ -141,7 +220,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter return 0; } - return _splitterDestinations.Length / _splitters.Length; + int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length; + + return length / _splitters.Length; } /// <summary> @@ -178,7 +259,39 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter } /// <summary> - /// Update one or multiple <see cref="SplitterDestination"/> from user parameters. + /// Update one splitter destination data from user parameters. + /// </summary> + /// <param name="input">The raw data after the splitter header.</param> + /// <returns>True if the update was successful, false otherwise</returns> + private bool UpdateData<T>(ref SequenceReader<byte> input) where T : unmanaged, ISplitterDestinationInParameter + { + ref readonly T parameter = ref input.GetRefOrRefToCopy<T>(out _); + + Debug.Assert(parameter.IsMagicValid()); + + if (parameter.IsMagicValid()) + { + int length = _splitterDestinationsV2.IsEmpty ? _splitterDestinationsV1.Length : _splitterDestinationsV2.Length; + + if (parameter.Id >= 0 && parameter.Id < length) + { + SplitterDestination destination = GetDestination(parameter.Id); + + destination.Update(parameter); + } + + return true; + } + else + { + input.Rewind(Unsafe.SizeOf<T>()); + + return false; + } + } + + /// <summary> + /// Update one or multiple splitter destination data from user parameters. /// </summary> /// <param name="inputHeader">The splitter header.</param> /// <param name="input">The raw data after the splitter header.</param> @@ -186,23 +299,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { for (int i = 0; i < inputHeader.SplitterDestinationCount; i++) { - ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy<SplitterDestinationInParameter>(out _); - - Debug.Assert(parameter.IsMagicValid()); - - if (parameter.IsMagicValid()) + if (Version == 1) { - if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length) + if (!UpdateData<SplitterDestinationInParameterVersion1>(ref input)) { - ref SplitterDestination destination = ref GetDestination(parameter.Id); - - destination.Update(parameter); + break; + } + } + else if (Version == 2) + { + if (!UpdateData<SplitterDestinationInParameterVersion2>(ref input)) + { + break; } } else { - input.Rewind(Unsafe.SizeOf<SplitterDestinationInParameter>()); - break; + Debug.Fail($"Invalid splitter context version {Version}."); } } } @@ -214,7 +327,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// <returns>Return true if the update was successful.</returns> public bool Update(ref SequenceReader<byte> input) { - if (_splitterDestinations.IsEmpty || _splitters.IsEmpty) + if (!UsingSplitter()) { return true; } @@ -251,32 +364,29 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter } /// <summary> - /// Get a reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>. - /// </summary> - /// <param name="id">The index to use.</param> - /// <returns>A reference to a <see cref="SplitterDestination"/> at the given <paramref name="id"/>.</returns> - public ref SplitterDestination GetDestination(int id) - { - return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length); - } - - /// <summary> - /// Get a <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>. + /// Get a reference to the splitter destination data at the given <paramref name="id"/>. /// </summary> /// <param name="id">The index to use.</param> - /// <returns>A <see cref="Memory{SplitterDestination}"/> at the given <paramref name="id"/>.</returns> - public Memory<SplitterDestination> GetDestinationMemory(int id) + /// <returns>A reference to the splitter destination data at the given <paramref name="id"/>.</returns> + public SplitterDestination GetDestination(int id) { - return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length); + if (_splitterDestinationsV2.IsEmpty) + { + return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV1, id, (uint)_splitterDestinationsV1.Length)); + } + else + { + return new SplitterDestination(ref SpanIOHelper.GetFromMemory(_splitterDestinationsV2, id, (uint)_splitterDestinationsV2.Length)); + } } /// <summary> - /// Get a <see cref="Span{SplitterDestination}"/> in the <see cref="SplitterState"/> at <paramref name="id"/> and pass <paramref name="destinationId"/> to <see cref="SplitterState.GetData(int)"/>. + /// Get a <see cref="SplitterDestination"/> in the <see cref="SplitterState"/> at <paramref name="id"/> and pass <paramref name="destinationId"/> to <see cref="SplitterState.GetData(int)"/>. /// </summary> /// <param name="id">The index to use to get the <see cref="SplitterState"/>.</param> /// <param name="destinationId">The index of the <see cref="SplitterDestination"/>.</param> - /// <returns>A <see cref="Span{SplitterDestination}"/>.</returns> - public Span<SplitterDestination> GetDestination(int id, int destinationId) + /// <returns>A <see cref="SplitterDestination"/>.</returns> + public SplitterDestination GetDestination(int id, int destinationId) { ref SplitterState splitter = ref GetState(id); @@ -284,12 +394,22 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter } /// <summary> + /// Gets the biquad filter state for a given splitter destination. + /// </summary> + /// <param name="destination">The splitter destination.</param> + /// <returns>Biquad filter state for the specified destination.</returns> + public Memory<BiquadFilterState> GetBiquadFilterState(SplitterDestination destination) + { + return _splitterBqfStates.Slice(destination.Id * BqfStatesPerDestination, BqfStatesPerDestination); + } + + /// <summary> /// Return true if the audio renderer has any splitters. /// </summary> /// <returns>True if the audio renderer has any splitters.</returns> public bool UsingSplitter() { - return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty; + return !_splitters.IsEmpty && (!_splitterDestinationsV1.IsEmpty || !_splitterDestinationsV2.IsEmpty); } /// <summary> diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs index 1faf7921..36dfa5e4 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs @@ -1,115 +1,198 @@ using Ryujinx.Audio.Renderer.Parameter; -using Ryujinx.Common.Utilities; using System; using System.Diagnostics; -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; namespace Ryujinx.Audio.Renderer.Server.Splitter { /// <summary> /// Server state for a splitter destination. /// </summary> - [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)] - public struct SplitterDestination + public ref struct SplitterDestination { - public const int Alignment = 0x10; + private ref SplitterDestinationVersion1 _v1; + private ref SplitterDestinationVersion2 _v2; /// <summary> - /// The unique id of this <see cref="SplitterDestination"/>. + /// Checks if the splitter destination data reference is null. /// </summary> - public int Id; + public bool IsNull => Unsafe.IsNullRef(ref _v1) && Unsafe.IsNullRef(ref _v2); /// <summary> - /// The mix to output the result of the splitter. - /// </summary> - public int DestinationId; - - /// <summary> - /// Mix buffer volumes storage. - /// </summary> - private MixArray _mix; - private MixArray _previousMix; - - /// <summary> - /// Pointer to the next linked element. - /// </summary> - private unsafe SplitterDestination* _next; - - /// <summary> - /// Set to true if in use. + /// The splitter unique id. /// </summary> - [MarshalAs(UnmanagedType.I1)] - public bool IsUsed; + public int Id + { + get + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return 0; + } + else + { + return _v1.Id; + } + } + else + { + return _v2.Id; + } + } + } /// <summary> - /// Set to true if the internal state need to be updated. + /// The mix to output the result of the splitter. /// </summary> - [MarshalAs(UnmanagedType.I1)] - public bool NeedToUpdateInternalState; - - [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)] - private struct MixArray { } + public int DestinationId + { + get + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return 0; + } + else + { + return _v1.DestinationId; + } + } + else + { + return _v2.DestinationId; + } + } + } /// <summary> /// Mix buffer volumes. /// </summary> /// <remarks>Used when a splitter id is specified in the mix.</remarks> - public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix); + public Span<float> MixBufferVolume + { + get + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return Span<float>.Empty; + } + else + { + return _v1.MixBufferVolume; + } + } + else + { + return _v2.MixBufferVolume; + } + } + } /// <summary> /// Previous mix buffer volumes. /// </summary> /// <remarks>Used when a splitter id is specified in the mix.</remarks> - public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix); + public Span<float> PreviousMixBufferVolume + { + get + { + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return Span<float>.Empty; + } + else + { + return _v1.PreviousMixBufferVolume; + } + } + else + { + return _v2.PreviousMixBufferVolume; + } + } + } /// <summary> - /// Get the <see cref="Span{SplitterDestination}"/> of the next element or <see cref="Span{SplitterDestination}.Empty"/> if not present. + /// Get the <see cref="SplitterDestination"/> of the next element or null if not present. /// </summary> - public readonly Span<SplitterDestination> Next + public readonly SplitterDestination Next { get { unsafe { - return _next != null ? new Span<SplitterDestination>(_next, 1) : Span<SplitterDestination>.Empty; + if (Unsafe.IsNullRef(ref _v2)) + { + if (Unsafe.IsNullRef(ref _v1)) + { + return new SplitterDestination(); + } + else + { + return new SplitterDestination(ref _v1.Next); + } + } + else + { + return new SplitterDestination(ref _v2.Next); + } } } } /// <summary> - /// Create a new <see cref="SplitterDestination"/>. + /// Creates a new splitter destination wrapper for the version 1 splitter destination data. /// </summary> - /// <param name="id">The unique id of this <see cref="SplitterDestination"/>.</param> - public SplitterDestination(int id) : this() + /// <param name="v1">Version 1 splitter destination data</param> + public SplitterDestination(ref SplitterDestinationVersion1 v1) { - Id = id; - DestinationId = Constants.UnusedMixId; - - ClearVolumes(); + _v1 = ref v1; + _v2 = ref Unsafe.NullRef<SplitterDestinationVersion2>(); } /// <summary> - /// Update the <see cref="SplitterDestination"/> from user parameter. + /// Creates a new splitter destination wrapper for the version 2 splitter destination data. /// </summary> - /// <param name="parameter">The user parameter.</param> - public void Update(SplitterDestinationInParameter parameter) + /// <param name="v2">Version 2 splitter destination data</param> + public SplitterDestination(ref SplitterDestinationVersion2 v2) { - Debug.Assert(Id == parameter.Id); - - if (parameter.IsMagicValid() && Id == parameter.Id) - { - DestinationId = parameter.DestinationId; - parameter.MixBufferVolume.CopyTo(MixBufferVolume); - - if (!IsUsed && parameter.IsUsed) - { - MixBufferVolume.CopyTo(PreviousMixBufferVolume); + _v1 = ref Unsafe.NullRef<SplitterDestinationVersion1>(); + _v2 = ref v2; + } - NeedToUpdateInternalState = false; - } + /// <summary> + /// Creates a new splitter destination wrapper for the splitter destination data. + /// </summary> + /// <param name="v1">Version 1 splitter destination data</param> + /// <param name="v2">Version 2 splitter destination data</param> + public unsafe SplitterDestination(SplitterDestinationVersion1* v1, SplitterDestinationVersion2* v2) + { + _v1 = ref Unsafe.AsRef<SplitterDestinationVersion1>(v1); + _v2 = ref Unsafe.AsRef<SplitterDestinationVersion2>(v2); + } - IsUsed = parameter.IsUsed; + /// <summary> + /// Update the splitter destination data from user parameter. + /// </summary> + /// <param name="parameter">The user parameter.</param> + public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter + { + if (Unsafe.IsNullRef(ref _v2)) + { + _v1.Update(parameter); + } + else + { + _v2.Update(parameter); } } @@ -118,12 +201,14 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// </summary> public void UpdateInternalState() { - if (IsUsed && NeedToUpdateInternalState) + if (Unsafe.IsNullRef(ref _v2)) { - MixBufferVolume.CopyTo(PreviousMixBufferVolume); + _v1.UpdateInternalState(); + } + else + { + _v2.UpdateInternalState(); } - - NeedToUpdateInternalState = false; } /// <summary> @@ -131,16 +216,23 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// </summary> public void MarkAsNeedToUpdateInternalState() { - NeedToUpdateInternalState = true; + if (Unsafe.IsNullRef(ref _v2)) + { + _v1.MarkAsNeedToUpdateInternalState(); + } + else + { + _v2.MarkAsNeedToUpdateInternalState(); + } } /// <summary> - /// Return true if the <see cref="SplitterDestination"/> is used and has a destination. + /// Return true if the splitter destination is used and has a destination. /// </summary> - /// <returns>True if the <see cref="SplitterDestination"/> is used and has a destination.</returns> + /// <returns>True if the splitter destination is used and has a destination.</returns> public readonly bool IsConfigured() { - return IsUsed && DestinationId != Constants.UnusedMixId; + return Unsafe.IsNullRef(ref _v2) ? _v1.IsConfigured() : _v2.IsConfigured(); } /// <summary> @@ -150,9 +242,17 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// <returns>The volume for the given destination.</returns> public float GetMixVolume(int destinationIndex) { - Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolume(destinationIndex) : _v2.GetMixVolume(destinationIndex); + } - return MixBufferVolume[destinationIndex]; + /// <summary> + /// Get the previous volume for a given destination. + /// </summary> + /// <param name="destinationIndex">The destination index to use.</param> + /// <returns>The volume for the given destination.</returns> + public float GetMixVolumePrev(int destinationIndex) + { + return Unsafe.IsNullRef(ref _v2) ? _v1.GetMixVolumePrev(destinationIndex) : _v2.GetMixVolumePrev(destinationIndex); } /// <summary> @@ -160,22 +260,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// </summary> public void ClearVolumes() { - MixBufferVolume.Clear(); - PreviousMixBufferVolume.Clear(); + if (Unsafe.IsNullRef(ref _v2)) + { + _v1.ClearVolumes(); + } + else + { + _v2.ClearVolumes(); + } } /// <summary> - /// Link the next element to the given <see cref="SplitterDestination"/>. + /// Link the next element to the given splitter destination. /// </summary> - /// <param name="next">The given <see cref="SplitterDestination"/> to link.</param> - public void Link(ref SplitterDestination next) + /// <param name="next">The given splitter destination to link.</param> + public void Link(SplitterDestination next) { - unsafe + if (Unsafe.IsNullRef(ref _v2)) { - fixed (SplitterDestination* nextPtr = &next) - { - _next = nextPtr; - } + Debug.Assert(!Unsafe.IsNullRef(ref next._v1)); + + _v1.Link(ref next._v1); + } + else + { + Debug.Assert(!Unsafe.IsNullRef(ref next._v2)); + + _v2.Link(ref next._v2); } } @@ -184,10 +295,74 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// </summary> public void Unlink() { - unsafe + if (Unsafe.IsNullRef(ref _v2)) + { + _v1.Unlink(); + } + else + { + _v2.Unlink(); + } + } + + /// <summary> + /// Checks if any biquad filter is enabled. + /// </summary> + /// <returns>True if any biquad filter is enabled.</returns> + public bool IsBiquadFilterEnabled() + { + return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabled(); + } + + /// <summary> + /// Checks if any biquad filter was previously enabled. + /// </summary> + /// <returns>True if any biquad filter was previously enabled.</returns> + public bool IsBiquadFilterEnabledPrev() + { + return !Unsafe.IsNullRef(ref _v2) && _v2.IsBiquadFilterEnabledPrev(); + } + + /// <summary> + /// Gets the biquad filter parameters. + /// </summary> + /// <param name="index">Biquad filter index (0 or 1).</param> + /// <returns>Biquad filter parameters.</returns> + public ref BiquadFilterParameter GetBiquadFilterParameter(int index) + { + Debug.Assert(!Unsafe.IsNullRef(ref _v2)); + + return ref _v2.GetBiquadFilterParameter(index); + } + + /// <summary> + /// Checks if any biquad filter was previously enabled. + /// </summary> + /// <param name="index">Biquad filter index (0 or 1).</param> + public void UpdateBiquadFilterEnabledPrev(int index) + { + if (!Unsafe.IsNullRef(ref _v2)) { - _next = null; + _v2.UpdateBiquadFilterEnabledPrev(index); } } + + /// <summary> + /// Get the reference for the version 1 splitter destination data, or null if version 2 is being used or the destination is null. + /// </summary> + /// <returns>Reference for the version 1 splitter destination data.</returns> + public ref SplitterDestinationVersion1 GetV1RefOrNull() + { + return ref _v1; + } + + /// <summary> + /// Get the reference for the version 2 splitter destination data, or null if version 1 is being used or the destination is null. + /// </summary> + /// <returns>Reference for the version 2 splitter destination data.</returns> + public ref SplitterDestinationVersion2 GetV2RefOrNull() + { + return ref _v2; + } } } diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs new file mode 100644 index 00000000..5d2b8fb0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion1.cs @@ -0,0 +1,206 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// <summary> + /// Server state for a splitter destination (version 1). + /// </summary> + [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)] + public struct SplitterDestinationVersion1 + { + public const int Alignment = 0x10; + + /// <summary> + /// The unique id of this <see cref="SplitterDestinationVersion1"/>. + /// </summary> + public int Id; + + /// <summary> + /// The mix to output the result of the splitter. + /// </summary> + public int DestinationId; + + /// <summary> + /// Mix buffer volumes storage. + /// </summary> + private MixArray _mix; + private MixArray _previousMix; + + /// <summary> + /// Pointer to the next linked element. + /// </summary> + private unsafe SplitterDestinationVersion1* _next; + + /// <summary> + /// Set to true if in use. + /// </summary> + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// <summary> + /// Set to true if the internal state need to be updated. + /// </summary> + [MarshalAs(UnmanagedType.I1)] + public bool NeedToUpdateInternalState; + + [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// <summary> + /// Mix buffer volumes. + /// </summary> + /// <remarks>Used when a splitter id is specified in the mix.</remarks> + public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix); + + /// <summary> + /// Previous mix buffer volumes. + /// </summary> + /// <remarks>Used when a splitter id is specified in the mix.</remarks> + public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix); + + /// <summary> + /// Get the reference of the next element or null if not present. + /// </summary> + public readonly ref SplitterDestinationVersion1 Next + { + get + { + unsafe + { + return ref Unsafe.AsRef<SplitterDestinationVersion1>(_next); + } + } + } + + /// <summary> + /// Create a new <see cref="SplitterDestinationVersion1"/>. + /// </summary> + /// <param name="id">The unique id of this <see cref="SplitterDestinationVersion1"/>.</param> + public SplitterDestinationVersion1(int id) : this() + { + Id = id; + DestinationId = Constants.UnusedMixId; + + ClearVolumes(); + } + + /// <summary> + /// Update the <see cref="SplitterDestinationVersion1"/> from user parameter. + /// </summary> + /// <param name="parameter">The user parameter.</param> + public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter + { + Debug.Assert(Id == parameter.Id); + + if (parameter.IsMagicValid() && Id == parameter.Id) + { + DestinationId = parameter.DestinationId; + + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + if (!IsUsed && parameter.IsUsed) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + + NeedToUpdateInternalState = false; + } + + IsUsed = parameter.IsUsed; + } + } + + /// <summary> + /// Update the internal state of the instance. + /// </summary> + public void UpdateInternalState() + { + if (IsUsed && NeedToUpdateInternalState) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + } + + NeedToUpdateInternalState = false; + } + + /// <summary> + /// Set the update internal state marker. + /// </summary> + public void MarkAsNeedToUpdateInternalState() + { + NeedToUpdateInternalState = true; + } + + /// <summary> + /// Return true if the <see cref="SplitterDestinationVersion1"/> is used and has a destination. + /// </summary> + /// <returns>True if the <see cref="SplitterDestinationVersion1"/> is used and has a destination.</returns> + public readonly bool IsConfigured() + { + return IsUsed && DestinationId != Constants.UnusedMixId; + } + + /// <summary> + /// Get the volume for a given destination. + /// </summary> + /// <param name="destinationIndex">The destination index to use.</param> + /// <returns>The volume for the given destination.</returns> + public float GetMixVolume(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return MixBufferVolume[destinationIndex]; + } + + /// <summary> + /// Get the previous volume for a given destination. + /// </summary> + /// <param name="destinationIndex">The destination index to use.</param> + /// <returns>The volume for the given destination.</returns> + public float GetMixVolumePrev(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return PreviousMixBufferVolume[destinationIndex]; + } + + /// <summary> + /// Clear the volumes. + /// </summary> + public void ClearVolumes() + { + MixBufferVolume.Clear(); + PreviousMixBufferVolume.Clear(); + } + + /// <summary> + /// Link the next element to the given <see cref="SplitterDestinationVersion1"/>. + /// </summary> + /// <param name="next">The given <see cref="SplitterDestinationVersion1"/> to link.</param> + public void Link(ref SplitterDestinationVersion1 next) + { + unsafe + { + fixed (SplitterDestinationVersion1* nextPtr = &next) + { + _next = nextPtr; + } + } + } + + /// <summary> + /// Remove the link to the next element. + /// </summary> + public void Unlink() + { + unsafe + { + _next = null; + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs new file mode 100644 index 00000000..f9487909 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestinationVersion2.cs @@ -0,0 +1,250 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// <summary> + /// Server state for a splitter destination (version 2). + /// </summary> + [StructLayout(LayoutKind.Sequential, Size = 0x110, Pack = Alignment)] + public struct SplitterDestinationVersion2 + { + public const int Alignment = 0x10; + + /// <summary> + /// The unique id of this <see cref="SplitterDestinationVersion2"/>. + /// </summary> + public int Id; + + /// <summary> + /// The mix to output the result of the splitter. + /// </summary> + public int DestinationId; + + /// <summary> + /// Mix buffer volumes storage. + /// </summary> + private MixArray _mix; + private MixArray _previousMix; + + /// <summary> + /// Pointer to the next linked element. + /// </summary> + private unsafe SplitterDestinationVersion2* _next; + + /// <summary> + /// Set to true if in use. + /// </summary> + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// <summary> + /// Set to true if the internal state need to be updated. + /// </summary> + [MarshalAs(UnmanagedType.I1)] + public bool NeedToUpdateInternalState; + + [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// <summary> + /// Mix buffer volumes. + /// </summary> + /// <remarks>Used when a splitter id is specified in the mix.</remarks> + public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _mix); + + /// <summary> + /// Previous mix buffer volumes. + /// </summary> + /// <remarks>Used when a splitter id is specified in the mix.</remarks> + public Span<float> PreviousMixBufferVolume => SpanHelpers.AsSpan<MixArray, float>(ref _previousMix); + + /// <summary> + /// Get the reference of the next element or null if not present. + /// </summary> + public readonly ref SplitterDestinationVersion2 Next + { + get + { + unsafe + { + return ref Unsafe.AsRef<SplitterDestinationVersion2>(_next); + } + } + } + + private Array2<BiquadFilterParameter> _biquadFilters; + + private Array2<bool> _isPreviousBiquadFilterEnabled; + + /// <summary> + /// Create a new <see cref="SplitterDestinationVersion2"/>. + /// </summary> + /// <param name="id">The unique id of this <see cref="SplitterDestinationVersion2"/>.</param> + public SplitterDestinationVersion2(int id) : this() + { + Id = id; + DestinationId = Constants.UnusedMixId; + + ClearVolumes(); + } + + /// <summary> + /// Update the <see cref="SplitterDestinationVersion2"/> from user parameter. + /// </summary> + /// <param name="parameter">The user parameter.</param> + public void Update<T>(in T parameter) where T : ISplitterDestinationInParameter + { + Debug.Assert(Id == parameter.Id); + + if (parameter.IsMagicValid() && Id == parameter.Id) + { + DestinationId = parameter.DestinationId; + + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + _biquadFilters = parameter.BiquadFilters; + + if (!IsUsed && parameter.IsUsed) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + + NeedToUpdateInternalState = false; + } + + IsUsed = parameter.IsUsed; + } + } + + /// <summary> + /// Update the internal state of the instance. + /// </summary> + public void UpdateInternalState() + { + if (IsUsed && NeedToUpdateInternalState) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + } + + NeedToUpdateInternalState = false; + } + + /// <summary> + /// Set the update internal state marker. + /// </summary> + public void MarkAsNeedToUpdateInternalState() + { + NeedToUpdateInternalState = true; + } + + /// <summary> + /// Return true if the <see cref="SplitterDestinationVersion2"/> is used and has a destination. + /// </summary> + /// <returns>True if the <see cref="SplitterDestinationVersion2"/> is used and has a destination.</returns> + public readonly bool IsConfigured() + { + return IsUsed && DestinationId != Constants.UnusedMixId; + } + + /// <summary> + /// Get the volume for a given destination. + /// </summary> + /// <param name="destinationIndex">The destination index to use.</param> + /// <returns>The volume for the given destination.</returns> + public float GetMixVolume(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return MixBufferVolume[destinationIndex]; + } + + /// <summary> + /// Get the previous volume for a given destination. + /// </summary> + /// <param name="destinationIndex">The destination index to use.</param> + /// <returns>The volume for the given destination.</returns> + public float GetMixVolumePrev(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return PreviousMixBufferVolume[destinationIndex]; + } + + /// <summary> + /// Clear the volumes. + /// </summary> + public void ClearVolumes() + { + MixBufferVolume.Clear(); + PreviousMixBufferVolume.Clear(); + } + + /// <summary> + /// Link the next element to the given <see cref="SplitterDestinationVersion2"/>. + /// </summary> + /// <param name="next">The given <see cref="SplitterDestinationVersion2"/> to link.</param> + public void Link(ref SplitterDestinationVersion2 next) + { + unsafe + { + fixed (SplitterDestinationVersion2* nextPtr = &next) + { + _next = nextPtr; + } + } + } + + /// <summary> + /// Remove the link to the next element. + /// </summary> + public void Unlink() + { + unsafe + { + _next = null; + } + } + + /// <summary> + /// Checks if any biquad filter is enabled. + /// </summary> + /// <returns>True if any biquad filter is enabled.</returns> + public bool IsBiquadFilterEnabled() + { + return _biquadFilters[0].Enable || _biquadFilters[1].Enable; + } + + /// <summary> + /// Checks if any biquad filter was previously enabled. + /// </summary> + /// <returns>True if any biquad filter was previously enabled.</returns> + public bool IsBiquadFilterEnabledPrev() + { + return _isPreviousBiquadFilterEnabled[0]; + } + + /// <summary> + /// Gets the biquad filter parameters. + /// </summary> + /// <param name="index">Biquad filter index (0 or 1).</param> + /// <returns>Biquad filter parameters.</returns> + public ref BiquadFilterParameter GetBiquadFilterParameter(int index) + { + return ref _biquadFilters[index]; + } + + /// <summary> + /// Checks if any biquad filter was previously enabled. + /// </summary> + /// <param name="index">Biquad filter index (0 or 1).</param> + public void UpdateBiquadFilterEnabledPrev(int index) + { + _isPreviousBiquadFilterEnabled[index] = _biquadFilters[index].Enable; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs index 944f092d..3e7dce55 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs @@ -15,6 +15,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { public const int Alignment = 0x10; + private delegate void SplitterDestinationAction(SplitterDestination destination, int index); + /// <summary> /// The unique id of this <see cref="SplitterState"/>. /// </summary> @@ -26,7 +28,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter public uint SampleRate; /// <summary> - /// Count of splitter destinations (<see cref="SplitterDestination"/>). + /// Count of splitter destinations. /// </summary> public int DestinationCount; @@ -37,20 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter public bool HasNewConnection; /// <summary> - /// Linked list of <see cref="SplitterDestination"/>. + /// Linked list of <see cref="SplitterDestinationVersion1"/>. + /// </summary> + private unsafe SplitterDestinationVersion1* _destinationDataV1; + + /// <summary> + /// Linked list of <see cref="SplitterDestinationVersion2"/>. /// </summary> - private unsafe SplitterDestination* _destinationsData; + private unsafe SplitterDestinationVersion2* _destinationDataV2; /// <summary> - /// Span to the first element of the linked list of <see cref="SplitterDestination"/>. + /// First element of the linked list of splitter destinations data. /// </summary> - public readonly Span<SplitterDestination> Destinations + public readonly SplitterDestination Destination { get { unsafe { - return (IntPtr)_destinationsData != IntPtr.Zero ? new Span<SplitterDestination>(_destinationsData, 1) : Span<SplitterDestination>.Empty; + return new SplitterDestination(_destinationDataV1, _destinationDataV2); } } } @@ -64,20 +71,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter Id = id; } - public readonly Span<SplitterDestination> GetData(int index) + public readonly SplitterDestination GetData(int index) { int i = 0; - Span<SplitterDestination> result = Destinations; + SplitterDestination result = Destination; while (i < index) { - if (result.IsEmpty) + if (result.IsNull) { break; } - result = result[0].Next; + result = result.Next; i++; } @@ -93,25 +100,25 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter } /// <summary> - /// Utility function to apply a given <see cref="SpanAction{T, TArg}"/> to all <see cref="Destinations"/>. + /// Utility function to apply an action to all <see cref="Destination"/>. /// </summary> /// <param name="action">The action to execute on each elements.</param> - private readonly void ForEachDestination(SpanAction<SplitterDestination, int> action) + private readonly void ForEachDestination(SplitterDestinationAction action) { - Span<SplitterDestination> temp = Destinations; + SplitterDestination temp = Destination; int i = 0; while (true) { - if (temp.IsEmpty) + if (temp.IsNull) { break; } - Span<SplitterDestination> next = temp[0].Next; + SplitterDestination next = temp.Next; - action.Invoke(temp, i++); + action(temp, i++); temp = next; } @@ -142,9 +149,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { input.ReadLittleEndian(out int destinationId); - Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationId); + SplitterDestination destination = context.GetDestination(destinationId); - SetDestination(ref destination.Span[0]); + SetDestination(destination); DestinationCount = destinationCount; @@ -152,9 +159,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { input.ReadLittleEndian(out destinationId); - Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationId); + SplitterDestination nextDestination = context.GetDestination(destinationId); - destination.Span[0].Link(ref nextDestination.Span[0]); + destination.Link(nextDestination); destination = nextDestination; } } @@ -174,16 +181,21 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter } /// <summary> - /// Set the head of the linked list of <see cref="Destinations"/>. + /// Set the head of the linked list of <see cref="Destination"/>. /// </summary> - /// <param name="newValue">A reference to a <see cref="SplitterDestination"/>.</param> - public void SetDestination(ref SplitterDestination newValue) + /// <param name="newValue">New destination value.</param> + public void SetDestination(SplitterDestination newValue) { unsafe { - fixed (SplitterDestination* newValuePtr = &newValue) + fixed (SplitterDestinationVersion1* newValuePtr = &newValue.GetV1RefOrNull()) + { + _destinationDataV1 = newValuePtr; + } + + fixed (SplitterDestinationVersion2* newValuePtr = &newValue.GetV2RefOrNull()) { - _destinationsData = newValuePtr; + _destinationDataV2 = newValuePtr; } } } @@ -193,19 +205,20 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// </summary> public readonly void UpdateInternalState() { - ForEachDestination((destination, _) => destination[0].UpdateInternalState()); + ForEachDestination((destination, _) => destination.UpdateInternalState()); } /// <summary> - /// Clear all links from the <see cref="Destinations"/>. + /// Clear all links from the <see cref="Destination"/>. /// </summary> public void ClearLinks() { - ForEachDestination((destination, _) => destination[0].Unlink()); + ForEachDestination((destination, _) => destination.Unlink()); unsafe { - _destinationsData = (SplitterDestination*)IntPtr.Zero; + _destinationDataV1 = null; + _destinationDataV2 = null; } } @@ -219,7 +232,8 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { unsafe { - splitter._destinationsData = (SplitterDestination*)IntPtr.Zero; + splitter._destinationDataV1 = null; + splitter._destinationDataV2 = null; } splitter.DestinationCount = 0; |
