From cee712105850ac3385cd0091a923438167433f9f Mon Sep 17 00:00:00 2001
From: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Date: Sat, 8 Apr 2023 01:22:00 +0200
Subject: Move solution and projects to src
---
.../Renderer/Server/Mix/MixContext.cs | 259 +++++++++++++++++
src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs | 313 +++++++++++++++++++++
2 files changed, 572 insertions(+)
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
(limited to 'src/Ryujinx.Audio/Renderer/Server/Mix')
diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs
new file mode 100644
index 00000000..cda6f737
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs
@@ -0,0 +1,259 @@
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Server.Splitter;
+using Ryujinx.Audio.Renderer.Utils;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Server.Mix
+{
+ ///
+ /// Mix context.
+ ///
+ public class MixContext
+ {
+ ///
+ /// The total mix count.
+ ///
+ private uint _mixesCount;
+
+ ///
+ /// Storage for .
+ ///
+ private Memory _mixes;
+
+ ///
+ /// Storage of the sorted indices to .
+ ///
+ private Memory _sortedMixes;
+
+ ///
+ /// Graph state.
+ ///
+ public NodeStates NodeStates { get; }
+
+ ///
+ /// The instance of the adjacent matrix.
+ ///
+ public EdgeMatrix EdgeMatrix { get; }
+
+ ///
+ /// Create a new instance of .
+ ///
+ public MixContext()
+ {
+ NodeStates = new NodeStates();
+ EdgeMatrix = new EdgeMatrix();
+ }
+
+ ///
+ /// Initialize the .
+ ///
+ /// The storage for sorted indices.
+ /// The storage of .
+ /// The storage used for the .
+ /// The storage used for the .
+ public void Initialize(Memory sortedMixes, Memory mixes, Memory nodeStatesWorkBuffer, Memory edgeMatrixWorkBuffer)
+ {
+ _mixesCount = (uint)mixes.Length;
+ _mixes = mixes;
+ _sortedMixes = sortedMixes;
+
+ if (!nodeStatesWorkBuffer.IsEmpty && !edgeMatrixWorkBuffer.IsEmpty)
+ {
+ NodeStates.Initialize(nodeStatesWorkBuffer, mixes.Length);
+ EdgeMatrix.Initialize(edgeMatrixWorkBuffer, mixes.Length);
+ }
+
+ int sortedId = 0;
+ for (int i = 0; i < _mixes.Length; i++)
+ {
+ SetSortedState(sortedId++, i);
+ }
+ }
+
+ ///
+ /// Associate the given to a given .
+ ///
+ /// The sorted id.
+ /// The index to associate.
+ private void SetSortedState(int id, int targetIndex)
+ {
+ _sortedMixes.Span[id] = targetIndex;
+ }
+
+ ///
+ /// Get a reference to the final .
+ ///
+ /// A reference to the final .
+ public ref MixState GetFinalState()
+ {
+ return ref GetState(Constants.FinalMixId);
+ }
+
+ ///
+ /// Get a reference to a at the given .
+ ///
+ /// The index to use.
+ /// A reference to a at the given .
+ public ref MixState GetState(int id)
+ {
+ return ref SpanIOHelper.GetFromMemory(_mixes, id, _mixesCount);
+ }
+
+ ///
+ /// Get a reference to a at the given of the sorted mix info.
+ ///
+ /// The index to use.
+ /// A reference to a at the given .
+ public ref MixState GetSortedState(int id)
+ {
+ Debug.Assert(id >= 0 && id < _mixesCount);
+
+ return ref GetState(_sortedMixes.Span[id]);
+ }
+
+ ///
+ /// Get the total mix count.
+ ///
+ /// The total mix count.
+ public uint GetCount()
+ {
+ return _mixesCount;
+ }
+
+ ///
+ /// Update the internal distance from the final mix value of every .
+ ///
+ private void UpdateDistancesFromFinalMix()
+ {
+ foreach (ref MixState mix in _mixes.Span)
+ {
+ mix.ClearDistanceFromFinalMix();
+ }
+
+ for (int i = 0; i < GetCount(); i++)
+ {
+ ref MixState mix = ref GetState(i);
+
+ SetSortedState(i, i);
+
+ if (mix.IsUsed)
+ {
+ uint distance;
+
+ if (mix.MixId != Constants.FinalMixId)
+ {
+ int mixId = mix.MixId;
+
+ for (distance = 0; distance < GetCount(); distance++)
+ {
+ if (mixId == Constants.UnusedMixId)
+ {
+ distance = MixState.InvalidDistanceFromFinalMix;
+ break;
+ }
+
+ ref MixState distanceMix = ref GetState(mixId);
+
+ if (distanceMix.DistanceFromFinalMix != MixState.InvalidDistanceFromFinalMix)
+ {
+ distance = distanceMix.DistanceFromFinalMix + 1;
+ break;
+ }
+
+ mixId = distanceMix.DestinationMixId;
+
+ if (mixId == Constants.FinalMixId)
+ {
+ break;
+ }
+ }
+
+ if (distance > GetCount())
+ {
+ distance = MixState.InvalidDistanceFromFinalMix;
+ }
+ }
+ else
+ {
+ distance = MixState.InvalidDistanceFromFinalMix;
+ }
+
+ mix.DistanceFromFinalMix = distance;
+ }
+ }
+ }
+
+ ///
+ /// Update the internal mix buffer offset of all .
+ ///
+ private void UpdateMixBufferOffset()
+ {
+ uint offset = 0;
+
+ foreach (ref MixState mix in _mixes.Span)
+ {
+ mix.BufferOffset = offset;
+
+ offset += mix.BufferCount;
+ }
+ }
+
+ ///
+ /// Sort the mixes using distance from the final mix.
+ ///
+ public void Sort()
+ {
+ UpdateDistancesFromFinalMix();
+
+ int[] sortedMixesTemp = _sortedMixes.Slice(0, (int)GetCount()).ToArray();
+
+ Array.Sort(sortedMixesTemp, (a, b) =>
+ {
+ ref MixState stateA = ref GetState(a);
+ ref MixState stateB = ref GetState(b);
+
+ return stateB.DistanceFromFinalMix.CompareTo(stateA.DistanceFromFinalMix);
+ });
+
+ sortedMixesTemp.AsSpan().CopyTo(_sortedMixes.Span);
+
+ UpdateMixBufferOffset();
+ }
+
+ ///
+ /// Sort the mixes and splitters using an adjacency matrix.
+ ///
+ /// The used.
+ /// Return true, if no errors in the graph were detected.
+ public bool Sort(SplitterContext splitterContext)
+ {
+ if (splitterContext.UsingSplitter())
+ {
+ bool isValid = NodeStates.Sort(EdgeMatrix);
+
+ if (isValid)
+ {
+ ReadOnlySpan sortedMixesIndex = NodeStates.GetTsortResult();
+
+ int id = 0;
+
+ for (int i = sortedMixesIndex.Length - 1; i >= 0; i--)
+ {
+ SetSortedState(id++, sortedMixesIndex[i]);
+ }
+
+ UpdateMixBufferOffset();
+ }
+
+ return isValid;
+ }
+ else
+ {
+ UpdateMixBufferOffset();
+
+ return true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
new file mode 100644
index 00000000..146e6781
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
@@ -0,0 +1,313 @@
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Server.Effect;
+using Ryujinx.Audio.Renderer.Server.Splitter;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+using static Ryujinx.Audio.Constants;
+
+namespace Ryujinx.Audio.Renderer.Server.Mix
+{
+ ///
+ /// Server state for a mix.
+ ///
+ [StructLayout(LayoutKind.Sequential, Size = 0x940, Pack = Alignment)]
+ public struct MixState
+ {
+ public const uint InvalidDistanceFromFinalMix = 0x80000000;
+
+ public const int Alignment = 0x10;
+
+ ///
+ /// Base volume of the mix.
+ ///
+ public float Volume;
+
+ ///
+ /// Target sample rate of the mix.
+ ///
+ public uint SampleRate;
+
+ ///
+ /// Target buffer count.
+ ///
+ public uint BufferCount;
+
+ ///
+ /// Set to true if in use.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsUsed;
+
+ ///
+ /// The id of the mix.
+ ///
+ public int MixId;
+
+ ///
+ /// The mix node id.
+ ///
+ public int NodeId;
+
+ ///
+ /// the buffer offset to use for command generation.
+ ///
+ public uint BufferOffset;
+
+ ///
+ /// The distance of the mix from the final mix.
+ ///
+ public uint DistanceFromFinalMix;
+
+ ///
+ /// The effect processing order storage.
+ ///
+ private IntPtr _effectProcessingOrderArrayPointer;
+
+ ///
+ /// The max element count that can be found in the effect processing order storage.
+ ///
+ public uint EffectProcessingOrderArrayMaxCount;
+
+ ///
+ /// The mix to output the result of this mix.
+ ///
+ public int DestinationMixId;
+
+ ///
+ /// Mix buffer volumes storage.
+ ///
+ private MixVolumeArray _mixVolumeArray;
+
+ ///
+ /// The splitter to output the result of this mix.
+ ///
+ public uint DestinationSplitterId;
+
+ ///
+ /// If set to true, the long size pre-delay is supported on the reverb command.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsLongSizePreDelaySupported;
+
+ [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
+ private struct MixVolumeArray
+ {
+ private const int Size = 4 * MixBufferCountMax * MixBufferCountMax;
+ }
+
+ ///
+ /// Mix buffer volumes.
+ ///
+ /// Used when no splitter id is specified.
+ public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixVolumeArray);
+
+ ///
+ /// Get the volume for a given connection destination.
+ ///
+ /// The source node index.
+ /// The destination node index
+ /// The volume for the given connection destination.
+ public float GetMixBufferVolume(int sourceIndex, int destinationIndex)
+ {
+ return MixBufferVolume[sourceIndex * MixBufferCountMax + destinationIndex];
+ }
+
+ ///
+ /// The array used to order effects associated to this mix.
+ ///
+ public Span EffectProcessingOrderArray
+ {
+ get
+ {
+ if (_effectProcessingOrderArrayPointer == IntPtr.Zero)
+ {
+ return Span.Empty;
+ }
+
+ unsafe
+ {
+ return new Span((void*)_effectProcessingOrderArrayPointer, (int)EffectProcessingOrderArrayMaxCount);
+ }
+ }
+ }
+
+ ///
+ /// Create a new
+ ///
+ ///
+ ///
+ public MixState(Memory effectProcessingOrderArray, ref BehaviourContext behaviourContext) : this()
+ {
+ MixId = UnusedMixId;
+
+ DistanceFromFinalMix = InvalidDistanceFromFinalMix;
+
+ DestinationMixId = UnusedMixId;
+
+ DestinationSplitterId = UnusedSplitterId;
+
+ unsafe
+ {
+ // SAFETY: safe as effectProcessingOrderArray comes from the work buffer memory that is pinned.
+ _effectProcessingOrderArrayPointer = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetReference(effectProcessingOrderArray.Span));
+ }
+
+ EffectProcessingOrderArrayMaxCount = (uint)effectProcessingOrderArray.Length;
+
+ IsLongSizePreDelaySupported = behaviourContext.IsLongSizePreDelaySupported();
+
+ ClearEffectProcessingOrder();
+ }
+
+ ///
+ /// Clear the value to its default state.
+ ///
+ public void ClearDistanceFromFinalMix()
+ {
+ DistanceFromFinalMix = InvalidDistanceFromFinalMix;
+ }
+
+ ///
+ /// Clear the to its default state.
+ ///
+ public void ClearEffectProcessingOrder()
+ {
+ EffectProcessingOrderArray.Fill(-1);
+ }
+
+ ///
+ /// Return true if the mix has any destinations.
+ ///
+ /// True if the mix has any destinations.
+ public bool HasAnyDestination()
+ {
+ return DestinationMixId != UnusedMixId || DestinationSplitterId != UnusedSplitterId;
+ }
+
+ ///
+ /// Update the mix connection on the adjacency matrix.
+ ///
+ /// The adjacency matrix.
+ /// The input parameter of the mix.
+ /// The splitter context.
+ /// Return true, new connections were done on the adjacency matrix.
+ private bool UpdateConnection(EdgeMatrix edgeMatrix, ref MixParameter parameter, ref SplitterContext splitterContext)
+ {
+ bool hasNewConnections;
+
+ if (DestinationSplitterId == UnusedSplitterId)
+ {
+ hasNewConnections = false;
+ }
+ else
+ {
+ ref SplitterState splitter = ref splitterContext.GetState((int)DestinationSplitterId);
+
+ hasNewConnections = splitter.HasNewConnection;
+ }
+
+ if (DestinationMixId == parameter.DestinationMixId && DestinationSplitterId == parameter.DestinationSplitterId && !hasNewConnections)
+ {
+ return false;
+ }
+
+ edgeMatrix.RemoveEdges(MixId);
+
+ if (parameter.DestinationMixId == UnusedMixId)
+ {
+ if (parameter.DestinationSplitterId != UnusedSplitterId)
+ {
+ ref SplitterState splitter = ref splitterContext.GetState((int)parameter.DestinationSplitterId);
+
+ for (int i = 0; i < splitter.DestinationCount; i++)
+ {
+ Span destination = splitter.GetData(i);
+
+ if (!destination.IsEmpty)
+ {
+ int destinationMixId = destination[0].DestinationId;
+
+ if (destinationMixId != UnusedMixId)
+ {
+ edgeMatrix.Connect(MixId, destinationMixId);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ edgeMatrix.Connect(MixId, parameter.DestinationMixId);
+ }
+
+ DestinationMixId = parameter.DestinationMixId;
+ DestinationSplitterId = parameter.DestinationSplitterId;
+
+ return true;
+ }
+
+ ///
+ /// Update the mix from user information.
+ ///
+ /// The adjacency matrix.
+ /// The input parameter of the mix.
+ /// The effect context.
+ /// The splitter context.
+ /// The behaviour context.
+ /// Return true if the mix was changed.
+ public bool Update(EdgeMatrix edgeMatrix, ref MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext)
+ {
+ bool isDirty;
+
+ Volume = parameter.Volume;
+ SampleRate = parameter.SampleRate;
+ BufferCount = parameter.BufferCount;
+ IsUsed = parameter.IsUsed;
+ MixId = parameter.MixId;
+ NodeId = parameter.NodeId;
+ parameter.MixBufferVolume.CopyTo(MixBufferVolume);
+
+ if (behaviourContext.IsSplitterSupported())
+ {
+ isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext);
+ }
+ else
+ {
+ isDirty = DestinationMixId != parameter.DestinationMixId;
+
+ if (DestinationMixId != parameter.DestinationMixId)
+ {
+ DestinationMixId = parameter.DestinationMixId;
+ }
+
+ DestinationSplitterId = UnusedSplitterId;
+ }
+
+ ClearEffectProcessingOrder();
+
+ for (int i = 0; i < effectContext.GetCount(); i++)
+ {
+ ref BaseEffect effect = ref effectContext.GetEffect(i);
+
+ if (effect.MixId == MixId)
+ {
+ Debug.Assert(effect.ProcessingOrder <= EffectProcessingOrderArrayMaxCount);
+
+ if (effect.ProcessingOrder > EffectProcessingOrderArrayMaxCount)
+ {
+ return isDirty;
+ }
+
+ EffectProcessingOrderArray[(int)effect.ProcessingOrder] = i;
+ }
+ }
+
+ return isDirty;
+ }
+ }
+}
\ No newline at end of file
--
cgit v1.2.3