From f3b0b4831c323a20393aa0388f947317354372b7 Mon Sep 17 00:00:00 2001
From: Mary <me@thog.eu>
Date: Tue, 25 May 2021 19:01:09 +0200
Subject: amadeus: Update to REV9 (#2309)

* amadeus: Update to REV9

This implements all the changes made with REV9 on 12.0.0.

* Address Ac_k's comments
---
 Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs  |   4 +-
 .../Renderer/Dsp/Command/LimiterCommandVersion1.cs | 160 ++++++++++++++++++
 .../Renderer/Dsp/Command/LimiterCommandVersion2.cs | 179 +++++++++++++++++++++
 Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs   |  46 ++++++
 4 files changed, 388 insertions(+), 1 deletion(-)
 create mode 100644 Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
 create mode 100644 Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
 create mode 100644 Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs

(limited to 'Ryujinx.Audio/Renderer/Dsp')

diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
index 8ff1c581..997a080e 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
@@ -44,6 +44,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
         Reverb3d,
         Performance,
         ClearMixBuffer,
-        CopyMixBuffer
+        CopyMixBuffer,
+        LimiterVersion1,
+        LimiterVersion2
     }
 }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
new file mode 100644
index 00000000..975e61f9
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
@@ -0,0 +1,160 @@
+//
+// 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.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+    public class LimiterCommandVersion1 : ICommand
+    {
+        public bool Enabled { get; set; }
+
+        public int NodeId { get; }
+
+        public CommandType CommandType => CommandType.LimiterVersion1;
+
+        public ulong EstimatedProcessingTime { get; set; }
+
+        public LimiterParameter Parameter => _parameter;
+        public Memory<LimiterState> State { get; }
+        public ulong WorkBuffer { get; }
+        public ushort[] OutputBufferIndices { get; }
+        public ushort[] InputBufferIndices { get; }
+        public bool IsEffectEnabled { get; }
+
+        private LimiterParameter _parameter;
+
+        public LimiterCommandVersion1(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, bool isEnabled, ulong workBuffer, int nodeId)
+        {
+            Enabled = true;
+            NodeId = nodeId;
+            _parameter = parameter;
+            State = state;
+            WorkBuffer = workBuffer;
+
+            IsEffectEnabled = isEnabled;
+
+            InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+            OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+            for (int i = 0; i < Parameter.ChannelCount; i++)
+            {
+                InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
+                OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+            }
+        }
+
+        public void Process(CommandList context)
+        {
+            ref LimiterState state = ref State.Span[0];
+
+            if (IsEffectEnabled)
+            {
+                if (Parameter.Status == Server.Effect.UsageState.Invalid)
+                {
+                    state = new LimiterState(ref _parameter, WorkBuffer);
+                }
+                else if (Parameter.Status == Server.Effect.UsageState.New)
+                {
+                    state.UpdateParameter(ref _parameter);
+                }
+            }
+
+            ProcessLimiter(context);
+        }
+
+        private void ProcessLimiter(CommandList context)
+        {
+            Debug.Assert(Parameter.IsChannelCountValid());
+
+            if (IsEffectEnabled && Parameter.IsChannelCountValid())
+            {
+                ref LimiterState state = ref State.Span[0];
+
+                ReadOnlyMemory<float>[] inputBuffers = new ReadOnlyMemory<float>[Parameter.ChannelCount];
+                Memory<float>[] outputBuffers = new Memory<float>[Parameter.ChannelCount];
+
+                for (int i = 0; i < Parameter.ChannelCount; i++)
+                {
+                    inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]);
+                    outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]);
+                }
+
+                for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+                {
+                    for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
+                    {
+                        float inputSample = inputBuffers[channelIndex].Span[sampleIndex];
+
+                        float sampleInputMax = Math.Abs(inputSample * Parameter.InputGain);
+
+                        float inputCoefficient = Parameter.ReleaseCoefficient;
+
+                        if (sampleInputMax > state.DectectorAverage[channelIndex])
+                        {
+                            inputCoefficient = Parameter.AttackCoefficient;
+                        }
+
+                        state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
+
+                        float attenuation = 1.0f;
+
+                        if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
+                        {
+                            attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
+                        }
+
+                        float outputCoefficient = Parameter.ReleaseCoefficient;
+
+                        if (state.CompressionGain[channelIndex] > attenuation)
+                        {
+                            outputCoefficient = Parameter.AttackCoefficient;
+                        }
+
+                        state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
+
+                        ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
+
+                        outputBuffers[channelIndex].Span[sampleIndex] = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
+
+                        delayedSample = inputSample;
+
+                        state.DelayedSampleBufferPosition[channelIndex]++;
+
+                        while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
+                        {
+                            state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                for (int i = 0; i < Parameter.ChannelCount; i++)
+                {
+                    if (InputBufferIndices[i] != OutputBufferIndices[i])
+                    {
+                        context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
new file mode 100644
index 00000000..0a4b14b7
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
@@ -0,0 +1,179 @@
+//
+// 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.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+    public class LimiterCommandVersion2 : ICommand
+    {
+        public bool Enabled { get; set; }
+
+        public int NodeId { get; }
+
+        public CommandType CommandType => CommandType.LimiterVersion2;
+
+        public ulong EstimatedProcessingTime { get; set; }
+
+        public LimiterParameter Parameter => _parameter;
+        public Memory<LimiterState> State { get; }
+        public Memory<EffectResultState> ResultState { get; }
+        public ulong WorkBuffer { get; }
+        public ushort[] OutputBufferIndices { get; }
+        public ushort[] InputBufferIndices { get; }
+        public bool IsEffectEnabled { get; }
+
+        private LimiterParameter _parameter;
+
+        public LimiterCommandVersion2(uint bufferOffset, LimiterParameter parameter, Memory<LimiterState> state, Memory<EffectResultState> resultState, bool isEnabled, ulong workBuffer, int nodeId)
+        {
+            Enabled = true;
+            NodeId = nodeId;
+            _parameter = parameter;
+            State = state;
+            ResultState = resultState;
+            WorkBuffer = workBuffer;
+
+            IsEffectEnabled = isEnabled;
+
+            InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+            OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+            for (int i = 0; i < Parameter.ChannelCount; i++)
+            {
+                InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
+                OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+            }
+        }
+
+        public void Process(CommandList context)
+        {
+            ref LimiterState state = ref State.Span[0];
+
+            if (IsEffectEnabled)
+            {
+                if (Parameter.Status == Server.Effect.UsageState.Invalid)
+                {
+                    state = new LimiterState(ref _parameter, WorkBuffer);
+                }
+                else if (Parameter.Status == Server.Effect.UsageState.New)
+                {
+                    state.UpdateParameter(ref _parameter);
+                }
+            }
+
+            ProcessLimiter(context);
+        }
+
+        private void ProcessLimiter(CommandList context)
+        {
+            Debug.Assert(Parameter.IsChannelCountValid());
+
+            if (IsEffectEnabled && Parameter.IsChannelCountValid())
+            {
+                ref LimiterState state = ref State.Span[0];
+
+                if (!ResultState.IsEmpty && Parameter.StatisticsReset)
+                {
+                    ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
+
+                    statistics.Reset();
+                }
+
+                ReadOnlyMemory<float>[] inputBuffers = new ReadOnlyMemory<float>[Parameter.ChannelCount];
+                Memory<float>[] outputBuffers = new Memory<float>[Parameter.ChannelCount];
+
+                for (int i = 0; i < Parameter.ChannelCount; i++)
+                {
+                    inputBuffers[i] = context.GetBufferMemory(InputBufferIndices[i]);
+                    outputBuffers[i] = context.GetBufferMemory(OutputBufferIndices[i]);
+                }
+
+                for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+                {
+                    for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
+                    {
+                        float inputSample = inputBuffers[channelIndex].Span[sampleIndex];
+
+                        float sampleInputMax = Math.Abs(inputSample * Parameter.InputGain);
+
+                        float inputCoefficient = Parameter.ReleaseCoefficient;
+
+                        if (sampleInputMax > state.DectectorAverage[channelIndex])
+                        {
+                            inputCoefficient = Parameter.AttackCoefficient;
+                        }
+
+                        state.DectectorAverage[channelIndex] += inputCoefficient * (sampleInputMax - state.DectectorAverage[channelIndex]);
+
+                        float attenuation = 1.0f;
+
+                        if (state.DectectorAverage[channelIndex] > Parameter.Threshold)
+                        {
+                            attenuation = Parameter.Threshold / state.DectectorAverage[channelIndex];
+                        }
+
+                        float outputCoefficient = Parameter.ReleaseCoefficient;
+
+                        if (state.CompressionGain[channelIndex] > attenuation)
+                        {
+                            outputCoefficient = Parameter.AttackCoefficient;
+                        }
+
+                        state.CompressionGain[channelIndex] += outputCoefficient * (attenuation - state.CompressionGain[channelIndex]);
+
+                        ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
+
+                        outputBuffers[channelIndex].Span[sampleIndex] = delayedSample * state.CompressionGain[channelIndex] * Parameter.OutputGain;
+
+                        delayedSample = inputSample;
+
+                        state.DelayedSampleBufferPosition[channelIndex]++;
+
+                        while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
+                        {
+                            state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
+                        }
+
+                        if (!ResultState.IsEmpty)
+                        {
+                            ref LimiterStatistics statistics = ref MemoryMarshal.Cast<byte, LimiterStatistics>(ResultState.Span[0].SpecificData)[0];
+
+                            statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax);
+                            statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], state.CompressionGain[channelIndex]);
+                        }
+                    }
+                }
+            }
+            else
+            {
+                for (int i = 0; i < Parameter.ChannelCount; i++)
+                {
+                    if (InputBufferIndices[i] != OutputBufferIndices[i])
+                    {
+                        context.GetBufferMemory(InputBufferIndices[i]).CopyTo(context.GetBufferMemory(OutputBufferIndices[i]));
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs b/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs
new file mode 100644
index 00000000..53913bad
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs
@@ -0,0 +1,46 @@
+//
+// 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.Parameter.Effect;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.State
+{
+    public class LimiterState
+    {
+        public float[] DectectorAverage;
+        public float[] CompressionGain;
+        public float[] DelayedSampleBuffer;
+        public int[] DelayedSampleBufferPosition;
+
+        public LimiterState(ref LimiterParameter parameter, ulong workBuffer)
+        {
+            DectectorAverage = new float[parameter.ChannelCount];
+            CompressionGain = new float[parameter.ChannelCount];
+            DelayedSampleBuffer = new float[parameter.ChannelCount * parameter.DelayBufferSampleCountMax];
+            DelayedSampleBufferPosition = new int[parameter.ChannelCount];
+
+            DectectorAverage.AsSpan().Fill(0.0f);
+            CompressionGain.AsSpan().Fill(1.0f);
+            DelayedSampleBufferPosition.AsSpan().Fill(0);
+
+            UpdateParameter(ref parameter);
+        }
+
+        public void UpdateParameter(ref LimiterParameter parameter) {}
+    }
+}
-- 
cgit v1.2.3-70-g09d2