amadeus: Update to REV10 (#2654)

* amadeus: Update to REV10

This implements all the changes made with REV10 on 13.0.0.

* Address Ack's comment

* Address gdkchan's comment
This commit is contained in:
Mary 2021-09-19 12:29:19 +02:00 committed by GitHub
parent fe9d5a1981
commit e17eb7bfaf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 923 additions and 96 deletions

View file

@ -61,5 +61,10 @@ namespace Ryujinx.Audio.Renderer.Common
/// Effect applying a limiter (DRC).
/// </summary>
Limiter,
/// <summary>
/// Effect to capture mixes (via auxiliary buffers).
/// </summary>
CaptureBuffer
}
}

View file

@ -30,6 +30,7 @@ namespace Ryujinx.Audio.Renderer.Common
Reverb,
Reverb3d,
PcmFloat,
Limiter
Limiter,
CaptureBuffer
}
}

View file

@ -27,12 +27,13 @@ namespace Ryujinx.Audio.Renderer.Device
/// <summary>
/// All the defined virtual devices.
/// </summary>
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),
};
/// <summary>
@ -50,15 +51,22 @@ namespace Ryujinx.Audio.Renderer.Device
/// </summary>
public float MasterVolume { get; private set; }
/// <summary>
/// Define if the <see cref="VirtualDevice"/> is provided by an external interface.
/// </summary>
public bool IsExternalOutput { get; }
/// <summary>
/// Create a new <see cref="VirtualDevice"/> instance.
/// </summary>
/// <param name="name">The name of the <see cref="VirtualDevice"/>.</param>
/// <param name="channelCount">The count of channels supported by the <see cref="VirtualDevice"/>.</param>
private VirtualDevice(string name, uint channelCount)
/// <param name="isExternalOutput">Indicate if the <see cref="VirtualDevice"/> is provided by an external interface.</param>
private VirtualDevice(string name, uint channelCount, bool isExternalOutput)
{
Name = name;
ChannelCount = channelCount;
IsExternalOutput = isExternalOutput;
}
/// <summary>
@ -80,5 +88,19 @@ namespace Ryujinx.Audio.Renderer.Device
{
return Name.Equals("AudioUsbDeviceOutput");
}
/// <summary>
/// Get the output device name of the <see cref="VirtualDevice"/>.
/// </summary>
/// <returns>The output device name of the <see cref="VirtualDevice"/>.</returns>
public string GetOutputDeviceName()
{
if (IsExternalOutput)
{
return "AudioExternalOutput";
}
return Name;
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
//
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;
/// <summary>
/// Apply a single biquad filter.
/// </summary>
/// <remarks>This is implemented with a direct form 2.</remarks>
/// <param name="parameter">The biquad filter parameter</param>
/// <param name="state">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to write the result</param>
/// <param name="sampleCount">The count of samples to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span<float> outputBuffer, ReadOnlySpan<float> 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;
}
}
/// <summary>
/// Apply multiple biquad filter.
/// </summary>
/// <remarks>This is implemented with a direct form 1.</remarks>
/// <param name="parameters">The biquad filter parameter</param>
/// <param name="states">The biquad filter state</param>
/// <param name="outputBuffer">The output buffer to write the result</param>
/// <param name="inputBuffer">The input buffer to write the result</param>
/// <param name="sampleCount">The count of samples to process</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ProcessBiquadFilter(ReadOnlySpan<BiquadFilterParameter> parameters, Span<BiquadFilterState> states, Span<float> outputBuffer, ReadOnlySpan<float> 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;
}
}
}
}
}

View file

@ -156,7 +156,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
Span<int> outputBufferInt = MemoryMarshal.Cast<float, int>(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<AuxiliaryBufferInfo>(), 0);
context.MemoryManager.Fill(BufferInfo.ReturnBufferInfo, (ulong)Unsafe.SizeOf<AuxiliaryBufferInfo>(), 0);
AuxiliaryBufferInfo.Reset(context.MemoryManager, BufferInfo.SendBufferInfo);
AuxiliaryBufferInfo.Reset(context.MemoryManager, BufferInfo.ReturnBufferInfo);
if (InputBufferIndex != OutputBufferIndex)
{

View file

@ -32,15 +32,16 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
public ulong EstimatedProcessingTime { get; set; }
public BiquadFilterParameter Parameter { get; }
public Memory<BiquadFilterState> 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<BiquadFilterState> 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<float> outputBuffer, ReadOnlySpan<float> 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);
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
//
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<AuxiliaryBufferHeader>();
OutputBuffer = outputBuffer;
CountMax = countMax;
UpdateCount = updateCount;
WriteOffset = writeOffset;
IsEffectEnabled = isEnabled;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private uint Write(IVirtualMemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan<int> 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<int, byte>(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<float> inputBuffer = context.GetBuffer((int)InputBufferIndex);
if (IsEffectEnabled)
{
Span<int> inputBufferInt = MemoryMarshal.Cast<float, int>(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);
}
}
}
}

View file

@ -46,6 +46,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
ClearMixBuffer,
CopyMixBuffer,
LimiterVersion1,
LimiterVersion2
LimiterVersion2,
GroupedBiquadFilter,
CaptureBuffer
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
//
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<BiquadFilterState> _biquadFilterStates;
private int _inputBufferIndex;
private int _outputBufferIndex;
private bool[] _isInitialized;
public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> 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<BiquadFilterState> states = _biquadFilterStates.Span;
ReadOnlySpan<float> inputBuffer = context.GetBuffer(_inputBufferIndex);
Span<float> 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);
}
}
}
}

View file

@ -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<uint>(bufferAddress + WriteOffsetPosition);
}
public static uint GetLostSampleCount(IVirtualMemoryManager manager, ulong bufferAddress)
{
return manager.Read<uint>(bufferAddress + LostSampleCountPosition);
}
public static uint GetTotalSampleCount(IVirtualMemoryManager manager, ulong bufferAddress)
{
return manager.Read<uint>(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;
}
}

View file

@ -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;
}
}

View file

@ -21,7 +21,7 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Renderer.Parameter.Effect
{
/// <summary>
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.AuxiliaryBuffer"/>.
/// <see cref="IEffectInParameter.SpecificData"/> for <see cref="Common.EffectType.AuxiliaryBuffer"/> and <see cref="Common.EffectType.CaptureBuffer"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct AuxiliaryBufferParameter
@ -71,6 +71,7 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect
/// <summary>
/// The address of the start of the region containing two <see cref="Dsp.State.AuxiliaryBufferHeader"/> followed by the data that will be read by the <see cref="Dsp.AudioProcessor"/>.
/// </summary>
/// <remarks>Unused with <see cref="Common.EffectType.CaptureBuffer"/>.</remarks>
public ulong ReturnBufferInfoAddress;
/// <summary>

View file

@ -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()}.");
}

View file

@ -97,10 +97,20 @@ namespace Ryujinx.Audio.Renderer.Server
/// <remarks>This was added in system update 12.0.0</remarks>
public const int Revision9 = 9 << 24;
/// <summary>
/// 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.
/// </summary>
/// <remarks>This was added in system update 13.0.0</remarks>
public const int Revision10 = 10 << 24;
/// <summary>
/// Last revision supported by the implementation.
/// </summary>
public const int LastRevision = Revision9;
public const int LastRevision = Revision10;
/// <summary>
/// Target revision magic supported by the implementation.
@ -347,12 +357,26 @@ namespace Ryujinx.Audio.Renderer.Server
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision9);
}
/// <summary>
/// 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.
/// </summary>
/// <returns>True if the audio renderer should use the optimization.</returns>
public bool IsBiquadFilterGroupedOptimizationSupported()
{
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
}
/// <summary>
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
/// </summary>
/// <returns>The version of the <see cref="ICommandProcessingTimeEstimator"/>.</returns>
public int GetCommandProcessingTimeEstimatorVersion()
{
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10))
{
return 4;
}
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8))
{
return 3;

View file

@ -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);
}
/// <summary>
/// Create a new <see cref="GroupedBiquadFilterCommand"/>.
/// </summary>
/// <param name="baseIndex">The base index of the input and output buffer.</param>
/// <param name="filters">The biquad filter parameters.</param>
/// <param name="biquadFilterStatesMemory">The biquad states.</param>
/// <param name="inputBufferOffset">The input buffer offset.</param>
/// <param name="outputBufferOffset">The output buffer offset.</param>
/// <param name="isInitialized">Set to true if the biquad filter state is initialized.</param>
/// <param name="nodeId">The node id associated to this command.</param>
public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan<BiquadFilterParameter> filters, Memory<BiquadFilterState> biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan<bool> isInitialized, int nodeId)
{
GroupedBiquadFilterCommand command = new GroupedBiquadFilterCommand(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId);
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
AddCommand(command);
}
/// <summary>
/// Generate a new <see cref="MixRampGroupedCommand"/>.
/// </summary>
@ -440,6 +460,30 @@ namespace Ryujinx.Audio.Renderer.Server
}
}
/// <summary>
/// Generate a new <see cref="CaptureBufferCommand"/>.
/// </summary>
/// <param name="bufferOffset">The target buffer offset.</param>
/// <param name="inputBufferOffset">The input buffer offset.</param>
/// <param name="sendBufferInfo">The capture state.</param>
/// <param name="isEnabled">Set to true if the effect should be active.</param>
/// <param name="countMax">The limit of the circular buffer.</param>
/// <param name="outputBuffer">The guest address of the output buffer.</param>
/// <param name="updateCount">The count to add on the offset after write operations.</param>
/// <param name="writeOffset">The write offset.</param>
/// <param name="nodeId">The node id associated to this command.</param>
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);
}
}
/// <summary>
/// Generate a new <see cref="VolumeCommand"/>.
/// </summary>

View file

@ -151,23 +151,35 @@ namespace Ryujinx.Audio.Renderer.Server
private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory<VoiceUpdateState> 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<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount);
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.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<byte> biquadStateRawMemory = SpanMemoryManager<byte>.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount);
ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i];
Memory<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.Cast(biquadStateRawMemory);
if (filter.Enable)
{
Memory<byte> biquadStateRawMemory = SpanMemoryManager<byte>.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<BiquadFilterState> stateMemory = SpanMemoryManager<BiquadFilterState>.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}");
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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
/// </summary>
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;
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
//
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
{
/// <summary>
/// <see cref="ICommandProcessingTimeEstimator"/> version 4. (added with REV10)
/// </summary>
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;
}
}
}

View file

@ -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<int>() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf<AuxiliaryBufferHeader>() * 2;
ulong bufferSize = (ulong)Unsafe.SizeOf<int>() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf<AuxiliaryBufferHeader>();
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<AuxiliaryBufferHeader>();
State.SendBufferInfoBase = sendDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>() * 2;
State.SendBufferInfo = sendDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferInfo>();
State.SendBufferInfoBase = sendDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>();
State.ReturnBufferInfo = returnDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>();
State.ReturnBufferInfoBase = returnDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>() * 2;
State.ReturnBufferInfo = returnDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferInfo>();
State.ReturnBufferInfoBase = returnDspAddress + (uint)Unsafe.SizeOf<AuxiliaryBufferHeader>();
}
}
}

View file

@ -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}");
}

View file

@ -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 <https://www.gnu.org/licenses/>.
//
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
{
/// <summary>
/// Server state for an capture buffer effect.
/// </summary>
public class CaptureBufferEffect : BaseEffect
{
/// <summary>
/// The capture buffer parameter.
/// </summary>
public AuxiliaryBufferParameter Parameter;
/// <summary>
/// Capture buffer state.
/// </summary>
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<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter
{
Debug.Assert(IsTypeValid(ref parameter));
UpdateParameterBase(ref parameter);
Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0];
IsEnabled = parameter.IsEnabled;
updateErrorInfo = new BehaviourParameter.ErrorInfo();
if (BufferUnmapped || parameter.IsNew)
{
ulong bufferSize = (ulong)Unsafe.SizeOf<int>() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf<AuxiliaryBufferHeader>();
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<AuxiliaryBufferHeader>();
State.ReturnBufferInfo = 0;
State.ReturnBufferInfoBase = 0;
}
}
}
public override void UpdateForCommandGeneration()
{
UpdateUsageStateForCommandGeneration();
}
}
}

View file

@ -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);
}
}

View file

@ -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!");
}

View file

@ -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;

View file

@ -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<bytes, 5> name) -> f32
public ResultCode GetAudioSystemMasterVolumeSetting(ServiceCtx context)
[CommandHipc(13)] // 13.0.0+
// GetActiveAudioOutputDeviceName() -> buffer<bytes, 6>
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<bytes, 6>)
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;
}
}
}

View file

@ -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();
}
}

View file

@ -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());
}
}
}