aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Audio.Renderer/Server/Mix
diff options
context:
space:
mode:
authorMary <me@thog.eu>2021-02-26 01:11:56 +0100
committerGitHub <noreply@github.com>2021-02-26 01:11:56 +0100
commitf556c80d0230056335632b60c71f1567e177239e (patch)
tree748aa6be62b93a8e941e25dbd83f39e1dbb37035 /Ryujinx.Audio.Renderer/Server/Mix
parent1c49089ff00fc87dc4872f135dc6a0d36169a970 (diff)
Haydn: Part 1 (#2007)
* Haydn: Part 1 Based on my reverse of audio 11.0.0. As always, core implementation under LGPLv3 for the same reasons as for Amadeus. This place the bases of a more flexible audio system while making audout & audin accurate. This have the following improvements: - Complete reimplementation of audout and audin. - Audin currently only have a dummy backend. - Dramatically reduce CPU usage by up to 50% in common cases (SoundIO and OpenAL). - Audio Renderer now can output to 5.1 devices when supported. - Audio Renderer init its backend on demand instead of keeping two up all the time. - All backends implementation are now in their own project. - Ryujinx.Audio.Renderer was renamed Ryujinx.Audio and was refactored because of this. As a note, games having issues with OpenAL haven't improved and will not because of OpenAL design (stopping when buffers finish playing causing possible audio "pops" when buffers are very small). * Update for latest hexkyz's edits on Switchbrew * audren: Rollback channel configuration changes * Address gdkchan's comments * Fix typo in OpenAL backend driver * Address last comments * Fix a nit * Address gdkchan's comments
Diffstat (limited to 'Ryujinx.Audio.Renderer/Server/Mix')
-rw-r--r--Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs276
-rw-r--r--Ryujinx.Audio.Renderer/Server/Mix/MixState.cs330
2 files changed, 0 insertions, 606 deletions
diff --git a/Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs b/Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs
deleted file mode 100644
index 5a3c3a63..00000000
--- a/Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs
+++ /dev/null
@@ -1,276 +0,0 @@
-//
-// 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.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
deleted file mode 100644
index d697488a..00000000
--- a/Ryujinx.Audio.Renderer/Server/Mix/MixState.cs
+++ /dev/null
@@ -1,330 +0,0 @@
-//
-// 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;
- }
- }
-}