diff options
Diffstat (limited to 'Ryujinx.Audio.Renderer/Server/Mix')
| -rw-r--r-- | Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs | 276 | ||||
| -rw-r--r-- | Ryujinx.Audio.Renderer/Server/Mix/MixState.cs | 330 |
2 files changed, 606 insertions, 0 deletions
diff --git a/Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs b/Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs new file mode 100644 index 00000000..46c244bb --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs @@ -0,0 +1,276 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. +// + +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 +{ + /// <summary> + /// Mix context. + /// </summary> + public class MixContext + { + /// <summary> + /// The total mix count. + /// </summary> + private uint _mixesCount; + + /// <summary> + /// Storage for <see cref="MixState"/>. + /// </summary> + private Memory<MixState> _mixes; + + /// <summary> + /// Storage of the sorted indices to <see cref="MixState"/>. + /// </summary> + private Memory<int> _sortedMixes; + + /// <summary> + /// Graph state. + /// </summary> + public NodeStates NodeStates { get; } + + /// <summary> + /// The instance of the adjacent matrix. + /// </summary> + public EdgeMatrix EdgeMatrix { get; } + + /// <summary> + /// Create a new instance of <see cref="MixContext"/>. + /// </summary> + public MixContext() + { + NodeStates = new NodeStates(); + EdgeMatrix = new EdgeMatrix(); + } + + /// <summary> + /// Initialize the <see cref="MixContext"/>. + /// </summary> + /// <param name="sortedMixes">The storage for sorted indices.</param> + /// <param name="mixes">The storage of <see cref="MixState"/>.</param> + /// <param name="nodeStatesWorkBuffer">The storage used for the <see cref="NodeStates"/>.</param> + /// <param name="edgeMatrixWorkBuffer">The storage used for the <see cref="EdgeMatrix"/>.</param> + public void Initialize(Memory<int> sortedMixes, Memory<MixState> mixes, Memory<byte> nodeStatesWorkBuffer, Memory<byte> 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); + } + } + + /// <summary> + /// Associate the given <paramref name="targetIndex"/> to a given <paramref cref="id"/>. + /// </summary> + /// <param name="id">The sorted id.</param> + /// <param name="targetIndex">The index to associate.</param> + private void SetSortedState(int id, int targetIndex) + { + _sortedMixes.Span[id] = targetIndex; + } + + /// <summary> + /// Get a reference to the final <see cref="MixState"/>. + /// </summary> + /// <returns>A reference to the final <see cref="MixState"/>.</returns> + public ref MixState GetFinalState() + { + return ref GetState(RendererConstants.FinalMixId); + } + + /// <summary> + /// Get a reference to a <see cref="MixState"/> at the given <paramref name="id"/>. + /// </summary> + /// <param name="id">The index to use.</param> + /// <returns>A reference to a <see cref="MixState"/> at the given <paramref name="id"/>.</returns> + public ref MixState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_mixes, id, _mixesCount); + } + + /// <summary> + /// Get a reference to a <see cref="MixState"/> at the given <paramref name="id"/> of the sorted mix info. + /// </summary> + /// <param name="id">The index to use.</param> + /// <returns>A reference to a <see cref="MixState"/> at the given <paramref name="id"/>.</returns> + public ref MixState GetSortedState(int id) + { + Debug.Assert(id >= 0 && id < _mixesCount); + + return ref GetState(_sortedMixes.Span[id]); + } + + /// <summary> + /// Get the total mix count. + /// </summary> + /// <returns>The total mix count.</returns> + public uint GetCount() + { + return _mixesCount; + } + + /// <summary> + /// Update the internal distance from the final mix value of every <see cref="MixState"/>. + /// </summary> + 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 != RendererConstants.FinalMixId) + { + int mixId = mix.MixId; + + for (distance = 0; distance < GetCount(); distance++) + { + if (mixId == RendererConstants.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 == RendererConstants.FinalMixId) + { + break; + } + } + + if (distance > GetCount()) + { + distance = MixState.InvalidDistanceFromFinalMix; + } + } + else + { + distance = MixState.InvalidDistanceFromFinalMix; + } + + mix.DistanceFromFinalMix = distance; + } + } + } + + /// <summary> + /// Update the internal mix buffer offset of all <see cref="MixState"/>. + /// </summary> + private void UpdateMixBufferOffset() + { + uint offset = 0; + + foreach (ref MixState mix in _mixes.Span) + { + mix.BufferOffset = offset; + + offset += mix.BufferCount; + } + } + + /// <summary> + /// Sort the mixes using distance from the final mix. + /// </summary> + 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(); + } + + /// <summary> + /// Sort the mixes and splitters using an adjacency matrix. + /// </summary> + /// <param name="splitterContext">The <see cref="SplitterContext"/> used.</param> + /// <returns>Return true, if no errors in the graph were detected.</returns> + public bool Sort(SplitterContext splitterContext) + { + if (splitterContext.UsingSplitter()) + { + bool isValid = NodeStates.Sort(EdgeMatrix); + + if (isValid) + { + ReadOnlySpan<int> 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; + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Server/Mix/MixState.cs b/Ryujinx.Audio.Renderer/Server/Mix/MixState.cs new file mode 100644 index 00000000..ed4665f5 --- /dev/null +++ b/Ryujinx.Audio.Renderer/Server/Mix/MixState.cs @@ -0,0 +1,330 @@ +// +// Copyright (c) 2019-2020 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. +// + +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.Renderer.RendererConstants; + +namespace Ryujinx.Audio.Renderer.Server.Mix +{ + /// <summary> + /// Server state for a mix. + /// </summary> + [StructLayout(LayoutKind.Sequential, Size = 0x940, Pack = Alignment)] + public struct MixState + { + public const uint InvalidDistanceFromFinalMix = 0x80000000; + + public const int Alignment = 0x10; + + /// <summary> + /// Base volume of the mix. + /// </summary> + public float Volume; + + /// <summary> + /// Target sample rate of the mix. + /// </summary> + public uint SampleRate; + + /// <summary> + /// Target buffer count. + /// </summary> + public uint BufferCount; + + /// <summary> + /// Set to true if in use. + /// </summary> + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// <summary> + /// The id of the mix. + /// </summary> + public int MixId; + + /// <summary> + /// The mix node id. + /// </summary> + public int NodeId; + + /// <summary> + /// the buffer offset to use for command generation. + /// </summary> + public uint BufferOffset; + + /// <summary> + /// The distance of the mix from the final mix. + /// </summary> + public uint DistanceFromFinalMix; + + /// <summary> + /// The effect processing order storage. + /// </summary> + private IntPtr _effectProcessingOrderArrayPointer; + + /// <summary> + /// The max element count that can be found in the effect processing order storage. + /// </summary> + public uint EffectProcessingOrderArrayMaxCount; + + /// <summary> + /// The mix to output the result of this mix. + /// </summary> + public int DestinationMixId; + + /// <summary> + /// Mix buffer volumes storage. + /// </summary> + private MixVolumeArray _mixVolumeArray; + + /// <summary> + /// The splitter to output the result of this mix. + /// </summary> + public uint DestinationSplitterId; + + /// <summary> + /// If set to true, the long size pre-delay is supported on the reverb command. + /// </summary> + [MarshalAs(UnmanagedType.I1)] + public bool IsLongSizePreDelaySupported; + + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + private struct MixVolumeArray + { + private const int Size = 4 * MixBufferCountMax * MixBufferCountMax; + } + + /// <summary> + /// Mix buffer volumes. + /// </summary> + /// <remarks>Used when no splitter id is specified.</remarks> + public Span<float> MixBufferVolume => SpanHelpers.AsSpan<MixVolumeArray, float>(ref _mixVolumeArray); + + /// <summary> + /// Get the volume for a given connection destination. + /// </summary> + /// <param name="sourceIndex">The source node index.</param> + /// <param name="destinationIndex">The destination node index</param> + /// <returns>The volume for the given connection destination.</returns> + public float GetMixBufferVolume(int sourceIndex, int destinationIndex) + { + return MixBufferVolume[sourceIndex * MixBufferCountMax + destinationIndex]; + } + + /// <summary> + /// The array used to order effects associated to this mix. + /// </summary> + public Span<int> EffectProcessingOrderArray + { + get + { + if (_effectProcessingOrderArrayPointer == IntPtr.Zero) + { + return Span<int>.Empty; + } + + unsafe + { + return new Span<int>((void*)_effectProcessingOrderArrayPointer, (int)EffectProcessingOrderArrayMaxCount); + } + } + } + + /// <summary> + /// Create a new <see cref="MixState"/> + /// </summary> + /// <param name="effectProcessingOrderArray"></param> + /// <param name="behaviourContext"></param> + public MixState(Memory<int> 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(); + } + + /// <summary> + /// Clear the <see cref="DistanceFromFinalMix"/> value to its default state. + /// </summary> + public void ClearDistanceFromFinalMix() + { + DistanceFromFinalMix = InvalidDistanceFromFinalMix; + } + + /// <summary> + /// Clear the <see cref="EffectProcessingOrderArray"/> to its default state. + /// </summary> + public void ClearEffectProcessingOrder() + { + EffectProcessingOrderArray.Fill(-1); + } + + /// <summary> + /// Return true if the mix has any destinations. + /// </summary> + /// <returns>True if the mix has any destinations.</returns> + public bool HasAnyDestination() + { + return DestinationMixId != UnusedMixId || DestinationSplitterId != UnusedSplitterId; + } + + /// <summary> + /// Update the mix connection on the adjacency matrix. + /// </summary> + /// <param name="edgeMatrix">The adjacency matrix.</param> + /// <param name="parameter">The input parameter of the mix.</param> + /// <param name="splitterContext">The splitter context.</param> + /// <returns>Return true, new connections were done on the adjacency matrix.</returns> + 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<SplitterDestination> 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; + } + + /// <summary> + /// Update the mix from user information. + /// </summary> + /// <param name="edgeMatrix">The adjacency matrix.</param> + /// <param name="parameter">The input parameter of the mix.</param> + /// <param name="effectContext">The effect context.</param> + /// <param name="splitterContext">The splitter context.</param> + /// <param name="behaviourContext">The behaviour context.</param> + /// <returns>Return true if the mix was changed.</returns> + 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; + } + } +} |
