| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- //
- // Copyright (c) 2019-2021 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;
- }
- }
- }
|