From cee712105850ac3385cd0091a923438167433f9f Mon Sep 17 00:00:00 2001
From: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Date: Sat, 8 Apr 2023 01:22:00 +0200
Subject: Move solution and projects to src
---
.../Renderer/Common/AuxiliaryBufferAddresses.cs | 13 +
.../Renderer/Common/BehaviourParameter.cs | 50 +
src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs | 150 +++
src/Ryujinx.Audio/Renderer/Common/EffectType.cs | 58 ++
.../Renderer/Common/MemoryPoolUserState.cs | 43 +
src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs | 28 +
src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs | 33 +
src/Ryujinx.Audio/Renderer/Common/NodeStates.cs | 229 +++++
.../Renderer/Common/PerformanceDetailType.cs | 20 +
.../Renderer/Common/PerformanceEntryType.cs | 11 +
src/Ryujinx.Audio/Renderer/Common/PlayState.cs | 23 +
.../Renderer/Common/ReverbEarlyMode.cs | 33 +
.../Renderer/Common/ReverbLateMode.cs | 38 +
src/Ryujinx.Audio/Renderer/Common/SinkType.cs | 23 +
.../Renderer/Common/UpdateDataHeader.cs | 33 +
.../Renderer/Common/VoiceUpdateState.cs | 104 ++
src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs | 82 ++
.../Renderer/Common/WorkBufferAllocator.cs | 61 ++
src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs | 89 ++
.../Renderer/Device/VirtualDeviceSession.cs | 27 +
.../Device/VirtualDeviceSessionRegistry.cs | 62 ++
src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs | 216 ++++
src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs | 276 ++++++
.../Renderer/Dsp/BiquadFilterHelper.cs | 83 ++
.../Dsp/Command/AdpcmDataSourceCommandVersion1.cs | 75 ++
.../Renderer/Dsp/Command/AuxiliaryBufferCommand.cs | 173 ++++
.../Renderer/Dsp/Command/BiquadFilterCommand.cs | 51 +
.../Renderer/Dsp/Command/CaptureBufferCommand.cs | 136 +++
.../Dsp/Command/CircularBufferSinkCommand.cs | 76 ++
.../Renderer/Dsp/Command/ClearMixBufferCommand.cs | 24 +
.../Renderer/Dsp/Command/CommandList.cs | 155 +++
.../Renderer/Dsp/Command/CommandType.cs | 37 +
.../Renderer/Dsp/Command/CompressorCommand.cs | 173 ++++
.../Renderer/Dsp/Command/CopyMixBufferCommand.cs | 30 +
.../Dsp/Command/DataSourceVersion2Command.cs | 108 ++
.../Renderer/Dsp/Command/DelayCommand.cs | 280 ++++++
.../Dsp/Command/DepopForMixBuffersCommand.cs | 92 ++
.../Renderer/Dsp/Command/DepopPrepareCommand.cs | 57 ++
.../Renderer/Dsp/Command/DeviceSinkCommand.cs | 91 ++
.../Dsp/Command/DownMixSurroundToStereoCommand.cs | 68 ++
.../Dsp/Command/GroupedBiquadFilterCommand.cs | 62 ++
src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs | 20 +
.../Renderer/Dsp/Command/LimiterCommandVersion1.cs | 144 +++
.../Renderer/Dsp/Command/LimiterCommandVersion2.cs | 163 ++++
.../Renderer/Dsp/Command/MixCommand.cs | 137 +++
.../Renderer/Dsp/Command/MixRampCommand.cs | 68 ++
.../Renderer/Dsp/Command/MixRampGroupedCommand.cs | 91 ++
.../Command/PcmFloatDataSourceCommandVersion1.cs | 74 ++
.../Command/PcmInt16DataSourceCommandVersion1.cs | 74 ++
.../Renderer/Dsp/Command/PerformanceCommand.cs | 47 +
.../Renderer/Dsp/Command/Reverb3dCommand.cs | 254 +++++
.../Renderer/Dsp/Command/ReverbCommand.cs | 279 ++++++
.../Renderer/Dsp/Command/UpsampleCommand.cs | 70 ++
.../Renderer/Dsp/Command/VolumeCommand.cs | 137 +++
.../Renderer/Dsp/Command/VolumeRampCommand.cs | 56 ++
src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs | 466 +++++++++
.../Renderer/Dsp/Effect/DecayDelay.cs | 52 +
src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs | 78 ++
.../Renderer/Dsp/Effect/DelayLineReverb3d.cs | 76 ++
.../Dsp/Effect/ExponentialMovingAverage.cs | 26 +
.../Renderer/Dsp/Effect/IDelayLine.cs | 37 +
src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs | 39 +
.../Renderer/Dsp/FloatingPointHelper.cs | 115 +++
src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs | 130 +++
src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs | 604 ++++++++++++
.../Renderer/Dsp/State/AdpcmLoopContext.cs | 12 +
.../Renderer/Dsp/State/AuxiliaryBufferHeader.cs | 74 ++
.../Renderer/Dsp/State/BiquadFilterState.cs | 13 +
.../Renderer/Dsp/State/CompressorState.cs | 51 +
src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs | 67 ++
.../Renderer/Dsp/State/LimiterState.cs | 31 +
.../Renderer/Dsp/State/Reverb3dState.cs | 119 +++
.../Renderer/Dsp/State/ReverbState.cs | 204 ++++
src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs | 192 ++++
.../Parameter/AudioRendererConfiguration.cs | 99 ++
.../Parameter/BehaviourErrorInfoOutStatus.cs | 30 +
.../Renderer/Parameter/BiquadFilterParameter.cs | 34 +
.../Parameter/Effect/AuxiliaryBufferParameter.cs | 84 ++
.../Effect/BiquadFilterEffectParameter.cs | 44 +
.../Parameter/Effect/BufferMixerParameter.cs | 32 +
.../Parameter/Effect/CompressorParameter.cs | 115 +++
.../Renderer/Parameter/Effect/DelayParameter.cs | 101 ++
.../Renderer/Parameter/Effect/LimiterParameter.cs | 138 +++
.../Renderer/Parameter/Effect/LimiterStatistics.cs | 31 +
.../Renderer/Parameter/Effect/Reverb3dParameter.cs | 127 +++
.../Renderer/Parameter/Effect/ReverbParameter.cs | 119 +++
.../Parameter/EffectInParameterVersion1.cs | 97 ++
.../Parameter/EffectInParameterVersion2.cs | 97 ++
.../Renderer/Parameter/EffectOutStatusVersion1.cs | 23 +
.../Renderer/Parameter/EffectOutStatusVersion2.cs | 28 +
.../Renderer/Parameter/EffectResultState.cs | 26 +
.../Renderer/Parameter/EffectState.cs | 18 +
.../Renderer/Parameter/IEffectInParameter.cs | 53 +
.../Renderer/Parameter/IEffectOutStatus.cs | 13 +
.../Renderer/Parameter/MemoryPoolInParameter.cs | 33 +
.../Renderer/Parameter/MemoryPoolOutStatus.cs | 22 +
.../Parameter/MixInParameterDirtyOnlyUpdate.cs | 27 +
.../Renderer/Parameter/MixParameter.cs | 95 ++
.../Performance/PerformanceInParameter.cs | 21 +
.../Parameter/Performance/PerformanceOutStatus.cs | 21 +
.../Renderer/Parameter/RendererInfoOutStatus.cs | 21 +
.../Parameter/Sink/CircularBufferParameter.cs | 62 ++
.../Renderer/Parameter/Sink/DeviceParameter.cs | 58 ++
.../Renderer/Parameter/SinkInParameter.cs | 53 +
.../Renderer/Parameter/SinkOutStatus.cs | 26 +
.../Parameter/SplitterDestinationInParameter.cs | 67 ++
.../Renderer/Parameter/SplitterInParameter.cs | 46 +
.../Parameter/SplitterInParameterHeader.cs | 45 +
.../Parameter/VoiceChannelResourceInParameter.cs | 28 +
.../Renderer/Parameter/VoiceInParameter.cs | 344 +++++++
.../Renderer/Parameter/VoiceOutStatus.cs | 35 +
.../Renderer/Server/AudioRenderSystem.cs | 893 +++++++++++++++++
.../Renderer/Server/AudioRendererManager.cs | 405 ++++++++
.../Renderer/Server/BehaviourContext.cs | 453 +++++++++
src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs | 568 +++++++++++
.../Renderer/Server/CommandGenerator.cs | 1028 ++++++++++++++++++++
.../CommandProcessingTimeEstimatorVersion1.cs | 188 ++++
.../CommandProcessingTimeEstimatorVersion2.cs | 552 +++++++++++
.../CommandProcessingTimeEstimatorVersion3.cs | 756 ++++++++++++++
.../CommandProcessingTimeEstimatorVersion4.cs | 47 +
.../CommandProcessingTimeEstimatorVersion5.cs | 310 ++++++
.../Server/Effect/AuxiliaryBufferEffect.cs | 85 ++
.../Renderer/Server/Effect/BaseEffect.cs | 272 ++++++
.../Renderer/Server/Effect/BiquadFilterEffect.cs | 67 ++
.../Renderer/Server/Effect/BufferMixEffect.cs | 49 +
.../Renderer/Server/Effect/CaptureBufferEffect.cs | 82 ++
.../Renderer/Server/Effect/CompressorEffect.cs | 67 ++
.../Renderer/Server/Effect/DelayEffect.cs | 93 ++
.../Renderer/Server/Effect/EffectContext.cs | 123 +++
.../Renderer/Server/Effect/LimiterEffect.cs | 95 ++
.../Renderer/Server/Effect/Reverb3dEffect.cs | 92 ++
.../Renderer/Server/Effect/ReverbEffect.cs | 95 ++
.../Renderer/Server/Effect/UsageState.cs | 28 +
.../Server/ICommandProcessingTimeEstimator.cs | 40 +
.../Renderer/Server/MemoryPool/AddressInfo.cs | 133 +++
.../Renderer/Server/MemoryPool/MemoryPoolState.cs | 130 +++
.../Renderer/Server/MemoryPool/PoolMapper.cs | 366 +++++++
.../Renderer/Server/Mix/MixContext.cs | 259 +++++
src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs | 313 ++++++
.../Server/Performance/IPerformanceDetailEntry.cs | 52 +
.../Server/Performance/IPerformanceEntry.cs | 46 +
.../Server/Performance/IPerformanceHeader.cs | 80 ++
.../Performance/PerformanceDetailVersion1.cs | 72 ++
.../Performance/PerformanceDetailVersion2.cs | 72 ++
.../Performance/PerformanceEntryAddresses.cs | 56 ++
.../Server/Performance/PerformanceEntryVersion1.cs | 62 ++
.../Server/Performance/PerformanceEntryVersion2.cs | 62 ++
.../Performance/PerformanceFrameHeaderVersion1.cs | 101 ++
.../Performance/PerformanceFrameHeaderVersion2.cs | 117 +++
.../Server/Performance/PerformanceManager.cs | 106 ++
.../Performance/PerformanceManagerGeneric.cs | 304 ++++++
.../Renderer/Server/RendererSystemContext.cs | 48 +
src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs | 102 ++
.../Renderer/Server/Sink/CircularBufferSink.cs | 109 +++
.../Renderer/Server/Sink/DeviceSink.cs | 75 ++
.../Renderer/Server/Sink/SinkContext.cs | 56 ++
.../Renderer/Server/Splitter/SplitterContext.cs | 303 ++++++
.../Server/Splitter/SplitterDestination.cs | 193 ++++
.../Renderer/Server/Splitter/SplitterState.cs | 221 +++++
src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs | 643 ++++++++++++
.../Server/Types/AudioRendererExecutionMode.cs | 19 +
.../Server/Types/AudioRendererRenderingDevice.cs | 24 +
.../Renderer/Server/Types/PlayState.cs | 39 +
.../Server/Upsampler/UpsamplerBufferState.cs | 14 +
.../Renderer/Server/Upsampler/UpsamplerManager.cs | 84 ++
.../Renderer/Server/Upsampler/UpsamplerState.cs | 68 ++
.../Renderer/Server/Voice/VoiceChannelResource.cs | 40 +
.../Renderer/Server/Voice/VoiceContext.cs | 149 +++
.../Renderer/Server/Voice/VoiceState.cs | 699 +++++++++++++
.../Renderer/Server/Voice/WaveBuffer.cs | 104 ++
.../Renderer/Utils/AudioProcessorMemoryManager.cs | 57 ++
src/Ryujinx.Audio/Renderer/Utils/BitArray.cs | 103 ++
.../Renderer/Utils/FileHardwareDevice.cs | 99 ++
src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs | 55 ++
src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs | 71 ++
src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs | 97 ++
.../Renderer/Utils/Math/MatrixHelper.cs | 45 +
src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs | 56 ++
src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs | 171 ++++
.../Renderer/Utils/SpanMemoryManager.cs | 43 +
.../Renderer/Utils/SplitterHardwareDevice.cs | 58 ++
181 files changed, 22350 insertions(+)
create mode 100644 src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/EffectType.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/NodeStates.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/PlayState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/SinkType.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Utils/BitArray.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs
create mode 100644 src/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs
(limited to 'src/Ryujinx.Audio/Renderer')
diff --git a/src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs b/src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs
new file mode 100644
index 00000000..96647405
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs
@@ -0,0 +1,13 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct AuxiliaryBufferAddresses
+ {
+ public ulong SendBufferInfo;
+ public ulong SendBufferInfoBase;
+ public ulong ReturnBufferInfo;
+ public ulong ReturnBufferInfoBase;
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs
new file mode 100644
index 00000000..270f84d5
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs
@@ -0,0 +1,50 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Represents the input parameter for .
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct BehaviourParameter
+ {
+ ///
+ /// The current audio renderer revision in use.
+ ///
+ public int UserRevision;
+
+ ///
+ /// Reserved/padding.
+ ///
+ private uint _padding;
+
+ ///
+ /// The flags given controlling behaviour of the audio renderer
+ ///
+ /// See and .
+ public ulong Flags;
+
+ ///
+ /// Represents an error during .
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct ErrorInfo
+ {
+ ///
+ /// The error code to report.
+ ///
+ public ResultCode ErrorCode;
+
+ ///
+ /// Reserved/padding.
+ ///
+ private uint _padding;
+
+ ///
+ /// Extra information given with the
+ ///
+ /// This is usually used to report a faulting cpu address when a mapping fail.
+ public ulong ExtraErrorInfo;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs b/src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs
new file mode 100644
index 00000000..24a9350f
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs
@@ -0,0 +1,150 @@
+using Ryujinx.Audio.Renderer.Utils;
+using Ryujinx.Common;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Represents a adjacent matrix.
+ ///
+ /// This is used for splitter routing.
+ public class EdgeMatrix
+ {
+ ///
+ /// Backing used for node connections.
+ ///
+ private BitArray _storage;
+
+ ///
+ /// The count of nodes of the current instance.
+ ///
+ private int _nodeCount;
+
+ ///
+ /// Get the required work buffer size memory needed for the .
+ ///
+ /// The count of nodes.
+ /// The size required for the given .
+ public static int GetWorkBufferSize(int nodeCount)
+ {
+ int size = BitUtils.AlignUp(nodeCount * nodeCount, Constants.BufferAlignment);
+
+ return size / Unsafe.SizeOf();
+ }
+
+ ///
+ /// Initializes the instance with backing memory.
+ ///
+ /// The backing memory.
+ /// The count of nodes.
+ public void Initialize(Memory edgeMatrixWorkBuffer, int nodeCount)
+ {
+ Debug.Assert(edgeMatrixWorkBuffer.Length >= GetWorkBufferSize(nodeCount));
+
+ _storage = new BitArray(edgeMatrixWorkBuffer);
+
+ _nodeCount = nodeCount;
+
+ _storage.Reset();
+ }
+
+ ///
+ /// Test if the bit at the given index is set.
+ ///
+ /// A bit index.
+ /// Returns true if the bit at the given index is set
+ public bool Test(int index)
+ {
+ return _storage.Test(index);
+ }
+
+ ///
+ /// Reset all bits in the storage.
+ ///
+ public void Reset()
+ {
+ _storage.Reset();
+ }
+
+ ///
+ /// Reset the bit at the given index.
+ ///
+ /// A bit index.
+ public void Reset(int index)
+ {
+ _storage.Reset(index);
+ }
+
+ ///
+ /// Set the bit at the given index.
+ ///
+ /// A bit index.
+ public void Set(int index)
+ {
+ _storage.Set(index);
+ }
+
+ ///
+ /// Connect a given source to a given destination.
+ ///
+ /// The source index.
+ /// The destination index.
+ public void Connect(int source, int destination)
+ {
+ Debug.Assert(source < _nodeCount);
+ Debug.Assert(destination < _nodeCount);
+
+ _storage.Set(_nodeCount * source + destination);
+ }
+
+ ///
+ /// Check if the given source is connected to the given destination.
+ ///
+ /// The source index.
+ /// The destination index.
+ /// Returns true if the given source is connected to the given destination.
+ public bool Connected(int source, int destination)
+ {
+ Debug.Assert(source < _nodeCount);
+ Debug.Assert(destination < _nodeCount);
+
+ return _storage.Test(_nodeCount * source + destination);
+ }
+
+ ///
+ /// Disconnect a given source from a given destination.
+ ///
+ /// The source index.
+ /// The destination index.
+ public void Disconnect(int source, int destination)
+ {
+ Debug.Assert(source < _nodeCount);
+ Debug.Assert(destination < _nodeCount);
+
+ _storage.Reset(_nodeCount * source + destination);
+ }
+
+ ///
+ /// Remove all edges from a given source.
+ ///
+ /// The source index.
+ public void RemoveEdges(int source)
+ {
+ for (int i = 0; i < _nodeCount; i++)
+ {
+ Disconnect(source, i);
+ }
+ }
+
+ ///
+ /// Get the total node count.
+ ///
+ /// The total node count.
+ public int GetNodeCount()
+ {
+ return _nodeCount;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/EffectType.cs b/src/Ryujinx.Audio/Renderer/Common/EffectType.cs
new file mode 100644
index 00000000..7128db4c
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/EffectType.cs
@@ -0,0 +1,58 @@
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// The type of an effect.
+ ///
+ public enum EffectType : byte
+ {
+ ///
+ /// Invalid effect.
+ ///
+ Invalid,
+
+ ///
+ /// Effect applying additional mixing capability.
+ ///
+ BufferMix,
+
+ ///
+ /// Effect applying custom user effect (via auxiliary buffers).
+ ///
+ AuxiliaryBuffer,
+
+ ///
+ /// Effect applying a delay.
+ ///
+ Delay,
+
+ ///
+ /// Effect applying a reverberation effect via a given preset.
+ ///
+ Reverb,
+
+ ///
+ /// Effect applying a 3D reverberation effect via a given preset.
+ ///
+ Reverb3d,
+
+ ///
+ /// Effect applying a biquad filter.
+ ///
+ BiquadFilter,
+
+ ///
+ /// Effect applying a limiter (DRC).
+ ///
+ Limiter,
+
+ ///
+ /// Effect to capture mixes (via auxiliary buffers).
+ ///
+ CaptureBuffer,
+
+ ///
+ /// Effect applying a compressor filter (DRC).
+ ///
+ Compressor,
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs b/src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs
new file mode 100644
index 00000000..590731c3
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs
@@ -0,0 +1,43 @@
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Represents the state of a memory pool.
+ ///
+ public enum MemoryPoolUserState : uint
+ {
+ ///
+ /// Invalid state.
+ ///
+ Invalid = 0,
+
+ ///
+ /// The memory pool is new. (client side only)
+ ///
+ New = 1,
+
+ ///
+ /// The user asked to detach the memory pool from the .
+ ///
+ RequestDetach = 2,
+
+ ///
+ /// The memory pool is detached from the .
+ ///
+ Detached = 3,
+
+ ///
+ /// The user asked to attach the memory pool to the .
+ ///
+ RequestAttach = 4,
+
+ ///
+ /// The memory pool is attached to the .
+ ///
+ Attached = 5,
+
+ ///
+ /// The memory pool is released. (client side only)
+ ///
+ Released = 6
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs b/src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs
new file mode 100644
index 00000000..a999e3ad
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs
@@ -0,0 +1,28 @@
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Helper for manipulating node ids.
+ ///
+ public static class NodeIdHelper
+ {
+ ///
+ /// Get the type of a node from a given node id.
+ ///
+ /// Id of the node.
+ /// The type of the node.
+ public static NodeIdType GetType(int nodeId)
+ {
+ return (NodeIdType)(nodeId >> 28);
+ }
+
+ ///
+ /// Get the base of a node from a given node id.
+ ///
+ /// Id of the node.
+ /// The base of the node.
+ public static int GetBase(int nodeId)
+ {
+ return (nodeId >> 16) & 0xFFF;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs b/src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs
new file mode 100644
index 00000000..69b58f6b
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs
@@ -0,0 +1,33 @@
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// The type of a node.
+ ///
+ public enum NodeIdType : byte
+ {
+ ///
+ /// Invalid node id.
+ ///
+ Invalid = 0,
+
+ ///
+ /// Voice related node id. (data source, biquad filter, ...)
+ ///
+ Voice = 1,
+
+ ///
+ /// Mix related node id. (mix, effects, splitters, ...)
+ ///
+ Mix = 2,
+
+ ///
+ /// Sink related node id. (device & circular buffer sink)
+ ///
+ Sink = 3,
+
+ ///
+ /// Performance monitoring related node id (performance commands)
+ ///
+ Performance = 15
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/NodeStates.cs b/src/Ryujinx.Audio/Renderer/Common/NodeStates.cs
new file mode 100644
index 00000000..45748d60
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/NodeStates.cs
@@ -0,0 +1,229 @@
+using Ryujinx.Audio.Renderer.Utils;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ public class NodeStates
+ {
+ private class Stack
+ {
+ private Memory _storage;
+ private int _index;
+
+ private int _nodeCount;
+
+ public void Reset(Memory storage, int nodeCount)
+ {
+ Debug.Assert(storage.Length * sizeof(int) >= CalcBufferSize(nodeCount));
+
+ _storage = storage;
+ _index = 0;
+ _nodeCount = nodeCount;
+ }
+
+ public int GetCurrentCount()
+ {
+ return _index;
+ }
+
+ public void Push(int data)
+ {
+ Debug.Assert(_index + 1 <= _nodeCount);
+
+ _storage.Span[_index++] = data;
+ }
+
+ public int Pop()
+ {
+ Debug.Assert(_index > 0);
+
+ return _storage.Span[--_index];
+ }
+
+ public int Top()
+ {
+ return _storage.Span[_index - 1];
+ }
+
+ public static int CalcBufferSize(int nodeCount)
+ {
+ return nodeCount * sizeof(int);
+ }
+ }
+
+ private int _nodeCount;
+ private EdgeMatrix _discovered;
+ private EdgeMatrix _finished;
+ private Memory _resultArray;
+ private Stack _stack;
+ private int _tsortResultIndex;
+
+ private enum NodeState : byte
+ {
+ Unknown,
+ Discovered,
+ Finished
+ }
+
+ public NodeStates()
+ {
+ _stack = new Stack();
+ _discovered = new EdgeMatrix();
+ _finished = new EdgeMatrix();
+ }
+
+ public static int GetWorkBufferSize(int nodeCount)
+ {
+ return Stack.CalcBufferSize(nodeCount * nodeCount) + 0xC * nodeCount + 2 * EdgeMatrix.GetWorkBufferSize(nodeCount);
+ }
+
+ public void Initialize(Memory nodeStatesWorkBuffer, int nodeCount)
+ {
+ int workBufferSize = GetWorkBufferSize(nodeCount);
+
+ Debug.Assert(nodeStatesWorkBuffer.Length >= workBufferSize);
+
+ _nodeCount = nodeCount;
+
+ int edgeMatrixWorkBufferSize = EdgeMatrix.GetWorkBufferSize(nodeCount);
+
+ _discovered.Initialize(nodeStatesWorkBuffer.Slice(0, edgeMatrixWorkBufferSize), nodeCount);
+ _finished.Initialize(nodeStatesWorkBuffer.Slice(edgeMatrixWorkBufferSize, edgeMatrixWorkBufferSize), nodeCount);
+
+ nodeStatesWorkBuffer = nodeStatesWorkBuffer.Slice(edgeMatrixWorkBufferSize * 2);
+
+ _resultArray = SpanMemoryManager.Cast(nodeStatesWorkBuffer.Slice(0, sizeof(int) * nodeCount));
+
+ nodeStatesWorkBuffer = nodeStatesWorkBuffer.Slice(sizeof(int) * nodeCount);
+
+ Memory stackWorkBuffer = SpanMemoryManager.Cast(nodeStatesWorkBuffer.Slice(0, Stack.CalcBufferSize(nodeCount * nodeCount)));
+
+ _stack.Reset(stackWorkBuffer, nodeCount * nodeCount);
+ }
+
+ private void Reset()
+ {
+ _discovered.Reset();
+ _finished.Reset();
+ _tsortResultIndex = 0;
+ _resultArray.Span.Fill(-1);
+ }
+
+ private NodeState GetState(int index)
+ {
+ Debug.Assert(index < _nodeCount);
+
+ if (_discovered.Test(index))
+ {
+ Debug.Assert(!_finished.Test(index));
+
+ return NodeState.Discovered;
+ }
+ else if (_finished.Test(index))
+ {
+ Debug.Assert(!_discovered.Test(index));
+
+ return NodeState.Finished;
+ }
+
+ return NodeState.Unknown;
+ }
+
+ private void SetState(int index, NodeState state)
+ {
+ switch (state)
+ {
+ case NodeState.Unknown:
+ _discovered.Reset(index);
+ _finished.Reset(index);
+ break;
+ case NodeState.Discovered:
+ _discovered.Set(index);
+ _finished.Reset(index);
+ break;
+ case NodeState.Finished:
+ _finished.Set(index);
+ _discovered.Reset(index);
+ break;
+ }
+ }
+
+ private void PushTsortResult(int index)
+ {
+ Debug.Assert(index < _nodeCount);
+
+ _resultArray.Span[_tsortResultIndex++] = index;
+ }
+
+ public ReadOnlySpan GetTsortResult()
+ {
+ return _resultArray.Span.Slice(0, _tsortResultIndex);
+ }
+
+ public bool Sort(EdgeMatrix edgeMatrix)
+ {
+ Reset();
+
+ if (_nodeCount <= 0)
+ {
+ return true;
+ }
+
+ for (int i = 0; i < _nodeCount; i++)
+ {
+ if (GetState(i) == NodeState.Unknown)
+ {
+ _stack.Push(i);
+ }
+
+ while (_stack.GetCurrentCount() > 0)
+ {
+ int topIndex = _stack.Top();
+
+ NodeState topState = GetState(topIndex);
+
+ if (topState == NodeState.Discovered)
+ {
+ SetState(topIndex, NodeState.Finished);
+ PushTsortResult(topIndex);
+ _stack.Pop();
+ }
+ else if (topState == NodeState.Finished)
+ {
+ _stack.Pop();
+ }
+ else
+ {
+ if (topState == NodeState.Unknown)
+ {
+ SetState(topIndex, NodeState.Discovered);
+ }
+
+ for (int j = 0; j < edgeMatrix.GetNodeCount(); j++)
+ {
+ if (edgeMatrix.Connected(topIndex, j))
+ {
+ NodeState jState = GetState(j);
+
+ if (jState == NodeState.Unknown)
+ {
+ _stack.Push(j);
+ }
+ // Found a loop, reset and propagate rejection.
+ else if (jState == NodeState.Discovered)
+ {
+ Reset();
+
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs b/src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
new file mode 100644
index 00000000..805d5518
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Audio.Renderer.Common
+{
+ public enum PerformanceDetailType : byte
+ {
+ Unknown,
+ PcmInt16,
+ Adpcm,
+ VolumeRamp,
+ BiquadFilter,
+ Mix,
+ Delay,
+ Aux,
+ Reverb,
+ Reverb3d,
+ PcmFloat,
+ Limiter,
+ CaptureBuffer,
+ Compressor
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs b/src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs
new file mode 100644
index 00000000..bde72aae
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Audio.Renderer.Common
+{
+ public enum PerformanceEntryType : byte
+ {
+ Invalid,
+ Voice,
+ SubMix,
+ FinalMix,
+ Sink
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/PlayState.cs b/src/Ryujinx.Audio/Renderer/Common/PlayState.cs
new file mode 100644
index 00000000..4a6929e0
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/PlayState.cs
@@ -0,0 +1,23 @@
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Common play state.
+ ///
+ public enum PlayState : byte
+ {
+ ///
+ /// The user request the voice to be started.
+ ///
+ Start,
+
+ ///
+ /// The user request the voice to be stopped.
+ ///
+ Stop,
+
+ ///
+ /// The user request the voice to be paused.
+ ///
+ Pause
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs b/src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs
new file mode 100644
index 00000000..aa768562
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs
@@ -0,0 +1,33 @@
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Early reverb reflection.
+ ///
+ public enum ReverbEarlyMode : uint
+ {
+ ///
+ /// Room early reflection. (small acoustic space, fast reflection)
+ ///
+ Room,
+
+ ///
+ /// Chamber early reflection. (bigger than 's acoustic space, short reflection)
+ ///
+ Chamber,
+
+ ///
+ /// Hall early reflection. (large acoustic space, warm reflection)
+ ///
+ Hall,
+
+ ///
+ /// Cathedral early reflection. (very large acoustic space, pronounced bright reflection)
+ ///
+ Cathedral,
+
+ ///
+ /// No early reflection.
+ ///
+ Disabled
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs b/src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs
new file mode 100644
index 00000000..8aa88165
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs
@@ -0,0 +1,38 @@
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Late reverb reflection.
+ ///
+ public enum ReverbLateMode : uint
+ {
+ ///
+ /// Room late reflection. (small acoustic space, fast reflection)
+ ///
+ Room,
+
+ ///
+ /// Hall late reflection. (large acoustic space, warm reflection)
+ ///
+ Hall,
+
+ ///
+ /// Classic plate late reflection. (clean distinctive reverb)
+ ///
+ Plate,
+
+ ///
+ /// Cathedral late reflection. (very large acoustic space, pronounced bright reflection)
+ ///
+ Cathedral,
+
+ ///
+ /// Do not apply any delay. (max delay)
+ ///
+ NoDelay,
+
+ ///
+ /// Max delay. (used for delay line limits)
+ ///
+ Limit = NoDelay
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/SinkType.cs b/src/Ryujinx.Audio/Renderer/Common/SinkType.cs
new file mode 100644
index 00000000..2e17201e
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/SinkType.cs
@@ -0,0 +1,23 @@
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// The type of a sink.
+ ///
+ public enum SinkType : byte
+ {
+ ///
+ /// The sink is in an invalid state.
+ ///
+ Invalid,
+
+ ///
+ /// The sink is a device.
+ ///
+ Device,
+
+ ///
+ /// The sink is a circular buffer.
+ ///
+ CircularBuffer
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs
new file mode 100644
index 00000000..70dbfa94
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs
@@ -0,0 +1,33 @@
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Update data header used for input and output of .
+ ///
+ public struct UpdateDataHeader
+ {
+ public int Revision;
+ public uint BehaviourSize;
+ public uint MemoryPoolsSize;
+ public uint VoicesSize;
+ public uint VoiceResourcesSize;
+ public uint EffectsSize;
+ public uint MixesSize;
+ public uint SinksSize;
+ public uint PerformanceBufferSize;
+ public uint Unknown24;
+ public uint RenderInfoSize;
+
+ private unsafe fixed int _reserved[4];
+
+ public uint TotalSize;
+
+ public void Initialize(int revision)
+ {
+ Revision = revision;
+
+ TotalSize = (uint)Unsafe.SizeOf();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
new file mode 100644
index 00000000..f52c2f4c
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs
@@ -0,0 +1,104 @@
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// Represent the update state of a voice.
+ ///
+ /// This is shared between the server and audio processor.
+ [StructLayout(LayoutKind.Sequential, Pack = Align)]
+ public struct VoiceUpdateState
+ {
+ public const int Align = 0x10;
+ public const int BiquadStateOffset = 0x0;
+ public const int BiquadStateSize = 0x10;
+
+ ///
+ /// The state of the biquad filters of this voice.
+ ///
+ public Array2 BiquadFilterState;
+
+ ///
+ /// The total amount of samples that was played.
+ ///
+ /// This is reset to 0 when a finishes playing and is set.
+ /// This is reset to 0 when looping while is set.
+ public ulong PlayedSampleCount;
+
+ ///
+ /// The current sample offset in the pointed by .
+ ///
+ public int Offset;
+
+ ///
+ /// The current index of the in use.
+ ///
+ public uint WaveBufferIndex;
+
+ private WaveBufferValidArray _isWaveBufferValid;
+
+ ///
+ /// The total amount of consumed.
+ ///
+ public uint WaveBufferConsumed;
+
+ ///
+ /// Pitch used for Sample Rate Conversion.
+ ///
+ public Array8 Pitch;
+
+ public float Fraction;
+
+ ///
+ /// The ADPCM loop context when is in use.
+ ///
+ public AdpcmLoopContext LoopContext;
+
+ ///
+ /// The last samples after a mix ramp.
+ ///
+ /// This is used for depop (to perform voice drop).
+ public Array24 LastSamples;
+
+ ///
+ /// The current count of loop performed.
+ ///
+ public int LoopCount;
+
+ [StructLayout(LayoutKind.Sequential, Size = 1 * Constants.VoiceWaveBufferCount, Pack = 1)]
+ private struct WaveBufferValidArray { }
+
+ ///
+ /// Contains information of validity.
+ ///
+ public Span IsWaveBufferValid => SpanHelpers.AsSpan(ref _isWaveBufferValid);
+
+ ///
+ /// Mark the current as played and switch to the next one.
+ ///
+ /// The current
+ /// The wavebuffer index.
+ /// The amount of wavebuffers consumed.
+ /// The total count of sample played.
+ public void MarkEndOfBufferWaveBufferProcessing(ref WaveBuffer waveBuffer, ref int waveBufferIndex, ref uint waveBufferConsumed, ref ulong playedSampleCount)
+ {
+ IsWaveBufferValid[waveBufferIndex++] = false;
+ LoopCount = 0;
+ waveBufferConsumed++;
+
+ if (waveBufferIndex >= Constants.VoiceWaveBufferCount)
+ {
+ waveBufferIndex = 0;
+ }
+
+ if (waveBuffer.IsEndOfStream)
+ {
+ playedSampleCount = 0;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs b/src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs
new file mode 100644
index 00000000..0d00e838
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs
@@ -0,0 +1,82 @@
+using System.Runtime.InteropServices;
+
+using DspAddr = System.UInt64;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ ///
+ /// A wavebuffer used for data source commands.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct WaveBuffer
+ {
+ ///
+ /// The DSP address of the sample data of the wavebuffer.
+ ///
+ public DspAddr Buffer;
+
+ ///
+ /// The DSP address of the context of the wavebuffer.
+ ///
+ /// Only used by .
+ public DspAddr Context;
+
+ ///
+ /// The size of the sample buffer data.
+ ///
+ public uint BufferSize;
+
+ ///
+ /// The size of the context buffer.
+ ///
+ public uint ContextSize;
+
+ ///
+ /// First sample to play on the wavebuffer.
+ ///
+ public uint StartSampleOffset;
+
+ ///
+ /// Last sample to play on the wavebuffer.
+ ///
+ public uint EndSampleOffset;
+
+ ///
+ /// First sample to play when looping the wavebuffer.
+ ///
+ ///
+ /// If or is equal to zero,, it will default to and .
+ ///
+ public uint LoopStartSampleOffset;
+
+ ///
+ /// Last sample to play when looping the wavebuffer.
+ ///
+ ///
+ /// If or is equal to zero, it will default to and .
+ ///
+ public uint LoopEndSampleOffset;
+
+ ///
+ /// The max loop count.
+ ///
+ public int LoopCount;
+
+ ///
+ /// Set to true if the wavebuffer is looping.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool Looping;
+
+ ///
+ /// Set to true if the wavebuffer is the end of stream.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool IsEndOfStream;
+
+ ///
+ /// Padding/Reserved.
+ ///
+ private ushort _padding;
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs b/src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs
new file mode 100644
index 00000000..f35dbec7
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs
@@ -0,0 +1,61 @@
+using Ryujinx.Audio.Renderer.Utils;
+using Ryujinx.Common;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Common
+{
+ public class WorkBufferAllocator
+ {
+ public Memory BackingMemory { get; }
+
+ public ulong Offset { get; private set; }
+
+ public WorkBufferAllocator(Memory backingMemory)
+ {
+ BackingMemory = backingMemory;
+ }
+
+ public Memory Allocate(ulong size, int align)
+ {
+ Debug.Assert(align != 0);
+
+ if (size != 0)
+ {
+ ulong alignedOffset = BitUtils.AlignUp(Offset, (ulong)align);
+
+ if (alignedOffset + size <= (ulong)BackingMemory.Length)
+ {
+ Memory result = BackingMemory.Slice((int)alignedOffset, (int)size);
+
+ Offset = alignedOffset + size;
+
+ // Clear the memory to be sure that is does not contain any garbage.
+ result.Span.Fill(0);
+
+ return result;
+ }
+ }
+
+ return Memory.Empty;
+ }
+
+ public Memory Allocate(ulong count, int align) where T : unmanaged
+ {
+ Memory allocatedMemory = Allocate((ulong)Unsafe.SizeOf() * count, align);
+
+ if (allocatedMemory.IsEmpty)
+ {
+ return Memory.Empty;
+ }
+
+ return SpanMemoryManager.Cast(allocatedMemory);
+ }
+
+ public static ulong GetTargetSize(ulong currentSize, ulong count, int align) where T : unmanaged
+ {
+ return BitUtils.AlignUp(currentSize, (ulong)align) + (ulong)Unsafe.SizeOf() * count;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs b/src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs
new file mode 100644
index 00000000..2fa030a8
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs
@@ -0,0 +1,89 @@
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Device
+{
+ ///
+ /// Represents a virtual device used by IAudioDevice.
+ ///
+ public class VirtualDevice
+ {
+ ///
+ /// All the defined virtual devices.
+ ///
+ public static readonly VirtualDevice[] Devices = new VirtualDevice[5]
+ {
+ 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),
+ };
+
+ ///
+ /// The name of the .
+ ///
+ public string Name { get; }
+
+ ///
+ /// The count of channels supported by the .
+ ///
+ public uint ChannelCount { get; }
+
+ ///
+ /// The system master volume of the .
+ ///
+ public float MasterVolume { get; private set; }
+
+ ///
+ /// Define if the is provided by an external interface.
+ ///
+ public bool IsExternalOutput { get; }
+
+ ///
+ /// Create a new instance.
+ ///
+ /// The name of the .
+ /// The count of channels supported by the .
+ /// Indicate if the is provided by an external interface.
+ private VirtualDevice(string name, uint channelCount, bool isExternalOutput)
+ {
+ Name = name;
+ ChannelCount = channelCount;
+ IsExternalOutput = isExternalOutput;
+ }
+
+ ///
+ /// Update the master volume of the .
+ ///
+ /// The new master volume.
+ public void UpdateMasterVolume(float volume)
+ {
+ Debug.Assert(volume >= 0.0f && volume <= 1.0f);
+
+ MasterVolume = volume;
+ }
+
+ ///
+ /// Check if the is a usb device.
+ ///
+ /// Returns true if the is a usb device.
+ public bool IsUsbDevice()
+ {
+ return Name.Equals("AudioUsbDeviceOutput");
+ }
+
+ ///
+ /// Get the output device name of the .
+ ///
+ /// The output device name of the .
+ public string GetOutputDeviceName()
+ {
+ if (IsExternalOutput)
+ {
+ return "AudioExternalOutput";
+ }
+
+ return Name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs
new file mode 100644
index 00000000..db35d26d
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs
@@ -0,0 +1,27 @@
+namespace Ryujinx.Audio.Renderer.Device
+{
+ ///
+ /// Represents a virtual device session used by IAudioDevice.
+ ///
+ public class VirtualDeviceSession
+ {
+ ///
+ /// The associated to this session.
+ ///
+ public VirtualDevice Device { get; }
+
+ ///
+ /// The user volume of this session.
+ ///
+ public float Volume { get; set; }
+
+ ///
+ /// Create a new instance.
+ ///
+ /// The associated to this session.
+ public VirtualDeviceSession(VirtualDevice virtualDevice)
+ {
+ Device = virtualDevice;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs
new file mode 100644
index 00000000..927e45ad
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Audio.Renderer.Device
+{
+ ///
+ /// Represent an instance containing a registry of .
+ ///
+ public class VirtualDeviceSessionRegistry
+ {
+ ///
+ /// The session registry, used to store the sessions of a given AppletResourceId.
+ ///
+ private Dictionary _sessionsRegistry = new Dictionary();
+
+ ///
+ /// The default .
+ ///
+ /// This is used when the USB device is the default one on older revision.
+ public VirtualDevice DefaultDevice => VirtualDevice.Devices[0];
+
+ ///
+ /// The current active .
+ ///
+ // TODO: make this configurable
+ public VirtualDevice ActiveDevice = VirtualDevice.Devices[2];
+
+ ///
+ /// Get the associated from an AppletResourceId.
+ ///
+ /// The AppletResourceId used.
+ /// The associated from an AppletResourceId.
+ public VirtualDeviceSession[] GetSessionByAppletResourceId(ulong resourceAppletId)
+ {
+ if (_sessionsRegistry.TryGetValue(resourceAppletId, out VirtualDeviceSession[] result))
+ {
+ return result;
+ }
+
+ result = CreateSessionsFromBehaviourContext();
+
+ _sessionsRegistry.Add(resourceAppletId, result);
+
+ return result;
+ }
+
+ ///
+ /// Create a new array of sessions for each .
+ ///
+ /// A new array of sessions for each .
+ private static VirtualDeviceSession[] CreateSessionsFromBehaviourContext()
+ {
+ VirtualDeviceSession[] virtualDeviceSession = new VirtualDeviceSession[VirtualDevice.Devices.Length];
+
+ for (int i = 0; i < virtualDeviceSession.Length; i++)
+ {
+ virtualDeviceSession[i] = new VirtualDeviceSession(VirtualDevice.Devices[i]);
+ }
+
+ return virtualDeviceSession;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs
new file mode 100644
index 00000000..2680dcb1
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs
@@ -0,0 +1,216 @@
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Common.Logging;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp
+{
+ public static class AdpcmHelper
+ {
+ private const int FixedPointPrecision = 11;
+ private const int SamplesPerFrame = 14;
+ private const int NibblesPerFrame = SamplesPerFrame + 2;
+ private const int BytesPerFrame = 8;
+ private const int BitsPerFrame = BytesPerFrame * 8;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint GetAdpcmDataSize(int sampleCount)
+ {
+ Debug.Assert(sampleCount >= 0);
+
+ int frames = sampleCount / SamplesPerFrame;
+ int extraSize = 0;
+
+ if ((sampleCount % SamplesPerFrame) != 0)
+ {
+ extraSize = (sampleCount % SamplesPerFrame) / 2 + 1 + (sampleCount % 2);
+ }
+
+ return (uint)(BytesPerFrame * frames + extraSize);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetAdpcmOffsetFromSampleOffset(int sampleOffset)
+ {
+ Debug.Assert(sampleOffset >= 0);
+
+ return GetNibblesFromSampleCount(sampleOffset) / 2;
+ }
+
+ public static int NibbleToSample(int nibble)
+ {
+ int frames = nibble / NibblesPerFrame;
+ int extraNibbles = nibble % NibblesPerFrame;
+ int samples = SamplesPerFrame * frames;
+
+ return samples + extraNibbles - 2;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetNibblesFromSampleCount(int sampleCount)
+ {
+ byte headerSize = 0;
+
+ if ((sampleCount % SamplesPerFrame) != 0)
+ {
+ headerSize = 2;
+ }
+
+ return sampleCount % SamplesPerFrame + NibblesPerFrame * (sampleCount / SamplesPerFrame) + headerSize;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static short Saturate(int value)
+ {
+ if (value > short.MaxValue)
+ value = short.MaxValue;
+
+ if (value < short.MinValue)
+ value = short.MinValue;
+
+ return (short)value;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static short GetCoefficientAtIndex(ReadOnlySpan coefficients, int index)
+ {
+ if ((uint)index > (uint)coefficients.Length)
+ {
+ Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
+
+ return 0;
+ }
+
+ return coefficients[index];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int Decode(Span output, ReadOnlySpan input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan coefficients, ref AdpcmLoopContext loopContext)
+ {
+ if (input.IsEmpty || endSampleOffset < startSampleOffset)
+ {
+ return 0;
+ }
+
+ byte predScale = (byte)loopContext.PredScale;
+ byte scale = (byte)(predScale & 0xF);
+ byte coefficientIndex = (byte)((predScale >> 4) & 0xF);
+ short history0 = loopContext.History0;
+ short history1 = loopContext.History1;
+ short coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 0);
+ short coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
+
+ int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset);
+ int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset);
+ int remaining = decodedCount;
+ int outputBufferIndex = 0;
+ int inputIndex = 0;
+
+ ReadOnlySpan targetInput;
+
+ targetInput = input.Slice(nibbles / 2);
+
+ while (remaining > 0)
+ {
+ int samplesCount;
+
+ if (((uint)nibbles % NibblesPerFrame) == 0)
+ {
+ predScale = targetInput[inputIndex++];
+
+ scale = (byte)(predScale & 0xF);
+
+ coefficientIndex = (byte)((predScale >> 4) & 0xF);
+
+ coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2);
+ coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1);
+
+ nibbles += 2;
+
+ samplesCount = Math.Min(remaining, SamplesPerFrame);
+ }
+ else
+ {
+ samplesCount = 1;
+ }
+
+ int scaleFixedPoint = FixedPointHelper.ToFixed(1.0f, FixedPointPrecision) << scale;
+
+ if (samplesCount < SamplesPerFrame)
+ {
+ for (int i = 0; i < samplesCount; i++)
+ {
+ int value = targetInput[inputIndex];
+
+ int sample;
+
+ if ((nibbles & 1) != 0)
+ {
+ sample = (value << 28) >> 28;
+
+ inputIndex++;
+ }
+ else
+ {
+ sample = (value << 24) >> 28;
+ }
+
+ nibbles++;
+
+ int prediction = coefficient0 * history0 + coefficient1 * history1;
+
+ sample = FixedPointHelper.RoundUpAndToInt(sample * scaleFixedPoint + prediction, FixedPointPrecision);
+
+ short saturatedSample = Saturate(sample);
+
+ history1 = history0;
+ history0 = saturatedSample;
+
+ output[outputBufferIndex++] = saturatedSample;
+
+ remaining--;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < SamplesPerFrame / 2; i++)
+ {
+ int value = targetInput[inputIndex];
+
+ int sample0;
+ int sample1;
+
+ sample0 = (value << 24) >> 28;
+ sample1 = (value << 28) >> 28;
+
+ inputIndex++;
+
+ int prediction0 = coefficient0 * history0 + coefficient1 * history1;
+ sample0 = FixedPointHelper.RoundUpAndToInt(sample0 * scaleFixedPoint + prediction0, FixedPointPrecision);
+ short saturatedSample0 = Saturate(sample0);
+
+ int prediction1 = coefficient0 * saturatedSample0 + coefficient1 * history0;
+ sample1 = FixedPointHelper.RoundUpAndToInt(sample1 * scaleFixedPoint + prediction1, FixedPointPrecision);
+ short saturatedSample1 = Saturate(sample1);
+
+ history1 = saturatedSample0;
+ history0 = saturatedSample1;
+
+ output[outputBufferIndex++] = saturatedSample0;
+ output[outputBufferIndex++] = saturatedSample1;
+ }
+
+ nibbles += SamplesPerFrame;
+ remaining -= SamplesPerFrame;
+ }
+ }
+
+ loopContext.PredScale = predScale;
+ loopContext.History0 = history0;
+ loopContext.History1 = history1;
+
+ return decodedCount;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs b/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs
new file mode 100644
index 00000000..7bd0443c
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs
@@ -0,0 +1,276 @@
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Renderer.Dsp.Command;
+using Ryujinx.Audio.Renderer.Utils;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using System;
+using System.Threading;
+
+namespace Ryujinx.Audio.Renderer.Dsp
+{
+ public class AudioProcessor : IDisposable
+ {
+ private const int MaxBufferedFrames = 5;
+ private const int TargetBufferedFrames = 3;
+
+ private enum MailboxMessage : uint
+ {
+ Start,
+ Stop,
+ RenderStart,
+ RenderEnd
+ }
+
+ private class RendererSession
+ {
+ public CommandList CommandList;
+ public int RenderingLimit;
+ public ulong AppletResourceId;
+ }
+
+ private Mailbox _mailbox;
+ private RendererSession[] _sessionCommandList;
+ private Thread _workerThread;
+
+ public IHardwareDevice[] OutputDevices { get; private set; }
+
+ private long _lastTime;
+ private long _playbackEnds;
+ private ManualResetEvent _event;
+
+ private ManualResetEvent _pauseEvent;
+
+ public AudioProcessor()
+ {
+ _event = new ManualResetEvent(false);
+ }
+
+ private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
+ {
+ // Get the real device driver (In case the compat layer is on top of it).
+ deviceDriver = deviceDriver.GetRealDeviceDriver();
+
+ if (deviceDriver.SupportsChannelCount(6))
+ {
+ return 6;
+ }
+ else
+ {
+ // NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible.
+ return 2;
+ }
+ }
+
+ public void Start(IHardwareDeviceDriver deviceDriver, float volume)
+ {
+ OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
+
+ // TODO: Before enabling this, we need up-mixing from stereo to 5.1.
+ // uint channelCount = GetHardwareChannelCount(deviceDriver);
+ uint channelCount = 2;
+
+ for (int i = 0; i < OutputDevices.Length; i++)
+ {
+ // TODO: Don't hardcode sample rate.
+ OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate, volume);
+ }
+
+ _mailbox = new Mailbox();
+ _sessionCommandList = new RendererSession[Constants.AudioRendererSessionCountMax];
+ _event.Reset();
+ _lastTime = PerformanceCounter.ElapsedNanoseconds;
+ _pauseEvent = deviceDriver.GetPauseEvent();
+
+ StartThread();
+
+ _mailbox.SendMessage(MailboxMessage.Start);
+
+ if (_mailbox.ReceiveResponse() != MailboxMessage.Start)
+ {
+ throw new InvalidOperationException("Audio Processor Start response was invalid!");
+ }
+ }
+
+ public void Stop()
+ {
+ _mailbox.SendMessage(MailboxMessage.Stop);
+
+ if (_mailbox.ReceiveResponse() != MailboxMessage.Stop)
+ {
+ throw new InvalidOperationException("Audio Processor Stop response was invalid!");
+ }
+
+ foreach (IHardwareDevice device in OutputDevices)
+ {
+ device.Dispose();
+ }
+ }
+
+ public void Send(int sessionId, CommandList commands, int renderingLimit, ulong appletResourceId)
+ {
+ _sessionCommandList[sessionId] = new RendererSession
+ {
+ CommandList = commands,
+ RenderingLimit = renderingLimit,
+ AppletResourceId = appletResourceId
+ };
+ }
+
+ public bool HasRemainingCommands(int sessionId)
+ {
+ return _sessionCommandList[sessionId] != null;
+ }
+
+ public void Signal()
+ {
+ _mailbox.SendMessage(MailboxMessage.RenderStart);
+ }
+
+ public void Wait()
+ {
+ if (_mailbox.ReceiveResponse() != MailboxMessage.RenderEnd)
+ {
+ throw new InvalidOperationException("Audio Processor Wait response was invalid!");
+ }
+
+ long increment = Constants.AudioProcessorMaxUpdateTimeTarget;
+
+ long timeNow = PerformanceCounter.ElapsedNanoseconds;
+
+ if (timeNow > _playbackEnds)
+ {
+ // Playback has restarted.
+ _playbackEnds = timeNow;
+ }
+
+ _playbackEnds += increment;
+
+ // The number of frames we are behind where the timer says we should be.
+ long framesBehind = (timeNow - _lastTime) / increment;
+
+ // The number of frames yet to play on the backend.
+ long bufferedFrames = (_playbackEnds - timeNow) / increment + framesBehind;
+
+ // If we've entered a situation where a lot of buffers will be queued on the backend,
+ // Skip some audio frames so that playback can catch up.
+ if (bufferedFrames > MaxBufferedFrames)
+ {
+ // Skip a few frames so that we're not too far behind. (the target number of frames)
+ _lastTime += increment * (bufferedFrames - TargetBufferedFrames);
+ }
+
+ while (timeNow < _lastTime + increment)
+ {
+ _event.WaitOne(1);
+
+ timeNow = PerformanceCounter.ElapsedNanoseconds;
+ }
+
+ _lastTime += increment;
+ }
+
+ private void StartThread()
+ {
+ _workerThread = new Thread(Work)
+ {
+ Name = "AudioProcessor.Worker"
+ };
+
+ _workerThread.Start();
+ }
+
+ private void Work()
+ {
+ if (_mailbox.ReceiveMessage() != MailboxMessage.Start)
+ {
+ throw new InvalidOperationException("Audio Processor Start message was invalid!");
+ }
+
+ _mailbox.SendResponse(MailboxMessage.Start);
+ _mailbox.SendResponse(MailboxMessage.RenderEnd);
+
+ Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio processor");
+
+ while (true)
+ {
+ _pauseEvent?.WaitOne();
+
+ MailboxMessage message = _mailbox.ReceiveMessage();
+
+ if (message == MailboxMessage.Stop)
+ {
+ break;
+ }
+
+ if (message == MailboxMessage.RenderStart)
+ {
+ long startTicks = PerformanceCounter.ElapsedNanoseconds;
+
+ for (int i = 0; i < _sessionCommandList.Length; i++)
+ {
+ if (_sessionCommandList[i] != null)
+ {
+ _sessionCommandList[i].CommandList.Process(OutputDevices[i]);
+ _sessionCommandList[i].CommandList.Dispose();
+ _sessionCommandList[i] = null;
+ }
+ }
+
+ long endTicks = PerformanceCounter.ElapsedNanoseconds;
+
+ long elapsedTime = endTicks - startTicks;
+
+ if (Constants.AudioProcessorMaxUpdateTime < elapsedTime)
+ {
+ Logger.Debug?.Print(LogClass.AudioRenderer, $"DSP too slow (exceeded by {elapsedTime - Constants.AudioProcessorMaxUpdateTime}ns)");
+ }
+
+ _mailbox.SendResponse(MailboxMessage.RenderEnd);
+ }
+ }
+
+ Logger.Info?.Print(LogClass.AudioRenderer, "Stopping audio processor");
+ _mailbox.SendResponse(MailboxMessage.Stop);
+ }
+
+ public float GetVolume()
+ {
+ if (OutputDevices != null)
+ {
+ foreach (IHardwareDevice outputDevice in OutputDevices)
+ {
+ if (outputDevice != null)
+ {
+ return outputDevice.GetVolume();
+ }
+ }
+ }
+
+ return 0f;
+ }
+
+ public void SetVolume(float volume)
+ {
+ if (OutputDevices != null)
+ {
+ foreach (IHardwareDevice outputDevice in OutputDevices)
+ {
+ outputDevice?.SetVolume(volume);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _event.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
new file mode 100644
index 00000000..98460ff1
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs
@@ -0,0 +1,83 @@
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp
+{
+ public static class BiquadFilterHelper
+ {
+ private const int FixedPointPrecisionForParameter = 14;
+
+ ///
+ /// Apply a single biquad filter.
+ ///
+ /// This is implemented with a direct form 2.
+ /// The biquad filter parameter
+ /// The biquad filter state
+ /// The output buffer to write the result
+ /// The input buffer to write the result
+ /// The count of samples to process
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount)
+ {
+ float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
+ float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
+ float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
+ float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a0 + state.State0;
+
+ state.State0 = input * a1 + output * b1 + state.State1;
+ state.State1 = input * a2 + output * b2;
+
+ outputBuffer[i] = output;
+ }
+ }
+
+ ///
+ /// Apply multiple biquad filter.
+ ///
+ /// This is implemented with a direct form 1.
+ /// The biquad filter parameter
+ /// The biquad filter state
+ /// The output buffer to write the result
+ /// The input buffer to write the result
+ /// The count of samples to process
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ProcessBiquadFilter(ReadOnlySpan parameters, Span states, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount)
+ {
+ for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++)
+ {
+ BiquadFilterParameter parameter = parameters[stageIndex];
+
+ ref BiquadFilterState state = ref states[stageIndex];
+
+ float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter);
+ float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter);
+ float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter);
+
+ float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter);
+ float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i];
+ float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2;
+
+ state.State1 = state.State0;
+ state.State0 = input;
+ state.State3 = state.State2;
+ state.State2 = output;
+
+ outputBuffer[i] = output;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs
new file mode 100644
index 00000000..1fe6069f
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs
@@ -0,0 +1,75 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using System;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class AdpcmDataSourceCommandVersion1 : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.AdpcmDataSourceVersion1;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort OutputBufferIndex { get; }
+ public uint SampleRate { get; }
+
+ public float Pitch { get; }
+
+ public WaveBuffer[] WaveBuffers { get; }
+
+ public Memory State { get; }
+
+ public ulong AdpcmParameter { get; }
+ public ulong AdpcmParameterSize { get; }
+
+ public DecodingBehaviour DecodingBehaviour { get; }
+
+ public AdpcmDataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory state, ushort outputBufferIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ OutputBufferIndex = outputBufferIndex;
+ SampleRate = serverState.SampleRate;
+ Pitch = serverState.Pitch;
+
+ WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
+
+ for (int i = 0; i < WaveBuffers.Length; i++)
+ {
+ ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i];
+
+ WaveBuffers[i] = voiceWaveBuffer.ToCommon(1);
+ }
+
+ AdpcmParameter = serverState.DataSourceStateAddressInfo.GetReference(true);
+ AdpcmParameterSize = serverState.DataSourceStateAddressInfo.Size;
+ State = state;
+ DecodingBehaviour = serverState.DecodingBehaviour;
+ }
+
+ public void Process(CommandList context)
+ {
+ Span outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation
+ {
+ SourceSampleRate = SampleRate,
+ SampleFormat = SampleFormat.Adpcm,
+ Pitch = Pitch,
+ DecodingBehaviour = DecodingBehaviour,
+ ExtraParameter = AdpcmParameter,
+ ExtraParameterSize = AdpcmParameterSize,
+ ChannelIndex = 0,
+ ChannelCount = 1,
+ };
+
+ DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
new file mode 100644
index 00000000..5c3c0324
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs
@@ -0,0 +1,173 @@
+using Ryujinx.Audio.Renderer.Common;
+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 AuxiliaryBufferCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.AuxiliaryBuffer;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public uint InputBufferIndex { get; }
+ public uint OutputBufferIndex { get; }
+
+ public AuxiliaryBufferAddresses BufferInfo { get; }
+
+ public CpuAddress InputBuffer { get; }
+ public CpuAddress OutputBuffer { get; }
+ public uint CountMax { get; }
+ public uint UpdateCount { get; }
+ public uint WriteOffset { get; }
+
+ public bool IsEffectEnabled { get; }
+
+ public AuxiliaryBufferCommand(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset,
+ ref AuxiliaryBufferAddresses sendBufferInfo, bool isEnabled, uint countMax,
+ CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ InputBufferIndex = bufferOffset + inputBufferOffset;
+ OutputBufferIndex = bufferOffset + outputBufferOffset;
+ BufferInfo = sendBufferInfo;
+ InputBuffer = inputBuffer;
+ OutputBuffer = outputBuffer;
+ CountMax = countMax;
+ UpdateCount = updateCount;
+ WriteOffset = writeOffset;
+ IsEffectEnabled = isEnabled;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private uint Read(IVirtualMemoryManager memoryManager, ulong bufferAddress, uint countMax, Span outBuffer, uint count, uint readOffset, uint updateCount)
+ {
+ if (countMax == 0 || bufferAddress == 0)
+ {
+ return 0;
+ }
+
+ uint targetReadOffset = readOffset + AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo);
+
+ if (targetReadOffset > countMax)
+ {
+ return 0;
+ }
+
+ uint remaining = count;
+
+ uint outBufferOffset = 0;
+
+ while (remaining != 0)
+ {
+ uint countToWrite = Math.Min(countMax - targetReadOffset, remaining);
+
+ memoryManager.Read(bufferAddress + targetReadOffset * sizeof(int), MemoryMarshal.Cast(outBuffer.Slice((int)outBufferOffset, (int)countToWrite)));
+
+ targetReadOffset = (targetReadOffset + countToWrite) % countMax;
+ remaining -= countToWrite;
+ outBufferOffset += countToWrite;
+ }
+
+ if (updateCount != 0)
+ {
+ uint newReadOffset = (AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo) + updateCount) % countMax;
+
+ AuxiliaryBufferInfo.SetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo, newReadOffset);
+ }
+
+ return count;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private uint Write(IVirtualMemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan buffer, uint count, uint writeOffset, uint updateCount)
+ {
+ if (countMax == 0 || outBufferAddress == 0)
+ {
+ return 0;
+ }
+
+ uint targetWriteOffset = writeOffset + AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo);
+
+ if (targetWriteOffset > countMax)
+ {
+ return 0;
+ }
+
+ uint remaining = count;
+
+ uint inBufferOffset = 0;
+
+ while (remaining != 0)
+ {
+ uint countToWrite = Math.Min(countMax - targetWriteOffset, remaining);
+
+ memoryManager.Write(outBufferAddress + targetWriteOffset * sizeof(int), MemoryMarshal.Cast(buffer.Slice((int)inBufferOffset, (int)countToWrite)));
+
+ targetWriteOffset = (targetWriteOffset + countToWrite) % countMax;
+ remaining -= countToWrite;
+ inBufferOffset += countToWrite;
+ }
+
+ if (updateCount != 0)
+ {
+ uint newWriteOffset = (AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo) + updateCount) % countMax;
+
+ AuxiliaryBufferInfo.SetWriteOffset(memoryManager, BufferInfo.SendBufferInfo, newWriteOffset);
+ }
+
+ return count;
+ }
+
+ public void Process(CommandList context)
+ {
+ Span inputBuffer = context.GetBuffer((int)InputBufferIndex);
+ Span outputBuffer = context.GetBuffer((int)OutputBufferIndex);
+
+ if (IsEffectEnabled)
+ {
+ Span inputBufferInt = MemoryMarshal.Cast(inputBuffer);
+ Span outputBufferInt = MemoryMarshal.Cast(outputBuffer);
+
+ // 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 just in case it's reused
+ DataSourceHelper.ToFloat(inputBuffer, inputBufferInt, inputBuffer.Length);
+
+ // Retrieve the input from user
+ uint readResult = Read(context.MemoryManager, InputBuffer, CountMax, outputBufferInt, context.SampleCount, WriteOffset, UpdateCount);
+
+ // Convert the outputBuffer back to the target format of the renderer (float)
+ DataSourceHelper.ToFloat(outputBuffer, outputBufferInt, outputBuffer.Length);
+
+ if (readResult != context.SampleCount)
+ {
+ outputBuffer.Slice((int)readResult, (int)context.SampleCount - (int)readResult).Fill(0);
+ }
+ }
+ else
+ {
+ AuxiliaryBufferInfo.Reset(context.MemoryManager, BufferInfo.SendBufferInfo);
+ AuxiliaryBufferInfo.Reset(context.MemoryManager, BufferInfo.ReturnBufferInfo);
+
+ if (InputBufferIndex != OutputBufferIndex)
+ {
+ inputBuffer.CopyTo(outputBuffer);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
new file mode 100644
index 00000000..b994c1cb
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs
@@ -0,0 +1,51 @@
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class BiquadFilterCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.BiquadFilter;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public Memory BiquadFilterState { get; }
+ public int InputBufferIndex { get; }
+ public int OutputBufferIndex { get; }
+ public bool NeedInitialization { get; }
+
+ private BiquadFilterParameter _parameter;
+
+ public BiquadFilterCommand(int baseIndex, ref BiquadFilterParameter filter, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId)
+ {
+ _parameter = filter;
+ BiquadFilterState = biquadFilterStateMemory;
+ InputBufferIndex = baseIndex + inputBufferOffset;
+ OutputBufferIndex = baseIndex + outputBufferOffset;
+ NeedInitialization = needInitialization;
+
+ Enabled = true;
+ NodeId = nodeId;
+ }
+
+ public void Process(CommandList context)
+ {
+ ref BiquadFilterState state = ref BiquadFilterState.Span[0];
+
+ ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ if (NeedInitialization)
+ {
+ state = new BiquadFilterState();
+ }
+
+ BiquadFilterHelper.ProcessBiquadFilter(ref _parameter, ref state, outputBuffer, inputBuffer, context.SampleCount);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs
new file mode 100644
index 00000000..da1cb254
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs
@@ -0,0 +1,136 @@
+using Ryujinx.Audio.Renderer.Dsp.State;
+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 uint EstimatedProcessingTime { get; set; }
+
+ public uint InputBufferIndex { get; }
+
+ public ulong CpuBufferInfoAddress { get; }
+ public ulong DspBufferInfoAddress { get; }
+
+ public CpuAddress OutputBuffer { get; }
+ public uint CountMax { get; }
+ public uint UpdateCount { get; }
+ public uint WriteOffset { get; }
+
+ public bool IsEffectEnabled { get; }
+
+ public CaptureBufferCommand(uint bufferOffset, byte inputBufferOffset, ulong sendBufferInfo, bool isEnabled,
+ uint countMax, CpuAddress outputBuffer, uint updateCount, uint writeOffset, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ InputBufferIndex = bufferOffset + inputBufferOffset;
+ CpuBufferInfoAddress = sendBufferInfo;
+ DspBufferInfoAddress = sendBufferInfo + (ulong)Unsafe.SizeOf();
+ OutputBuffer = outputBuffer;
+ CountMax = countMax;
+ UpdateCount = updateCount;
+ WriteOffset = writeOffset;
+ IsEffectEnabled = isEnabled;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private uint Write(IVirtualMemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan buffer, uint count, uint writeOffset, uint updateCount)
+ {
+ if (countMax == 0 || outBufferAddress == 0)
+ {
+ return 0;
+ }
+
+ uint targetWriteOffset = writeOffset + AuxiliaryBufferInfo.GetWriteOffset(memoryManager, DspBufferInfoAddress);
+
+ if (targetWriteOffset > countMax)
+ {
+ return 0;
+ }
+
+ uint remaining = count;
+
+ uint inBufferOffset = 0;
+
+ while (remaining != 0)
+ {
+ uint countToWrite = Math.Min(countMax - targetWriteOffset, remaining);
+
+ memoryManager.Write(outBufferAddress + targetWriteOffset * sizeof(int), MemoryMarshal.Cast(buffer.Slice((int)inBufferOffset, (int)countToWrite)));
+
+ targetWriteOffset = (targetWriteOffset + countToWrite) % countMax;
+ remaining -= countToWrite;
+ inBufferOffset += countToWrite;
+ }
+
+ if (updateCount != 0)
+ {
+ uint dspTotalSampleCount = AuxiliaryBufferInfo.GetTotalSampleCount(memoryManager, DspBufferInfoAddress);
+ uint cpuTotalSampleCount = AuxiliaryBufferInfo.GetTotalSampleCount(memoryManager, CpuBufferInfoAddress);
+
+ uint totalSampleCountDiff = dspTotalSampleCount - cpuTotalSampleCount;
+
+ if (totalSampleCountDiff >= countMax)
+ {
+ uint dspLostSampleCount = AuxiliaryBufferInfo.GetLostSampleCount(memoryManager, DspBufferInfoAddress);
+ uint cpuLostSampleCount = AuxiliaryBufferInfo.GetLostSampleCount(memoryManager, CpuBufferInfoAddress);
+
+ uint lostSampleCountDiff = dspLostSampleCount - cpuLostSampleCount;
+ uint newLostSampleCount = lostSampleCountDiff + updateCount;
+
+ if (lostSampleCountDiff > newLostSampleCount)
+ {
+ newLostSampleCount = cpuLostSampleCount - 1;
+ }
+
+ AuxiliaryBufferInfo.SetLostSampleCount(memoryManager, DspBufferInfoAddress, newLostSampleCount);
+ }
+
+ uint newWriteOffset = (AuxiliaryBufferInfo.GetWriteOffset(memoryManager, DspBufferInfoAddress) + updateCount) % countMax;
+
+ AuxiliaryBufferInfo.SetWriteOffset(memoryManager, DspBufferInfoAddress, newWriteOffset);
+
+ uint newTotalSampleCount = totalSampleCountDiff + newWriteOffset;
+
+ AuxiliaryBufferInfo.SetTotalSampleCount(memoryManager, DspBufferInfoAddress, newTotalSampleCount);
+ }
+
+ return count;
+ }
+
+ public void Process(CommandList context)
+ {
+ Span inputBuffer = context.GetBuffer((int)InputBufferIndex);
+
+ if (IsEffectEnabled)
+ {
+ Span inputBufferInt = MemoryMarshal.Cast(inputBuffer);
+
+ // Convert input data to the target format for user (int)
+ DataSourceHelper.ToInt(inputBufferInt, inputBuffer, inputBuffer.Length);
+
+ // Send the input to the user
+ Write(context.MemoryManager, OutputBuffer, CountMax, inputBufferInt, context.SampleCount, WriteOffset, UpdateCount);
+
+ // Convert back to float
+ DataSourceHelper.ToFloat(inputBuffer, inputBufferInt, inputBuffer.Length);
+ }
+ else
+ {
+ AuxiliaryBufferInfo.Reset(context.MemoryManager, DspBufferInfoAddress);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs
new file mode 100644
index 00000000..e50637eb
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs
@@ -0,0 +1,76 @@
+using Ryujinx.Audio.Renderer.Parameter.Sink;
+using Ryujinx.Audio.Renderer.Server.MemoryPool;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class CircularBufferSinkCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.CircularBufferSink;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort[] Input { get; }
+ public uint InputCount { get; }
+
+ public ulong CircularBuffer { get; }
+ public ulong CircularBufferSize { get; }
+ public ulong CurrentOffset { get; }
+
+ public CircularBufferSinkCommand(uint bufferOffset, ref CircularBufferParameter parameter, ref AddressInfo circularBufferAddressInfo, uint currentOffset, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ Input = new ushort[Constants.ChannelCountMax];
+ InputCount = parameter.InputCount;
+
+ for (int i = 0; i < InputCount; i++)
+ {
+ Input[i] = (ushort)(bufferOffset + parameter.Input[i]);
+ }
+
+ CircularBuffer = circularBufferAddressInfo.GetReference(true);
+ CircularBufferSize = parameter.BufferSize;
+ CurrentOffset = currentOffset;
+
+ Debug.Assert(CircularBuffer != 0);
+ }
+
+ public void Process(CommandList context)
+ {
+ const int targetChannelCount = 2;
+
+ ulong currentOffset = CurrentOffset;
+
+ if (CircularBufferSize > 0)
+ {
+ for (int i = 0; i < InputCount; i++)
+ {
+ unsafe
+ {
+ float* inputBuffer = (float*)context.GetBufferPointer(Input[i]);
+
+ ulong targetOffset = CircularBuffer + currentOffset;
+
+ for (int y = 0; y < context.SampleCount; y++)
+ {
+ context.MemoryManager.Write(targetOffset + (ulong)y * targetChannelCount, PcmHelper.Saturate(inputBuffer[y]));
+ }
+
+ currentOffset += context.SampleCount * targetChannelCount;
+
+ if (currentOffset >= CircularBufferSize)
+ {
+ currentOffset = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs
new file mode 100644
index 00000000..9e653e80
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs
@@ -0,0 +1,24 @@
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class ClearMixBufferCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.ClearMixBuffer;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ClearMixBufferCommand(int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ }
+
+ public void Process(CommandList context)
+ {
+ context.ClearBuffers();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs
new file mode 100644
index 00000000..2cbed9c2
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs
@@ -0,0 +1,155 @@
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Renderer.Server;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Memory;
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class CommandList : IDisposable
+ {
+ public ulong StartTime { get; private set; }
+ public ulong EndTime { get; private set; }
+ public uint SampleCount { get; }
+ public uint SampleRate { get; }
+
+ public Memory Buffers { get; }
+ public uint BufferCount { get; }
+
+ public List Commands { get; }
+
+ public IVirtualMemoryManager MemoryManager { get; }
+
+ public IHardwareDevice OutputDevice { get; private set; }
+
+ private readonly int _sampleCount;
+ private readonly int _buffersEntryCount;
+ private readonly MemoryHandle _buffersMemoryHandle;
+
+ public CommandList(AudioRenderSystem renderSystem) : this(renderSystem.MemoryManager,
+ renderSystem.GetMixBuffer(),
+ renderSystem.GetSampleCount(),
+ renderSystem.GetSampleRate(),
+ renderSystem.GetMixBufferCount(),
+ renderSystem.GetVoiceChannelCountMax())
+ {
+ }
+
+ public CommandList(IVirtualMemoryManager memoryManager, Memory mixBuffer, uint sampleCount, uint sampleRate, uint mixBufferCount, uint voiceChannelCountMax)
+ {
+ SampleCount = sampleCount;
+ _sampleCount = (int)SampleCount;
+ SampleRate = sampleRate;
+ BufferCount = mixBufferCount + voiceChannelCountMax;
+ Buffers = mixBuffer;
+ Commands = new List();
+ MemoryManager = memoryManager;
+
+ _buffersEntryCount = Buffers.Length;
+ _buffersMemoryHandle = Buffers.Pin();
+ }
+
+ public void AddCommand(ICommand command)
+ {
+ Commands.Add(command);
+ }
+
+ public void AddCommand(T command) where T : unmanaged, ICommand
+ {
+ throw new NotImplementedException();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe IntPtr GetBufferPointer(int index)
+ {
+ if (index >= 0 && index < _buffersEntryCount)
+ {
+ return (IntPtr)((float*)_buffersMemoryHandle.Pointer + index * _sampleCount);
+ }
+
+ throw new ArgumentOutOfRangeException();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ClearBuffer(int index)
+ {
+ Unsafe.InitBlock((void*)GetBufferPointer(index), 0, SampleCount);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ClearBuffers()
+ {
+ Unsafe.InitBlock(_buffersMemoryHandle.Pointer, 0, (uint)_buffersEntryCount * sizeof(float));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void CopyBuffer(int outputBufferIndex, int inputBufferIndex)
+ {
+ Unsafe.CopyBlock((void*)GetBufferPointer(outputBufferIndex), (void*)GetBufferPointer(inputBufferIndex), SampleCount);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span GetBuffer(int index)
+ {
+ if (index < 0 || index >= _buffersEntryCount)
+ {
+ return Span.Empty;
+ }
+
+ unsafe
+ {
+ return new Span((float*)_buffersMemoryHandle.Pointer + index * _sampleCount, _sampleCount);
+ }
+ }
+
+ public ulong GetTimeElapsedSinceDspStartedProcessing()
+ {
+ return (ulong)PerformanceCounter.ElapsedNanoseconds - StartTime;
+ }
+
+ public void Process(IHardwareDevice outputDevice)
+ {
+ OutputDevice = outputDevice;
+
+ StartTime = (ulong)PerformanceCounter.ElapsedNanoseconds;
+
+ foreach (ICommand command in Commands)
+ {
+ if (command.Enabled)
+ {
+ bool shouldMeter = command.ShouldMeter();
+
+ long startTime = 0;
+
+ if (shouldMeter)
+ {
+ startTime = PerformanceCounter.ElapsedNanoseconds;
+ }
+
+ command.Process(this);
+
+ if (shouldMeter)
+ {
+ ulong effectiveElapsedTime = (ulong)(PerformanceCounter.ElapsedNanoseconds - startTime);
+
+ if (effectiveElapsedTime > command.EstimatedProcessingTime)
+ {
+ Logger.Warning?.Print(LogClass.AudioRenderer, $"Command {command.GetType().Name} took {effectiveElapsedTime}ns (expected {command.EstimatedProcessingTime}ns)");
+ }
+ }
+ }
+ }
+
+ EndTime = (ulong)PerformanceCounter.ElapsedNanoseconds;
+ }
+
+ public void Dispose()
+ {
+ _buffersMemoryHandle.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
new file mode 100644
index 00000000..9ce181b1
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs
@@ -0,0 +1,37 @@
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public enum CommandType : byte
+ {
+ Invalid,
+ PcmInt16DataSourceVersion1,
+ PcmInt16DataSourceVersion2,
+ PcmFloatDataSourceVersion1,
+ PcmFloatDataSourceVersion2,
+ AdpcmDataSourceVersion1,
+ AdpcmDataSourceVersion2,
+ Volume,
+ VolumeRamp,
+ BiquadFilter,
+ Mix,
+ MixRamp,
+ MixRampGrouped,
+ DepopPrepare,
+ DepopForMixBuffers,
+ Delay,
+ Upsample,
+ DownMixSurroundToStereo,
+ AuxiliaryBuffer,
+ DeviceSink,
+ CircularBufferSink,
+ Reverb,
+ Reverb3d,
+ Performance,
+ ClearMixBuffer,
+ CopyMixBuffer,
+ LimiterVersion1,
+ LimiterVersion2,
+ GroupedBiquadFilter,
+ CaptureBuffer,
+ Compressor
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs
new file mode 100644
index 00000000..34231e61
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs
@@ -0,0 +1,173 @@
+using Ryujinx.Audio.Renderer.Dsp.Effect;
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class CompressorCommand : ICommand
+ {
+ private const int FixedPointPrecision = 15;
+
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Compressor;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public CompressorParameter Parameter => _parameter;
+ public Memory State { get; }
+ public ushort[] OutputBufferIndices { get; }
+ public ushort[] InputBufferIndices { get; }
+ public bool IsEffectEnabled { get; }
+
+ private CompressorParameter _parameter;
+
+ public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory state, bool isEnabled, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+
+ IsEffectEnabled = isEnabled;
+
+ InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+ for (int i = 0; i < _parameter.ChannelCount; i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]);
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ref CompressorState state = ref State.Span[0];
+
+ if (IsEffectEnabled)
+ {
+ if (_parameter.Status == Server.Effect.UsageState.Invalid)
+ {
+ state = new CompressorState(ref _parameter);
+ }
+ else if (_parameter.Status == Server.Effect.UsageState.New)
+ {
+ state.UpdateParameter(ref _parameter);
+ }
+ }
+
+ ProcessCompressor(context, ref state);
+ }
+
+ private unsafe void ProcessCompressor(CommandList context, ref CompressorState state)
+ {
+ Debug.Assert(_parameter.IsChannelCountValid());
+
+ if (IsEffectEnabled && _parameter.IsChannelCountValid())
+ {
+ Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+ Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+ Span channelInput = stackalloc float[Parameter.ChannelCount];
+ ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage;
+ float unknown4 = state.Unknown4;
+ ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage;
+ float previousCompressionEmaAlpha = state.PreviousCompressionEmaAlpha;
+
+ for (int i = 0; i < _parameter.ChannelCount; i++)
+ {
+ inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
+ outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
+ }
+
+ for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
+ {
+ for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++)
+ {
+ channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
+ }
+
+ float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain);
+ float y = FloatingPointHelper.Log10(newMean) * 10.0f;
+ float z = 0.0f;
+
+ bool unknown10OutOfRange = false;
+
+ if (newMean < 1.0e-10f)
+ {
+ z = 1.0f;
+
+ unknown10OutOfRange = state.Unknown10 < -100.0f;
+ }
+
+ if (y >= state.Unknown10 || unknown10OutOfRange)
+ {
+ float tmpGain;
+
+ if (y >= state.Unknown14)
+ {
+ tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold);
+ }
+ else
+ {
+ tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction);
+ }
+
+ z = FloatingPointHelper.DecibelToLinearExtended(tmpGain);
+ }
+
+ float unknown4New = z;
+ float compressionEmaAlpha;
+
+ if ((unknown4 - z) <= 0.08f)
+ {
+ compressionEmaAlpha = Parameter.ReleaseCoefficient;
+
+ if ((unknown4 - z) >= -0.08f)
+ {
+ if (MathF.Abs(compressionGainAverage.Read() - z) >= 0.001f)
+ {
+ unknown4New = unknown4;
+ }
+
+ compressionEmaAlpha = previousCompressionEmaAlpha;
+ }
+ }
+ else
+ {
+ compressionEmaAlpha = Parameter.AttackCoefficient;
+ }
+
+ float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha);
+
+ for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ {
+ *((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain;
+ }
+
+ unknown4 = unknown4New;
+ previousCompressionEmaAlpha = compressionEmaAlpha;
+ }
+
+ state.InputMovingAverage = inputMovingAverage;
+ state.Unknown4 = unknown4;
+ state.CompressionGainAverage = compressionGainAverage;
+ state.PreviousCompressionEmaAlpha = previousCompressionEmaAlpha;
+ }
+ else
+ {
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ if (InputBufferIndices[i] != OutputBufferIndices[i])
+ {
+ context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs
new file mode 100644
index 00000000..7237fddf
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs
@@ -0,0 +1,30 @@
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class CopyMixBufferCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.CopyMixBuffer;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ public CopyMixBufferCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)inputBufferIndex;
+ OutputBufferIndex = (ushort)outputBufferIndex;
+ }
+
+ public void Process(CommandList context)
+ {
+ context.CopyBuffer(OutputBufferIndex, InputBufferIndex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs
new file mode 100644
index 00000000..c1503b6a
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs
@@ -0,0 +1,108 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using System;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class DataSourceVersion2Command : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType { get; }
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort OutputBufferIndex { get; }
+ public uint SampleRate { get; }
+
+ public float Pitch { get; }
+
+ public WaveBuffer[] WaveBuffers { get; }
+
+ public Memory State { get; }
+
+ public ulong ExtraParameter { get; }
+ public ulong ExtraParameterSize { get; }
+
+ public uint ChannelIndex { get; }
+
+ public uint ChannelCount { get; }
+
+ public DecodingBehaviour DecodingBehaviour { get; }
+
+ public SampleFormat SampleFormat { get; }
+
+ public SampleRateConversionQuality SrcQuality { get; }
+
+ public DataSourceVersion2Command(ref Server.Voice.VoiceState serverState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ ChannelIndex = channelIndex;
+ ChannelCount = serverState.ChannelsCount;
+ SampleFormat = serverState.SampleFormat;
+ SrcQuality = serverState.SrcQuality;
+ CommandType = GetCommandTypeBySampleFormat(SampleFormat);
+
+ OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex);
+ SampleRate = serverState.SampleRate;
+ Pitch = serverState.Pitch;
+
+ WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
+
+ for (int i = 0; i < WaveBuffers.Length; i++)
+ {
+ ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i];
+
+ WaveBuffers[i] = voiceWaveBuffer.ToCommon(2);
+ }
+
+ if (SampleFormat == SampleFormat.Adpcm)
+ {
+ ExtraParameter = serverState.DataSourceStateAddressInfo.GetReference(true);
+ ExtraParameterSize = serverState.DataSourceStateAddressInfo.Size;
+ }
+
+ State = state;
+ DecodingBehaviour = serverState.DecodingBehaviour;
+ }
+
+ private static CommandType GetCommandTypeBySampleFormat(SampleFormat sampleFormat)
+ {
+ switch (sampleFormat)
+ {
+ case SampleFormat.Adpcm:
+ return CommandType.AdpcmDataSourceVersion2;
+ case SampleFormat.PcmInt16:
+ return CommandType.PcmInt16DataSourceVersion2;
+ case SampleFormat.PcmFloat:
+ return CommandType.PcmFloatDataSourceVersion2;
+ default:
+ throw new NotImplementedException($"{sampleFormat}");
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ Span outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation
+ {
+ SourceSampleRate = SampleRate,
+ SampleFormat = SampleFormat,
+ Pitch = Pitch,
+ DecodingBehaviour = DecodingBehaviour,
+ ExtraParameter = ExtraParameter,
+ ExtraParameterSize = ExtraParameterSize,
+ ChannelIndex = (int)ChannelIndex,
+ ChannelCount = (int)ChannelCount,
+ SrcQuality = SrcQuality
+ };
+
+ DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs
new file mode 100644
index 00000000..cb5678c7
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs
@@ -0,0 +1,280 @@
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.Effect;
+using Ryujinx.Audio.Renderer.Utils.Math;
+using System;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class DelayCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Delay;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public DelayParameter Parameter => _parameter;
+ public Memory State { get; }
+ public ulong WorkBuffer { get; }
+ public ushort[] OutputBufferIndices { get; }
+ public ushort[] InputBufferIndices { get; }
+ public bool IsEffectEnabled { get; }
+
+ private DelayParameter _parameter;
+
+ private const int FixedPointPrecision = 14;
+
+ public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+ WorkBuffer = workBuffer;
+
+ IsEffectEnabled = isEnabled;
+
+ InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+ }
+
+ DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, InputBufferIndices);
+ DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, OutputBufferIndices);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount)
+ {
+ const ushort channelCount = 1;
+
+ float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
+ float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
+ float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
+ float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ float input = inputBuffer[i] * 64;
+ float delayLineValue = state.DelayLines[0].Read();
+
+ float temp = input * inGain + delayLineValue * feedbackGain;
+
+ state.UpdateLowPassFilter(ref temp, channelCount);
+
+ outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ private unsafe void ProcessDelayStereo(ref DelayState state, Span outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount)
+ {
+ const ushort channelCount = 2;
+
+ float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
+ float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
+ float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
+ float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
+ float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
+
+ Matrix2x2 delayFeedback = new Matrix2x2(delayFeedbackBaseGain, delayFeedbackCrossGain,
+ delayFeedbackCrossGain, delayFeedbackBaseGain);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ Vector2 channelInput = new Vector2
+ {
+ X = *((float*)inputBuffers[0] + i) * 64,
+ Y = *((float*)inputBuffers[1] + i) * 64,
+ };
+
+ Vector2 delayLineValues = new Vector2()
+ {
+ X = state.DelayLines[0].Read(),
+ Y = state.DelayLines[1].Read(),
+ };
+
+ Vector2 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
+
+ state.UpdateLowPassFilter(ref Unsafe.As(ref temp), channelCount);
+
+ *((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
+ *((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ private unsafe void ProcessDelayQuadraphonic(ref DelayState state, Span outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount)
+ {
+ const ushort channelCount = 4;
+
+ float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
+ float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
+ float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
+ float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
+ float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
+
+ Matrix4x4 delayFeedback = new Matrix4x4(delayFeedbackBaseGain, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f,
+ delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain,
+ delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
+ 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain);
+
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ Vector4 channelInput = new Vector4
+ {
+ X = *((float*)inputBuffers[0] + i) * 64,
+ Y = *((float*)inputBuffers[1] + i) * 64,
+ Z = *((float*)inputBuffers[2] + i) * 64,
+ W = *((float*)inputBuffers[3] + i) * 64
+ };
+
+ Vector4 delayLineValues = new Vector4()
+ {
+ X = state.DelayLines[0].Read(),
+ Y = state.DelayLines[1].Read(),
+ Z = state.DelayLines[2].Read(),
+ W = state.DelayLines[3].Read()
+ };
+
+ Vector4 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
+
+ state.UpdateLowPassFilter(ref Unsafe.As(ref temp), channelCount);
+
+ *((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
+ *((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
+ *((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64;
+ *((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ private unsafe void ProcessDelaySurround(ref DelayState state, Span outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount)
+ {
+ const ushort channelCount = 6;
+
+ float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision);
+ float delayFeedbackBaseGain = state.DelayFeedbackBaseGain;
+ float delayFeedbackCrossGain = state.DelayFeedbackCrossGain;
+ float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision);
+ float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
+ float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
+
+ Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f,
+ 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain,
+ delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f,
+ delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain,
+ 0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain);
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ Vector6 channelInput = new Vector6
+ {
+ X = *((float*)inputBuffers[0] + i) * 64,
+ Y = *((float*)inputBuffers[1] + i) * 64,
+ Z = *((float*)inputBuffers[2] + i) * 64,
+ W = *((float*)inputBuffers[3] + i) * 64,
+ V = *((float*)inputBuffers[4] + i) * 64,
+ U = *((float*)inputBuffers[5] + i) * 64
+ };
+
+ Vector6 delayLineValues = new Vector6
+ {
+ X = state.DelayLines[0].Read(),
+ Y = state.DelayLines[1].Read(),
+ Z = state.DelayLines[2].Read(),
+ W = state.DelayLines[3].Read(),
+ V = state.DelayLines[4].Read(),
+ U = state.DelayLines[5].Read()
+ };
+
+ Vector6 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain;
+
+ state.UpdateLowPassFilter(ref Unsafe.As(ref temp), channelCount);
+
+ *((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64;
+ *((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64;
+ *((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64;
+ *((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64;
+ *((float*)outputBuffers[4] + i) = (channelInput.V * dryGain + delayLineValues.V * outGain) / 64;
+ *((float*)outputBuffers[5] + i) = (channelInput.U * dryGain + delayLineValues.U * outGain) / 64;
+ }
+ }
+
+ private unsafe void ProcessDelay(CommandList context, ref DelayState state)
+ {
+ Debug.Assert(Parameter.IsChannelCountValid());
+
+ if (IsEffectEnabled && Parameter.IsChannelCountValid())
+ {
+ Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+ Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
+ outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
+ }
+
+ switch (Parameter.ChannelCount)
+ {
+ case 1:
+ ProcessDelayMono(ref state, (float*)outputBuffers[0], (float*)inputBuffers[0], context.SampleCount);
+ break;
+ case 2:
+ ProcessDelayStereo(ref state, outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 4:
+ ProcessDelayQuadraphonic(ref state, outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 6:
+ ProcessDelaySurround(ref state, outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ default:
+ throw new NotImplementedException(Parameter.ChannelCount.ToString());
+ }
+ }
+ else
+ {
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ if (InputBufferIndices[i] != OutputBufferIndices[i])
+ {
+ context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
+ }
+ }
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ref DelayState state = ref State.Span[0];
+
+ if (IsEffectEnabled)
+ {
+ if (Parameter.Status == UsageState.Invalid)
+ {
+ state = new DelayState(ref _parameter, WorkBuffer);
+ }
+ else if (Parameter.Status == UsageState.New)
+ {
+ state.UpdateParameter(ref _parameter);
+ }
+ }
+
+ ProcessDelay(context, ref state);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
new file mode 100644
index 00000000..1dba56e6
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class DepopForMixBuffersCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.DepopForMixBuffers;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public uint MixBufferOffset { get; }
+
+ public uint MixBufferCount { get; }
+
+ public float Decay { get; }
+
+ public Memory DepopBuffer { get; }
+
+ public DepopForMixBuffersCommand(Memory depopBuffer, uint bufferOffset, uint mixBufferCount, int nodeId, uint sampleRate)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ MixBufferOffset = bufferOffset;
+ MixBufferCount = mixBufferCount;
+ DepopBuffer = depopBuffer;
+
+ if (sampleRate == 48000)
+ {
+ Decay = 0.962189f;
+ }
+ else // if (sampleRate == 32000)
+ {
+ Decay = 0.943695f;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe float ProcessDepopMix(float* buffer, float depopValue, uint sampleCount)
+ {
+ if (depopValue < 0)
+ {
+ depopValue = -depopValue;
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue);
+
+ buffer[i] -= depopValue;
+ }
+
+ return -depopValue;
+ }
+ else
+ {
+ for (int i = 0; i < sampleCount; i++)
+ {
+ depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue);
+
+ buffer[i] += depopValue;
+ }
+
+ return depopValue;
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ Span depopBuffer = DepopBuffer.Span;
+
+ uint bufferCount = Math.Min(MixBufferOffset + MixBufferCount, context.BufferCount);
+
+ for (int i = (int)MixBufferOffset; i < bufferCount; i++)
+ {
+ float depopValue = depopBuffer[i];
+ if (depopValue != 0)
+ {
+ unsafe
+ {
+ float* buffer = (float*)context.GetBufferPointer(i);
+
+ depopBuffer[i] = ProcessDepopMix(buffer, depopValue, context.SampleCount);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs
new file mode 100644
index 00000000..d02f7c12
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs
@@ -0,0 +1,57 @@
+using Ryujinx.Audio.Renderer.Common;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class DepopPrepareCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.DepopPrepare;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public uint MixBufferCount { get; }
+
+ public ushort[] OutputBufferIndices { get; }
+
+ public Memory State { get; }
+ public Memory DepopBuffer { get; }
+
+ public DepopPrepareCommand(Memory state, Memory depopBuffer, uint mixBufferCount, uint bufferOffset, int nodeId, bool enabled)
+ {
+ Enabled = enabled;
+ NodeId = nodeId;
+ MixBufferCount = mixBufferCount;
+
+ OutputBufferIndices = new ushort[Constants.MixBufferCountMax];
+
+ for (int i = 0; i < Constants.MixBufferCountMax; i++)
+ {
+ OutputBufferIndices[i] = (ushort)(bufferOffset + i);
+ }
+
+ State = state;
+ DepopBuffer = depopBuffer;
+ }
+
+ public void Process(CommandList context)
+ {
+ ref VoiceUpdateState state = ref State.Span[0];
+
+ Span depopBuffer = DepopBuffer.Span;
+
+ for (int i = 0; i < MixBufferCount; i++)
+ {
+ if (state.LastSamples[i] != 0)
+ {
+ depopBuffer[OutputBufferIndices[i]] += state.LastSamples[i];
+
+ state.LastSamples[i] = 0;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
new file mode 100644
index 00000000..9c88a4e7
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs
@@ -0,0 +1,91 @@
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Renderer.Server.Sink;
+using System;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class DeviceSinkCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.DeviceSink;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public string DeviceName { get; }
+
+ public int SessionId { get; }
+
+ public uint InputCount { get; }
+ public ushort[] InputBufferIndices { get; }
+
+ public Memory Buffers { get; }
+
+ public DeviceSinkCommand(uint bufferOffset, DeviceSink sink, int sessionId, Memory buffers, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ DeviceName = Encoding.ASCII.GetString(sink.Parameter.DeviceName).TrimEnd('\0');
+ SessionId = sessionId;
+ InputCount = sink.Parameter.InputCount;
+ InputBufferIndices = new ushort[InputCount];
+
+ for (int i = 0; i < Math.Min(InputCount, Constants.ChannelCountMax); i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + sink.Parameter.Input[i]);
+ }
+
+ if (sink.UpsamplerState != null)
+ {
+ Buffers = sink.UpsamplerState.OutputBuffer;
+ }
+ else
+ {
+ Buffers = buffers;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private Span GetBuffer(int index, int sampleCount)
+ {
+ return Buffers.Span.Slice(index * sampleCount, sampleCount);
+ }
+
+ public void Process(CommandList context)
+ {
+ IHardwareDevice device = context.OutputDevice;
+
+ if (device.GetSampleRate() == Constants.TargetSampleRate)
+ {
+ int channelCount = (int)device.GetChannelCount();
+ uint bufferCount = Math.Min(device.GetChannelCount(), InputCount);
+
+ const int sampleCount = Constants.TargetSampleCount;
+
+ short[] outputBuffer = new short[bufferCount * sampleCount];
+
+ for (int i = 0; i < bufferCount; i++)
+ {
+ ReadOnlySpan inputBuffer = GetBuffer(InputBufferIndices[i], sampleCount);
+
+ for (int j = 0; j < sampleCount; j++)
+ {
+ outputBuffer[i + j * channelCount] = PcmHelper.Saturate(inputBuffer[j]);
+ }
+ }
+
+ device.AppendBuffer(outputBuffer, InputCount);
+ }
+ else
+ {
+ // TODO: support resampling for device only supporting something different
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs
new file mode 100644
index 00000000..79cefcc5
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class DownMixSurroundToStereoCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.DownMixSurroundToStereo;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort[] InputBufferIndices { get; }
+ public ushort[] OutputBufferIndices { get; }
+
+ public float[] Coefficients { get; }
+
+ public DownMixSurroundToStereoCommand(uint bufferOffset, Span inputBufferOffset, Span outputBufferOffset, float[] downMixParameter, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+ for (int i = 0; i < Constants.VoiceChannelCountMax; i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + outputBufferOffset[i]);
+ }
+
+ Coefficients = downMixParameter;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static float DownMixSurroundToStereo(ReadOnlySpan coefficients, float back, float lfe, float center, float front)
+ {
+ return FloatingPointHelper.RoundUp(coefficients[3] * back + coefficients[2] * lfe + coefficients[1] * center + coefficients[0] * front);
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan frontLeft = context.GetBuffer(InputBufferIndices[0]);
+ ReadOnlySpan frontRight = context.GetBuffer(InputBufferIndices[1]);
+ ReadOnlySpan frontCenter = context.GetBuffer(InputBufferIndices[2]);
+ ReadOnlySpan lowFrequency = context.GetBuffer(InputBufferIndices[3]);
+ ReadOnlySpan backLeft = context.GetBuffer(InputBufferIndices[4]);
+ ReadOnlySpan backRight = context.GetBuffer(InputBufferIndices[5]);
+
+ Span stereoLeft = context.GetBuffer(OutputBufferIndices[0]);
+ Span stereoRight = context.GetBuffer(OutputBufferIndices[1]);
+
+ for (int i = 0; i < context.SampleCount; i++)
+ {
+ stereoLeft[i] = DownMixSurroundToStereo(Coefficients, backLeft[i], lowFrequency[i], frontCenter[i], frontLeft[i]);
+ stereoRight[i] = DownMixSurroundToStereo(Coefficients, backRight[i], lowFrequency[i], frontCenter[i], frontRight[i]);
+ }
+
+ context.ClearBuffer(OutputBufferIndices[2]);
+ context.ClearBuffer(OutputBufferIndices[3]);
+ context.ClearBuffer(OutputBufferIndices[4]);
+ context.ClearBuffer(OutputBufferIndices[5]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
new file mode 100644
index 00000000..b190cc10
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs
@@ -0,0 +1,62 @@
+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 uint EstimatedProcessingTime { get; set; }
+
+ private BiquadFilterParameter[] _parameters;
+ private Memory _biquadFilterStates;
+ private int _inputBufferIndex;
+ private int _outputBufferIndex;
+ private bool[] _isInitialized;
+
+ public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId)
+ {
+ _parameters = filters.ToArray();
+ _biquadFilterStates = biquadFilterStateMemory;
+ _inputBufferIndex = baseIndex + inputBufferOffset;
+ _outputBufferIndex = baseIndex + outputBufferOffset;
+ _isInitialized = isInitialized.ToArray();
+
+ Enabled = true;
+ NodeId = nodeId;
+ }
+
+ public void Process(CommandList context)
+ {
+ Span states = _biquadFilterStates.Span;
+
+ ReadOnlySpan inputBuffer = context.GetBuffer(_inputBufferIndex);
+ Span outputBuffer = context.GetBuffer(_outputBufferIndex);
+
+ for (int i = 0; i < _parameters.Length; i++)
+ {
+ if (!_isInitialized[i])
+ {
+ states[i] = new BiquadFilterState();
+ }
+ }
+
+ // NOTE: Nintendo only implement single and double biquad filters but no generic path when the command definition suggests it could be done.
+ // As such we currently only implement a generic path for simplicity for double biquad.
+ if (_parameters.Length == 1)
+ {
+ BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount);
+ }
+ else
+ {
+ BiquadFilterHelper.ProcessBiquadFilter(_parameters, states, outputBuffer, inputBuffer, context.SampleCount);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs
new file mode 100644
index 00000000..d281e6e9
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public interface ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType { get; }
+
+ public uint EstimatedProcessingTime { get; }
+
+ public void Process(CommandList context);
+
+ public bool ShouldMeter()
+ {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
new file mode 100644
index 00000000..a464ad70
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs
@@ -0,0 +1,144 @@
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class LimiterCommandVersion1 : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.LimiterVersion1;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public LimiterParameter Parameter => _parameter;
+ public Memory State { get; }
+ public ulong WorkBuffer { get; }
+ public ushort[] OutputBufferIndices { get; }
+ public ushort[] InputBufferIndices { get; }
+ public bool IsEffectEnabled { get; }
+
+ private LimiterParameter _parameter;
+
+ public LimiterCommandVersion1(uint bufferOffset, LimiterParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+ WorkBuffer = workBuffer;
+
+ IsEffectEnabled = isEnabled;
+
+ InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ref LimiterState state = ref State.Span[0];
+
+ if (IsEffectEnabled)
+ {
+ if (Parameter.Status == Server.Effect.UsageState.Invalid)
+ {
+ state = new LimiterState(ref _parameter, WorkBuffer);
+ }
+ else if (Parameter.Status == Server.Effect.UsageState.New)
+ {
+ state.UpdateParameter(ref _parameter);
+ }
+ }
+
+ ProcessLimiter(context, ref state);
+ }
+
+ private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
+ {
+ Debug.Assert(Parameter.IsChannelCountValid());
+
+ if (IsEffectEnabled && Parameter.IsChannelCountValid())
+ {
+ Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+ Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
+ outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
+ }
+
+ for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ {
+ for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
+ {
+ float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
+
+ float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
+
+ float sampleInputMax = Math.Abs(inputSample);
+
+ float inputCoefficient = Parameter.ReleaseCoefficient;
+
+ if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
+ {
+ inputCoefficient = Parameter.AttackCoefficient;
+ }
+
+ float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
+ float attenuation = 1.0f;
+
+ if (detectorValue > Parameter.Threshold)
+ {
+ attenuation = Parameter.Threshold / detectorValue;
+ }
+
+ float outputCoefficient = Parameter.ReleaseCoefficient;
+
+ if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
+ {
+ outputCoefficient = Parameter.AttackCoefficient;
+ }
+
+ float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
+
+ ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
+
+ float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
+
+ *((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
+
+ delayedSample = inputSample;
+
+ state.DelayedSampleBufferPosition[channelIndex]++;
+
+ while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
+ {
+ state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
+ }
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ if (InputBufferIndices[i] != OutputBufferIndices[i])
+ {
+ context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
new file mode 100644
index 00000000..950de97b
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs
@@ -0,0 +1,163 @@
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class LimiterCommandVersion2 : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.LimiterVersion2;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public LimiterParameter Parameter => _parameter;
+ public Memory State { get; }
+ public Memory ResultState { get; }
+ public ulong WorkBuffer { get; }
+ public ushort[] OutputBufferIndices { get; }
+ public ushort[] InputBufferIndices { get; }
+ public bool IsEffectEnabled { get; }
+
+ private LimiterParameter _parameter;
+
+ public LimiterCommandVersion2(uint bufferOffset, LimiterParameter parameter, Memory state, Memory resultState, bool isEnabled, ulong workBuffer, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+ ResultState = resultState;
+ WorkBuffer = workBuffer;
+
+ IsEffectEnabled = isEnabled;
+
+ InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ref LimiterState state = ref State.Span[0];
+
+ if (IsEffectEnabled)
+ {
+ if (Parameter.Status == Server.Effect.UsageState.Invalid)
+ {
+ state = new LimiterState(ref _parameter, WorkBuffer);
+ }
+ else if (Parameter.Status == Server.Effect.UsageState.New)
+ {
+ state.UpdateParameter(ref _parameter);
+ }
+ }
+
+ ProcessLimiter(context, ref state);
+ }
+
+ private unsafe void ProcessLimiter(CommandList context, ref LimiterState state)
+ {
+ Debug.Assert(Parameter.IsChannelCountValid());
+
+ if (IsEffectEnabled && Parameter.IsChannelCountValid())
+ {
+ if (!ResultState.IsEmpty && Parameter.StatisticsReset)
+ {
+ ref LimiterStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0];
+
+ statistics.Reset();
+ }
+
+ Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+ Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
+ outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
+ }
+
+ for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ {
+ for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++)
+ {
+ float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex);
+
+ float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain;
+
+ float sampleInputMax = Math.Abs(inputSample);
+
+ float inputCoefficient = Parameter.ReleaseCoefficient;
+
+ if (sampleInputMax > state.DetectorAverage[channelIndex].Read())
+ {
+ inputCoefficient = Parameter.AttackCoefficient;
+ }
+
+ float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient);
+ float attenuation = 1.0f;
+
+ if (detectorValue > Parameter.Threshold)
+ {
+ attenuation = Parameter.Threshold / detectorValue;
+ }
+
+ float outputCoefficient = Parameter.ReleaseCoefficient;
+
+ if (state.CompressionGainAverage[channelIndex].Read() > attenuation)
+ {
+ outputCoefficient = Parameter.AttackCoefficient;
+ }
+
+ float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient);
+
+ ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]];
+
+ float outputSample = delayedSample * compressionGain * Parameter.OutputGain;
+
+ *((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue;
+
+ delayedSample = inputSample;
+
+ state.DelayedSampleBufferPosition[channelIndex]++;
+
+ while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin)
+ {
+ state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin;
+ }
+
+ if (!ResultState.IsEmpty)
+ {
+ ref LimiterStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0];
+
+ statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax);
+ statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], compressionGain);
+ }
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ if (InputBufferIndices[i] != OutputBufferIndices[i])
+ {
+ context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs
new file mode 100644
index 00000000..2616bda5
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.Arm;
+using System.Runtime.Intrinsics.X86;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class MixCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Mix;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ public float Volume { get; }
+
+ public MixCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)inputBufferIndex;
+ OutputBufferIndex = (ushort)outputBufferIndex;
+
+ Volume = volume;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessMixAvx(Span outputMix, ReadOnlySpan inputMix)
+ {
+ Vector256 volumeVec = Vector256.Create(Volume);
+
+ ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix);
+ Span> outputVec = MemoryMarshal.Cast>(outputMix);
+
+ int sisdStart = inputVec.Length * 8;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = Avx.Add(outputVec[i], Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec)));
+ }
+
+ for (int i = sisdStart; i < inputMix.Length; i++)
+ {
+ outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessMixSse41(Span outputMix, ReadOnlySpan inputMix)
+ {
+ Vector128 volumeVec = Vector128.Create(Volume);
+
+ ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix);
+ Span> outputVec = MemoryMarshal.Cast>(outputMix);
+
+ int sisdStart = inputVec.Length * 4;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = Sse.Add(outputVec[i], Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec)));
+ }
+
+ for (int i = sisdStart; i < inputMix.Length; i++)
+ {
+ outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessMixAdvSimd(Span outputMix, ReadOnlySpan inputMix)
+ {
+ Vector128 volumeVec = Vector128.Create(Volume);
+
+ ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix);
+ Span> outputVec = MemoryMarshal.Cast>(outputMix);
+
+ int sisdStart = inputVec.Length * 4;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = AdvSimd.Add(outputVec[i], AdvSimd.Ceiling(AdvSimd.Multiply(inputVec[i], volumeVec)));
+ }
+
+ for (int i = sisdStart; i < inputMix.Length; i++)
+ {
+ outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessMixSlowPath(Span outputMix, ReadOnlySpan inputMix)
+ {
+ for (int i = 0; i < inputMix.Length; i++)
+ {
+ outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessMix(Span outputMix, ReadOnlySpan inputMix)
+ {
+ if (Avx.IsSupported)
+ {
+ ProcessMixAvx(outputMix, inputMix);
+ }
+ else if (Sse41.IsSupported)
+ {
+ ProcessMixSse41(outputMix, inputMix);
+ }
+ else if (AdvSimd.IsSupported)
+ {
+ ProcessMixAdvSimd(outputMix, inputMix);
+ }
+ else
+ {
+ ProcessMixSlowPath(outputMix, inputMix);
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ ProcessMix(outputBuffer, inputBuffer);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs
new file mode 100644
index 00000000..76a1aba2
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs
@@ -0,0 +1,68 @@
+using Ryujinx.Audio.Renderer.Common;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class MixRampCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.MixRamp;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ public float Volume0 { get; }
+ public float Volume1 { get; }
+
+ public Memory State { get; }
+
+ public int LastSampleIndex { get; }
+
+ public MixRampCommand(float volume0, float volume1, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory state, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)inputBufferIndex;
+ OutputBufferIndex = (ushort)outputBufferIndex;
+
+ Volume0 = volume0;
+ Volume1 = volume1;
+
+ State = state;
+ LastSampleIndex = lastSampleIndex;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float ProcessMixRamp(Span outputBuffer, ReadOnlySpan inputBuffer, int sampleCount)
+ {
+ float ramp = (Volume1 - Volume0) / sampleCount;
+ float volume = Volume0;
+ float state = 0;
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume);
+
+ outputBuffer[i] += state;
+ volume += ramp;
+ }
+
+ return state;
+ }
+
+ public void Process(CommandList context)
+ {
+ ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex);
+ Span outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ State.Span[0].LastSamples[LastSampleIndex] = ProcessMixRamp(outputBuffer, inputBuffer, (int)context.SampleCount);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
new file mode 100644
index 00000000..e348e358
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs
@@ -0,0 +1,91 @@
+using Ryujinx.Audio.Renderer.Common;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class MixRampGroupedCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.MixRampGrouped;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public uint MixBufferCount { get; }
+
+ public ushort[] InputBufferIndices { get; }
+ public ushort[] OutputBufferIndices { get; }
+
+ public float[] Volume0 { get; }
+ public float[] Volume1 { get; }
+
+ public Memory State { get; }
+
+ public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span volume0, Span volume1, Memory state, int nodeId)
+ {
+ Enabled = true;
+ MixBufferCount = mixBufferCount;
+ NodeId = nodeId;
+
+ InputBufferIndices = new ushort[Constants.MixBufferCountMax];
+ OutputBufferIndices = new ushort[Constants.MixBufferCountMax];
+ Volume0 = new float[Constants.MixBufferCountMax];
+ Volume1 = new float[Constants.MixBufferCountMax];
+
+ for (int i = 0; i < mixBufferCount; i++)
+ {
+ InputBufferIndices[i] = (ushort)inputBufferIndex;
+ OutputBufferIndices[i] = (ushort)(outputBufferIndex + i);
+
+ Volume0[i] = volume0[i];
+ Volume1[i] = volume1[i];
+ }
+
+ State = state;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private float ProcessMixRampGrouped(Span outputBuffer, ReadOnlySpan inputBuffer, float volume0, float volume1, int sampleCount)
+ {
+ float ramp = (volume1 - volume0) / sampleCount;
+ float volume = volume0;
+ float state = 0;
+
+ for (int i = 0; i < sampleCount; i++)
+ {
+ state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume);
+
+ outputBuffer[i] += state;
+ volume += ramp;
+ }
+
+ return state;
+ }
+
+ public void Process(CommandList context)
+ {
+ for (int i = 0; i < MixBufferCount; i++)
+ {
+ ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndices[i]);
+ Span outputBuffer = context.GetBuffer(OutputBufferIndices[i]);
+
+ float volume0 = Volume0[i];
+ float volume1 = Volume1[i];
+
+ ref VoiceUpdateState state = ref State.Span[0];
+
+ if (volume0 != 0 || volume1 != 0)
+ {
+ state.LastSamples[i] = ProcessMixRampGrouped(outputBuffer, inputBuffer, volume0, volume1, (int)context.SampleCount);
+ }
+ else
+ {
+ state.LastSamples[i] = 0;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs
new file mode 100644
index 00000000..7cec7d2a
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs
@@ -0,0 +1,74 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using System;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class PcmFloatDataSourceCommandVersion1 : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort OutputBufferIndex { get; }
+ public uint SampleRate { get; }
+ public uint ChannelIndex { get; }
+
+ public uint ChannelCount { get; }
+
+ public float Pitch { get; }
+
+ public WaveBuffer[] WaveBuffers { get; }
+
+ public Memory State { get; }
+ public DecodingBehaviour DecodingBehaviour { get; }
+
+ public PcmFloatDataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex);
+ SampleRate = serverState.SampleRate;
+ ChannelIndex = channelIndex;
+ ChannelCount = serverState.ChannelsCount;
+ Pitch = serverState.Pitch;
+
+ WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
+
+ for (int i = 0; i < WaveBuffers.Length; i++)
+ {
+ ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i];
+
+ WaveBuffers[i] = voiceWaveBuffer.ToCommon(1);
+ }
+
+ State = state;
+ DecodingBehaviour = serverState.DecodingBehaviour;
+ }
+
+ public void Process(CommandList context)
+ {
+ Span outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation
+ {
+ SourceSampleRate = SampleRate,
+ SampleFormat = SampleFormat.PcmFloat,
+ Pitch = Pitch,
+ DecodingBehaviour = DecodingBehaviour,
+ ExtraParameter = 0,
+ ExtraParameterSize = 0,
+ ChannelIndex = (int)ChannelIndex,
+ ChannelCount = (int)ChannelCount,
+ };
+
+ DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs
new file mode 100644
index 00000000..dfe9814f
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs
@@ -0,0 +1,74 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Audio.Renderer.Common;
+using System;
+using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class PcmInt16DataSourceCommandVersion1 : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort OutputBufferIndex { get; }
+ public uint SampleRate { get; }
+ public uint ChannelIndex { get; }
+
+ public uint ChannelCount { get; }
+
+ public float Pitch { get; }
+
+ public WaveBuffer[] WaveBuffers { get; }
+
+ public Memory State { get; }
+ public DecodingBehaviour DecodingBehaviour { get; }
+
+ public PcmInt16DataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex);
+ SampleRate = serverState.SampleRate;
+ ChannelIndex = channelIndex;
+ ChannelCount = serverState.ChannelsCount;
+ Pitch = serverState.Pitch;
+
+ WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount];
+
+ for (int i = 0; i < WaveBuffers.Length; i++)
+ {
+ ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i];
+
+ WaveBuffers[i] = voiceWaveBuffer.ToCommon(1);
+ }
+
+ State = state;
+ DecodingBehaviour = serverState.DecodingBehaviour;
+ }
+
+ public void Process(CommandList context)
+ {
+ Span outputBuffer = context.GetBuffer(OutputBufferIndex);
+
+ DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation
+ {
+ SourceSampleRate = SampleRate,
+ SampleFormat = SampleFormat.PcmInt16,
+ Pitch = Pitch,
+ DecodingBehaviour = DecodingBehaviour,
+ ExtraParameter = 0,
+ ExtraParameterSize = 0,
+ ChannelIndex = (int)ChannelIndex,
+ ChannelCount = (int)ChannelCount,
+ };
+
+ DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs
new file mode 100644
index 00000000..d3e3f805
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs
@@ -0,0 +1,47 @@
+using Ryujinx.Audio.Renderer.Server.Performance;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class PerformanceCommand : ICommand
+ {
+ public enum Type
+ {
+ Invalid,
+ Start,
+ End
+ }
+
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Performance;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public PerformanceEntryAddresses PerformanceEntryAddresses { get; }
+
+ public Type PerformanceType { get; set; }
+
+ public PerformanceCommand(ref PerformanceEntryAddresses performanceEntryAddresses, Type performanceType, int nodeId)
+ {
+ Enabled = true;
+ PerformanceEntryAddresses = performanceEntryAddresses;
+ PerformanceType = performanceType;
+ NodeId = nodeId;
+ }
+
+ public void Process(CommandList context)
+ {
+ if (PerformanceType == Type.Start)
+ {
+ PerformanceEntryAddresses.SetStartTime(context.GetTimeElapsedSinceDspStartedProcessing());
+ }
+ else if (PerformanceType == Type.End)
+ {
+ PerformanceEntryAddresses.SetProcessingTime(context.GetTimeElapsedSinceDspStartedProcessing());
+ PerformanceEntryAddresses.IncrementEntryCount();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs
new file mode 100644
index 00000000..eeb64567
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs
@@ -0,0 +1,254 @@
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using Ryujinx.Audio.Renderer.Server.Effect;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class Reverb3dCommand : ICommand
+ {
+ private static readonly int[] OutputEarlyIndicesTableMono = new int[20] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableMono = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableMono = new int[1] { 0 };
+
+ private static readonly int[] OutputEarlyIndicesTableStereo = new int[20] { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableStereo = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableStereo = new int[2] { 0, 1 };
+
+ private static readonly int[] OutputEarlyIndicesTableQuadraphonic = new int[20] { 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableQuadraphonic = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
+
+ private static readonly int[] OutputEarlyIndicesTableSurround = new int[40] { 4, 5, 0, 5, 0, 5, 1, 5, 1, 5, 1, 5, 1, 5, 2, 5, 2, 5, 2, 5, 1, 5, 1, 5, 1, 5, 0, 5, 0, 5, 0, 5, 0, 5, 3, 5, 3, 5, 3, 5 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[40] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[6] { 0, 1, 2, 3, -1, 3 };
+
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Reverb3d;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ public Reverb3dParameter Parameter => _parameter;
+ public Memory State { get; }
+ public ulong WorkBuffer { get; }
+ public ushort[] OutputBufferIndices { get; }
+ public ushort[] InputBufferIndices { get; }
+
+ public bool IsEffectEnabled { get; }
+
+ private Reverb3dParameter _parameter;
+
+ public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported)
+ {
+ Enabled = true;
+ IsEffectEnabled = isEnabled;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+ WorkBuffer = workBuffer;
+
+ InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+ }
+
+ // NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
+ // TODO: Update reverb 3d processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
+ DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
+ DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessReverb3dMono(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount)
+ {
+ ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableMono, TargetEarlyDelayLineIndicesTableMono, TargetOutputFeedbackIndicesTableMono);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessReverb3dStereo(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount)
+ {
+ ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableStereo, TargetEarlyDelayLineIndicesTableStereo, TargetOutputFeedbackIndicesTableStereo);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessReverb3dQuadraphonic(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount)
+ {
+ ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableQuadraphonic, TargetEarlyDelayLineIndicesTableQuadraphonic, TargetOutputFeedbackIndicesTableQuadraphonic);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessReverb3dSurround(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount)
+ {
+ ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableSurround, TargetEarlyDelayLineIndicesTableSurround, TargetOutputFeedbackIndicesTableSurround);
+ }
+
+ private unsafe void ProcessReverb3dGeneric(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable)
+ {
+ const int delayLineSampleIndexOffset = 1;
+
+ bool isMono = Parameter.ChannelCount == 1;
+ bool isSurround = Parameter.ChannelCount == 6;
+
+ Span outputValues = stackalloc float[Constants.ChannelCountMax];
+ Span channelInput = stackalloc float[Parameter.ChannelCount];
+ Span feedbackValues = stackalloc float[4];
+ Span feedbackOutputValues = stackalloc float[4];
+ Span values = stackalloc float[4];
+
+ for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
+ {
+ outputValues.Fill(0);
+
+ float tapOut = state.PreDelayLine.TapUnsafe(state.ReflectionDelayTime, delayLineSampleIndexOffset);
+
+ for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++)
+ {
+ int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i];
+ int outputIndex = outputEarlyIndicesTable[i];
+
+ float tempTapOut = state.PreDelayLine.TapUnsafe(state.EarlyDelayTime[earlyDelayIndex], delayLineSampleIndexOffset);
+
+ outputValues[outputIndex] += tempTapOut * state.EarlyGain[earlyDelayIndex];
+ }
+
+ float targetPreDelayValue = 0;
+
+ for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ {
+ channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex);
+ targetPreDelayValue += channelInput[channelIndex];
+ }
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ outputValues[i] *= state.EarlyReflectionsGain;
+ }
+
+ state.PreviousPreDelayValue = (targetPreDelayValue * state.TargetPreDelayGain) + (state.PreviousPreDelayValue * state.PreviousPreDelayGain);
+
+ state.PreDelayLine.Update(state.PreviousPreDelayValue);
+
+ for (int i = 0; i < state.FdnDelayLines.Length; i++)
+ {
+ float fdnValue = state.FdnDelayLines[i].Read();
+
+ float feedbackOutputValue = fdnValue * state.DecayDirectFdnGain[i] + state.PreviousFeedbackOutputDecayed[i];
+
+ state.PreviousFeedbackOutputDecayed[i] = (fdnValue * state.DecayCurrentFdnGain[i]) + (feedbackOutputValue * state.DecayCurrentOutputGain[i]);
+
+ feedbackOutputValues[i] = feedbackOutputValue;
+ }
+
+ feedbackValues[0] = feedbackOutputValues[2] + feedbackOutputValues[1];
+ feedbackValues[1] = -feedbackOutputValues[0] - feedbackOutputValues[3];
+ feedbackValues[2] = feedbackOutputValues[0] - feedbackOutputValues[3];
+ feedbackValues[3] = feedbackOutputValues[1] - feedbackOutputValues[2];
+
+ for (int i = 0; i < state.DecayDelays1.Length; i++)
+ {
+ float temp = state.DecayDelays1[i].Update(tapOut * state.LateReverbGain + feedbackValues[i]);
+
+ values[i] = state.DecayDelays2[i].Update(temp);
+
+ state.FdnDelayLines[i].Update(values[i]);
+ }
+
+ for (int channelIndex = 0; channelIndex < targetOutputFeedbackIndicesTable.Length; channelIndex++)
+ {
+ int targetOutputFeedbackIndex = targetOutputFeedbackIndicesTable[channelIndex];
+
+ if (targetOutputFeedbackIndex >= 0)
+ {
+ *((float*)outputBuffers[channelIndex] + sampleIndex) = (outputValues[channelIndex] + values[targetOutputFeedbackIndex] + channelInput[channelIndex] * state.DryGain);
+ }
+ }
+
+ if (isMono)
+ {
+ *((float*)outputBuffers[0] + sampleIndex) += values[1];
+ }
+
+ if (isSurround)
+ {
+ *((float*)outputBuffers[4] + sampleIndex) += (outputValues[4] + state.FrontCenterDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain);
+ }
+ }
+ }
+
+ public void ProcessReverb3d(CommandList context, ref Reverb3dState state)
+ {
+ Debug.Assert(Parameter.IsChannelCountValid());
+
+ if (IsEffectEnabled && Parameter.IsChannelCountValid())
+ {
+ Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+ Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
+ outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
+ }
+
+ switch (Parameter.ChannelCount)
+ {
+ case 1:
+ ProcessReverb3dMono(ref state, outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 2:
+ ProcessReverb3dStereo(ref state, outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 4:
+ ProcessReverb3dQuadraphonic(ref state, outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 6:
+ ProcessReverb3dSurround(ref state, outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ default:
+ throw new NotImplementedException(Parameter.ChannelCount.ToString());
+ }
+ }
+ else
+ {
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ if (InputBufferIndices[i] != OutputBufferIndices[i])
+ {
+ context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
+ }
+ }
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ref Reverb3dState state = ref State.Span[0];
+
+ if (IsEffectEnabled)
+ {
+ if (Parameter.ParameterStatus == UsageState.Invalid)
+ {
+ state = new Reverb3dState(ref _parameter, WorkBuffer);
+ }
+ else if (Parameter.ParameterStatus == UsageState.New)
+ {
+ state.UpdateParameter(ref _parameter);
+ }
+ }
+
+ ProcessReverb3d(context, ref state);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs
new file mode 100644
index 00000000..0a32a065
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs
@@ -0,0 +1,279 @@
+using Ryujinx.Audio.Renderer.Dsp.State;
+using Ryujinx.Audio.Renderer.Parameter.Effect;
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class ReverbCommand : ICommand
+ {
+ private static readonly int[] OutputEarlyIndicesTableMono = new int[10] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableMono = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ private static readonly int[] OutputIndicesTableMono = new int[4] { 0, 0, 0, 0 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableMono = new int[4] { 0, 1, 2, 3 };
+
+ private static readonly int[] OutputEarlyIndicesTableStereo = new int[10] { 0, 0, 1, 1, 0, 1, 0, 0, 1, 1 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableStereo = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ private static readonly int[] OutputIndicesTableStereo = new int[4] { 0, 0, 1, 1 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableStereo = new int[4] { 2, 0, 3, 1 };
+
+ private static readonly int[] OutputEarlyIndicesTableQuadraphonic = new int[10] { 0, 0, 1, 1, 0, 1, 2, 2, 3, 3 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableQuadraphonic = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ private static readonly int[] OutputIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 };
+
+ private static readonly int[] OutputEarlyIndicesTableSurround = new int[20] { 0, 5, 0, 5, 1, 5, 1, 5, 4, 5, 4, 5, 2, 5, 2, 5, 3, 5, 3, 5 };
+ private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[20] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 };
+ private static readonly int[] OutputIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, 4, 5 };
+ private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, -1, 3 };
+
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Reverb;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ReverbParameter Parameter => _parameter;
+ public Memory State { get; }
+ public ulong WorkBuffer { get; }
+ public ushort[] OutputBufferIndices { get; }
+ public ushort[] InputBufferIndices { get; }
+ public bool IsLongSizePreDelaySupported { get; }
+
+ public bool IsEffectEnabled { get; }
+
+ private ReverbParameter _parameter;
+
+ private const int FixedPointPrecision = 14;
+
+ public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
+ {
+ Enabled = true;
+ IsEffectEnabled = isEnabled;
+ NodeId = nodeId;
+ _parameter = parameter;
+ State = state;
+ WorkBuffer = workBuffer;
+
+ InputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+ OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
+ OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
+ }
+
+ IsLongSizePreDelaySupported = isLongSizePreDelaySupported;
+
+ // NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
+ // TODO: Update reverb processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
+ DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
+ DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessReverbMono(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount)
+ {
+ ProcessReverbGeneric(ref state,
+ outputBuffers,
+ inputBuffers,
+ sampleCount,
+ OutputEarlyIndicesTableMono,
+ TargetEarlyDelayLineIndicesTableMono,
+ TargetOutputFeedbackIndicesTableMono,
+ OutputIndicesTableMono);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessReverbStereo(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount)
+ {
+ ProcessReverbGeneric(ref state,
+ outputBuffers,
+ inputBuffers,
+ sampleCount,
+ OutputEarlyIndicesTableStereo,
+ TargetEarlyDelayLineIndicesTableStereo,
+ TargetOutputFeedbackIndicesTableStereo,
+ OutputIndicesTableStereo);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessReverbQuadraphonic(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount)
+ {
+ ProcessReverbGeneric(ref state,
+ outputBuffers,
+ inputBuffers,
+ sampleCount,
+ OutputEarlyIndicesTableQuadraphonic,
+ TargetEarlyDelayLineIndicesTableQuadraphonic,
+ TargetOutputFeedbackIndicesTableQuadraphonic,
+ OutputIndicesTableQuadraphonic);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessReverbSurround(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount)
+ {
+ ProcessReverbGeneric(ref state,
+ outputBuffers,
+ inputBuffers,
+ sampleCount,
+ OutputEarlyIndicesTableSurround,
+ TargetEarlyDelayLineIndicesTableSurround,
+ TargetOutputFeedbackIndicesTableSurround,
+ OutputIndicesTableSurround);
+ }
+
+ private unsafe void ProcessReverbGeneric(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable, ReadOnlySpan outputIndicesTable)
+ {
+ bool isSurround = Parameter.ChannelCount == 6;
+
+ float reverbGain = FixedPointHelper.ToFloat(Parameter.ReverbGain, FixedPointPrecision);
+ float lateGain = FixedPointHelper.ToFloat(Parameter.LateGain, FixedPointPrecision);
+ float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision);
+ float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision);
+
+ Span outputValues = stackalloc float[Constants.ChannelCountMax];
+ Span feedbackValues = stackalloc float[4];
+ Span feedbackOutputValues = stackalloc float[4];
+ Span channelInput = stackalloc float[Parameter.ChannelCount];
+
+ for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++)
+ {
+ outputValues.Fill(0);
+
+ for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++)
+ {
+ int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i];
+ int outputIndex = outputEarlyIndicesTable[i];
+
+ float tapOutput = state.PreDelayLine.TapUnsafe(state.EarlyDelayTime[earlyDelayIndex], 0);
+
+ outputValues[outputIndex] += tapOutput * state.EarlyGain[earlyDelayIndex];
+ }
+
+ if (isSurround)
+ {
+ outputValues[5] *= 0.2f;
+ }
+
+ float targetPreDelayValue = 0;
+
+ for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ {
+ channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex) * 64;
+ targetPreDelayValue += channelInput[channelIndex] * reverbGain;
+ }
+
+ state.PreDelayLine.Update(targetPreDelayValue);
+
+ float lateValue = state.PreDelayLine.Tap(state.PreDelayLineDelayTime) * lateGain;
+
+ for (int i = 0; i < state.FdnDelayLines.Length; i++)
+ {
+ feedbackOutputValues[i] = state.FdnDelayLines[i].Read() * state.HighFrequencyDecayDirectGain[i] + state.PreviousFeedbackOutput[i] * state.HighFrequencyDecayPreviousGain[i];
+ state.PreviousFeedbackOutput[i] = feedbackOutputValues[i];
+ }
+
+ feedbackValues[0] = feedbackOutputValues[2] + feedbackOutputValues[1];
+ feedbackValues[1] = -feedbackOutputValues[0] - feedbackOutputValues[3];
+ feedbackValues[2] = feedbackOutputValues[0] - feedbackOutputValues[3];
+ feedbackValues[3] = feedbackOutputValues[1] - feedbackOutputValues[2];
+
+ for (int i = 0; i < state.FdnDelayLines.Length; i++)
+ {
+ feedbackOutputValues[i] = state.DecayDelays[i].Update(feedbackValues[i] + lateValue);
+ state.FdnDelayLines[i].Update(feedbackOutputValues[i]);
+ }
+
+ for (int i = 0; i < targetOutputFeedbackIndicesTable.Length; i++)
+ {
+ int targetOutputFeedbackIndex = targetOutputFeedbackIndicesTable[i];
+ int outputIndex = outputIndicesTable[i];
+
+ if (targetOutputFeedbackIndex >= 0)
+ {
+ outputValues[outputIndex] += feedbackOutputValues[targetOutputFeedbackIndex];
+ }
+ }
+
+ if (isSurround)
+ {
+ outputValues[4] += state.FrontCenterDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f);
+ }
+
+ for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
+ {
+ *((float*)outputBuffers[channelIndex] + sampleIndex) = (outputValues[channelIndex] * outGain + channelInput[channelIndex] * dryGain) / 64;
+ }
+ }
+ }
+
+ private void ProcessReverb(CommandList context, ref ReverbState state)
+ {
+ Debug.Assert(Parameter.IsChannelCountValid());
+
+ if (IsEffectEnabled && Parameter.IsChannelCountValid())
+ {
+ Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+ Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount];
+
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]);
+ outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]);
+ }
+
+ switch (Parameter.ChannelCount)
+ {
+ case 1:
+ ProcessReverbMono(ref state, outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 2:
+ ProcessReverbStereo(ref state, outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 4:
+ ProcessReverbQuadraphonic(ref state, outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ case 6:
+ ProcessReverbSurround(ref state, outputBuffers, inputBuffers, context.SampleCount);
+ break;
+ default:
+ throw new NotImplementedException(Parameter.ChannelCount.ToString());
+ }
+ }
+ else
+ {
+ for (int i = 0; i < Parameter.ChannelCount; i++)
+ {
+ if (InputBufferIndices[i] != OutputBufferIndices[i])
+ {
+ context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]);
+ }
+ }
+ }
+ }
+
+ public void Process(CommandList context)
+ {
+ ref ReverbState state = ref State.Span[0];
+
+ if (IsEffectEnabled)
+ {
+ if (Parameter.Status == Server.Effect.UsageState.Invalid)
+ {
+ state = new ReverbState(ref _parameter, WorkBuffer, IsLongSizePreDelaySupported);
+ }
+ else if (Parameter.Status == Server.Effect.UsageState.New)
+ {
+ state.UpdateParameter(ref _parameter);
+ }
+ }
+
+ ProcessReverb(context, ref state);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs
new file mode 100644
index 00000000..0870d59c
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs
@@ -0,0 +1,70 @@
+using Ryujinx.Audio.Renderer.Server.Upsampler;
+using System;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class UpsampleCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Upsample;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public uint BufferCount { get; }
+ public uint InputBufferIndex { get; }
+ public uint InputSampleCount { get; }
+ public uint InputSampleRate { get; }
+
+ public UpsamplerState UpsamplerInfo { get; }
+
+ public Memory OutBuffer { get; }
+
+ public UpsampleCommand(uint bufferOffset, UpsamplerState info, uint inputCount, Span inputBufferOffset, uint bufferCount, uint sampleCount, uint sampleRate, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = 0;
+ OutBuffer = info.OutputBuffer;
+ BufferCount = bufferCount;
+ InputSampleCount = sampleCount;
+ InputSampleRate = sampleRate;
+ info.SourceSampleCount = inputCount;
+ info.InputBufferIndices = new ushort[inputCount];
+
+ for (int i = 0; i < inputCount; i++)
+ {
+ info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]);
+ }
+
+ if (info.BufferStates?.Length != (int)inputCount)
+ {
+ // Keep state if possible.
+ info.BufferStates = new UpsamplerBufferState[(int)inputCount];
+ }
+
+ UpsamplerInfo = info;
+ }
+
+ private Span GetBuffer(int index, int sampleCount)
+ {
+ return UpsamplerInfo.OutputBuffer.Span.Slice(index * sampleCount, sampleCount);
+ }
+
+ public void Process(CommandList context)
+ {
+ uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount);
+
+ for (int i = 0; i < bufferCount; i++)
+ {
+ Span inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]);
+ Span outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount);
+
+ UpsamplerHelper.Upsample(outputBuffer, inputBuffer, (int)UpsamplerInfo.SampleCount, (int)InputSampleCount, ref UpsamplerInfo.BufferStates[i]);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs
new file mode 100644
index 00000000..0628f6d8
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.Arm;
+using System.Runtime.Intrinsics.X86;
+
+namespace Ryujinx.Audio.Renderer.Dsp.Command
+{
+ public class VolumeCommand : ICommand
+ {
+ public bool Enabled { get; set; }
+
+ public int NodeId { get; }
+
+ public CommandType CommandType => CommandType.Volume;
+
+ public uint EstimatedProcessingTime { get; set; }
+
+ public ushort InputBufferIndex { get; }
+ public ushort OutputBufferIndex { get; }
+
+ public float Volume { get; }
+
+ public VolumeCommand(float volume, uint bufferIndex, int nodeId)
+ {
+ Enabled = true;
+ NodeId = nodeId;
+
+ InputBufferIndex = (ushort)bufferIndex;
+ OutputBufferIndex = (ushort)bufferIndex;
+
+ Volume = volume;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessVolumeAvx(Span outputBuffer, ReadOnlySpan inputBuffer)
+ {
+ Vector256 volumeVec = Vector256.Create(Volume);
+
+ ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer);
+ Span> outputVec = MemoryMarshal.Cast>(outputBuffer);
+
+ int sisdStart = inputVec.Length * 8;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec));
+ }
+
+ for (int i = sisdStart; i < inputBuffer.Length; i++)
+ {
+ outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessVolumeSse41(Span outputBuffer, ReadOnlySpan inputBuffer)
+ {
+ Vector128 volumeVec = Vector128.Create(Volume);
+
+ ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer);
+ Span> outputVec = MemoryMarshal.Cast>(outputBuffer);
+
+ int sisdStart = inputVec.Length * 4;
+
+ for (int i = 0; i < inputVec.Length; i++)
+ {
+ outputVec[i] = Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec));
+ }
+
+ for (int i = sisdStart; i < inputBuffer.Length; i++)
+ {
+ outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ProcessVolumeAdvSimd(Span outputBuffer, ReadOnlySpan inputBuffer)
+ {
+ Vector128 volumeVec = Vector128.Create(Volume);
+
+ ReadOnlySpan> inputVec = MemoryMarshal.Cast