Amadeus: Final Act (#1481)

* Amadeus: Final Act

This is my requiem, I present to you Amadeus, a complete reimplementation of the Audio Renderer!

This reimplementation is based on my reversing of every version of the audio system module that I carried for the past 10 months.
This supports every revision (at the time of writing REV1 to REV8 included) and all features proposed by the Audio Renderer on real hardware.

Because this component could be used outside an emulation context, and to avoid possible "inspirations" not crediting the project, I decided to license the Ryujinx.Audio.Renderer project under LGPLv3.

- FE3H voices in videos and chapter intro are not present.
- Games that use two audio renderer **at the same time** are probably going to have issues right now **until we rewrite the audio output interface** (Crash Team Racing is the only known game to use two renderer at the same time).

- Persona 5 Scrambler now goes ingame but audio is garbage. This is caused by the fact that the game engine is syncing audio and video in a really aggressive way. This will disappears the day this game run at full speed.

* Make timing more precise when sleeping on Windows

Improve precision to a 1ms resolution on Windows NT based OS.
This is used to avoid having totally erratic timings and unify all
Windows users to the same resolution.

NOTE: This is only active when emulation is running.
This commit is contained in:
Mary 2020-08-18 03:49:37 +02:00 committed by GitHub
parent 2a314f3c28
commit a389dd59bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
250 changed files with 23691 additions and 1512 deletions

View file

@ -2,6 +2,10 @@ using LibHac;
using LibHac.Bcat;
using LibHac.Fs;
using LibHac.FsSystem;
using Ryujinx.Audio.Renderer;
using Ryujinx.Audio.Renderer.Device;
using Ryujinx.Audio.Renderer.Integration;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.Common;
using Ryujinx.Configuration;
using Ryujinx.HLE.FileSystem.Content;
@ -12,6 +16,7 @@ using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
using Ryujinx.HLE.HOS.Services.Arp;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
using Ryujinx.HLE.HOS.Services.Mii;
using Ryujinx.HLE.HOS.Services.Nv;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
@ -43,6 +48,8 @@ namespace Ryujinx.HLE.HOS
internal Switch Device { get; private set; }
internal SurfaceFlinger SurfaceFlinger { get; private set; }
internal AudioRendererManager AudioRendererManager { get; private set; }
internal VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; private set; }
public SystemStateMgr State { get; private set; }
@ -182,6 +189,34 @@ namespace Ryujinx.HLE.HOS
ConfigurationState.Instance.System.EnableDockedMode.Event += OnDockedModeChange;
InitLibHacHorizon();
InitializeAudioRenderer();
}
private void InitializeAudioRenderer()
{
AudioRendererManager = new AudioRendererManager();
AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry();
IWritableEvent[] writableEvents = new IWritableEvent[RendererConstants.AudioRendererSessionCountMax];
for (int i = 0; i < writableEvents.Length; i++)
{
KEvent systemEvent = new KEvent(KernelContext);
writableEvents[i] = new AudioKernelEvent(systemEvent);
}
HardwareDevice[] devices = new HardwareDevice[RendererConstants.AudioRendererSessionCountMax];
// TODO: don't hardcode those values.
// TODO: keep the device somewhere and dispose it when exiting.
// TODO: This is kind of wrong, we should have an high level API for that and mix all buffers between them.
for (int i = 0; i < devices.Length; i++)
{
devices[i] = new AalHardwareDevice(i, Device.AudioOut, 2, RendererConstants.TargetSampleRate);
}
AudioRendererManager.Initialize(writableEvents, devices);
}
public void LoadKip(string kipPath)
@ -292,6 +327,8 @@ namespace Ryujinx.HLE.HOS
KernelContext.ThreadCounter.Signal();
KernelContext.ThreadCounter.Wait();
AudioRendererManager.Dispose();
KernelContext.Dispose();
}
}

View file

@ -0,0 +1,98 @@
using Ryujinx.Audio;
using Ryujinx.Audio.Renderer;
using Ryujinx.Audio.Renderer.Integration;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
public class AalHardwareDevice : HardwareDevice
{
private IAalOutput _output;
private int _trackId;
private int _bufferTag;
private int _nextTag;
private AutoResetEvent _releaseEvent;
private uint _channelCount;
private uint _sampleRate;
private short[] _buffer;
private Queue<long> _releasedTags;
public AalHardwareDevice(int bufferTag, IAalOutput output, uint channelCount, uint sampleRate)
{
_bufferTag = bufferTag;
_channelCount = channelCount;
_sampleRate = sampleRate;
_output = output;
_releaseEvent = new AutoResetEvent(true);
_trackId = _output.OpenTrack((int)sampleRate, (int)channelCount, AudioCallback);
_releasedTags = new Queue<long>();
_buffer = new short[RendererConstants.TargetSampleCount * channelCount];
_output.Start(_trackId);
}
private void AudioCallback()
{
long[] released = _output.GetReleasedBuffers(_trackId, int.MaxValue);
lock (_releasedTags)
{
foreach (long tag in released)
{
_releasedTags.Enqueue(tag);
}
}
}
private long GetReleasedTag()
{
lock (_releasedTags)
{
if (_releasedTags.Count > 0)
{
return _releasedTags.Dequeue();
}
return (_bufferTag << 16) | (_nextTag++);
}
}
public void AppendBuffer(ReadOnlySpan<short> data, uint channelCount)
{
data.CopyTo(_buffer.AsSpan());
_output.AppendBuffer(_trackId, GetReleasedTag(), _buffer);
}
public uint GetChannelCount()
{
return _channelCount;
}
public uint GetSampleRate()
{
return _sampleRate;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_output.Stop(_trackId);
_output.CloseTrack(_trackId);
_releaseEvent.Dispose();
}
}
}
}

View file

@ -0,0 +1,158 @@
using Ryujinx.Audio.Renderer.Device;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioDevice : IAudioDevice
{
private VirtualDeviceSession[] _sessions;
private ulong _appletResourceId;
private int _revision;
private bool _isUsbDeviceSupported;
private VirtualDeviceSessionRegistry _registry;
private KEvent _systemEvent;
public AudioDevice(VirtualDeviceSessionRegistry registry, KernelContext context, ulong appletResourceId, int revision)
{
_registry = registry;
_appletResourceId = appletResourceId;
_revision = revision;
BehaviourContext behaviourContext = new BehaviourContext();
behaviourContext.SetUserRevision(revision);
_isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported();
_sessions = _registry.GetSessionByAppletResourceId(appletResourceId);
// TODO: support the 3 different events correctly when we will have hot plugable audio devices.
_systemEvent = new KEvent(context);
_systemEvent.ReadableEvent.Signal();
}
private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false)
{
result = null;
foreach (VirtualDeviceSession session in _sessions)
{
if (session.Device.Name.Equals(name))
{
if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice())
{
return false;
}
result = session;
return true;
}
}
return false;
}
public string GetActiveAudioDeviceName()
{
VirtualDevice device = _registry.ActiveDevice;
if (!_isUsbDeviceSupported && device.IsUsbDevice())
{
device = _registry.DefaultDevice;
}
return device.Name;
}
public uint GetActiveChannelCount()
{
VirtualDevice device = _registry.ActiveDevice;
if (!_isUsbDeviceSupported && device.IsUsbDevice())
{
device = _registry.DefaultDevice;
}
return device.ChannelCount;
}
public ResultCode GetAudioDeviceOutputVolume(string name, out float volume)
{
if (TryGetDeviceByName(out VirtualDeviceSession result, name))
{
volume = result.Volume;
}
else
{
volume = 0.0f;
}
return ResultCode.Success;
}
public ResultCode SetAudioDeviceOutputVolume(string name, float volume)
{
if (TryGetDeviceByName(out VirtualDeviceSession result, name, true))
{
if (!_isUsbDeviceSupported && result.Device.IsUsbDevice())
{
result = _sessions[0];
}
result.Volume = volume;
}
return ResultCode.Success;
}
public ResultCode GetAudioSystemMasterVolumeSetting(string name, out float systemMasterVolume)
{
if (TryGetDeviceByName(out VirtualDeviceSession result, name, true))
{
systemMasterVolume = result.Device.MasterVolume;
}
else
{
systemMasterVolume = 0.0f;
}
return ResultCode.Success;
}
public string[] ListAudioDeviceName()
{
int deviceCount = _sessions.Length;
if (!_isUsbDeviceSupported)
{
deviceCount--;
}
string[] result = new string[deviceCount];
for (int i = 0; i < deviceCount; i++)
{
result[i] = _sessions[i].Device.Name;
}
return result;
}
public KEvent QueryAudioDeviceInputEvent()
{
return _systemEvent;
}
public KEvent QueryAudioDeviceOutputEvent()
{
return _systemEvent;
}
public KEvent QueryAudioDeviceSystemEvent()
{
return _systemEvent;
}
}
}

View file

@ -1,41 +1,40 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.SystemState;
using System;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class IAudioDevice : IpcService
class AudioDeviceServer : IpcService
{
private KEvent _systemEvent;
private const int AudioDeviceNameSize = 0x100;
public IAudioDevice(Horizon system)
private IAudioDevice _impl;
public AudioDeviceServer(IAudioDevice impl)
{
_systemEvent = new KEvent(system.KernelContext);
// TODO: We shouldn't be signaling this here.
_systemEvent.ReadableEvent.Signal();
_impl = impl;
}
[Command(0)]
// ListAudioDeviceName() -> (u32, buffer<bytes, 6>)
public ResultCode ListAudioDeviceName(ServiceCtx context)
{
string[] deviceNames = SystemStateMgr.AudioOutputs;
context.ResponseData.Write(deviceNames.Length);
string[] deviceNames = _impl.ListAudioDeviceName();
long position = context.Request.ReceiveBuff[0].Position;
long size = context.Request.ReceiveBuff[0].Size;
long size = context.Request.ReceiveBuff[0].Size;
long basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name + "\0");
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + buffer.Length > size)
{
@ -45,41 +44,58 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
}
context.Memory.Write((ulong)position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioDeviceNameSize - buffer.Length);
position += buffer.Length;
position += AudioDeviceNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[Command(1)]
// SetAudioDeviceOutputVolume(u32, buffer<bytes, 5>)
// SetAudioDeviceOutputVolume(f32 volume, buffer<bytes, 5> name)
public ResultCode SetAudioDeviceOutputVolume(ServiceCtx context)
{
float volume = context.RequestData.ReadSingle();
long position = context.Request.SendBuff[0].Position;
long size = context.Request.SendBuff[0].Size;
long size = context.Request.SendBuff[0].Size;
byte[] deviceNameBuffer = new byte[size];
context.Memory.Read((ulong)position, deviceNameBuffer);
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size);
string deviceName = Encoding.ASCII.GetString(deviceNameBuffer);
return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
}
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
[Command(2)]
// GetAudioDeviceOutputVolume(buffer<bytes, 5> name) -> f32 volume
public ResultCode GetAudioDeviceOutputVolume(ServiceCtx context)
{
long position = context.Request.SendBuff[0].Position;
long size = context.Request.SendBuff[0].Size;
return ResultCode.Success;
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size);
ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
if (result == ResultCode.Success)
{
context.ResponseData.Write(volume);
}
return result;
}
[Command(3)]
// GetActiveAudioDeviceName() -> buffer<bytes, 6>
public ResultCode GetActiveAudioDeviceName(ServiceCtx context)
{
string name = context.Device.System.State.ActiveAudioOutput;
string name = _impl.GetActiveAudioDeviceName();
long position = context.Request.ReceiveBuff[0].Position;
long size = context.Request.ReceiveBuff[0].Size;
long size = context.Request.ReceiveBuff[0].Size;
byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
@ -99,7 +115,9 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
// QueryAudioDeviceSystemEvent() -> handle<copy, event>
public ResultCode QueryAudioDeviceSystemEvent(ServiceCtx context)
{
if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out int handle) != KernelResult.Success)
KEvent deviceSystemEvent = _impl.QueryAudioDeviceSystemEvent();
if (context.Process.HandleTable.GenerateHandle(deviceSystemEvent.ReadableEvent, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
@ -115,28 +133,28 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
// GetActiveChannelCount() -> u32
public ResultCode GetActiveChannelCount(ServiceCtx context)
{
context.ResponseData.Write(2);
context.ResponseData.Write(_impl.GetActiveChannelCount());
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return ResultCode.Success;
}
[Command(6)]
[Command(6)] // 3.0.0+
// ListAudioDeviceNameAuto() -> (u32, buffer<bytes, 0x22>)
public ResultCode ListAudioDeviceNameAuto(ServiceCtx context)
{
string[] deviceNames = SystemStateMgr.AudioOutputs;
context.ResponseData.Write(deviceNames.Length);
string[] deviceNames = _impl.ListAudioDeviceName();
(long position, long size) = context.Request.GetBufferType0x22();
long basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.UTF8.GetBytes(name + '\0');
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + buffer.Length > size)
{
@ -146,48 +164,53 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
}
context.Memory.Write((ulong)position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioDeviceNameSize - buffer.Length);
position += buffer.Length;
position += AudioDeviceNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[Command(7)]
// SetAudioDeviceOutputVolumeAuto(u32, buffer<bytes, 0x21>)
[Command(7)] // 3.0.0+
// SetAudioDeviceOutputVolumeAuto(f32 volume, buffer<bytes, 0x21> name)
public ResultCode SetAudioDeviceOutputVolumeAuto(ServiceCtx context)
{
float volume = context.RequestData.ReadSingle();
(long position, long size) = context.Request.GetBufferType0x21();
byte[] deviceNameBuffer = new byte[size];
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size);
context.Memory.Read((ulong)position, deviceNameBuffer);
string deviceName = Encoding.UTF8.GetString(deviceNameBuffer);
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return ResultCode.Success;
return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
}
[Command(8)]
// GetAudioDeviceOutputVolumeAuto(buffer<bytes, 0x21>) -> u32
[Command(8)] // 3.0.0+
// GetAudioDeviceOutputVolumeAuto(buffer<bytes, 0x21> name) -> f32
public ResultCode GetAudioDeviceOutputVolumeAuto(ServiceCtx context)
{
context.ResponseData.Write(1f);
(long position, long size) = context.Request.GetBufferType0x21();
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size);
ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
if (result == ResultCode.Success)
{
context.ResponseData.Write(volume);
}
return ResultCode.Success;
}
[Command(10)]
[Command(10)] // 3.0.0+
// GetActiveAudioDeviceNameAuto() -> buffer<bytes, 0x22>
public ResultCode GetActiveAudioDeviceNameAuto(ServiceCtx context)
{
string name = context.Device.System.State.ActiveAudioOutput;
string name = _impl.GetActiveAudioDeviceName();
(long position, long size) = context.Request.GetBufferType0x22();
@ -205,11 +228,13 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
return ResultCode.Success;
}
[Command(11)]
[Command(11)] // 3.0.0+
// QueryAudioDeviceInputEvent() -> handle<copy, event>
public ResultCode QueryAudioDeviceInputEvent(ServiceCtx context)
{
if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out int handle) != KernelResult.Success)
KEvent deviceInputEvent = _impl.QueryAudioDeviceInputEvent();
if (context.Process.HandleTable.GenerateHandle(deviceInputEvent.ReadableEvent, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
@ -221,11 +246,13 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
return ResultCode.Success;
}
[Command(12)]
[Command(12)] // 3.0.0+
// QueryAudioDeviceOutputEvent() -> handle<copy, event>
public ResultCode QueryAudioDeviceOutputEvent(ServiceCtx context)
{
if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out int handle) != KernelResult.Success)
KEvent deviceOutputEvent = _impl.QueryAudioDeviceOutputEvent();
if (context.Process.HandleTable.GenerateHandle(deviceOutputEvent.ReadableEvent, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
@ -236,5 +263,24 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
return ResultCode.Success;
}
[Command(13)]
// GetAudioSystemMasterVolumeSetting(buffer<bytes, 5> name) -> f32
public ResultCode GetAudioSystemMasterVolumeSetting(ServiceCtx context)
{
long position = context.Request.SendBuff[0].Position;
long size = context.Request.SendBuff[0].Size;
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size);
ResultCode result = _impl.GetAudioSystemMasterVolumeSetting(deviceName, out float systemMasterVolume);
if (result == ResultCode.Success)
{
context.ResponseData.Write(systemMasterVolume);
}
return result;
}
}
}
}

View file

@ -0,0 +1,25 @@
using Ryujinx.Audio.Renderer.Integration;
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioKernelEvent : IWritableEvent
{
public KEvent Event { get; }
public AudioKernelEvent(KEvent evnt)
{
Event = evnt;
}
public void Clear()
{
Event.WritableEvent.Clear();
}
public void Signal()
{
Event.WritableEvent.Signal();
}
}
}

View file

@ -0,0 +1,112 @@
using Ryujinx.Audio.Renderer.Integration;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioRenderer : IAudioRenderer
{
private AudioRenderSystem _impl;
public AudioRenderer(AudioRenderSystem impl)
{
_impl = impl;
}
public ResultCode ExecuteAudioRendererRendering()
{
throw new NotImplementedException();
}
public uint GetMixBufferCount()
{
return _impl.GetMixBufferCount();
}
public uint GetRenderingTimeLimit()
{
return _impl.GetRenderingTimeLimit();
}
public uint GetSampleCount()
{
return _impl.GetSampleCount();
}
public uint GetSampleRate()
{
return _impl.GetSampleRate();
}
public int GetState()
{
if (_impl.IsActive())
{
return 0;
}
return 1;
}
public ResultCode QuerySystemEvent(out KEvent systemEvent)
{
ResultCode resultCode = (ResultCode)_impl.QuerySystemEvent(out IWritableEvent outEvent);
if (resultCode == ResultCode.Success)
{
if (outEvent is AudioKernelEvent)
{
systemEvent = ((AudioKernelEvent)outEvent).Event;
}
else
{
throw new NotImplementedException();
}
}
else
{
systemEvent = null;
}
return resultCode;
}
public ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input)
{
return (ResultCode)_impl.Update(output, performanceOutput, input);
}
public void SetRenderingTimeLimit(uint percent)
{
_impl.SetRenderingTimeLimitPercent(percent);
}
public ResultCode Start()
{
_impl.Start();
return ResultCode.Success;
}
public ResultCode Stop()
{
_impl.Stop();
return ResultCode.Success;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_impl.Dispose();
}
}
}
}

View file

@ -0,0 +1,188 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
using System.Buffers;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioRendererServer : IpcService, IDisposable
{
private IAudioRenderer _impl;
public AudioRendererServer(IAudioRenderer impl)
{
_impl = impl;
}
[Command(0)]
// GetSampleRate() -> u32
public ResultCode GetSampleRate(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetSampleRate());
return ResultCode.Success;
}
[Command(1)]
// GetSampleCount() -> u32
public ResultCode GetSampleCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetSampleCount());
return ResultCode.Success;
}
[Command(2)]
// GetMixBufferCount() -> u32
public ResultCode GetMixBufferCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetMixBufferCount());
return ResultCode.Success;
}
[Command(3)]
// GetState() -> u32
public ResultCode GetState(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetState());
return ResultCode.Success;
}
[Command(4)]
// RequestUpdate(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 5> input)
// -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> performanceOutput)
public ResultCode RequestUpdate(ServiceCtx context)
{
long inputPosition = context.Request.SendBuff[0].Position;
long inputSize = context.Request.SendBuff[0].Size;
long outputPosition = context.Request.ReceiveBuff[0].Position;
long outputSize = context.Request.ReceiveBuff[0].Size;
long performanceOutputPosition = context.Request.ReceiveBuff[1].Position;
long performanceOutputSize = context.Request.ReceiveBuff[1].Size;
ReadOnlyMemory<byte> input = context.Memory.GetSpan((ulong)inputPosition, (int)inputSize).ToArray();
Memory<byte> output = new byte[outputSize];
Memory<byte> performanceOutput = new byte[performanceOutputSize];
using MemoryHandle outputHandle = output.Pin();
using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
if (result == ResultCode.Success)
{
context.Memory.Write((ulong)outputPosition, output.Span);
context.Memory.Write((ulong)performanceOutputPosition, performanceOutput.Span);
}
else
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{result}");
}
return result;
}
[Command(5)]
// Start()
public ResultCode Start(ServiceCtx context)
{
return _impl.Start();
}
[Command(6)]
// Stop()
public ResultCode Stop(ServiceCtx context)
{
return _impl.Stop();
}
[Command(7)]
// QuerySystemEvent() -> handle<copy, event>
public ResultCode QuerySystemEvent(ServiceCtx context)
{
ResultCode result = _impl.QuerySystemEvent(out KEvent systemEvent);
if (result == ResultCode.Success)
{
if (context.Process.HandleTable.GenerateHandle(systemEvent.ReadableEvent, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
}
return result;
}
[Command(8)]
// SetAudioRendererRenderingTimeLimit(u32 limit)
public ResultCode SetAudioRendererRenderingTimeLimit(ServiceCtx context)
{
uint limit = context.RequestData.ReadUInt32();
_impl.SetRenderingTimeLimit(limit);
return ResultCode.Success;
}
[Command(9)]
// GetAudioRendererRenderingTimeLimit() -> u32 limit
public ResultCode GetAudioRendererRenderingTimeLimit(ServiceCtx context)
{
uint limit = _impl.GetRenderingTimeLimit();
context.ResponseData.Write(limit);
return ResultCode.Success;
}
[Command(10)] // 3.0.0+
// RequestUpdateAuto(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x21> input)
// -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> performanceOutput)
public ResultCode RequestUpdateAuto(ServiceCtx context)
{
(long inputPosition, long inputSize) = context.Request.GetBufferType0x21();
(long outputPosition, long outputSize) = context.Request.GetBufferType0x22(0);
(long performanceOutputPosition, long performanceOutputSize) = context.Request.GetBufferType0x22(1);
ReadOnlyMemory<byte> input = context.Memory.GetSpan((ulong)inputPosition, (int)inputSize).ToArray();
Memory<byte> output = new byte[outputSize];
Memory<byte> performanceOutput = new byte[performanceOutputSize];
using MemoryHandle outputHandle = output.Pin();
using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
if (result == ResultCode.Success)
{
context.Memory.Write((ulong)outputPosition, output.Span);
context.Memory.Write((ulong)performanceOutputPosition, performanceOutput.Span);
}
return result;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_impl.Dispose();
}
}
}
}

View file

@ -0,0 +1,17 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
interface IAudioDevice
{
string[] ListAudioDeviceName();
ResultCode SetAudioDeviceOutputVolume(string name, float volume);
ResultCode GetAudioDeviceOutputVolume(string name, out float volume);
string GetActiveAudioDeviceName();
KEvent QueryAudioDeviceSystemEvent();
uint GetActiveChannelCount();
KEvent QueryAudioDeviceInputEvent();
KEvent QueryAudioDeviceOutputEvent();
ResultCode GetAudioSystemMasterVolumeSetting(string name, out float systemMasterVolume);
}
}

View file

@ -0,0 +1,20 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
interface IAudioRenderer : IDisposable
{
uint GetSampleRate();
uint GetSampleCount();
uint GetMixBufferCount();
int GetState();
ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input);
ResultCode Start();
ResultCode Stop();
ResultCode QuerySystemEvent(out KEvent systemEvent);
void SetRenderingTimeLimit(uint percent);
uint GetRenderingTimeLimit();
ResultCode ExecuteAudioRendererRendering();
}
}

View file

@ -0,0 +1,50 @@
using Ryujinx.Audio.Renderer.Device;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
using AudioRendererManagerImpl = Ryujinx.Audio.Renderer.Server.AudioRendererManager;
namespace Ryujinx.HLE.HOS.Services.Audio
{
class AudioRendererManager : IAudioRendererManager
{
private AudioRendererManagerImpl _impl;
private VirtualDeviceSessionRegistry _registry;
public AudioRendererManager(AudioRendererManagerImpl impl, VirtualDeviceSessionRegistry registry)
{
_impl = impl;
_registry = registry;
}
public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId)
{
outObject = new AudioDevice(_registry, context.Device.System.KernelContext, appletResourceUserId, revision);
return ResultCode.Success;
}
public ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter)
{
return AudioRendererManagerImpl.GetWorkBufferSize(ref parameter);
}
public ResultCode OpenAudioRenderer(ServiceCtx context, out IAudioRenderer obj, ref AudioRendererConfiguration parameter, ulong workBufferSize, ulong appletResourceUserId, KTransferMemory workBufferTransferMemory, uint processHandle)
{
ResultCode result = (ResultCode)_impl.OpenAudioRenderer(out AudioRenderSystem renderer, context.Memory, ref parameter, appletResourceUserId, workBufferTransferMemory.Address, workBufferTransferMemory.Size, processHandle);
if (result == ResultCode.Success)
{
obj = new AudioRenderer.AudioRenderer(renderer);
}
else
{
obj = null;
}
return result;
}
}
}

View file

@ -1,9 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
static class AudioRendererCommon
{
public static bool CheckValidRevision(AudioRendererParameter parameters) => GetRevisionVersion(parameters.Revision) <= AudioRendererConsts.Revision;
public static bool CheckFeatureSupported(int revision, int supportedRevision) => revision >= supportedRevision;
public static int GetRevisionVersion(int revision) => (revision - AudioRendererConsts.Rev0Magic) >> 24;
}
}

View file

@ -1,30 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
class BehaviorInfo
{
private const int _revision = AudioRendererConsts.Revision;
private int _userRevision = 0;
public BehaviorInfo()
{
/* TODO: this class got a size of 0xC0
0x00 - uint - Internal Revision
0x04 - uint - User Revision
0x08 - ... unknown ...
*/
}
public bool IsSplitterSupported() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.Splitter);
public bool IsSplitterBugFixed() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.SplitterBugFix);
public bool IsVariadicCommandBufferSizeSupported() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.VariadicCommandBufferSize);
public bool IsElapsedFrameCountSupported() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.ElapsedFrameCount);
public int GetPerformanceMetricsDataFormat() => AudioRendererCommon.CheckFeatureSupported(_userRevision, SupportTags.PerformanceMetricsDataFormatVersion2) ? 2 : 1;
public void SetUserLibRevision(int revision)
{
_userRevision = AudioRendererCommon.GetRevisionVersion(revision);
}
}
}

View file

@ -1,16 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
static class CommandGenerator
{
public static long CalculateCommandBufferSize(AudioRendererParameter parameters)
{
return parameters.EffectCount * 0x840 +
parameters.SubMixCount * 0x5A38 +
parameters.SinkCount * 0x148 +
parameters.SplitterDestinationDataCount * 0x540 +
(parameters.SplitterCount * 0x68 + 0x2E0) * parameters.VoiceCount +
((parameters.VoiceCount + parameters.SubMixCount + parameters.EffectCount + parameters.SinkCount + 0x65) << 6) +
0x3F8;
}
}
}

View file

@ -1,19 +0,0 @@
using Ryujinx.Common;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
static class EdgeMatrix
{
public static int GetWorkBufferSize(int totalMixCount)
{
int size = BitUtils.AlignUp(totalMixCount * totalMixCount, AudioRendererConsts.BufferAlignment);
if (size < 0)
{
size |= 7;
}
return size / 8;
}
}
}

View file

@ -1,7 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
class EffectContext
{
public EffectOut OutStatus;
}
}

View file

@ -1,452 +0,0 @@
using Ryujinx.Audio;
using Ryujinx.Audio.Adpcm;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.Utilities;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
class IAudioRenderer : IpcService, IDisposable
{
// This is the amount of samples that are going to be appended
// each time that RequestUpdateAudioRenderer is called. Ideally,
// this value shouldn't be neither too small (to avoid the player
// starving due to running out of samples) or too large (to avoid
// high latency).
private const int MixBufferSamplesCount = 960;
private KEvent _updateEvent;
private MemoryManager _memory;
private IAalOutput _audioOut;
private AudioRendererParameter _params;
private MemoryPoolContext[] _memoryPools;
private VoiceContext[] _voices;
private EffectContext[] _effects;
private int _track;
private PlayState _playState;
private ulong _elapsedFrameCount;
public IAudioRenderer(
Horizon system,
MemoryManager memory,
IAalOutput audioOut,
AudioRendererParameter rendererParams)
{
_updateEvent = new KEvent(system.KernelContext);
_memory = memory;
_audioOut = audioOut;
_params = rendererParams;
_track = audioOut.OpenTrack(
AudioRendererConsts.HostSampleRate,
AudioRendererConsts.HostChannelsCount,
AudioCallback);
_memoryPools = CreateArray<MemoryPoolContext>(rendererParams.EffectCount + rendererParams.VoiceCount * 4);
_voices = CreateArray<VoiceContext>(rendererParams.VoiceCount);
_effects = CreateArray<EffectContext>(rendererParams.EffectCount);
_elapsedFrameCount = 0;
InitializeAudioOut();
_playState = PlayState.Stopped;
}
[Command(0)]
// GetSampleRate() -> u32
public ResultCode GetSampleRate(ServiceCtx context)
{
context.ResponseData.Write(_params.SampleRate);
return ResultCode.Success;
}
[Command(1)]
// GetSampleCount() -> u32
public ResultCode GetSampleCount(ServiceCtx context)
{
context.ResponseData.Write(_params.SampleCount);
return ResultCode.Success;
}
[Command(2)]
// GetMixBufferCount() -> u32
public ResultCode GetMixBufferCount(ServiceCtx context)
{
context.ResponseData.Write(_params.SubMixCount);
return ResultCode.Success;
}
[Command(3)]
// GetState() -> u32
public ResultCode GetState(ServiceCtx context)
{
context.ResponseData.Write((int)_playState);
Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { State = Enum.GetName(typeof(PlayState), _playState) });
return ResultCode.Success;
}
private void AudioCallback()
{
_updateEvent.ReadableEvent.Signal();
}
private static T[] CreateArray<T>(int size) where T : new()
{
T[] output = new T[size];
for (int index = 0; index < size; index++)
{
output[index] = new T();
}
return output;
}
private void InitializeAudioOut()
{
AppendMixedBuffer(0);
AppendMixedBuffer(1);
AppendMixedBuffer(2);
_audioOut.Start(_track);
}
[Command(4)]
// RequestUpdateAudioRenderer(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 5>)
// -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6>, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6>)
public ResultCode RequestUpdateAudioRenderer(ServiceCtx context)
{
long outputPosition = context.Request.ReceiveBuff[0].Position;
long outputSize = context.Request.ReceiveBuff[0].Size;
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
long inputPosition = context.Request.SendBuff[0].Position;
StructReader reader = new StructReader(context.Memory, inputPosition);
StructWriter writer = new StructWriter(context.Memory, outputPosition);
UpdateDataHeader inputHeader = reader.Read<UpdateDataHeader>();
BehaviorInfo behaviorInfo = new BehaviorInfo();
behaviorInfo.SetUserLibRevision(inputHeader.Revision);
reader.Read<BehaviorIn>(inputHeader.BehaviorSize);
MemoryPoolIn[] memoryPoolsIn = reader.Read<MemoryPoolIn>(inputHeader.MemoryPoolSize);
for (int index = 0; index < memoryPoolsIn.Length; index++)
{
MemoryPoolIn memoryPool = memoryPoolsIn[index];
if (memoryPool.State == MemoryPoolState.RequestAttach)
{
_memoryPools[index].OutStatus.State = MemoryPoolState.Attached;
}
else if (memoryPool.State == MemoryPoolState.RequestDetach)
{
_memoryPools[index].OutStatus.State = MemoryPoolState.Detached;
}
}
reader.Read<VoiceChannelResourceIn>(inputHeader.VoiceResourceSize);
VoiceIn[] voicesIn = reader.Read<VoiceIn>(inputHeader.VoiceSize);
for (int index = 0; index < voicesIn.Length; index++)
{
VoiceIn voice = voicesIn[index];
VoiceContext voiceCtx = _voices[index];
voiceCtx.SetAcquireState(voice.Acquired != 0);
if (voice.Acquired == 0)
{
continue;
}
if (voice.FirstUpdate != 0)
{
voiceCtx.AdpcmCtx = GetAdpcmDecoderContext(
voice.AdpcmCoeffsPosition,
voice.AdpcmCoeffsSize);
voiceCtx.SampleFormat = voice.SampleFormat;
voiceCtx.SampleRate = voice.SampleRate;
voiceCtx.ChannelsCount = voice.ChannelsCount;
voiceCtx.SetBufferIndex(voice.BaseWaveBufferIndex);
}
voiceCtx.WaveBuffers[0] = voice.WaveBuffer0;
voiceCtx.WaveBuffers[1] = voice.WaveBuffer1;
voiceCtx.WaveBuffers[2] = voice.WaveBuffer2;
voiceCtx.WaveBuffers[3] = voice.WaveBuffer3;
voiceCtx.Volume = voice.Volume;
voiceCtx.PlayState = voice.PlayState;
}
EffectIn[] effectsIn = reader.Read<EffectIn>(inputHeader.EffectSize);
for (int index = 0; index < effectsIn.Length; index++)
{
if (effectsIn[index].IsNew != 0)
{
_effects[index].OutStatus.State = EffectState.New;
}
}
UpdateAudio();
UpdateDataHeader outputHeader = new UpdateDataHeader();
int updateHeaderSize = Marshal.SizeOf<UpdateDataHeader>();
outputHeader.Revision = AudioRendererConsts.RevMagic;
outputHeader.BehaviorSize = 0xb0;
outputHeader.MemoryPoolSize = (_params.EffectCount + _params.VoiceCount * 4) * 0x10;
outputHeader.VoiceSize = _params.VoiceCount * 0x10;
outputHeader.EffectSize = _params.EffectCount * 0x10;
outputHeader.SinkSize = _params.SinkCount * 0x20;
outputHeader.PerformanceManagerSize = 0x10;
if (behaviorInfo.IsElapsedFrameCountSupported())
{
outputHeader.ElapsedFrameCountInfoSize = 0x10;
}
outputHeader.TotalSize = updateHeaderSize +
outputHeader.BehaviorSize +
outputHeader.MemoryPoolSize +
outputHeader.VoiceSize +
outputHeader.EffectSize +
outputHeader.SinkSize +
outputHeader.PerformanceManagerSize +
outputHeader.ElapsedFrameCountInfoSize;
writer.Write(outputHeader);
foreach (MemoryPoolContext memoryPool in _memoryPools)
{
writer.Write(memoryPool.OutStatus);
}
foreach (VoiceContext voice in _voices)
{
writer.Write(voice.OutStatus);
}
foreach (EffectContext effect in _effects)
{
writer.Write(effect.OutStatus);
}
writer.SkipBytes(_params.SinkCount * 0x20);
writer.SkipBytes(outputHeader.PerformanceManagerSize);
writer.SkipBytes(outputHeader.BehaviorSize);
if (behaviorInfo.IsElapsedFrameCountSupported())
{
writer.Write(new RendererInfoOut
{
ElapsedFrameCount = _elapsedFrameCount
});
}
return ResultCode.Success;
}
[Command(5)]
// Start()
public ResultCode StartAudioRenderer(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
_playState = PlayState.Playing;
return ResultCode.Success;
}
[Command(6)]
// Stop()
public ResultCode StopAudioRenderer(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
_playState = PlayState.Stopped;
return ResultCode.Success;
}
[Command(7)]
// QuerySystemEvent() -> handle<copy, event>
public ResultCode QuerySystemEvent(ServiceCtx context)
{
if (context.Process.HandleTable.GenerateHandle(_updateEvent.ReadableEvent, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
return ResultCode.Success;
}
private AdpcmDecoderContext GetAdpcmDecoderContext(long position, long size)
{
if (size == 0)
{
return null;
}
AdpcmDecoderContext context = new AdpcmDecoderContext
{
Coefficients = new short[size >> 1]
};
for (int offset = 0; offset < size; offset += 2)
{
context.Coefficients[offset >> 1] = _memory.Read<short>((ulong)(position + offset));
}
return context;
}
private void UpdateAudio()
{
long[] released = _audioOut.GetReleasedBuffers(_track, 2);
for (int index = 0; index < released.Length; index++)
{
AppendMixedBuffer(released[index]);
}
_elapsedFrameCount++;
}
private void AppendMixedBuffer(long tag)
{
int[] mixBuffer = new int[MixBufferSamplesCount * AudioRendererConsts.HostChannelsCount];
foreach (VoiceContext voice in _voices)
{
if (!voice.Playing || voice.CurrentWaveBuffer.Size == 0)
{
continue;
}
int outOffset = 0;
int pendingSamples = MixBufferSamplesCount;
while (pendingSamples > 0)
{
int[] samples = voice.GetBufferData(_memory, pendingSamples, out int returnedSamples);
if (returnedSamples == 0)
{
break;
}
pendingSamples -= returnedSamples;
for (int offset = 0; offset < samples.Length; offset++)
{
mixBuffer[outOffset++] += (int)(samples[offset] * voice.Volume);
}
}
}
_audioOut.AppendBuffer(_track, tag, GetFinalBuffer(mixBuffer));
}
private unsafe static short[] GetFinalBuffer(int[] buffer)
{
short[] output = new short[buffer.Length];
int offset = 0;
// Perform Saturation using SSE2 if supported
if (Sse2.IsSupported)
{
fixed (int* inptr = buffer)
fixed (short* outptr = output)
{
for (; offset + 32 <= buffer.Length; offset += 32)
{
// Unroll the loop a little to ensure the CPU pipeline
// is always full.
Vector128<int> block1A = Sse2.LoadVector128(inptr + offset + 0);
Vector128<int> block1B = Sse2.LoadVector128(inptr + offset + 4);
Vector128<int> block2A = Sse2.LoadVector128(inptr + offset + 8);
Vector128<int> block2B = Sse2.LoadVector128(inptr + offset + 12);
Vector128<int> block3A = Sse2.LoadVector128(inptr + offset + 16);
Vector128<int> block3B = Sse2.LoadVector128(inptr + offset + 20);
Vector128<int> block4A = Sse2.LoadVector128(inptr + offset + 24);
Vector128<int> block4B = Sse2.LoadVector128(inptr + offset + 28);
Vector128<short> output1 = Sse2.PackSignedSaturate(block1A, block1B);
Vector128<short> output2 = Sse2.PackSignedSaturate(block2A, block2B);
Vector128<short> output3 = Sse2.PackSignedSaturate(block3A, block3B);
Vector128<short> output4 = Sse2.PackSignedSaturate(block4A, block4B);
Sse2.Store(outptr + offset + 0, output1);
Sse2.Store(outptr + offset + 8, output2);
Sse2.Store(outptr + offset + 16, output3);
Sse2.Store(outptr + offset + 24, output4);
}
}
}
// Process left overs
for (; offset < buffer.Length; offset++)
{
output[offset] = DspUtils.Saturate(buffer[offset]);
}
return output;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_audioOut.CloseTrack(_track);
}
}
}
}

View file

@ -1,12 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
class MemoryPoolContext
{
public MemoryPoolOut OutStatus;
public MemoryPoolContext()
{
OutStatus.State = MemoryPoolState.Detached;
}
}
}

View file

@ -1,19 +0,0 @@
using Ryujinx.Common;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
static class NodeStates
{
public static long GetWorkBufferSize(int totalMixCount)
{
int size = BitUtils.AlignUp(totalMixCount, AudioRendererConsts.BufferAlignment);
if (size < 0)
{
size |= 7;
}
return 4 * (totalMixCount * totalMixCount) + 12 * totalMixCount + 2 * (size / 8);
}
}
}

View file

@ -1,30 +0,0 @@
using Ryujinx.Common.Logging;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
static class PerformanceManager
{
public static long GetRequiredBufferSizeForPerformanceMetricsPerFrame(BehaviorInfo behaviorInfo, AudioRendererParameter parameters)
{
int performanceMetricsDataFormat = behaviorInfo.GetPerformanceMetricsDataFormat();
if (performanceMetricsDataFormat == 2)
{
return 24 * (parameters.VoiceCount +
parameters.EffectCount +
parameters.SubMixCount +
parameters.SinkCount + 1) + 0x990;
}
if (performanceMetricsDataFormat != 1)
{
Logger.Warning?.Print(LogClass.ServiceAudio, $"PerformanceMetricsDataFormat: {performanceMetricsDataFormat} is not supported!");
}
return (((parameters.VoiceCount +
parameters.EffectCount +
parameters.SubMixCount +
parameters.SinkCount + 1) << 32) >> 0x1C) + 0x658;
}
}
}

View file

@ -1,191 +0,0 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
static class Resampler
{
#region "LookUp Tables"
private static short[] _curveLut0 = new short[]
{
6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, 19412, 7093, 22,
6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, 7472, 41, 5773, 19361, 7600, 48,
5659, 19342, 7728, 55, 5546, 19321, 7857, 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77,
5213, 19245, 8249, 84, 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109,
4785, 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, 9183, 147,
4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, 179, 4083, 18793, 9726, 190,
3987, 18738, 9863, 202, 3893, 18682, 10000, 215, 3800, 18624, 10137, 228, 3709, 18563, 10274, 241,
3618, 18500, 10411, 255, 3529, 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300,
3269, 18230, 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, 369,
2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, 2709, 17681, 11925, 449,
2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, 17418, 12334, 517, 2418, 17327, 12470, 541,
2348, 17234, 12606, 566, 2280, 17140, 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647,
2083, 16846, 13144, 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766,
1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, 16109, 14062, 901,
1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, 14445, 1013, 1457, 15657, 14571, 1052,
1407, 15540, 14695, 1093, 1359, 15423, 14819, 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221,
1221, 15064, 15185, 1266, 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407,
1052, 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, 15998, 1613,
901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, 1780, 798, 13673, 16434, 1838,
766, 13541, 16539, 1897, 735, 13409, 16643, 1958, 704, 13277, 16745, 2020, 675, 13144, 16846, 2083,
647, 13010, 16946, 2147, 619, 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348,
541, 12470, 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, 2635,
449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, 388, 11513, 17927, 2942,
369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, 11100, 18157, 3186, 317, 10962, 18230, 3269,
300, 10824, 18300, 3355, 285, 10687, 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618,
241, 10274, 18563, 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987,
190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, 9318, 18942, 4377,
147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, 19073, 4681, 118, 8780, 19112, 4785,
109, 8646, 19148, 4890, 101, 8513, 19183, 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213,
77, 8118, 19273, 5323, 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659,
48, 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, 19403, 6121,
22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, 6479, 3, 6722, 19426, 6600
};
private static short[] _curveLut1 = new short[]
{
-68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, 32586, 512, -36,
-568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, 1000, -69, -891, 32393, 1174, -80,
-990, 32323, 1352, -92, -1084, 32244, 1536, -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128,
-1338, 31956, 2118, -140, -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180,
-1617, 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, 3657, -240,
-1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, -289, -1951, 30272, 4648, -307,
-1984, 30072, 4908, -325, -2014, 29866, 5172, -343, -2040, 29652, 5442, -362, -2063, 29431, 5716, -382,
-2083, 29203, 5994, -403, -2100, 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468,
-2133, 28226, 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, -563,
-2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, -2121, 26285, 9336, -668,
-2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, 25375, 10326, -753, -2067, 25063, 10662, -783,
-2049, 24746, 11000, -813, -2030, 24425, 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907,
-1962, 23438, 12382, -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039,
-1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, 21027, 14877, -1176,
-1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, 15965, -1282, -1633, 19600, 16329, -1317,
-1599, 19239, 16694, -1353, -1564, 18878, 17058, -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459,
-1459, 17787, 18151, -1495, -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599,
-1317, 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, 20673, -1732,
-1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, -1825, -1072, 13798, 22077, -1855,
-1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, -972, 12733, 23103, -1937, -939, 12382, 23438, -1962,
-907, 12033, 23771, -1986, -875, 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049,
-783, 10662, 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, -2111,
-668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, -588, 8378, 27151, -2141,
-563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, 7453, 27966, -2139, -490, 7153, 28226, -2133,
-468, 6857, 28480, -2125, -445, 6565, 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083,
-382, 5716, 29431, -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984,
-307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, 3897, 30826, -1830,
-240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, 31310, -1676, -194, 2967, 31456, -1617,
-180, 2747, 31593, -1554, -167, 2532, 31723, -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338,
-128, 1919, 32061, -1258, -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990,
-80, 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, 32551, -568,
-36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, -200, -5, 69, 32639, -68
};
private static short[] _curveLut2 = new short[]
{
3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, 26253, 3751, -42,
2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, 4199, -54, 2338, 26130, 4354, -58,
2227, 26085, 4512, -63, 2120, 26035, 4673, -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76,
1813, 25852, 5174, -81, 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98,
1442, 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, 6442, -121,
1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, -140, 897, 24776, 7227, -146,
829, 24648, 7430, -153, 764, 24516, 7635, -159, 701, 24379, 7842, -166, 641, 24237, 8052, -174,
583, 24091, 8264, -181, 526, 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202,
371, 23462, 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, -230,
194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, 81, 22208, 10735, -258,
47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, 21618, 11444, -277, -44, 21415, 11684, -283,
-71, 21208, 11924, -290, -97, 20999, 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306,
-163, 20354, 12898, -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325,
-234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, 18765, 14625, -337,
-284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, 15369, -341, -310, 17817, 15617, -341,
-317, 17577, 15864, -340, -323, 17335, 16111, -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336,
-336, 16603, 16848, -332, -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317,
-341, 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, 18531, -284,
-337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, -248, -328, 13882, 19459, -234,
-325, 13635, 19686, -218, -321, 13389, 19911, -201, -316, 13143, 20134, -183, -311, 12898, 20354, -163,
-306, 12653, 20571, -143, -302, 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71,
-283, 11684, 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, 47,
-258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, -237, 10038, 22769, 194,
-230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, 9358, 23295, 324, -209, 9135, 23462, 371,
-202, 8914, 23626, 420, -194, 8695, 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583,
-174, 8052, 24237, 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829,
-146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, 6635, 25131, 1115,
-121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, 25440, 1357, -103, 5882, 25533, 1442,
-98, 5701, 25621, 1531, -92, 5522, 25704, 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813,
-76, 5004, 25919, 1912, -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227,
-58, 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, 26230, 2688,
-42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, 3064, -32, 3329, 26287, 3195
};
#endregion
public static int[] Resample2Ch(
int[] buffer,
int srcSampleRate,
int dstSampleRate,
int samplesCount,
ref int fracPart)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (srcSampleRate <= 0)
{
throw new ArgumentOutOfRangeException(nameof(srcSampleRate));
}
if (dstSampleRate <= 0)
{
throw new ArgumentOutOfRangeException(nameof(dstSampleRate));
}
double ratio = (double)srcSampleRate / dstSampleRate;
int newSamplesCount = (int)(samplesCount / ratio);
int step = (int)(ratio * 0x8000);
int[] output = new int[newSamplesCount * 2];
short[] lut;
if (step > 0xaaaa)
{
lut = _curveLut0;
}
else if (step <= 0x8000)
{
lut = _curveLut1;
}
else
{
lut = _curveLut2;
}
int inOffs = 0;
for (int outOffs = 0; outOffs < output.Length; outOffs += 2)
{
int lutIndex = (fracPart >> 8) * 4;
int sample0 = buffer[(inOffs + 0) * 2 + 0] * lut[lutIndex + 0] +
buffer[(inOffs + 1) * 2 + 0] * lut[lutIndex + 1] +
buffer[(inOffs + 2) * 2 + 0] * lut[lutIndex + 2] +
buffer[(inOffs + 3) * 2 + 0] * lut[lutIndex + 3];
int sample1 = buffer[(inOffs + 0) * 2 + 1] * lut[lutIndex + 0] +
buffer[(inOffs + 1) * 2 + 1] * lut[lutIndex + 1] +
buffer[(inOffs + 2) * 2 + 1] * lut[lutIndex + 2] +
buffer[(inOffs + 3) * 2 + 1] * lut[lutIndex + 3];
int newOffset = fracPart + step;
inOffs += newOffset >> 15;
fracPart = newOffset & 0x7fff;
output[outOffs + 0] = sample0 >> 15;
output[outOffs + 1] = sample1 >> 15;
}
return output;
}
}
}

View file

@ -1,25 +0,0 @@
using Ryujinx.Common;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
class SplitterContext
{
public static long CalcWorkBufferSize(BehaviorInfo behaviorInfo, AudioRendererParameter parameters)
{
if (!behaviorInfo.IsSplitterSupported())
{
return 0;
}
long size = parameters.SplitterDestinationDataCount * 0xE0 +
parameters.SplitterCount * 0x20;
if (!behaviorInfo.IsSplitterBugFixed())
{
size += BitUtils.AlignUp(4 * parameters.SplitterDestinationDataCount, 16);
}
return size;
}
}
}

View file

@ -1,17 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
static class AudioRendererConsts
{
// Revision Consts
public const int Revision = 8;
public const int Rev0Magic = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('0' << 24);
public const int RevMagic = Rev0Magic + (Revision << 24);
// Misc Consts
public const int BufferAlignment = 0x40;
// Host Consts
public const int HostSampleRate = 48000;
public const int HostChannelsCount = 2;
}
}

View file

@ -1,22 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
[StructLayout(LayoutKind.Sequential)]
struct AudioRendererParameter
{
public int SampleRate;
public int SampleCount;
public int MixBufferCount;
public int SubMixCount;
public int VoiceCount;
public int SinkCount;
public int EffectCount;
public int PerformanceManagerCount;
public int VoiceDropEnable;
public int SplitterCount;
public int SplitterDestinationDataCount;
public int Unknown2C;
public int Revision;
}
}

View file

@ -1,11 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
struct BehaviorIn
{
public long Unknown0;
public long Unknown8;
}
}

View file

@ -1,16 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
[StructLayout(LayoutKind.Sequential, Size = 0xc, Pack = 1)]
struct BiquadFilter
{
public byte Enable;
public byte Padding;
public short B0;
public short B1;
public short B2;
public short A1;
public short A2;
}
}

View file

@ -1,12 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
[StructLayout(LayoutKind.Sequential, Size = 0xc0, Pack = 1)]
unsafe struct EffectIn
{
public byte Unknown0x0;
public byte IsNew;
public fixed byte Unknown[0xbe];
}
}

View file

@ -1,11 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 1)]
unsafe struct EffectOut
{
public EffectState State;
public fixed byte Reserved[15];
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
enum EffectState : byte
{
None = 0,
New = 1
}
}

View file

@ -1,14 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 4)]
struct MemoryPoolIn
{
public long Address;
public long Size;
public MemoryPoolState State;
public int Unknown14;
public long Unknown18;
}
}

View file

@ -1,12 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
struct MemoryPoolOut
{
public MemoryPoolState State;
public int Unknown14;
public long Unknown18;
}
}

View file

@ -1,13 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
enum MemoryPoolState
{
Invalid = 0,
Unknown = 1,
RequestDetach = 2,
Detached = 3,
RequestAttach = 4,
Attached = 5,
Released = 6
}
}

View file

@ -1,9 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
enum PlayState : byte
{
Playing = 0,
Stopped = 1,
Paused = 2
}
}

View file

@ -1,11 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
struct RendererInfoOut
{
public ulong ElapsedFrameCount;
public ulong Reserved;
}
}

View file

@ -1,11 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
static class SupportTags
{
public const int Splitter = 2;
public const int SplitterBugFix = 5;
public const int PerformanceMetricsDataFormatVersion2 = 5;
public const int VariadicCommandBufferSize = 5;
public const int ElapsedFrameCount = 5;
}
}

View file

@ -1,24 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
struct UpdateDataHeader
{
#pragma warning disable CS0649
public int Revision;
public int BehaviorSize;
public int MemoryPoolSize;
public int VoiceSize;
public int VoiceResourceSize;
public int EffectSize;
public int MixSize;
public int SinkSize;
public int PerformanceManagerSize;
public int Unknown24;
public int ElapsedFrameCountInfoSize;
public int Unknown2C;
public int Unknown30;
public int Unknown34;
public int Unknown38;
public int TotalSize;
#pragma warning restore CS0649
}
}

View file

@ -1,10 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)]
struct VoiceChannelResourceIn
{
// ???
}
}

View file

@ -1,49 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)]
struct VoiceIn
{
public int VoiceSlot;
public int NodeId;
public byte FirstUpdate;
public byte Acquired;
public PlayState PlayState;
public SampleFormat SampleFormat;
public int SampleRate;
public int Priority;
public int Unknown14;
public int ChannelsCount;
public float Pitch;
public float Volume;
public BiquadFilter BiquadFilter0;
public BiquadFilter BiquadFilter1;
public int AppendedWaveBuffersCount;
public int BaseWaveBufferIndex;
public int Unknown44;
public long AdpcmCoeffsPosition;
public long AdpcmCoeffsSize;
public int VoiceDestination;
public int Padding;
public WaveBuffer WaveBuffer0;
public WaveBuffer WaveBuffer1;
public WaveBuffer WaveBuffer2;
public WaveBuffer WaveBuffer3;
}
}

View file

@ -1,12 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
struct VoiceOut
{
public long PlayedSamplesCount;
public int PlayedWaveBuffersCount;
public int VoiceDropsCount; //?
}
}

View file

@ -1,20 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)]
struct WaveBuffer
{
public long Position;
public long Size;
public int FirstSampleOffset;
public int LastSampleOffset;
public byte Looping;
public byte LastBuffer;
public short Unknown1A;
public int Unknown1C;
public long AdpcmLoopContextPosition;
public long AdpcmLoopContextSize;
public long Unknown30;
}
}

View file

@ -1,201 +0,0 @@
using Ryujinx.Audio.Adpcm;
using Ryujinx.Cpu;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager
{
class VoiceContext
{
private bool _acquired;
private bool _bufferReload;
private int _resamplerFracPart;
private int _bufferIndex;
private int _offset;
public int SampleRate { get; set; }
public int ChannelsCount { get; set; }
public float Volume { get; set; }
public PlayState PlayState { get; set; }
public SampleFormat SampleFormat { get; set; }
public AdpcmDecoderContext AdpcmCtx { get; set; }
public WaveBuffer[] WaveBuffers { get; }
public WaveBuffer CurrentWaveBuffer => WaveBuffers[_bufferIndex];
private VoiceOut _outStatus;
public VoiceOut OutStatus => _outStatus;
private int[] _samples;
public bool Playing => _acquired && PlayState == PlayState.Playing;
public VoiceContext()
{
WaveBuffers = new WaveBuffer[4];
}
public void SetAcquireState(bool newState)
{
if (_acquired && !newState)
{
// Release.
Reset();
}
_acquired = newState;
}
private void Reset()
{
_bufferReload = true;
_bufferIndex = 0;
_offset = 0;
_outStatus.PlayedSamplesCount = 0;
_outStatus.PlayedWaveBuffersCount = 0;
_outStatus.VoiceDropsCount = 0;
}
public int[] GetBufferData(MemoryManager memory, int maxSamples, out int samplesCount)
{
if (!Playing)
{
samplesCount = 0;
return null;
}
if (_bufferReload)
{
_bufferReload = false;
UpdateBuffer(memory);
}
WaveBuffer wb = WaveBuffers[_bufferIndex];
int maxSize = _samples.Length - _offset;
int size = maxSamples * AudioRendererConsts.HostChannelsCount;
if (size > maxSize)
{
size = maxSize;
}
int[] output = new int[size];
Array.Copy(_samples, _offset, output, 0, size);
samplesCount = size / AudioRendererConsts.HostChannelsCount;
_outStatus.PlayedSamplesCount += samplesCount;
_offset += size;
if (_offset == _samples.Length)
{
_offset = 0;
if (wb.Looping == 0)
{
SetBufferIndex(_bufferIndex + 1);
}
_outStatus.PlayedWaveBuffersCount++;
if (wb.LastBuffer != 0)
{
PlayState = PlayState.Paused;
}
}
return output;
}
private void UpdateBuffer(MemoryManager memory)
{
// TODO: Implement conversion for formats other
// than interleaved stereo (2 channels).
// As of now, it assumes that HostChannelsCount == 2.
WaveBuffer wb = WaveBuffers[_bufferIndex];
if (wb.Position == 0)
{
_samples = new int[0];
return;
}
if (SampleFormat == SampleFormat.PcmInt16)
{
int samplesCount = (int)(wb.Size / (sizeof(short) * ChannelsCount));
_samples = new int[samplesCount * AudioRendererConsts.HostChannelsCount];
if (ChannelsCount == 1)
{
for (int index = 0; index < samplesCount; index++)
{
short sample = memory.Read<short>((ulong)(wb.Position + index * 2));
_samples[index * 2 + 0] = sample;
_samples[index * 2 + 1] = sample;
}
}
else
{
for (int index = 0; index < samplesCount * 2; index++)
{
_samples[index] = memory.Read<short>((ulong)(wb.Position + index * 2));
}
}
}
else if (SampleFormat == SampleFormat.Adpcm)
{
byte[] buffer = new byte[wb.Size];
memory.Read((ulong)wb.Position, buffer);
_samples = AdpcmDecoder.Decode(buffer, AdpcmCtx);
}
else
{
throw new InvalidOperationException();
}
if (SampleRate != AudioRendererConsts.HostSampleRate)
{
// TODO: We should keep the frames being discarded (see the 4 below)
// on a buffer and include it on the next samples buffer, to allow
// the resampler to do seamless interpolation between wave buffers.
int samplesCount = _samples.Length / AudioRendererConsts.HostChannelsCount;
samplesCount = Math.Max(samplesCount - 4, 0);
_samples = Resampler.Resample2Ch(
_samples,
SampleRate,
AudioRendererConsts.HostSampleRate,
samplesCount,
ref _resamplerFracPart);
}
}
public void SetBufferIndex(int index)
{
_bufferIndex = index & 3;
_bufferReload = true;
}
}
}

View file

@ -0,0 +1,105 @@
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audren:u")]
class AudioRendererManagerServer : IpcService
{
private const int InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24);
private IAudioRendererManager _impl;
public AudioRendererManagerServer(ServiceCtx context) : this(new AudioRendererManager(context.Device.System.AudioRendererManager, context.Device.System.AudioDeviceSessionRegistry)) { }
public AudioRendererManagerServer(IAudioRendererManager impl)
{
_impl = impl;
}
[Command(0)]
// OpenAudioRenderer(nn::audio::detail::AudioRendererParameterInternal parameter, u64 workBufferSize, nn::applet::AppletResourceUserId appletResourceId, pid, handle<copy> workBuffer, handle<copy> processHandle)
// -> object<nn::audio::detail::IAudioRenderer>
public ResultCode OpenAudioRenderer(ServiceCtx context)
{
AudioRendererConfiguration parameter = context.RequestData.ReadStruct<AudioRendererConfiguration>();
ulong workBufferSize = context.RequestData.ReadUInt64();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
KTransferMemory workBufferTransferMemory = context.Process.HandleTable.GetObject<KTransferMemory>(context.Request.HandleDesc.ToCopy[0]);
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[1];
ResultCode result = _impl.OpenAudioRenderer(context, out IAudioRenderer renderer, ref parameter, workBufferSize, appletResourceUserId, workBufferTransferMemory, processHandle);
if (result == ResultCode.Success)
{
MakeObject(context, new AudioRendererServer(renderer));
}
return result;
}
[Command(1)]
// GetWorkBufferSize(nn::audio::detail::AudioRendererParameterInternal parameter) -> u64 workBufferSize
public ResultCode GetAudioRendererWorkBufferSize(ServiceCtx context)
{
AudioRendererConfiguration parameter = context.RequestData.ReadStruct<AudioRendererConfiguration>();
if (BehaviourContext.CheckValidRevision(parameter.Revision))
{
ulong size = _impl.GetWorkBufferSize(ref parameter);
context.ResponseData.Write(size);
Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{size:x16}.");
return ResultCode.Success;
}
else
{
context.ResponseData.Write(0L);
Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Revision)} is not supported!");
return ResultCode.UnsupportedRevision;
}
}
[Command(2)]
// GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object<nn::audio::detail::IAudioDevice>
public ResultCode GetAudioDeviceService(ServiceCtx context)
{
ulong appletResourceUserId = context.RequestData.ReadUInt64();
ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, InitialRevision, appletResourceUserId);
if (result == ResultCode.Success)
{
MakeObject(context, new AudioDeviceServer(device));
}
return result;
}
[Command(4)] // 4.0.0+
// GetAudioDeviceServiceWithRevisionInfo(s32 revision, nn::applet::AppletResourceUserId appletResourceId) -> object<nn::audio::detail::IAudioDevice>
public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context)
{
int revision = context.RequestData.ReadInt32();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, revision, appletResourceUserId);
if (result == ResultCode.Success)
{
MakeObject(context, new AudioDeviceServer(device));
}
return result;
}
}
}

View file

@ -1,148 +1,19 @@
using Ryujinx.Audio;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Audio.AudioRendererManager;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audren:u")]
class IAudioRendererManager : IpcService
interface IAudioRendererManager
{
public IAudioRendererManager(ServiceCtx context) { }
// TODO: Remove ServiceCtx argument
// BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend.
ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId);
[Command(0)]
// OpenAudioRenderer(nn::audio::detail::AudioRendererParameterInternal, u64, nn::applet::AppletResourceUserId, pid, handle<copy>, handle<copy>)
// -> object<nn::audio::detail::IAudioRenderer>
public ResultCode OpenAudioRenderer(ServiceCtx context)
{
IAalOutput audioOut = context.Device.AudioOut;
// TODO: Remove ServiceCtx argument
// BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend.
ResultCode OpenAudioRenderer(ServiceCtx context, out IAudioRenderer obj, ref AudioRendererConfiguration parameter, ulong workBufferSize, ulong appletResourceUserId, KTransferMemory workBufferTransferMemory, uint processHandle);
AudioRendererParameter Params = GetAudioRendererParameter(context);
MakeObject(context, new IAudioRenderer(
context.Device.System,
context.Memory,
audioOut,
Params));
return ResultCode.Success;
}
[Command(1)]
// GetWorkBufferSize(nn::audio::detail::AudioRendererParameterInternal) -> u64
public ResultCode GetAudioRendererWorkBufferSize(ServiceCtx context)
{
AudioRendererParameter parameters = GetAudioRendererParameter(context);
if (AudioRendererCommon.CheckValidRevision(parameters))
{
BehaviorInfo behaviorInfo = new BehaviorInfo();
behaviorInfo.SetUserLibRevision(parameters.Revision);
long size;
int totalMixCount = parameters.SubMixCount + 1;
size = BitUtils.AlignUp(parameters.MixBufferCount * 4, AudioRendererConsts.BufferAlignment) +
parameters.SubMixCount * 0x400 +
totalMixCount * 0x940 +
parameters.VoiceCount * 0x3F0 +
BitUtils.AlignUp(totalMixCount * 8, 16) +
BitUtils.AlignUp(parameters.VoiceCount * 8, 16) +
BitUtils.AlignUp(((parameters.SinkCount + parameters.SubMixCount) * 0x3C0 + parameters.SampleCount * 4) *
(parameters.MixBufferCount + 6), AudioRendererConsts.BufferAlignment) +
(parameters.SinkCount + parameters.SubMixCount) * 0x2C0 +
(parameters.EffectCount + parameters.VoiceCount * 4) * 0x30 +
0x50;
if (behaviorInfo.IsSplitterSupported())
{
size += BitUtils.AlignUp(NodeStates.GetWorkBufferSize(totalMixCount) + EdgeMatrix.GetWorkBufferSize(totalMixCount), 16);
}
size = parameters.SinkCount * 0x170 +
(parameters.SinkCount + parameters.SubMixCount) * 0x280 +
parameters.EffectCount * 0x4C0 +
((size + SplitterContext.CalcWorkBufferSize(behaviorInfo, parameters) + 0x30 * parameters.EffectCount + (4 * parameters.VoiceCount) + 0x8F) & ~0x3FL) +
((parameters.VoiceCount << 8) | 0x40);
if (parameters.PerformanceManagerCount >= 1)
{
size += (PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(behaviorInfo, parameters) *
(parameters.PerformanceManagerCount + 1) + 0xFF) & ~0x3FL;
}
if (behaviorInfo.IsVariadicCommandBufferSizeSupported())
{
size += CommandGenerator.CalculateCommandBufferSize(parameters) + 0x7E;
}
else
{
size += 0x1807E;
}
size = BitUtils.AlignUp(size, 0x1000);
context.ResponseData.Write(size);
Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{size:x16}.");
return ResultCode.Success;
}
else
{
context.ResponseData.Write(0L);
Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{AudioRendererCommon.GetRevisionVersion(parameters.Revision)} is not supported!");
return ResultCode.UnsupportedRevision;
}
}
private AudioRendererParameter GetAudioRendererParameter(ServiceCtx context)
{
AudioRendererParameter Params = new AudioRendererParameter
{
SampleRate = context.RequestData.ReadInt32(),
SampleCount = context.RequestData.ReadInt32(),
MixBufferCount = context.RequestData.ReadInt32(),
SubMixCount = context.RequestData.ReadInt32(),
VoiceCount = context.RequestData.ReadInt32(),
SinkCount = context.RequestData.ReadInt32(),
EffectCount = context.RequestData.ReadInt32(),
PerformanceManagerCount = context.RequestData.ReadInt32(),
VoiceDropEnable = context.RequestData.ReadInt32(),
SplitterCount = context.RequestData.ReadInt32(),
SplitterDestinationDataCount = context.RequestData.ReadInt32(),
Unknown2C = context.RequestData.ReadInt32(),
Revision = context.RequestData.ReadInt32()
};
return Params;
}
[Command(2)]
// GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object<nn::audio::detail::IAudioDevice>
public ResultCode GetAudioDeviceService(ServiceCtx context)
{
long appletResourceUserId = context.RequestData.ReadInt64();
MakeObject(context, new IAudioDevice(context.Device.System));
return ResultCode.Success;
}
[Command(4)] // 4.0.0+
// GetAudioDeviceServiceWithRevisionInfo(u32 revision_info, nn::applet::AppletResourceUserId) -> object<nn::audio::detail::IAudioDevice>
public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context)
{
int revisionInfo = context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64();
Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { appletResourceUserId, revisionInfo });
return GetAudioDeviceService(context);
}
ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter);
}
}

View file

@ -28,13 +28,6 @@ namespace Ryujinx.HLE.HOS.SystemState
"zh-Hant"
};
internal static string[] AudioOutputs = new string[]
{
"AudioTvOutput",
"AudioStereoJackOutput",
"AudioBuiltInSpeakerOutput"
};
internal long DesiredKeyboardLayout { get; private set; }
internal SystemLanguage DesiredSystemLanguage { get; private set; }
@ -57,8 +50,6 @@ namespace Ryujinx.HLE.HOS.SystemState
public SystemStateMgr()
{
SetAudioOutputAsBuiltInSpeaker();
Account = new AccountUtils();
Account.AddUser(DefaultUserId, "Player");
@ -94,21 +85,6 @@ namespace Ryujinx.HLE.HOS.SystemState
DesiredRegionCode = (uint)region;
}
public void SetAudioOutputAsTv()
{
ActiveAudioOutput = AudioOutputs[0];
}
public void SetAudioOutputAsStereoJack()
{
ActiveAudioOutput = AudioOutputs[1];
}
public void SetAudioOutputAsBuiltInSpeaker()
{
ActiveAudioOutput = AudioOutputs[2];
}
internal static long GetLanguageCode(int index)
{
if ((uint)index >= LanguageCodes.Length)