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 readonly 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 readonly 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 readonly void ClearEffectProcessingOrder() { EffectProcessingOrderArray.Fill(-1); } /// /// Return true if the mix has any destinations. /// /// True if the mix has any destinations. public readonly 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, in 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, in 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, in 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; } } }