aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Audio.Renderer/Server/Mix/MixState.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Audio.Renderer/Server/Mix/MixState.cs')
-rw-r--r--Ryujinx.Audio.Renderer/Server/Mix/MixState.cs330
1 files changed, 330 insertions, 0 deletions
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;
+ }
+ }
+}