diff --git a/Ryujinx.Audio/Renderer/Common/EffectType.cs b/Ryujinx.Audio/Renderer/Common/EffectType.cs
index daa220360..f57f12795 100644
--- a/Ryujinx.Audio/Renderer/Common/EffectType.cs
+++ b/Ryujinx.Audio/Renderer/Common/EffectType.cs
@@ -61,5 +61,10 @@ namespace Ryujinx.Audio.Renderer.Common
/// Effect applying a limiter (DRC).
///
Limiter,
+
+ ///
+ /// Effect to capture mixes (via auxiliary buffers).
+ ///
+ CaptureBuffer
}
}
diff --git a/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs b/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
index e9e946ce4..60de60792 100644
--- a/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
+++ b/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
@@ -30,6 +30,7 @@ namespace Ryujinx.Audio.Renderer.Common
Reverb,
Reverb3d,
PcmFloat,
- Limiter
+ Limiter,
+ CaptureBuffer
}
}
diff --git a/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs b/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs
index 29395e5c2..3ec37069b 100644
--- a/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs
+++ b/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs
@@ -27,12 +27,13 @@ namespace Ryujinx.Audio.Renderer.Device
///
/// All the defined virtual devices.
///
- public static readonly VirtualDevice[] Devices = new VirtualDevice[4]
+ public static readonly VirtualDevice[] Devices = new VirtualDevice[5]
{
- new VirtualDevice("AudioStereoJackOutput", 2),
- new VirtualDevice("AudioBuiltInSpeakerOutput", 2),
- new VirtualDevice("AudioTvOutput", 6),
- new VirtualDevice("AudioUsbDeviceOutput", 2),
+ new VirtualDevice("AudioStereoJackOutput", 2, true),
+ new VirtualDevice("AudioBuiltInSpeakerOutput", 2, false),
+ new VirtualDevice("AudioTvOutput", 6, false),
+ new VirtualDevice("AudioUsbDeviceOutput", 2, true),
+ new VirtualDevice("AudioExternalOutput", 6, true),
};
///
@@ -50,15 +51,22 @@ namespace Ryujinx.Audio.Renderer.Device
///
public float MasterVolume { get; private set; }
+ ///
+ /// Define if the is provided by an external interface.
+ ///
+ public bool IsExternalOutput { get; }
+
///
/// Create a new instance.
///
/// The name of the .
/// The count of channels supported by the .
- private VirtualDevice(string name, uint channelCount)
+ /// Indicate if the is provided by an external interface.
+ private VirtualDevice(string name, uint channelCount, bool isExternalOutput)
{
Name = name;
ChannelCount = channelCount;
+ IsExternalOutput = isExternalOutput;
}
///
@@ -80,5 +88,19 @@ namespace Ryujinx.Audio.Renderer.Device
{
return Name.Equals("AudioUsbDeviceOutput");
}
+
+ ///
+ /// Get the output device name of the .
+ ///
+ /// The output device name of the .
+ public string GetOutputDeviceName()
+ {
+ if (IsExternalOutput)
+ {
+ return "AudioExternalOutput";
+ }
+
+ return Name;
+ }
}
}
diff --git a/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs b/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
new file mode 100644
index 000000000..113f20f99
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
@@ -0,0 +1,100 @@
+//
+// 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 .
+//
+
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp
+{
+ public static class BiquadFilterHelper
+ {
+ private const int FixedPointPrecisionForParameter = 14;
+
+ ///
+ /// Apply a single biquad filter.
+ ///
+ /// This is implemented with a direct form 2.
+ /// The biquad filter parameter
+ /// The biquad filter state
+ /// The output buffer to write the result
+ /// The input buffer to write the result
+ /// The count of samples to process
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount)
+ {
+ float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
+ float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
+ float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
+ float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a0 + state.State0;
+
+ state.State0 = input * a1 + output * b1 + state.State1;
+ state.State1 = input * a2 + output * b2;
+
+ outputBuffer[i] = output;
+ }
+ }
+
+ ///
+ /// Apply multiple biquad filter.
+ ///
+ /// This is implemented with a direct form 1.
+ /// The biquad filter parameter
+ /// The biquad filter state
+ /// The output buffer to write the result
+ /// The input buffer to write the result
+ /// The count of samples to process
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ProcessBiquadFilter(ReadOnlySpan parameters, Span states, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount)
+ {
+ for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++)
+ {
+ BiquadFilterParameter parameter = parameters[stageIndex];
+
+ ref BiquadFilterState state = ref states[stageIndex];
+
+ float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
+ float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
+ float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
+ float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
+
+ state.State1 = state.State0;
+ state.State0 = input;
+ state.State3 = state.State2;
+ state.State2 = output;
+
+ outputBuffer[i] = output;
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
index 78d7ea878..15293dd09 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
@@ -156,7 +156,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
Span outputBufferInt = MemoryMarshal.Cast(outputBuffer);
// Convert input data to the target format for user (int)
- DataSourceHelper.ToInt(inputBufferInt, inputBuffer, outputBuffer.Length);
+ DataSourceHelper.ToInt(inputBufferInt, inputBuffer, inputBuffer.Length);
// Send the input to the user
Write(context.MemoryManager, OutputBuffer, CountMax, inputBufferInt, context.SampleCount, WriteOffset, UpdateCount);
@@ -177,8 +177,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
}
else
{
- context.MemoryManager.Fill(BufferInfo.SendBufferInfo, (ulong)Unsafe.SizeOf(), 0);
- context.MemoryManager.Fill(BufferInfo.ReturnBufferInfo, (ulong)Unsafe.SizeOf(), 0);
+ AuxiliaryBufferInfo.Reset(context.MemoryManager, BufferInfo.SendBufferInfo);
+ AuxiliaryBufferInfo.Reset(context.MemoryManager, BufferInfo.ReturnBufferInfo);
if (InputBufferIndex != OutputBufferIndex)
{
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
index c850bb01f..6a1d8ac03 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
@@ -32,15 +32,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public ulong EstimatedProcessingTime { get; set; }
- public BiquadFilterParameter Parameter { get; }
public Memory BiquadFilterState { get; }
public int InputBufferIndex { get; }
public int OutputBufferIndex { get; }
public bool NeedInitialization { get; }
+ private BiquadFilterParameter _parameter;
+
public BiquadFilterCommand(int baseIndex, ref BiquadFilterParameter filter, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId)
{
- Parameter = filter;
+ _parameter = filter;
BiquadFilterState = biquadFilterStateMemory;
InputBufferIndex = baseIndex + inputBufferOffset;
OutputBufferIndex = baseIndex + outputBufferOffset;
@@ -50,30 +51,6 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
NodeId = nodeId;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void ProcessBiquadFilter(ref BiquadFilterState state, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount)
- {
- const int fixedPointPrecisionForParameter = 14;
-
- float a0 = FixedPointHelper.ToFloat(Parameter.Numerator[0], fixedPointPrecisionForParameter);
- float a1 = FixedPointHelper.ToFloat(Parameter.Numerator[1], fixedPointPrecisionForParameter);
- float a2 = FixedPointHelper.ToFloat(Parameter.Numerator[2], fixedPointPrecisionForParameter);
-
- float b1 = FixedPointHelper.ToFloat(Parameter.Denominator[0], fixedPointPrecisionForParameter);
- float b2 = FixedPointHelper.ToFloat(Parameter.Denominator[1], fixedPointPrecisionForParameter);
-
- for (int i = 0; i < sampleCount; i++)
- {
- float input = inputBuffer[i];
- float output = input * a0 + state.Z1;
-
- state.Z1 = input * a1 + output * b1 + state.Z2;
- state.Z2 = input * a2 + output * b2;
-
- outputBuffer[i] = output;
- }
- }
-
public void Process(CommandList context)
{
ref BiquadFilterState state = ref BiquadFilterState.Span[0];
@@ -86,7 +63,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
state = new BiquadFilterState();
}
- ProcessBiquadFilter(ref state, outputBuffer, inputBuffer, context.SampleCount);
+ BiquadFilterHelper.ProcessBiquadFilter(ref _parameter, ref state, outputBuffer, inputBuffer, context.SampleCount);
}
}
}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs
new file mode 100644
index 000000000..c6c579d36
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs
@@ -0,0 +1,154 @@
+//
+// 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 .
+//
+
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Memory;
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader;
+using CpuAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class CaptureBufferCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.CaptureBuffer;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ public uint InputBufferIndex { get; }
+
+ public ulong CpuBufferInfoAddress { get; }
+ public ulong DspBufferInfoAddress { get; }
+
+ public CpuAddress OutputBuffer { get; }
+ public uint CountMax { get; }
+ public uint UpdateCount { get; }
+ public uint WriteOffset { get; }
+
+ public bool IsEffectEnabled { get; }
+
+ public CaptureBufferCommand(uint bufferOffset, byte inputBufferOffset, ulong sendBufferInfo, bool isEnabled,
+ uint countMax, CpuAddress outputBuffer, uint updateCount, uint writeOffset, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ InputBufferIndex = bufferOffset + inputBufferOffset;
+ CpuBufferInfoAddress = sendBufferInfo;
+ DspBufferInfoAddress = sendBufferInfo + (ulong)Unsafe.SizeOf();
+ OutputBuffer = outputBuffer;
+ CountMax = countMax;
+ UpdateCount = updateCount;
+ WriteOffset = writeOffset;
+ IsEffectEnabled = isEnabled;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private uint Write(IVirtualMemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan buffer, uint count, uint writeOffset, uint updateCount)
+ {
+ if (countMax == 0 || outBufferAddress == 0)
+ {
+ return 0;
+ }
+
+ uint targetWriteOffset = writeOffset + AuxiliaryBufferInfo.GetWriteOffset(memoryManager, DspBufferInfoAddress);
+
+ if (targetWriteOffset > countMax)
+ {
+ return 0;
+ }
+
+ uint remaining = count;
+
+ uint inBufferOffset = 0;
+
+ while (remaining != 0)
+ {
+ uint countToWrite = Math.Min(countMax - targetWriteOffset, remaining);
+
+ memoryManager.Write(outBufferAddress + targetWriteOffset * sizeof(int), MemoryMarshal.Cast(buffer.Slice((int)inBufferOffset, (int)countToWrite)));
+
+ targetWriteOffset = (targetWriteOffset + countToWrite) % countMax;
+ remaining -= countToWrite;
+ inBufferOffset += countToWrite;
+ }
+
+ if (updateCount != 0)
+ {
+ uint dspTotalSampleCount = AuxiliaryBufferInfo.GetTotalSampleCount(memoryManager, DspBufferInfoAddress);
+ uint cpuTotalSampleCount = AuxiliaryBufferInfo.GetTotalSampleCount(memoryManager, CpuBufferInfoAddress);
+
+ uint totalSampleCountDiff = dspTotalSampleCount - cpuTotalSampleCount;
+
+ if (totalSampleCountDiff >= countMax)
+ {
+ uint dspLostSampleCount = AuxiliaryBufferInfo.GetLostSampleCount(memoryManager, DspBufferInfoAddress);
+ uint cpuLostSampleCount = AuxiliaryBufferInfo.GetLostSampleCount(memoryManager, CpuBufferInfoAddress);
+
+ uint lostSampleCountDiff = dspLostSampleCount - cpuLostSampleCount;
+ uint newLostSampleCount = lostSampleCountDiff + updateCount;
+
+ if (lostSampleCountDiff > newLostSampleCount)
+ {
+ newLostSampleCount = cpuLostSampleCount - 1;
+ }
+
+ AuxiliaryBufferInfo.SetLostSampleCount(memoryManager, DspBufferInfoAddress, newLostSampleCount);
+ }
+
+ uint newWriteOffset = (AuxiliaryBufferInfo.GetWriteOffset(memoryManager, DspBufferInfoAddress) + updateCount) % countMax;
+
+ AuxiliaryBufferInfo.SetWriteOffset(memoryManager, DspBufferInfoAddress, newWriteOffset);
+
+ uint newTotalSampleCount = totalSampleCountDiff + newWriteOffset;
+
+ AuxiliaryBufferInfo.SetTotalSampleCount(memoryManager, DspBufferInfoAddress, newTotalSampleCount);
+ }
+
+ return count;
+ }
+
+ public void Process(CommandList context)
+ {
+ Span inputBuffer = context.GetBuffer((int)InputBufferIndex);
+
+ if (IsEffectEnabled)
+ {
+ Span inputBufferInt = MemoryMarshal.Cast(inputBuffer);
+
+ // Convert input data to the target format for user (int)
+ DataSourceHelper.ToInt(inputBufferInt, inputBuffer, inputBuffer.Length);
+
+ // Send the input to the user
+ Write(context.MemoryManager, OutputBuffer, CountMax, inputBufferInt, context.SampleCount, WriteOffset, UpdateCount);
+
+ // Convert back to float
+ DataSourceHelper.ToFloat(inputBuffer, inputBufferInt, inputBuffer.Length);
+ }
+ else
+ {
+ AuxiliaryBufferInfo.Reset(context.MemoryManager, DspBufferInfoAddress);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
index 997a080e8..6f324d0e0 100644
--- a/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
@@ -46,6 +46,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
ClearMixBuffer,
CopyMixBuffer,
LimiterVersion1,
- LimiterVersion2
+ LimiterVersion2,
+ GroupedBiquadFilter,
+ CaptureBuffer
}
}
diff --git a/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
new file mode 100644
index 000000000..394d74ba7
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
@@ -0,0 +1,80 @@
+//
+// 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 .
+//
+
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class GroupedBiquadFilterCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.GroupedBiquadFilter;
+
+ public ulong EstimatedProcessingTime { get; set; }
+
+ private BiquadFilterParameter[] _parameters;
+ private Memory _biquadFilterStates;
+ private int _inputBufferIndex;
+ private int _outputBufferIndex;
+ private bool[] _isInitialized;
+
+ public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId)
+ {
+ _parameters = filters.ToArray();
+ _biquadFilterStates = biquadFilterStateMemory;
+ _inputBufferIndex = baseIndex + inputBufferOffset;
+ _outputBufferIndex = baseIndex + outputBufferOffset;
+ _isInitialized = isInitialized.ToArray();
+
+ Enabled = true;
+ NodeId = nodeId;
+ }
+
+ public void Process(CommandList context)
+ {
+ Span states = _biquadFilterStates.Span;
+
+ ReadOnlySpan inputBuffer = context.GetBuffer(_inputBufferIndex);
+ Span outputBuffer = context.GetBuffer(_outputBufferIndex);
+
+ for (int i = 0; i < _parameters.Length; i++)
+ {
+ if (!_isInitialized[i])
+ {
+ states[i] = new BiquadFilterState();
+ }
+ }
+
+ // 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.
+ if (_parameters.Length == 1)
+ {
+ BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount);
+ }
+ else
+ {
+ BiquadFilterHelper.ProcessBiquadFilter(_parameters, states, outputBuffer, inputBuffer, context.SampleCount);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs b/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs
index 3cf24302c..69a16a3d3 100644
--- a/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs
@@ -20,18 +20,22 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Dsp.State
{
- [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x40)]
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x80)]
public struct AuxiliaryBufferHeader
{
- [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0xC)]
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x40)]
public struct AuxiliaryBufferInfo
{
private const uint ReadOffsetPosition = 0x0;
private const uint WriteOffsetPosition = 0x4;
+ private const uint LostSampleCountPosition = 0x8;
+ private const uint TotalSampleCountPosition = 0xC;
public uint ReadOffset;
public uint WriteOffset;
- private uint _reserved;
+ public uint LostSampleCount;
+ public uint TotalSampleCount;
+ private unsafe fixed uint _unknown[12];
public static uint GetReadOffset(IVirtualMemoryManager manager, ulong bufferAddress)
{
@@ -43,6 +47,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
return manager.Read(bufferAddress + WriteOffsetPosition);
}
+ public static uint GetLostSampleCount(IVirtualMemoryManager manager, ulong bufferAddress)
+ {
+ return manager.Read(bufferAddress + LostSampleCountPosition);
+ }
+
+ public static uint GetTotalSampleCount(IVirtualMemoryManager manager, ulong bufferAddress)
+ {
+ return manager.Read(bufferAddress + TotalSampleCountPosition);
+ }
+
public static void SetReadOffset(IVirtualMemoryManager manager, ulong bufferAddress, uint value)
{
manager.Write(bufferAddress + ReadOffsetPosition, value);
@@ -52,9 +66,26 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
{
manager.Write(bufferAddress + WriteOffsetPosition, value);
}
+
+ public static void SetLostSampleCount(IVirtualMemoryManager manager, ulong bufferAddress, uint value)
+ {
+ manager.Write(bufferAddress + LostSampleCountPosition, value);
+ }
+
+ public static void SetTotalSampleCount(IVirtualMemoryManager manager, ulong bufferAddress, uint value)
+ {
+ manager.Write(bufferAddress + TotalSampleCountPosition, value);
+ }
+
+ public static void Reset(IVirtualMemoryManager manager, ulong bufferAddress)
+ {
+ // NOTE: Lost sample count is never reset, since REV10.
+ manager.Write(bufferAddress + ReadOffsetPosition, 0UL);
+ manager.Write(bufferAddress + TotalSampleCountPosition, 0);
+ }
}
- public AuxiliaryBufferInfo BufferInfo;
- public unsafe fixed uint Unknown[13];
+ public AuxiliaryBufferInfo CpuBufferInfo;
+ public AuxiliaryBufferInfo DspBufferInfo;
}
}
diff --git a/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs b/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
index 9677333da..f9e677e09 100644
--- a/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
@@ -22,7 +22,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
public struct BiquadFilterState
{
- public float Z1;
- public float Z2;
+ public float State0;
+ public float State1;
+ public float State2;
+ public float State3;
}
}
diff --git a/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs
index c30c4013f..b1cbd6799 100644
--- a/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs
+++ b/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs
@@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
///
- /// for .
+ /// for and .
///
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct AuxiliaryBufferParameter
@@ -71,6 +71,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
///
/// The address of the start of the region containing two followed by the data that will be read by the .
///
+ /// Unused with .
public ulong ReturnBufferInfoAddress;
///
diff --git a/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
index afbe56a69..277c2474d 100644
--- a/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
+++ b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
@@ -360,6 +360,9 @@ namespace Ryujinx.Audio.Renderer.Server
case 3:
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion3(_sampleCount, _mixBufferCount);
break;
+ case 4:
+ _commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion4(_sampleCount, _mixBufferCount);
+ break;
default:
throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}.");
}
diff --git a/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
index ed1f402eb..d3a65b725 100644
--- a/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
+++ b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
@@ -97,10 +97,20 @@ namespace Ryujinx.Audio.Renderer.Server
/// This was added in system update 12.0.0
public const int Revision9 = 9 << 24;
+ ///
+ /// REV10:
+ /// Added Bluetooth audio device support and removed the unused "GetAudioSystemMasterVolumeSetting" audio device API.
+ /// A new effect was added: Capture. This effect allows the client side to capture audio buffers of a mix.
+ /// A new command was added for double biquad filters on voices. This is implemented using a direct form 1 (instead of the usual direct form 2).
+ /// A new version of the command estimator was added to support the new commands.
+ ///
+ /// This was added in system update 13.0.0
+ public const int Revision10 = 10 << 24;
+
///
/// Last revision supported by the implementation.
///
- public const int LastRevision = Revision9;
+ public const int LastRevision = Revision10;
///
/// Target revision magic supported by the implementation.
@@ -347,12 +357,26 @@ namespace Ryujinx.Audio.Renderer.Server
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision9);
}
+ ///
+ /// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice.
+ ///
+ /// True if the audio renderer should use the optimization.
+ public bool IsBiquadFilterGroupedOptimizationSupported()
+ {
+ return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
+ }
+
///
/// Get the version of the .
///
/// The version of the .
public int GetCommandProcessingTimeEstimatorVersion()
{
+ if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10))
+ {
+ return 4;
+ }
+
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8))
{
return 3;
diff --git a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
index deb20f3dd..0e74c301e 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
@@ -24,6 +24,7 @@ using Ryujinx.Audio.Renderer.Server.Performance;
using Ryujinx.Audio.Renderer.Server.Sink;
using Ryujinx.Audio.Renderer.Server.Upsampler;
using Ryujinx.Audio.Renderer.Server.Voice;
+using Ryujinx.Common.Memory;
using System;
using CpuAddress = System.UInt64;
@@ -220,6 +221,25 @@ namespace Ryujinx.Audio.Renderer.Server
AddCommand(command);
}
+ ///
+ /// Create a new .
+ ///
+ /// The base index of the input and output buffer.
+ /// The biquad filter parameters.
+ /// The biquad states.
+ /// The input buffer offset.
+ /// The output buffer offset.
+ /// Set to true if the biquad filter state is initialized.
+ /// The node id associated to this command.
+ public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId)
+ {
+ GroupedBiquadFilterCommand command = new GroupedBiquadFilterCommand(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+
///
/// Generate a new .
///
@@ -440,6 +460,30 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
+ ///
+ /// Generate a new .
+ ///
+ /// The target buffer offset.
+ /// The input buffer offset.
+ /// The capture state.
+ /// Set to true if the effect should be active.
+ /// The limit of the circular buffer.
+ /// The guest address of the output buffer.
+ /// The count to add on the offset after write operations.
+ /// The write offset.
+ /// The node id associated to this command.
+ public void GenerateCaptureEffect(uint bufferOffset, byte inputBufferOffset, ulong sendBufferInfo, bool isEnabled, uint countMax, CpuAddress outputBuffer, uint updateCount, uint writeOffset, int nodeId)
+ {
+ if (sendBufferInfo != 0)
+ {
+ CaptureBufferCommand command = new CaptureBufferCommand(bufferOffset, inputBufferOffset, sendBufferInfo, isEnabled, countMax, outputBuffer, updateCount, writeOffset, nodeId);
+
+ command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
+
+ AddCommand(command);
+ }
+ }
+
///
/// Generate a new .
///
diff --git a/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
index 01e7c9276..8e4ecd259 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
@@ -151,23 +151,35 @@ namespace Ryujinx.Audio.Renderer.Server
private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory state, int baseIndex, int bufferOffset, int nodeId)
{
- for (int i = 0; i < voiceState.BiquadFilters.Length; i++)
+ bool supportsOptimizedPath = _rendererContext.BehaviourContext.IsBiquadFilterGroupedOptimizationSupported();
+
+ if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable)
{
- ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i];
+ Memory biquadStateRawMemory = SpanMemoryManager.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount);
+ Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory);
- if (filter.Enable)
+ _commandBuffer.GenerateGroupedBiquadFilter(baseIndex, voiceState.BiquadFilters.ToSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId);
+ }
+ else
+ {
+ for (int i = 0; i < voiceState.BiquadFilters.Length; i++)
{
- Memory biquadStateRawMemory = SpanMemoryManager.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount);
+ ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i];
- Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory);
+ if (filter.Enable)
+ {
+ Memory biquadStateRawMemory = SpanMemoryManager.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount);
- _commandBuffer.GenerateBiquadFilter(baseIndex,
- ref filter,
- stateMemory.Slice(i, 1),
- bufferOffset,
- bufferOffset,
- !voiceState.BiquadFilterNeedInitialization[i],
- nodeId);
+ Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory);
+
+ _commandBuffer.GenerateBiquadFilter(baseIndex,
+ ref filter,
+ stateMemory.Slice(i, 1),
+ bufferOffset,
+ bufferOffset,
+ !voiceState.BiquadFilterNeedInitialization[i],
+ nodeId);
+ }
}
}
}
@@ -443,7 +455,7 @@ namespace Ryujinx.Audio.Renderer.Server
uint updateCount;
- if ((channelIndex - 1) != 0)
+ if (channelIndex != 1)
{
updateCount = 0;
}
@@ -556,6 +568,52 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
+ private void GenerateCaptureEffect(uint bufferOffset, CaptureBufferEffect effect, int nodeId)
+ {
+ Debug.Assert(effect.Type == EffectType.CaptureBuffer);
+
+ if (effect.IsEnabled)
+ {
+ effect.GetWorkBuffer(0);
+ }
+
+ if (effect.State.SendBufferInfoBase != 0)
+ {
+ int i = 0;
+ uint writeOffset = 0;
+
+ for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--)
+ {
+ uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount;
+
+ uint updateCount;
+
+ if (channelIndex != 1)
+ {
+ updateCount = 0;
+ }
+ else
+ {
+ updateCount = newUpdateCount;
+ }
+
+ _commandBuffer.GenerateCaptureEffect(bufferOffset,
+ effect.Parameter.Input[i],
+ effect.State.SendBufferInfo,
+ effect.IsEnabled,
+ effect.Parameter.BufferStorageSize,
+ effect.State.SendBufferInfoBase,
+ updateCount,
+ writeOffset,
+ nodeId);
+
+ writeOffset = newUpdateCount;
+
+ i++;
+ }
+ }
+ }
+
private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect)
{
int nodeId = mix.NodeId;
@@ -597,6 +655,9 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.Limiter:
GenerateLimiterEffect(mix.BufferOffset, (LimiterEffect)effect, nodeId, effectId);
break;
+ case EffectType.CaptureBuffer:
+ GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId);
+ break;
default:
throw new NotImplementedException($"Unsupported effect type {effect.Type}");
}
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
index feb3706fd..6821cccf1 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
@@ -186,5 +186,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public uint Estimate(GroupedBiquadFilterCommand command)
+ {
+ return 0;
+ }
+
+ public uint Estimate(CaptureBufferCommand command)
+ {
+ return 0;
+ }
}
}
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
index 227f3c815..daf50de97 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
@@ -550,5 +550,15 @@ namespace Ryujinx.Audio.Renderer.Server
{
return 0;
}
+
+ public uint Estimate(GroupedBiquadFilterCommand command)
+ {
+ return 0;
+ }
+
+ public uint Estimate(CaptureBufferCommand command)
+ {
+ return 0;
+ }
}
}
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
index e00fcf7b1..75d3d05bc 100644
--- a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
@@ -16,7 +16,6 @@
//
using Ryujinx.Audio.Common;
-using Ryujinx.Audio.Renderer.Common;
using Ryujinx.Audio.Renderer.Dsp.Command;
using Ryujinx.Audio.Renderer.Parameter.Effect;
using System;
@@ -30,8 +29,8 @@ namespace Ryujinx.Audio.Renderer.Server
///
public class CommandProcessingTimeEstimatorVersion3 : ICommandProcessingTimeEstimator
{
- private uint _sampleCount;
- private uint _bufferCount;
+ protected uint _sampleCount;
+ protected uint _bufferCount;
public CommandProcessingTimeEstimatorVersion3(uint sampleCount, uint bufferCount)
{
@@ -755,5 +754,15 @@ namespace Ryujinx.Audio.Renderer.Server
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
}
}
+
+ public virtual uint Estimate(GroupedBiquadFilterCommand command)
+ {
+ return 0;
+ }
+
+ public virtual uint Estimate(CaptureBufferCommand command)
+ {
+ return 0;
+ }
}
}
diff --git a/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs
new file mode 100644
index 000000000..ea11f69cf
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs
@@ -0,0 +1,68 @@
+//
+// 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 .
+//
+
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Dsp.Command;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+using System.Diagnostics;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ ///
+ /// version 4. (added with REV10)
+ ///
+ public class CommandProcessingTimeEstimatorVersion4 : CommandProcessingTimeEstimatorVersion3
+ {
+ public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { }
+
+ public override uint Estimate(GroupedBiquadFilterCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ return (uint)7424.5f;
+ }
+
+ return (uint)9730.4f;
+ }
+
+ public override uint Estimate(CaptureBufferCommand command)
+ {
+ Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
+
+ if (_sampleCount == 160)
+ {
+ if (command.Enabled)
+ {
+ return (uint)435.2f;
+ }
+
+ return (uint)4261.0f;
+ }
+
+ if (command.Enabled)
+ {
+ return (uint)5858.26f;
+ }
+
+ return (uint)435.2f;
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs
index eac1708ed..3a13d377a 100644
--- a/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs
+++ b/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs
@@ -23,7 +23,7 @@ using Ryujinx.Audio.Renderer.Server.MemoryPool;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-
+using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader;
using DspAddress = System.UInt64;
namespace Ryujinx.Audio.Renderer.Server.Effect
@@ -73,7 +73,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
if (BufferUnmapped || parameter.IsNew)
{
- ulong bufferSize = (ulong)Unsafe.SizeOf() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf() * 2;
+ ulong bufferSize = (ulong)Unsafe.SizeOf() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf();
bool sendBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize);
bool returnBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[1], Parameter.ReturnBufferInfoAddress, bufferSize);
@@ -85,11 +85,11 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
DspAddress sendDspAddress = WorkBuffers[0].GetReference(false);
DspAddress returnDspAddress = WorkBuffers[1].GetReference(false);
- State.SendBufferInfo = sendDspAddress + (uint)Unsafe.SizeOf();
- State.SendBufferInfoBase = sendDspAddress + (uint)Unsafe.SizeOf() * 2;
+ State.SendBufferInfo = sendDspAddress + (uint)Unsafe.SizeOf();
+ State.SendBufferInfoBase = sendDspAddress + (uint)Unsafe.SizeOf();
- State.ReturnBufferInfo = returnDspAddress + (uint)Unsafe.SizeOf();
- State.ReturnBufferInfoBase = returnDspAddress + (uint)Unsafe.SizeOf() * 2;
+ State.ReturnBufferInfo = returnDspAddress + (uint)Unsafe.SizeOf();
+ State.ReturnBufferInfoBase = returnDspAddress + (uint)Unsafe.SizeOf();
}
}
}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
index c7b06e7aa..40e875303 100644
--- a/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
+++ b/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
@@ -277,6 +277,8 @@ namespace Ryujinx.Audio.Renderer.Server.Effect
return PerformanceDetailType.Mix;
case EffectType.Limiter:
return PerformanceDetailType.Limiter;
+ case EffectType.CaptureBuffer:
+ return PerformanceDetailType.CaptureBuffer;
default:
throw new NotImplementedException($"{Type}");
}
diff --git a/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs
new file mode 100644
index 000000000..6ba0040ef
--- /dev/null
+++ b/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs
@@ -0,0 +1,99 @@
+//
+// 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 .
+//
+
+using Ryujinx.Audio.Renderer.Common;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using DspAddress = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Server.Effect
+{
+ ///
+ /// Server state for an capture buffer effect.
+ ///
+ public class CaptureBufferEffect : BaseEffect
+ {
+ ///
+ /// The capture buffer parameter.
+ ///
+ public AuxiliaryBufferParameter Parameter;
+
+ ///
+ /// Capture buffer state.
+ ///
+ public AuxiliaryBufferAddresses State;
+
+ public override EffectType TargetEffectType => EffectType.CaptureBuffer;
+
+ public override DspAddress GetWorkBuffer(int index)
+ {
+ return WorkBuffers[index].GetReference(true);
+ }
+
+ public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper)
+ {
+ Update(out updateErrorInfo, ref parameter, mapper);
+ }
+
+ public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper)
+ {
+ Update(out updateErrorInfo, ref parameter, mapper);
+ }
+
+ public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
+ {
+ Debug.Assert(IsTypeValid(ref parameter));
+
+ UpdateParameterBase(ref parameter);
+
+ Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0];
+ IsEnabled = parameter.IsEnabled;
+
+ updateErrorInfo = new BehaviourParameter.ErrorInfo();
+
+ if (BufferUnmapped || parameter.IsNew)
+ {
+ ulong bufferSize = (ulong)Unsafe.SizeOf() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf();
+
+ bool sendBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize);
+
+ BufferUnmapped = sendBufferUnmapped;
+
+ if (!BufferUnmapped)
+ {
+ DspAddress sendDspAddress = WorkBuffers[0].GetReference(false);
+
+ // NOTE: Nintendo directly interact with the CPU side structure in the processing of the DSP command.
+ State.SendBufferInfo = sendDspAddress;
+ State.SendBufferInfoBase = sendDspAddress + (ulong)Unsafe.SizeOf();
+ State.ReturnBufferInfo = 0;
+ State.ReturnBufferInfoBase = 0;
+ }
+ }
+ }
+
+ public override void UpdateForCommandGeneration()
+ {
+ UpdateUsageStateForCommandGeneration();
+ }
+ }
+}
diff --git a/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs b/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
index eae48be65..4100f3578 100644
--- a/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
+++ b/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
@@ -50,5 +50,7 @@ namespace Ryujinx.Audio.Renderer.Server
uint Estimate(UpsampleCommand command);
uint Estimate(LimiterCommandVersion1 command);
uint Estimate(LimiterCommandVersion2 command);
+ uint Estimate(GroupedBiquadFilterCommand command);
+ uint Estimate(CaptureBufferCommand command);
}
}
diff --git a/Ryujinx.Audio/Renderer/Server/StateUpdater.cs b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs
index e7a982c4a..1f50864d6 100644
--- a/Ryujinx.Audio/Renderer/Server/StateUpdater.cs
+++ b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs
@@ -254,6 +254,9 @@ namespace Ryujinx.Audio.Renderer.Server
case EffectType.Limiter:
effect = new LimiterEffect();
break;
+ case EffectType.CaptureBuffer:
+ effect = new CaptureBufferEffect();
+ break;
default:
throw new NotImplementedException($"EffectType {parameter.Type} not implemented!");
}
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs
index 8ba10946c..beecb0a36 100644
--- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs
@@ -107,18 +107,9 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
return ResultCode.Success;
}
- public ResultCode GetAudioSystemMasterVolumeSetting(string name, out float systemMasterVolume)
+ public string GetActiveAudioOutputDeviceName()
{
- if (TryGetDeviceByName(out VirtualDeviceSession result, name, true))
- {
- systemMasterVolume = result.Device.MasterVolume;
- }
- else
- {
- systemMasterVolume = 0.0f;
- }
-
- return ResultCode.Success;
+ return _registry.ActiveDevice.GetOutputDeviceName();
}
public string[] ListAudioDeviceName()
@@ -132,9 +123,32 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
string[] result = new string[deviceCount];
+ int i = 0;
+
+ foreach (VirtualDeviceSession session in _sessions)
+ {
+ if (!_isUsbDeviceSupported && _sessions[i].Device.IsUsbDevice())
+ {
+ continue;
+ }
+
+ result[i] = _sessions[i].Device.Name;
+
+ i++;
+ }
+
+ return result;
+ }
+
+ public string[] ListAudioOutputDeviceName()
+ {
+ int deviceCount = _sessions.Length;
+
+ string[] result = new string[deviceCount];
+
for (int i = 0; i < deviceCount; i++)
{
- result[i] = _sessions[i].Device.Name;
+ result[i] = _sessions[i].Device.GetOutputDeviceName();
}
return result;
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs
index 87ec2f6a9..1ef97ecce 100644
--- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs
@@ -38,8 +38,6 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
if ((position - basePosition) + (ulong)buffer.Length > size)
{
- Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
-
break;
}
@@ -158,8 +156,6 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
if ((position - basePosition) + (ulong)buffer.Length > size)
{
- Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
-
break;
}
@@ -264,23 +260,61 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
return ResultCode.Success;
}
- [CommandHipc(13)]
- // GetAudioSystemMasterVolumeSetting(buffer name) -> f32
- public ResultCode GetAudioSystemMasterVolumeSetting(ServiceCtx context)
+ [CommandHipc(13)] // 13.0.0+
+ // GetActiveAudioOutputDeviceName() -> buffer
+ public ResultCode GetActiveAudioOutputDeviceName(ServiceCtx context)
{
- ulong position = context.Request.SendBuff[0].Position;
- ulong size = context.Request.SendBuff[0].Size;
+ string name = _impl.GetActiveAudioOutputDeviceName();
- string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
+ ulong position = context.Request.ReceiveBuff[0].Position;
+ ulong size = context.Request.ReceiveBuff[0].Size;
- ResultCode result = _impl.GetAudioSystemMasterVolumeSetting(deviceName, out float systemMasterVolume);
+ byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
- if (result == ResultCode.Success)
+ if ((ulong)deviceNameBuffer.Length <= size)
{
- context.ResponseData.Write(systemMasterVolume);
+ context.Memory.Write(position, deviceNameBuffer);
+ }
+ else
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
}
- return result;
+ return ResultCode.Success;
+ }
+
+ [CommandHipc(14)] // 13.0.0+
+ // ListAudioOutputDeviceName() -> (u32, buffer)
+ public ResultCode ListAudioOutputDeviceName(ServiceCtx context)
+ {
+ string[] deviceNames = _impl.ListAudioOutputDeviceName();
+
+ ulong position = context.Request.ReceiveBuff[0].Position;
+ ulong size = context.Request.ReceiveBuff[0].Size;
+
+ ulong basePosition = position;
+
+ int count = 0;
+
+ foreach (string name in deviceNames)
+ {
+ byte[] buffer = Encoding.ASCII.GetBytes(name);
+
+ if ((position - basePosition) + (ulong)buffer.Length > size)
+ {
+ break;
+ }
+
+ context.Memory.Write(position, buffer);
+ MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
+
+ position += AudioDeviceNameSize;
+ count++;
+ }
+
+ context.ResponseData.Write(count);
+
+ return ResultCode.Success;
}
}
}
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs
index 42ea727f5..1918a977f 100644
--- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs
@@ -12,6 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
uint GetActiveChannelCount();
KEvent QueryAudioDeviceInputEvent();
KEvent QueryAudioDeviceOutputEvent();
- ResultCode GetAudioSystemMasterVolumeSetting(string name, out float systemMasterVolume);
+ string GetActiveAudioOutputDeviceName();
+ string[] ListAudioOutputDeviceName();
}
}
diff --git a/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs b/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs
index 016e02a2b..df946a12f 100644
--- a/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs
+++ b/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs
@@ -51,6 +51,8 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -75,6 +77,8 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -99,6 +103,8 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.AreEqual(0.70f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -123,6 +129,8 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.AreEqual(0.75f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(1, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -147,6 +155,8 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsFalse(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -171,6 +181,8 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
Assert.IsFalse(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -195,6 +207,8 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsFalse(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(2, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
@@ -219,10 +233,64 @@ namespace Ryujinx.Tests.Audio.Renderer.Server
Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
}
+
+ [Test]
+ public void TestRevision9()
+ {
+ BehaviourContext behaviourContext = new BehaviourContext();
+
+ behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision9);
+
+ Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed());
+ Assert.IsTrue(behaviourContext.IsSplitterSupported());
+ Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported());
+ Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported());
+ Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported());
+ Assert.IsTrue(behaviourContext.IsSplitterBugFixed());
+ Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported());
+ Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported());
+ Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
+ Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
+ Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsFalse(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
+
+ Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
+ Assert.AreEqual(3, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
+ Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
+ }
+
+ [Test]
+ public void TestRevision10()
+ {
+ BehaviourContext behaviourContext = new BehaviourContext();
+
+ behaviourContext.SetUserRevision(BehaviourContext.BaseRevisionMagic + BehaviourContext.Revision10);
+
+ Assert.IsTrue(behaviourContext.IsAdpcmLoopContextBugFixed());
+ Assert.IsTrue(behaviourContext.IsSplitterSupported());
+ Assert.IsTrue(behaviourContext.IsLongSizePreDelaySupported());
+ Assert.IsTrue(behaviourContext.IsAudioUsbDeviceOutputSupported());
+ Assert.IsTrue(behaviourContext.IsFlushVoiceWaveBuffersSupported());
+ Assert.IsTrue(behaviourContext.IsSplitterBugFixed());
+ Assert.IsTrue(behaviourContext.IsElapsedFrameCountSupported());
+ Assert.IsTrue(behaviourContext.IsDecodingBehaviourFlagSupported());
+ Assert.IsTrue(behaviourContext.IsBiquadFilterEffectStateClearBugFixed());
+ Assert.IsTrue(behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported());
+ Assert.IsTrue(behaviourContext.IsWaveBufferVersion2Supported());
+ Assert.IsTrue(behaviourContext.IsEffectInfoVersion2Supported());
+ Assert.IsTrue(behaviourContext.IsBiquadFilterGroupedOptimizationSupported());
+
+ Assert.AreEqual(0.80f, behaviourContext.GetAudioRendererProcessingTimeLimit());
+ Assert.AreEqual(4, behaviourContext.GetCommandProcessingTimeEstimatorVersion());
+ Assert.AreEqual(2, behaviourContext.GetPerformanceMetricsDataFormat());
+ }
}
}
\ No newline at end of file