From dff138229c79483c189be6f3829ed88a5f95575d Mon Sep 17 00:00:00 2001
From: Mary-nyan <mary@mary.zone>
Date: Mon, 28 Nov 2022 08:28:45 +0100
Subject: [PATCH] amadeus: Fixes and initial 15.0.0 support (#3908)

* amadeus: Allow OOB read of GC-ADPCM coefficients

Fixes "Ninja Gaiden Sigma 2" and possibly "NINJA GAIDEN 3: Razor's Edge"

* amadeus: Fix wrong variable usage in delay effect

We should transform the delay line values, not the input.

* amadeus: Update GroupedBiquadFilterCommand documentation

* amadeus: Simplify PoolMapper alignment checks

* amadeus: Update Surround delay effect matrix to REV11

* amadeus: Add drop parameter support and use 32 bits integers for estimate time

Also implement accurate ExecuteAudioRendererRendering stub.

* Address gdkchan's comments

* Address gdkchan's other comments

* Address gdkchan's comment
---
 Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs     | 22 +++++++++---
 .../Command/AdpcmDataSourceCommandVersion1.cs |  2 +-
 .../Dsp/Command/AuxiliaryBufferCommand.cs     |  2 +-
 .../Dsp/Command/BiquadFilterCommand.cs        |  2 +-
 .../Dsp/Command/CaptureBufferCommand.cs       |  2 +-
 .../Dsp/Command/CircularBufferSinkCommand.cs  |  2 +-
 .../Dsp/Command/ClearMixBufferCommand.cs      |  2 +-
 .../Dsp/Command/CopyMixBufferCommand.cs       |  2 +-
 .../Dsp/Command/DataSourceVersion2Command.cs  |  2 +-
 .../Renderer/Dsp/Command/DelayCommand.cs      | 30 ++++++++--------
 .../Dsp/Command/DepopForMixBuffersCommand.cs  |  2 +-
 .../Dsp/Command/DepopPrepareCommand.cs        |  2 +-
 .../Renderer/Dsp/Command/DeviceSinkCommand.cs |  2 +-
 .../Command/DownMixSurroundToStereoCommand.cs |  2 +-
 .../Dsp/Command/GroupedBiquadFilterCommand.cs |  7 ++--
 .../Renderer/Dsp/Command/ICommand.cs          |  2 +-
 .../Dsp/Command/LimiterCommandVersion1.cs     |  2 +-
 .../Dsp/Command/LimiterCommandVersion2.cs     |  2 +-
 .../Renderer/Dsp/Command/MixCommand.cs        |  2 +-
 .../Renderer/Dsp/Command/MixRampCommand.cs    |  2 +-
 .../Dsp/Command/MixRampGroupedCommand.cs      |  2 +-
 .../PcmFloatDataSourceCommandVersion1.cs      |  2 +-
 .../PcmInt16DataSourceCommandVersion1.cs      |  2 +-
 .../Dsp/Command/PerformanceCommand.cs         |  2 +-
 .../Renderer/Dsp/Command/Reverb3dCommand.cs   |  2 +-
 .../Renderer/Dsp/Command/ReverbCommand.cs     |  2 +-
 .../Renderer/Dsp/Command/UpsampleCommand.cs   |  2 +-
 .../Renderer/Dsp/Command/VolumeCommand.cs     |  2 +-
 .../Renderer/Dsp/Command/VolumeRampCommand.cs |  2 +-
 .../Renderer/Server/AudioRenderSystem.cs      | 34 ++++++++++++++++---
 .../Renderer/Server/BehaviourContext.cs       |  3 +-
 .../Renderer/Server/CommandBuffer.cs          |  2 +-
 .../Renderer/Server/MemoryPool/PoolMapper.cs  |  4 +--
 Ryujinx.Audio/ResultCode.cs                   |  1 +
 .../Audio/AudioRenderer/AudioRenderer.cs      | 12 ++++++-
 .../AudioRenderer/AudioRendererServer.cs      | 29 ++++++++++++++++
 .../Audio/AudioRenderer/IAudioRenderer.cs     |  2 ++
 37 files changed, 140 insertions(+), 58 deletions(-)

diff --git a/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs b/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs
index f6638a9af3..2680dcb1e0 100644
--- a/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs
@@ -1,4 +1,5 @@
 using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Common.Logging;
 using System;
 using System.Diagnostics;
 using System.Runtime.CompilerServices;
@@ -71,6 +72,19 @@ namespace Ryujinx.Audio.Renderer.Dsp
             return (short)value;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index)
+        {
+            if ((uint)index > (uint)coefficients.Length)
+            {
+                Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
+
+                return 0;
+            }
+
+            return coefficients[index];
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static int Decode(Span<short> output, ReadOnlySpan<byte> input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan<short> coefficients, ref AdpcmLoopContext loopContext)
         {
@@ -84,8 +98,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
             byte coefficientIndex = (byte)((predScale >> 4) & 0xF);
             short history0 = loopContext.History0;
             short history1 = loopContext.History1;
-            short coefficient0 = coefficients[coefficientIndex * 2 + 0];
-            short coefficient1 = coefficients[coefficientIndex * 2 + 1];
+            short coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 0);
+            short coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
 
             int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset);
             int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset);
@@ -109,8 +123,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
 
                     coefficientIndex = (byte)((predScale >> 4) & 0xF);
 
-                    coefficient0 = coefficients[coefficientIndex * 2 + 0];
-                    coefficient1 = coefficients[coefficientIndex * 2 + 1];
+                    coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2);
+                    coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
 
                     nibbles += 2;
 
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs
index 1ad629f49c..1fe6069f72 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.AdpcmDataSourceVersion1;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ushort OutputBufferIndex { get; }
         public uint SampleRate { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
index cfa5400c12..5c3c0324b3 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
@@ -16,7 +16,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.AuxiliaryBuffer;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public uint InputBufferIndex { get; }
         public uint OutputBufferIndex { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
index e35911b24d..b994c1cb95 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.BiquadFilter;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public Memory<BiquadFilterState> BiquadFilterState { get; }
         public int InputBufferIndex { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs
index ab1ea77d96..da1cb25466 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs
@@ -16,7 +16,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.CaptureBuffer;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public uint InputBufferIndex { get; }
 
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs
index 27cd6e8421..e50637eb3d 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.CircularBufferSink;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ushort[] Input { get; }
         public uint InputCount { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs
index c3530db1aa..9e653e8047 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs
@@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.ClearMixBuffer;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ClearMixBufferCommand(int nodeId)
         {
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs
index 64c297d11a..7237fddf65 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs
@@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.CopyMixBuffer;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ushort InputBufferIndex { get; }
         public ushort OutputBufferIndex { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs
index f602262e3e..c1503b6a02 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType { get; }
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ushort OutputBufferIndex { get; }
         public uint SampleRate { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs
index 8f11da9582..cb5678c7b3 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs
@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.Delay;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public DelayParameter Parameter => _parameter;
         public Memory<DelayState> State { get; }
@@ -49,15 +49,15 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
                 OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
             }
 
-            // NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
-            // TODO: Update delay processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
-            DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
-            DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
+            DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, InputBufferIndices);
+            DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, OutputBufferIndices);
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
         private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount)
         {
+            const ushort channelCount = 1;
+
             float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
             float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
             float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
@@ -70,7 +70,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
                 float temp = input * inGain + delayLineValue * feedbackGain;
 
-                state.UpdateLowPassFilter(ref temp, 1);
+                state.UpdateLowPassFilter(ref temp, channelCount);
 
                 outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
             }
@@ -104,7 +104,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
                     Y = state.DelayLines[1].Read(),
                 };
 
-                Vector2 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
+                Vector2 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
 
                 state.UpdateLowPassFilter(ref Unsafe.As<Vector2, float>(ref temp), channelCount);
 
@@ -148,7 +148,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
                     W = state.DelayLines[3].Read()
                 };
 
-                Vector4 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
+                Vector4 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
 
                 state.UpdateLowPassFilter(ref Unsafe.As<Vector4, float>(ref temp), channelCount);
 
@@ -171,12 +171,12 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
             float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
             float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
 
-            Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain,
-                                                    0.0f, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f,
-                                                    delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f,
-                                                    0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f,
-                                                    delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackBaseGain, 0.0f,
-                                                    0.0f, 0.0f, 0.0f, 0.0f, 0.0f, feedbackGain);
+            Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f,
+                                                    0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain,
+                                                    delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f,
+                                                    0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f,
+                                                    delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
+                                                    0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain);
 
             for (int i = 0; i < sampleCount; i++)
             {
@@ -200,7 +200,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
                     U = state.DelayLines[5].Read()
                 };
 
-                Vector6 temp = MatrixHelper.Transform(ref channelInput, ref delayFeedback) + channelInput * inGain;
+                Vector6 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
 
                 state.UpdateLowPassFilter(ref Unsafe.As<Vector6, float>(ref temp), channelCount);
 
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
index e3a87c1023..1dba56e6cc 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.DepopForMixBuffers;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public uint MixBufferOffset { get; }
 
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs
index 1e37ff7150..d02f7c1218 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.DepopPrepare;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public uint MixBufferCount { get; }
 
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
index a34fbc562c..9c88a4e7f4 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.DeviceSink;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public string DeviceName { get; }
 
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs
index d75da6f9fa..79cefcc53a 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.DownMixSurroundToStereo;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ushort[] InputBufferIndices { get; }
         public ushort[] OutputBufferIndices { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
index ae1ab12c56..b190cc10d5 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.GroupedBiquadFilter;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         private BiquadFilterParameter[] _parameters;
         private Memory<BiquadFilterState> _biquadFilterStates;
@@ -47,9 +47,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
                 }
             }
 
-            // NOTE: Nintendo also implements a hot path for double biquad filters, but no generic path when the command definition suggests it could be done.
-            // As such we currently only implement a generic path for simplicity.
-            // TODO: Implement double biquad filters fast path.
+            // NOTE: Nintendo only implement single and double biquad filters but no generic path when the command definition suggests it could be done.
+            // As such we currently only implement a generic path for simplicity for double biquad.
             if (_parameters.Length == 1)
             {
                 BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount);
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs
index dddd2511ea..d281e6e9fc 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs
@@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType { get; }
 
-        public ulong EstimatedProcessingTime { get; }
+        public uint EstimatedProcessingTime { get; }
 
         public void Process(CommandList context);
 
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
index a393c88599..9cfef736e3 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.LimiterVersion1;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public LimiterParameter Parameter => _parameter;
         public Memory<LimiterState> State { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
index ad703de14b..46c95e4f9d 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
@@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.LimiterVersion2;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public LimiterParameter Parameter => _parameter;
         public Memory<LimiterState> State { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs
index d50309964a..2616bda57a 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs
@@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.Mix;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ushort InputBufferIndex { get; }
         public ushort OutputBufferIndex { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs
index 06af9f6fa7..76a1aba25b 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.MixRamp;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ushort InputBufferIndex { get; }
         public ushort OutputBufferIndex { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
index 97bb0f508f..e348e35889 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
@@ -12,7 +12,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.MixRampGrouped;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public uint MixBufferCount { get; }
 
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs
index 7c48a511cc..7cec7d2ab9 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ushort OutputBufferIndex { get; }
         public uint SampleRate { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs
index 8483f6d416..dfe9814fed 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs
@@ -13,7 +13,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ushort OutputBufferIndex { get; }
         public uint SampleRate { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs
index c2f94474d2..d3e3f80563 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs
@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.Performance;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public PerformanceEntryAddresses PerformanceEntryAddresses { get; }
 
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs
index 04809245f8..eeb6456735 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs
@@ -31,7 +31,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.Reverb3d;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ushort InputBufferIndex { get; }
         public ushort OutputBufferIndex { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs
index 130706d10c..0a32a065d3 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs
@@ -34,7 +34,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.Reverb;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ReverbParameter Parameter => _parameter;
         public Memory<ReverbState> State { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs
index 6df44b3276..1617a6421d 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.Upsample;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public uint BufferCount { get; }
         public uint InputBufferIndex { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs
index e294789116..0628f6d810 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs
@@ -15,7 +15,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.Volume;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ushort InputBufferIndex { get; }
         public ushort OutputBufferIndex { get; }
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs
index ffda8b1a0a..5c0c88451e 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs
@@ -11,7 +11,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
 
         public CommandType CommandType => CommandType.VolumeRamp;
 
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         public ushort InputBufferIndex { get; }
         public ushort OutputBufferIndex { get; }
diff --git a/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
index af163ae040..34fdef8a17 100644
--- a/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
+++ b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
@@ -28,6 +28,7 @@ namespace Ryujinx.Audio.Renderer.Server
     {
         private object _lock = new object();
 
+        private AudioRendererRenderingDevice _renderingDevice;
         private AudioRendererExecutionMode _executionMode;
         private IWritableEvent _systemEvent;
         private ManualResetEvent _terminationEvent;
@@ -63,6 +64,7 @@ namespace Ryujinx.Audio.Renderer.Server
         private uint _renderingTimeLimitPercent;
         private bool _voiceDropEnabled;
         private uint _voiceDropCount;
+        private float _voiceDropParameter;
         private bool _isDspRunningBehind;
 
         private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator;
@@ -95,6 +97,7 @@ namespace Ryujinx.Audio.Renderer.Server
 
             _totalElapsedTicksUpdating = 0;
             _sessionId = 0;
+            _voiceDropParameter = 1.0f;
         }
 
         public ResultCode Initialize(
@@ -130,6 +133,7 @@ namespace Ryujinx.Audio.Renderer.Server
             _upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount;
             _appletResourceId = appletResourceId;
             _memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount;
+            _renderingDevice = parameter.RenderingDevice;
             _executionMode = parameter.ExecutionMode;
             _sessionId = sessionId;
             MemoryManager = memoryManager;
@@ -337,6 +341,7 @@ namespace Ryujinx.Audio.Renderer.Server
 
             _processHandle = processHandle;
             _elapsedFrameCount = 0;
+            _voiceDropParameter = 1.0f;
 
             switch (_behaviourContext.GetCommandProcessingTimeEstimatorVersion())
             {
@@ -515,7 +520,7 @@ namespace Ryujinx.Audio.Renderer.Server
             return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency);
         }
 
-        private uint ComputeVoiceDrop(CommandBuffer commandBuffer, long voicesEstimatedTime, long deltaTimeDsp)
+        private uint ComputeVoiceDrop(CommandBuffer commandBuffer, uint voicesEstimatedTime, long deltaTimeDsp)
         {
             int i;
 
@@ -584,7 +589,7 @@ namespace Ryujinx.Audio.Renderer.Server
                     {
                         command.Enabled = false;
 
-                        voicesEstimatedTime -= (long)command.EstimatedProcessingTime;
+                        voicesEstimatedTime -= (uint)(_voiceDropParameter * command.EstimatedProcessingTime);
                     }
                 }
             }
@@ -618,13 +623,13 @@ namespace Ryujinx.Audio.Renderer.Server
             _voiceContext.Sort();
             commandGenerator.GenerateVoices();
 
-            long voicesEstimatedTime = (long)commandBuffer.EstimatedProcessingTime;
+            uint voicesEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime);
 
             commandGenerator.GenerateSubMixes();
             commandGenerator.GenerateFinalMixes();
             commandGenerator.GenerateSinks();
 
-            long totalEstimatedTime = (long)commandBuffer.EstimatedProcessingTime;
+            uint totalEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime);
 
             if (_voiceDropEnabled)
             {
@@ -856,5 +861,26 @@ namespace Ryujinx.Audio.Renderer.Server
                 }
             }
         }
+
+        public void SetVoiceDropParameter(float voiceDropParameter)
+        {
+            _voiceDropParameter = Math.Clamp(voiceDropParameter, 0.0f, 2.0f);
+        }
+
+        public float GetVoiceDropParameter()
+        {
+            return _voiceDropParameter;
+        }
+
+        public ResultCode ExecuteAudioRendererRendering()
+        {
+            if (_executionMode == AudioRendererExecutionMode.Manual && _renderingDevice == AudioRendererRenderingDevice.Cpu)
+            {
+                // NOTE: Here Nintendo aborts with this error code, we don't want that.
+                return ResultCode.InvalidExecutionContextOperation;
+            }
+
+            return ResultCode.UnsupportedOperation;
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
index 4172041513..adf5294ea0 100644
--- a/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
+++ b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
@@ -94,8 +94,9 @@ namespace Ryujinx.Audio.Renderer.Server
         /// REV11:
         /// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
         /// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
+        /// A voice drop parameter was added in 15.0.0: This allows an application to amplify or attenuate the estimated time of DSP commands.
         /// </summary>
-        /// <remarks>This was added in system update 14.0.0</remarks>
+        /// <remarks>This was added in system update 14.0.0 but some changes were made in 15.0.0</remarks>
         public const int Revision11 = 11 << 24;
 
         /// <summary>
diff --git a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
index 3a3a981d78..e0741cc6e3 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
@@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Server
         /// <summary>
         /// The estimated total processing time.
         /// </summary>
-        public ulong EstimatedProcessingTime { get; set; }
+        public uint EstimatedProcessingTime { get; set; }
 
         /// <summary>
         /// The command list that is populated by the <see cref="CommandBuffer"/>.
diff --git a/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs b/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs
index cf0fc067e2..6c79da1576 100644
--- a/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs
+++ b/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs
@@ -263,12 +263,12 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool
                 return UpdateResult.Success;
             }
 
-            if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress & (pageSize - 1)) != 0)
+            if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress % pageSize) != 0)
             {
                 return UpdateResult.InvalidParameter;
             }
 
-            if (inParameter.Size == 0 || (inParameter.Size & (pageSize - 1)) != 0)
+            if (inParameter.Size == 0 || (inParameter.Size % pageSize) != 0)
             {
                 return UpdateResult.InvalidParameter;
             }
diff --git a/Ryujinx.Audio/ResultCode.cs b/Ryujinx.Audio/ResultCode.cs
index 8e0bfcb0c9..1d05ac65ed 100644
--- a/Ryujinx.Audio/ResultCode.cs
+++ b/Ryujinx.Audio/ResultCode.cs
@@ -17,5 +17,6 @@ namespace Ryujinx.Audio
         InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId,
         InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId,
         UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId,
+        InvalidExecutionContextOperation = (514 << ErrorCodeShift) | ModuleId,
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs
index d69bde0377..5b682bf845 100644
--- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs
@@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
 
         public ResultCode ExecuteAudioRendererRendering()
         {
-            throw new NotImplementedException();
+            return (ResultCode)_impl.ExecuteAudioRendererRendering();
         }
 
         public uint GetMixBufferCount()
@@ -108,5 +108,15 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
                 _impl.Dispose();
             }
         }
+
+        public void SetVoiceDropParameter(float voiceDropParameter)
+        {
+            _impl.SetVoiceDropParameter(voiceDropParameter);
+        }
+
+        public float GetVoiceDropParameter()
+        {
+            return _impl.GetVoiceDropParameter();
+        }
     }
 }
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs
index dd48a666b2..b2ddb697a2 100644
--- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs
@@ -172,6 +172,35 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
             return result;
         }
 
+        [CommandHipc(11)] // 3.0.0+
+        // ExecuteAudioRendererRendering()
+        public ResultCode ExecuteAudioRendererRendering(ServiceCtx context)
+        {
+            return _impl.ExecuteAudioRendererRendering();
+        }
+
+        [CommandHipc(12)] // 15.0.0+
+        // SetVoiceDropParameter(f32 voiceDropParameter)
+        public ResultCode SetVoiceDropParameter(ServiceCtx context)
+        {
+            float voiceDropParameter = context.RequestData.ReadSingle();
+
+            _impl.SetVoiceDropParameter(voiceDropParameter);
+
+            return ResultCode.Success;
+        }
+
+        [CommandHipc(13)] // 15.0.0+
+        // GetVoiceDropParameter() -> f32 voiceDropParameter
+        public ResultCode GetVoiceDropParameter(ServiceCtx context)
+        {
+            float voiceDropParameter = _impl.GetVoiceDropParameter();
+
+            context.ResponseData.Write(voiceDropParameter);
+
+            return ResultCode.Success;
+        }
+
         protected override void Dispose(bool isDisposing)
         {
             if (isDisposing)
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs
index a59c94e976..404bf4c102 100644
--- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs
@@ -16,5 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
         void SetRenderingTimeLimit(uint percent);
         uint GetRenderingTimeLimit();
         ResultCode ExecuteAudioRendererRendering();
+        void SetVoiceDropParameter(float voiceDropParameter);
+        float GetVoiceDropParameter();
     }
 }