diff options
| author | Mary <me@thog.eu> | 2021-02-26 01:11:56 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-02-26 01:11:56 +0100 |
| commit | f556c80d0230056335632b60c71f1567e177239e (patch) | |
| tree | 748aa6be62b93a8e941e25dbd83f39e1dbb37035 /Ryujinx.Audio.Renderer/Server/Mix | |
| parent | 1c49089ff00fc87dc4872f135dc6a0d36169a970 (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.cs | 276 | ||||
| -rw-r--r-- | Ryujinx.Audio.Renderer/Server/Mix/MixState.cs | 330 |
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; - } - } -} |
